@import url("tokens.css");
@import url("anim.css");
@import url("rahmen.css");
@import url("home.css");
@import url("flyhigh.css");
@import url("splash.css");
@import url("settings.css");
@import url("notes.css");
@import url("mail.css");
@import url("start.css");
@import url("phone.css");
@import url("wicker.css");
@import url("levelup.css");
@import url("comeback.css");
@import url("bust.css");
@import url("ricoshop.css");
@import url("inventory.css");
@import url("store.css");
@import url("seedshop.css");
@import url("trapazon.css");
@import url("phl.css");
@import url("messenger.css");
@import url("gramtrap.css");
@import url("grower.css");
@import url("runner.css");
@import url("kanal.css");
@import url("contact.css");
@import url("browser.css");
@import url("wiki.css");
@import url("silkroad.css");
@import url("mixer.css");
@import url("vpn.css");
@import url("crypto.css");
@import url("torgle.css");
@import url("mobilo.css");
@import url("stake.css");
@import url("cointrapper.css");
@import url("minispiel.css");
@import url("tpdriver.css");
@import url("profile.css");
@import url("leaderboard.css");
@import url("safehouse.css");
@import url("trim.css");
@import url("immo.css");
@import url("casino.css");

/* PLATZIERUNGSREGEL (fuer jede kommende Welle): Stile fuer eine geteilte
   Schale-/Shell-Komponente aus Components/ (PhoneStatusBar, PhoneDock,
   GameCanvas - alles, was GameHost auf JEDER Route einhaengt oder was mehr
   als eine Seite benutzt) gehoeren in DIESE Datei. Stile, die nur EINE Seite
   aus Pages/ betreffen (z. B. .flyhigh-root fuer FlyHigh.razor), gehoeren in
   eine eigene <seitenname>.css und werden hier oben per @import eingehaengt -
   NICHT direkt in diese Datei geschrieben. Ohne diese Trennung wird app.css
   die Ablage fuer vierzig Seiten. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--trap-bg);
  color: #fff;
  font-family: "Open Sans", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  overscroll-behavior: none;
}

/* Blazor-Wurzel beider Hosts (TrapPhone.Web/wwwroot/index.html und
   TrapPhone/wwwroot/index.html: <div id="app">). Ohne eigene Hoehe ist sie
   height:auto - ein Prozent-Hoehen-Kettenglied braucht aber einen Vorfahren
   mit DEFINIERTER Hoehe, sonst loest "height:100%" ueberall darunter (bis
   hinunter zu .flyhigh-root/.game-canvas-wrap/canvas) zu auto auf. Bei einem
   <canvas> ohne HTML-width/height-Attribute (Standard-Intrinsik 300x150, also
   Seitenverhaeltnis 2:1) rechnet der Browser dieses auto dann NICHT auf volle
   Fensterhoehe, sondern haelt bei definierter Breite (420px via width:100%,
   das klappt: Bloecke fuellen die verfuegbare Breite auch bei auto) das 2:1-
   Seitenverhaeltnis ein - 210px hohe Zeichenflaeche statt voller Hoehe (in
   Task 6 mit Playwright gemessen: Puffer 840x420 = 210 CSS-px * DPR 2, obwohl
   der Viewport 860px hoch war). Home.razor blieb davon unbemerkt, weil
   .phone-content dort sowieso nur so hoch wird wie sein Inhalt (Dashboard +
   App-Raster) - fuer eine Spiel-Zeichenflaeche ohne Eigeninhalt zaehlt das
   nicht. */
#app { height: 100%; }

/* Der Verlauf des Homescreens, aus MainPage.xaml uebernommen:
   #12161E oben, #0B0D11 bei 45 %, #07080B unten. */
.phone-root {
  height: 100%;
  display: flex;
  flex-direction: column;
  /* Containing Block fuer .overlay-layer (position: absolute; inset: 0 weiter
     unten) und .splash-wurzel (splash.css) - beide sollen ueber GENAU diese
     Telefon-Flaeche schweben, nicht ueber den ganzen Dokument-Viewport. Aktuell
     faellt das zufaellig zusammen, weil #app/body/html nirgends positioniert
     sind und .phone-root den Viewport 1:1 ausfuellt - sobald eine kuenftige
     Welle einen positionierten Vorfahren einfuehrt (z. B. ein Desktop-
     "Telefonrahmen"), waere das ohne diese Zeile stillschweigend falsch.
     position:relative nimmt ein Flex-Kind NICHT aus dem Fluss - die Anordnung
     bleibt unveraendert, nur die Containing-Block-Kette wird explizit statt
     zufaellig richtig. Dieselbe Schreibweise traegt bereits .status-bar (fuer
     .status-notch) und .game-canvas-wrap (fuer .game-canvas-stats). */
  position: relative;
  /* Stapelkontext fuer die Hintergrundbild-Ebene des Homescreens (home.css,
     .home-hintergrund, z-index:-1). Ohne ihn waere der naechste Stapelkontext das
     Wurzelelement der Seite - eine negative Ebene fiele dann hinter den Verlauf
     dieser Zeile und waere unsichtbar. isolation:isolate aendert NUR die
     Stapelreihenfolge nach aussen: die Kinder von .phone-root behalten ihre
     Reihenfolge untereinander unveraendert (auch .overlay-layer). */
  isolation: isolate;
  background: linear-gradient(180deg, #12161E 0%, #0B0D11 45%, #07080B 100%);
}

.phone-content { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* Der Homescreen SCROLLT NICHT (Fund des Auftraggebers: "home seite ist aktuell
   scrollbar. soll sich aber responsive an den bildschirm anpassen"). Er ist im
   Ist-Code keine ScrollView, sondern ein Grid mit festen Zeilen (MainPage.xaml:33,
   RowDefinitions) - was nicht passt, wird dort enger, nicht scrollbar. Hier trug
   .phone-content overflow-y:auto fuer ALLE Routen, und Dashboard + drei
   Kachelreihen + Dock sind auf einem 640px-Schirm zusammen hoeher als die Flaeche.
   Diese Huelle nimmt das Scrollen zurueck und macht aus dem Inhaltsbereich eine
   Flex-Spalte - erst dadurch kann .home-root/.app-grid (home.css) die verbleibende
   Hoehe messen und die Kacheln darauf verteilen.
   Gesetzt von GameHost.razor an derselben Bedingung wie das Dock (MitDock ==
   AppRoot.IstHomescreen) - der Homescreen ist die einzige Route, die das will;
   jede andere Seite bringt beliebig langen Inhalt mit und braucht ihren Bildlauf. */
.phone-content-home {
  overflow-y: hidden;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Huelle fuer den Inhalt einer IFullScreenPage (Stufe 2 der SCHALE-REGEL in
   GameHost.razor). Sie ersetzt NICHT .phone-content: kein eigenes Scrollen
   (eine Vollbildseite regelt ihren Bildlauf selbst, siehe .br-wurzel/.tpd-wurzel)
   und keine Hoehenbegrenzung ueber den Inhalt hinaus.
   WOZU DANN UEBERHAUPT EINE HUELLE: fast jede Vollbildseite setzt auf ihrer Wurzel
   "height: 100%" (.vpn-root, .br-wurzel, .tt-wurzel, .kr-wurzel, .store-root,
   .cas-root, .mail-root, .gt-wurzel, .stk-root ...). Ohne Huelle waere das - seit
   die Statusleiste auch hier haengt - 100% des GANZEN Telefons, die Seite stuende
   also um die Leistenhoehe zu tief und ihr unteres Ende laege ausserhalb.
   Mit flex:1 + min-height:0 ist die Hoehe dieser Huelle die Flaeche UNTER der
   Leiste, und dieselben 100% beziehen sich wieder auf genau das Richtige.
   display:flex/column, damit eine Seitenwurzel mit "flex:1" statt "height:100%"
   (z. B. .settings-root mit min-height) ebenfalls aufgeht. */
.phone-content-voll { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.phone-content-voll > * { flex: 1; min-height: 0; }

/* Overlays (Banner, Toasts, Dialoge) schweben ueber dem Bildschirm, statt
   das Dock nach unten zu schieben. Ohne diese Regel sieht das erste Banner
   nach einem Fehler in der Overlay-Komponente aus - es waere keiner. */
.overlay-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;   /* Kinder setzen es fuer sich selbst wieder auf auto */
}

/* GameCanvas-Wirtselement (Components/GameCanvas.razor) - jedes kommende
   Minigame teilt sich diese Klassen, deshalb hier statt in einer Seiten-Datei.
   GameCanvas rendert selbst ein nacktes <canvas> ohne Rahmen; die fps/DPR/
   Puffergroesse-Anzeige aus ShowStats braucht ein umschliessendes
   position:relative-Element MIT eigener height:100% - sonst faellt die
   Zeichenflaeche darin auf Hoehe 0 zurueck, bevor die Neu-Vermessung aus
   Task 5 es beim naechsten Versuch wieder heilt. */
.game-canvas      { width: 100%; height: 100%; display: block; }
.game-canvas-wrap { position: relative; height: 100%; }
.game-canvas-stats {
  position: absolute;
  top: 6px;
  left: 6px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(var(--trap-bg-rgb), 0.7);
  color: var(--trap-text-muted);
  font-family: "Consolas", "SFMono-Regular", Menlo, monospace;
  font-size: 11px;
  line-height: 1.4;
  pointer-events: none;
  z-index: 1;
}

/* ── Statusleiste (MainPage.xaml Zeile 39 ff.) ───────────────────────────
   Components/PhoneStatusBar.razor - Schale, die GameHost auf JEDER
   Nicht-Vollbild-Route einhaengt, deshalb hier statt in einer Seiten-Datei. */
.status-bar {
  position: relative;
  height: 36px;
  flex: 0 0 36px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  cursor: pointer;   /* Pointerdown/-up auf der ganzen Leiste - siehe "DIE WISCHGESTE" im @code weiter unten */
  /* MainPage.xaml:43-44 hat #CC0B0D11 -> #590B0D11 (MAUI liest 8-stelliges Hex als
     AARRGGBB: 80% / 35% Deckkraft auf #0B0D11 = --trap-bg, ein Vignette-Verlauf).
     Woertlich als CSS-Hex uebernommen waere das RRGGBBAA und ergaebe stattdessen
     einen kaum sichtbaren ROTSTICH bei falscher Deckkraft — deshalb hier ueber den
     Token nachgebaut statt hex-woertlich kopiert.
     KEIN color-mix(): der Android-Floor dieses Projekts ist API 23 (TrapPhone.csproj,
     bewusst gehalten), dessen System-WebView bei Chromium <=106 eingefroren ist (Google
     stellte Updates fuer Android 6.0 nach M106 ein) — color-mix() kam erst mit 111.
     CSS verwirft eine Deklaration mit unbekannter Funktion komplett (Fallback auf
     transparent), lautlos, ohne Fehler — deshalb rgba() mit dem --trap-bg-rgb-Begleit-
     token aus tokens.css statt color-mix(); TokensCssTests haelt die zwei Werte synchron. */
  background: linear-gradient(180deg,
    rgba(var(--trap-bg-rgb), .8) 0%,
    rgba(var(--trap-bg-rgb), .35) 100%);
  /* Feine Trennlinie zum Homescreen (MainPage.xaml:102-103, Grid.Row="1", eigene Auto-Zeile
     UNTER dem Leisteninhalt - hier als border-bottom des ganzen 36px-Balkens nachgebaut,
     weil die Auto-Zeile im Ist-Code ohnehin nur die Hoehe der 1px-Linie selbst traegt):
     <BoxView HeightRequest="1" Color="#22FFFFFF"/>. MAUI liest 8-stelliges Hex als
     AARRGGBB: #22 = 13% Deckkraft auf REINEM WEISS. Woertlich als CSS-Hex kopiert waere das
     RRGGBBAA (#22FFFFFF -> R=0x22=34, G/B=0xFF=255) - rgb(34,255,255) bei VOLLER Deckkraft,
     ein TUERKISER Strich statt einer kaum sichtbaren 13%-Weiss-Linie (Musterfall dieser
     Falle). --divider-white-rgb (tokens.css) haelt das Tripel fest, damit hier kein
     Dezimal-Literal die Tokenkopplung umgeht (CssGuardTests). ABWEICHUNG: ersetzt eine
     zuvor hier stehende, nicht am Ist-Code belegte "border-bottom: 1px solid
     var(--trap-border)" - kein XAML-Fundstelle dafuer, vermutlich ein frueher Platzhalter. */
  border-bottom: 1px solid rgba(var(--divider-white-rgb), .13);
}

.status-left  { display: flex; align-items: center; gap: 7px; }   /* MainPage.xaml:55 Spacing="7" */
.status-right { display: flex; align-items: center; gap: 10px; }  /* MainPage.xaml:64 Spacing="10" - NICHT 7, die beiden Stapel haben verschiedene Abstaende */
.status-clock   { font-weight: 600; font-size: 13px; }
/* KORREKTUR (Welle B1/Task 2, selbst beim Farbabgleich in Schritt 1 gefunden): stand zuvor
   auf var(--trap-text-muted) (#8F96AC). MainPage.xaml:57 setzt TextColor="#6B7385" - eine
   ANDERE, dunklere Graunuance, kein Tippfehler-naher Treffer. Kein bestehendes Token deckt
   #6B7385 exakt (auch --trap-text-dim, #5E6673, liegt daneben) - deshalb hier woertliches
   Hex statt eines nur aehnlichen Tokens. */
.status-carrier { font-size: 10px; letter-spacing: .5px; color: #6B7385; }  /* MainPage.xaml:57 FontSize="10" CharacterSpacing="0.5" */

/* Wicker-/Mail-/VPN-Symbole (MainPage.xaml:65-70), alle drei standardmaessig
   unsichtbar - hier per @if statt IsVisible weggelassen (Welle B1/Task 2). */
.status-mail-icon  { font-size: 12px; color: var(--trap-accent); }  /* :65, TextColor="#00E676" == --trap-accent */
/* :66 - kein TextColor-Attribut im Ist-Code: erbt den impliziten MAUI-Label-Standard
   (Styles.xaml, TargetType="Label", TextColor per AppThemeBinding) - App.xaml.cs:28 erzwingt
   AppTheme.Dark, dort ist der Standard Weiss, exakt die body-Vorgabe dieser Datei (Zeile 28
   oben). Deshalb hier bewusst KEINE eigene Farbe. */
.status-email-icon { font-size: 12px; }

.status-vpn-chip {
  display: flex; align-items: center;
  padding: 1px 6px;              /* :68, Padding="6,1" (links/rechts, oben/unten) */
  border-radius: 7px;            /* :68, StrokeShape="RoundRectangle 7" */
  background: #12291C;           /* :67 */
  border: 1px solid #2E5C46;     /* :67 */
}
.status-vpn-text {
  font-size: 8px; font-weight: 700; letter-spacing: 1px;  /* :69 */
  color: var(--trap-accent);     /* :69, TextColor="#00E676" == --trap-accent */
}

.status-signal, .status-battery { display: flex; align-items: center; font-size: 10px; font-weight: 700; }
/* KORREKTUR: beide teilten sich bislang dieselbe gap:3px-Regel. Der Ist-Code vergibt aber
   ZWEI verschiedene Spacing-Werte fuer die beiden HorizontalStackLayouts: :71 (Empfang) hat
   Spacing="3", :80 (Akku) hat Spacing="4" - eine Zeile darunter, leicht zu uebersehen. */
.status-signal  { gap: 3px; }   /* :71 */
.status-battery { gap: 4px; }   /* :80 */

/* Empfang: gezeichnete Balken statt 📶-Emoji (MainPage.xaml:71-78). */
.status-signal-bars {
  display: flex; align-items: flex-end;
  gap: 1.5px;      /* :72, Spacing="1.5" */
  height: 12px;    /* :72, HeightRequest="12" */
}
.status-signal-bar {
  width: 3px;              /* :73-76, WidthRequest="3" auf allen vier Balken */
  border-radius: 1px;      /* CornerRadius="1" */
  background: #fff;        /* :73-75, Color="White" - die ersten drei (aufsteigenden) Balken */
}
.status-signal-bar:nth-child(1) { height: 4.5px; }  /* :73 */
.status-signal-bar:nth-child(2) { height: 7px; }    /* :74 */
.status-signal-bar:nth-child(3) { height: 9.5px; }  /* :75 */
.status-signal-bar-dim {
  height: 12px;         /* :76 - voller Balken, aber ABGEDUNKELT: kein voller Empfang */
  background: #3E4757;  /* :76 */
}

/* Akku: gezeichneter Rahmen + Fuellung statt 🔋-Emoji (MainPage.xaml:80-89). */
.status-battery-pct {
  /* :81, TextColor="#C6CCDA" - FEST, aendert sich NICHT mit dem Akkustand:
     UpdateStatusBarBattery (xaml.cs:2120-2128) schreibt nur LblBattery.Text und
     BatteryFill.Color/WidthRequest, nie LblBattery.TextColor. Die drei Farbstufen
     (.batt-ok/-warn/-low) gehoeren deshalb NICHT hierher, sondern auf .status-battery-fill
     unten (siehe dortiger Kommentar - Korrektur gegenueber einer frueheren Fassung dieser
     Datei, die die Klassen auf den Text statt auf die Fuellung angewandt hatte). */
  color: #C6CCDA;
}
.status-battery-shape { display: flex; align-items: center; gap: 1px; }  /* :82, ColumnSpacing="1" */
.status-battery-frame {
  box-sizing: border-box;
  width: 22px; height: 11px;             /* :83, WidthRequest/HeightRequest */
  padding: 1.5px;                        /* :84, Padding="1.5" */
  border: 1px solid var(--trap-text-muted); /* :84, Stroke="#8F96AC" == --trap-text-muted, exakt */
  border-radius: 3px;                    /* :83, StrokeShape="RoundRectangle 3" */
}
.status-battery-fill {
  display: block;
  height: 100%;
  border-radius: 1.5px;   /* :85, CornerRadius="1.5" */
  /* Breite kommt aus BatteryFillWidthPx (style="width:...", @code unten) - dieselbe Formel
     wie xaml.cs:2127. Farbe ueber .batt-ok/-warn/-low (dieselben drei Stufen, die die
     Komponente bereits kennt) statt eines fest verdrahteten Werts. */
}
.status-battery-nub {
  width: 1.5px; height: 4px;              /* :88, WidthRequest/HeightRequest */
  border-radius: 0.75px;                  /* :88, CornerRadius="0.75" */
  background: var(--trap-text-muted);     /* :88, Color="#8F96AC" == --trap-text-muted, exakt */
}

.batt-ok   { background: var(--trap-accent); }   /* xaml.cs:2114 */
.batt-warn { background: var(--trap-warning); }  /* xaml.cs:2115 */
.batt-low  { background: var(--trap-danger); }   /* xaml.cs:2116 - naechstliegendes Token zu
                                                     #FF5252. --modal-danger-accent (app.css:371) IST exakt #FF5252, ist aber
                                                     semantisch an Gefahr-Bestaetigungen gebunden (SettingsPage.xaml.cs:332);
                                                     hier bewusst das Statusfarben-Token (siehe
                                                     PhoneStatusBar.razor's BatteryClass-Kommentar) */

/* Punch-Hole-Kamera: eigene, ueber die volle Breite zentrierte Ebene (MainPage.xaml:94-99),
   KEINE Zelle der Reihe - deshalb absolut positioniert statt eines Flex-Kinds.
   pointer-events:none == InputTransparent="True" (:97): sie sitzt GENAU auf dem Klickbereich
   der Leiste (.status-bar's @onpointerdown/@onpointerup) und darf die Wischgeste/den Tipp
   in der Mitte nicht schlucken. */
.status-notch {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);   /* KORREKTUR: vorher nur translateX - ohne "top:50%"
                                          haengt die vertikale Position an der statischen
                                          Flow-Position des Elements (CSS-Spezifikation fuer
                                          absolut positionierte Kinder ohne top/bottom), nicht
                                          garantiert mittig wie VerticalOptions="Center" (:97) */
  width: 11px; height: 11px;    /* :95, WidthRequest/HeightRequest="11" */
  border-radius: 5.5px;         /* :95, StrokeShape="RoundRectangle 5.5" */
  background: #04060A;          /* :96 */
  border: 1px solid #232B3C;    /* :96, Stroke="#232B3C" StrokeThickness="1" */
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.status-notch-dot {
  width: 3.5px; height: 3.5px;  /* :98, WidthRequest/HeightRequest="3.5" */
  border-radius: 1.75px;        /* :98, CornerRadius="1.75" */
  background: #1E3A5F;          /* :98 */
}

/* ── Dock (MainPage.xaml Zeile 372 ff.) ───────────────────────────────────
   Components/PhoneDock.razor - dieselbe Begruendung wie .status-bar oben. */
.phone-dock {
  flex: 0 0 auto;
  display: flex; justify-content: space-around; align-items: center;
  /* KEIN env(safe-area-inset-bottom) hier (Fix, Whole-Wave-Review): das war
     richtig, solange .phone-dock das unterste Element war. Seit GameHost.razor
     .app-tabs hinter .phone-dock einhaengt, ist .app-tabs das unterste Element -
     und reserviert den Inset bereits selbst (siehe dort). Mit env() an BEIDEN
     Stellen zaehlt die Aussparung doppelt: auf einem Geraet mit Home-Indicator
     (iPhone-Notch-Geraete, manche Android-Geraete mit Gesten-Leiste) entsteht so
     ein toter Leerraum von ca. 34px zwischen Dock-Icons und Tab-Leiste - auf
     Windows und im Desktop-Browser unsichtbar (dort ist der Inset 0), also genau
     auf den zwei Plattformen, die dieses Projekt nicht fernpruefen kann. */
  /* Schwebende Karte statt durchgehender Leiste - MainPage.xaml:373
     (Padding="15,15,15,25" Margin="15,0,15,-15" StrokeShape="RoundRectangle 30").
     Der negative Unterrand des Ist-Codes (-15) schiebt die Karte dort ueber den
     unteren Seitenrand hinaus; hier ist stattdessen der Unterrand des Polsters
     gekuerzt. Ein negativer margin-bottom waere in der Flex-Spalte etwas anderes
     als in MAUI: er zoege die .app-tabs darunter in die Karte hinein.

     UNTERRAND STATT NULL (Fund des Auftraggebers: "das dock der home seite ist zu
     hoch und beruehrt deswegen die unteren Handy, Lager, Bunker und Profil
     Reiter"): ohne margin-bottom sitzt die Karte buendig auf .app-tabs, ihr
     abgerundeter Rand beruehrt also die Reiterleiste. Im Ist-Code kann das nicht
     passieren, weil die Karte dort ueber den Seitenrand hinausragt und UNTER ihr
     nichts mehr liegt - die Reiterleiste ist in MAUI die TabBar der AppShell,
     ausserhalb der Seite. Hier sind beide Geschwister derselben Flex-Spalte und
     brauchen den Abstand ausdruecklich.
     Das Polster oben/unten ist zugleich von 15/10 auf 8/8 gekuerzt: dieselbe
     Beobachtung ("zu hoch") - die Karte ist mit 60px-Icons plus Beschriftung auch
     ohne grosszuegiges Polster hoch genug zum Treffen. */
  margin: 0 15px 10px;
  padding: 8px 15px;
  border: 1px solid var(--trap-border);   /* :373 Stroke="#232730" StrokeThickness="1" */
  border-radius: 30px;                    /* :373 StrokeShape="RoundRectangle 30" */
  /* :373 BackgroundColor="#E6121418" - in MAUI AARRGGBB: Alpha E6 (90%) auf
     #121418, also --trap-surface. Als CSS-Hex waere derselbe Text eine andere
     Farbe (RRGGBBAA), deshalb ueber das --trap-surface-rgb-Begleittoken wie bei
     .status-bar oben (kein color-mix(), kein dezimales Literal). */
  background: rgba(var(--trap-surface-rgb), .9);
}
.dock-item {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: none; border: 0; color: inherit;
  /* 2px statt 4px oben/unten - zusammen mit dem gekuerzten Polster von
     .phone-dock der zweite Teil desselben Funds ("das dock ... ist zu hoch"). */
  font: inherit; cursor: pointer; padding: 2px 10px;
}
/* Dock-Kachel - :379 (HeightRequest/WidthRequest="60", RoundRectangle 16). Aufbau
   identisch zu .app-icon (home.css), nur kleiner: das Icon-Bild fuellt die Flaeche
   (Aspect="AspectFill" -> object-fit: cover), die Rundung kommt vom Rahmen.
   Schriftgroesse 28 fuer den Emoji-Rueckfall - MainPage.xaml.cs:2547 (StyleHomeTile:
   "emoji.FontSize = dock ? 28 : 30"), also der Dock-Zweig desselben Ausdrucks, aus
   dem .app-icon oben die 30 nimmt. */
.dock-icon {
  width: 60px; height: 60px;
  display: grid; place-items: center;
  font-size: 28px;
  border-radius: 16px;
  overflow: hidden;
  background: rgb(var(--kachel, 22, 26, 32));   /* Rueckfall, bis das Bild geladen ist - siehe .app-icon (home.css) */
  /* Leuchtschatten wie im Raster, nur mit der Dock-Deckkraft 0.35 (:384/:404/:422,
     Shadow Radius="8" Offset="0,4"). Begruendung zu --kachel/--schatten und zum
     Verhalten bei fehlendem Wert steht bei .app-icon in home.css. */
  box-shadow: 0 4px 8px rgba(var(--schatten, var(--kachel)), var(--schatten-deckkraft, .35));
}
.dock-icon img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Eigene Farbe je Dock-Kachel - :379/:399/:417/:431 (Border.BackgroundColor).
   Wie im Raster (home.css, dortiger Kommentar) nur RUECKFALL hinter dem deckenden
   Bild, und zugleich die Farbe des Leuchtschattens oben.
   COIN LEUCHTET HELLER ALS SEINE FLAECHE: Hintergrund #1A237E, Schatten #3949AB
   (:431 gegen :436) - im Ist-Code sind das zwei verschiedene Werte, kein Vertipper,
   und zusammen mit Setup (home.css) der Grund, warum es --schatten gibt. Coin faellt
   auch bei der Deckkraft aus der Reihe: 0.4 statt der 0.35 der drei anderen (:436). */
.dock-icon[data-app="phone"] { --kachel:  52, 199,  89; }  /* :379 #34C759 */
.dock-icon[data-app="chat"]  { --kachel:  42, 171, 238; }  /* :399 #2AABEE */
.dock-icon[data-app="web"]   { --kachel: 255,  87,  34; }  /* :417 #FF5722 */
.dock-icon[data-app="coin"]  { --kachel:  26,  35, 126; --schatten: 57, 73, 171; --schatten-deckkraft: .4; }  /* :431 #1A237E / :436 #3949AB */
.dock-name { font-size: 10px; font-weight: 700; color: var(--trap-text-muted); }

/* Traeger fuer Symbol + Ungelesen-Abzeichen (Appsymbol.razor, Begruendung dort).
   Entspricht dem <Grid> um Border+Badge im Ist-Code (MainPage.xaml:247/283/407).
   Hier und nicht in home.css, weil ihn Raster UND Dock benutzen - und weil eine
   Kachel dank Anordnen-Modus zwischen beiden wechseln kann.
   display:block statt des voreingestellten inline: als Flex-Kind von .app-tile/
   .dock-item schrumpft der Block auf die Symbolbreite (align-items:center), ohne
   die Zeilenabstands-Luecke, die ein inline-Element unter sich laesst. */
.app-symbol { position: relative; display: block; }

/* Der rote Ungelesen-Zaehler - MainPage.xaml:257-262 (Mail), :293-295 (Wicker),
   :408-409 (Teletrap) und PhoneBadge. Die drei Abzeichen im Ist-Code weichen in
   Kleinigkeiten voneinander ab (Wicker/Teletrap fest 20 breit und ohne Polsterung,
   Mail mit MinimumWidthRequest+Padding fuer zweistellige Zahlen; Versatz einmal
   ueber TranslationX/Y, zweimal ueber Margin). Hier steht EINE Regel: die
   Mail-Fassung, weil nur sie mit "99+" umgehen kann, und der weisse Rand der beiden
   anderen (Stroke="White", :293/:408), weil das Abzeichen sonst auf einer roten oder
   orangen Kachel verschwindet.
   Der Zaehler ragt bewusst ueber die Kachelecke hinaus (:259 TranslationX="6"
   TranslationY="-6") - deshalb sitzt er am .app-symbol und nicht im Symbol selbst,
   das overflow:hidden traegt. */
.app-abzeichen {
  position: absolute; top: -6px; right: -6px;
  min-width: 20px; height: 20px;     /* :258 HeightRequest/MinimumWidthRequest="20" */
  padding: 0 5px;                    /* :258 Padding="5,0" */
  border-radius: 10px;               /* :258 StrokeShape="RoundRectangle 10" */
  border: 1px solid #FFFFFF;         /* :293/:408 Stroke="White" (Staerke 1 ist MAUIs Vorgabe) */
  background: #FF3B30;               /* :257 - die einzige Farbe des Kachelblocks ohne Token */
  color: #FFFFFF;
  font-size: 11px; font-weight: 700; /* :260 FontSize="11" FontAttributes="Bold" */
  line-height: 18px;                 /* 20px Hoehe minus die zwei 1px-Raender */
  text-align: center;
  pointer-events: none;              /* der Klick gehoert der Kachel darunter */
}

/* Anordnen-Modus im Dock (Fund des Auftraggebers: "die Apps im Dock lassen sich nicht
   wie alle anderen anordnen") - HomeEditTap (:2854) nimmt Dock-Kacheln genauso an wie
   Rasterkacheln. Wackeln und Auswahl-Sprung kommen unveraendert aus home.css
   (.app-tile-wiggle/.app-tile-ausgewaehlt, ueber den @import ganz oben global
   verfuegbar) - NUR der Auswahl-Rahmen braucht hier eine eigene Zeile: die Rasterregel
   setzt border-color/-width auf .app-icon, das bereits einen Rahmen traegt, .dock-icon
   dagegen hat gar keinen, ein blosser Farbwechsel bliebe also unsichtbar. 2.5px wie im
   Raster (:2864, StrokeThickness=2.5); dank box-sizing:border-box (ganz oben) bleibt
   die Kachel dabei exakt 60px breit. */
.app-tile-ausgewaehlt .dock-icon { border: 2.5px solid var(--trap-accent); }
/* Geisterplatz im Dock: 60px statt der 65px des Rasters (.app-tile-ghost, home.css),
   sonst wie dort. Im Ist-Code gibt es ihn nicht (ShowEditGhostSlots laeuft nur bis
   DockStart, :2906) - ohne ihn bleibt ein Dockplatz, von dem eine App ins Raster
   getauscht wurde, dauerhaft leer und unerreichbar. */
.dock-item-ghost {
  width: 60px; height: 60px;
  border-radius: 16px;
  align-self: center;   /* .app-tile-ghost bringt align-self:start mit - richtig im Raster,
                           im Flex-Dock stuende der Platz damit ueber den Icons statt neben ihnen */
}

/* "Kommt noch"-Optik: Ziel existiert in Blazor noch nicht - abgedunkelt statt
   anklickbar, kein Zeiger-Cursor (ein Knopf, der aussieht wie ein Knopf und
   nichts tut, waere schlechter). Geteilte Klasse (Whole-Wave-Review-Fund):
   bis hierhin dieselben zwei Deklarationen dreifach wortgleich in .dock-item-
   spaeter (PhoneDock.razor, Kommentar unten), .app-tab-spaeter (AppTabs.razor)
   UND .mail-link-spaeter (mail.css, Pages/Mail.razor - seit Welle C/Task 7
   weg, "/phl" existiert jetzt) - genau der Fall, den
   die PLATZIERUNGSREGEL oben als "mehr als eine Seite benutzt" meint. EINE
   Klasse hier statt drei Kopien; jede Aufrufstelle traegt jetzt zusaetzlich
   zu ihrer eigenen (weiterhin fuer Markup-Lesbarkeit/moegliche
   Sonderselektoren erhaltenen) Klasse "trap-spaeter". */
.trap-spaeter { cursor: default; opacity: .4; }

/* ── App-Tabs (AppShell.xaml Zeilen 13-27) ────────────────────────────────
   Components/AppTabs.razor - gedacht als App-Ebene AUSSERHALB des simulierten
   Telefons (nicht zu verwechseln mit .phone-dock, dem Dock IM Telefon) - das
   ist aber bislang nur ABSICHT, keine Beschreibung des Ist-Zustands (Fix,
   Whole-Wave-Review: dieser Kommentar behauptete faelschlich, es sei bereits
   so). Tatsaechlich rendert GameHost.razor <PhoneDock /> und <AppTabs /> heute
   als GESCHWISTER innerhalb von .phone-root - .app-tabs liegt im DOM genauso
   "im Telefon" wie .phone-dock, nur eine Flex-Reihe weiter unten. Die
   getrennte Ebene entsteht erst, wenn eine kuenftige Welle einen Desktop-
   "Telefonrahmen" baut (siehe PLATZIERUNGSREGEL oben in dieser Datei) und
   .app-tabs aus .phone-root heraus- und davor setzt. Schale, die GameHost auf
   JEDER Nicht-Vollbild-Route einhaengt, deshalb hier statt in einer
   Seiten-Datei. */
.app-tabs {
  flex: 0 0 auto;
  display: flex; justify-content: space-around; align-items: center;
  /* .app-tabs ist im Ist-DOM das UNTERSTE Element von .phone-root (siehe
     Kommentar oben) - der Safe-Area-Inset gehoert deshalb hierher, nicht (mehr)
     zu .phone-dock (siehe dortiger Kommentar zum entfernten env()). */
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
  background: rgba(var(--trap-surface-rgb), .92);
  border-top: 1px solid var(--trap-border);
}
.app-tab {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: none; border: 0; color: inherit;
  font: inherit; cursor: pointer; padding: 4px 10px;
}
.app-tab-icon { font-size: 20px; }
.app-tab-name { font-size: 10px; font-weight: 700; color: var(--trap-text-muted); }

/* Aktives Ziel (AppTabs.IstAktiv) hervorgehoben ueber denselben Akzentton
   wie batt-ok in der Statusleiste. */
.app-tab-aktiv .app-tab-icon,
.app-tab-aktiv .app-tab-name { color: var(--trap-accent); }

/* Ziel existiert in Blazor noch nicht (Route null, siehe AppTabs.razor) -
   dieselbe "kommt noch"-Optik wie oben beim Dock, ueber die geteilte Klasse
   .trap-spaeter (siehe dortiger Kommentar) statt einer eigenen Kopie der
   zwei Deklarationen. */

/* ── Modale (Components/ConfirmDialog.razor + jedes seitenlokale Modal, das
   demselben Markup-Muster folgt) ─────────────────────────────────────────
   Hierher gewandert aus settings.css (Welle A3/Task 1, ehemals
   .settings-modal-*): die vier seitenlokalen Modale aus Welle A2/Task 2
   (Sprachauswahl/Gesperrtes-Wallpaper/Umbenennen/Zuruecksetzen) waren zwar
   in Settings.razor entstanden, ihr CSS betraf aber nie etwas Seiten-
   Spezifisches - reines Overlay-/Panel-/Knopf-Markup, das JEDE Seite
   braucht. Die PLATZIERUNGSREGEL oben in dieser Datei sagt genau das: Stile
   fuer eine geteilte Komponente aus Components/ gehoeren hierher, nicht in
   eine <seitenname>.css. Seit Task 1 nutzt Components/ConfirmDialog.razor
   diese Klassen fuer den wiederverwendbaren Bestaetigungsdialog (Reset-
   Ablauf, beide Stufen); Settings.razor traegt ausserdem weiterhin drei
   eigene, seitenlokale Modal-Markup-Bloecke (Sprachauswahl/Gesperrtes-
   Wallpaper/Umbenennen), die dieselben .modal-*-Klassen wiederverwenden. */
:root {
  /* GameDialog.cs:309 (BuildButtonRow): "#10151B" ist dort ein FESTER
     Literal, der IMMER verwendet wird, wenn die wahrgenommene Helligkeit des
     Akzents ueber 0.5 liegt - nicht aus der jeweiligen Akzentfarbe berechnet.
     Sowohl dangerRed (#FF5252, --modal-danger-accent unten) als auch
     DefaultAccent (#00E676, --trap-accent) landen ueber der Schwelle, tragen
     also denselben Text-Ton - ein Token statt zwei fuer denselben
     Ist-Code-Literal. Siehe Hausregel unten (".modal-primary"). */
  --modal-confirm-text-dark: #10151B;
  --modal-danger-accent: #FF5252; /* SettingsPage.xaml.cs:332 (dangerRed) - Akzent jeder Gefahr-Bestaetigung (ConfirmAsync IstGefahr) */

  /* Welle C/Task 2 - die zwei Werte, die ConfirmDialog's neuer Bild-/Zeilenblock
     braucht. Beide stammen aus GameDialog.cs selbst und haben KEINE Entsprechung
     im gemeinsamen Farbsystem (--trap-surface ist #121418, --trap-border
     #232730 - beide sichtbar daneben), deshalb hier als eigene Modal-Token
     neben den zwei bestehenden statt als vermeintlich passendes trap-Token. */
  --modal-hero-bg: #0F1115;       /* GameDialog.cs:253 (BuildHero, Bildhintergrund) */
  --modal-divider: #232B36;       /* GameDialog.cs:30 (DividerColor, die zwei BoxViews um die Detailzeilen) */
}

/* HAUSREGEL (Whole-Wave-Review, Welle A2 Nachbesserung; hierher verschoben
   Welle A3/Task 1) - vier seitenlokale Modale gab es schon, sie sahen aber
   je nach Autor unterschiedlich aus. Diese Regel gilt fuer JEDES Modal, das
   GameDialog.ConfirmAsync/AlertAsync/PromptAsync/ChooseAsync ersetzt, egal
   ob ueber ConfirmDialog.razor oder eigenes Seiten-Markup:

   1. Knopfreihen-Form richtet sich NACH DER GameDialog-METHODE, die dieses
      Modal ersetzt (siehe Kommentar an der jeweiligen Markup-Stelle in der
      aufrufenden Seite fuer die genaue Fundstelle):
        - AlertAsync (ein OK, kein Cancel) -> EIN Knopf, .modal-primary,
          direktes Kind von .modal (kein Grid noetig - flex-direction:column
          streckt ihn ohnehin auf volle Breite, genau wie BuildButtonRow's
          Ein-Spalten-Grid bei cancelText=null).
        - ConfirmAsync/PromptAsync (BEIDE gehen durch BuildButtonRow,
          GameDialog.cs:298-339) -> .modal-buttons (1fr-1fr-Grid), Abbrechen
          LINKS, Bestaetigen RECHTS - NIE gestapelt. ConfirmDialog.razor
          setzt das fest um, kein Aufrufer kann davon abweichen.
        - ChooseAsync (Options-Liste + eigener Cancel-Knopf ausserhalb von
          BuildButtonRow, GameDialog.cs:228-238) -> mehrere .modal-option-
          Knoepfe untereinander, DANACH ein eigenstaendiger .modal-cancel
          als letzter Knopf.
      Zwei optisch aehnliche Layouts (gestapelte Liste vs. gestapeltes
      OK/Cancel-Paar) sind NICHT dasselbe Muster - welche GameDialog-Methode
      das Original nutzt, entscheidet, nicht wie das Vorgaenger-Modal aussah.

   2. "Primary" ist GENAU EINE Klasse: .modal-primary (Akzent-Hintergrund,
      dunkler Text). Jeder Knopf, der in GameDialog.BuildButtonRow auf
      `accent` sitzt (= das Bestaetigen/OK, NIE Cancel), bekommt sie - auch
      wenn `accent` in diesem Aufruf der Standardwert ist (kein `accent:`-
      Parameter uebergeben). Eine rote/gefahr-akzentuierte Variante bekommt
      eine EIGENE Klasse (.modal-confirm-danger) statt einer dritten Kopie
      derselben Rolle - beide teilen sich trotzdem --modal-confirm-text-dark
      (siehe Token-Kommentar oben). .modal-option ist NICHT primary - sie
      gehoert ausschliesslich den Options-Zeilen von ChooseAsync-Modalen.
      .modal-cancel ist IMMER Cancel, nie Confirm - unabhaengig vom Layout.

   3. Jeder :disabled-Zustand braucht eine SICHTBARE Aenderung (nicht nur
      das Attribut) - siehe Regel unten. Ein Knopf, der wie aktiv aussieht,
      aber Klicks verschluckt (z. B. ConfirmDialog's SperreMs gegen einen
      Doppelklick), ist ein UX-Fehler, kein Implementierungsdetail. */
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(var(--trap-bg-rgb), .7);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  /* 50, ueber ALLEN anderen Overlays dieses Projekts (bisher hoechster Wert: 40,
     home.css's .home-menu-root/.home-wallpaper-root, Welle B1/Task 6) - ein Modal
     muss unter jedem denkbaren Aufrufer oben liegen. KONKRET GEFUNDEN (Playwright,
     Task 6): HomeHintergrundSheet.razor zeigt ihr gesperrtes-Wallpaper-Modal
     WAEHREND das Blatt selbst offen bleibt (Ist-Code-Aequivalent: GameDialog.AlertAsync
     ueber der weiterhin offenen WallpaperSheetOverlay) - mit dem vorherigen Wert 30
     lag der Modal-Knopf unter dem Blatt-Verdunkler und war unklickbar (Playwright
     meldete "intercepts pointer events" auf .home-menu-dim), bis dieser Wert erhoeht
     wurde. Vorher war kein bestehendes Modal je gleichzeitig mit einem Overlay >=30
     offen - dieser Konflikt existierte schlicht noch nicht. */
  z-index: 50;
}
.modal {
  width: 100%; max-width: 320px;
  background: var(--trap-surface);
  border: 1px solid var(--trap-border);
  border-radius: 14px;
  padding: 18px;
  display: flex; flex-direction: column; gap: 10px;
}
.modal-head { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 700; color: #fff; }
.modal-emoji { font-size: 18px; }
.modal-message { white-space: pre-line; font-size: 13px; color: var(--trap-text-muted); margin: 0; }

/* ── Aufmacherbild + Detailzeilen (Welle C/Task 2) ──────────────────────────
   Beides gehoert zu ConfirmDialog.razor, also nach app.css (PLATZIERUNGSREGEL
   oben: geteilte Komponente aus Components/). Quelle: GameDialog.BuildHero
   (GameDialog.cs:246-264) und der rows-Block von ConfirmAsync (:73-87).

   HERO: HeightRequest 140 ("bewusst flach: Panel + Detailzeilen muessen auf
   kleine Displays passen", Ist-Kommentar :251), CornerRadius 12, Hintergrund
   #0F1115, Rahmen = Akzent bei 35 % Deckkraft. Der Akzent ist im Ist-Code je
   Aufruf frei waehlbar; ConfirmDialog kennt heute nur den Standardakzent
   (siehe PHASE-2-SCHULDEN.md, "ConfirmDialog kann nur gruen und Gefahr") -
   deshalb hier fest --trap-accent-rgb statt einer weiteren Custom Property,
   die kein Aufrufer setzen koennte. Aspect=AspectFill -> object-fit: cover
   (das Motiv gehoert in die Bildmitte, Ist-Kommentar :247). */
.modal-hero {
  height: 140px;
  border-radius: 12px;
  border: 1px solid rgba(var(--trap-accent-rgb), .35);
  background: var(--modal-hero-bg);
  overflow: hidden;
}
.modal-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ZEILEN: Beschriftung links gedaempft (FontSize 12, TextMuted #8F96AC =
   --trap-text-muted, GameDialog.cs:81), Wert rechts fett und rechtsbuendig
   (FontSize 13, Standard weiss, :82). Die zwei BoxViews um den Block
   (:75/:86, DividerColor #232B36) sind hier Rahmenlinien statt eigener
   Elemente - dasselbe Bild, ein Element weniger. */
.modal-zeilen {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--modal-divider);
  border-bottom: 1px solid var(--modal-divider);
}
.modal-zeile { display: flex; align-items: center; gap: 10px; }
.modal-zeile-name { flex: 0 0 auto; font-size: 12px; color: var(--trap-text-muted); }
.modal-zeile-wert { flex: 1 1 auto; text-align: right; font-size: 13px; font-weight: 700; color: #fff; }

/* ── Warnhinweis (Welle C/Task 6, ConfirmDialog.razor's Warn-Parameter) ──────
   Quelle: GameDialog.cs:89-98 (ConfirmAsync's warn-Block) - eigene, dunklere
   Border unter den Detailzeilen statt einer weiteren .modal-zeile: Hintergrund
   #1F1710, Rahmen #4A3A1A, Text #FFB74D (Warnorange), FontSize 11. Erster
   Aufrufer: TogglePrime (Trapazon.razor, "Der erste Monat wird sofort
   abgebucht..."). */
.modal-warn {
  background: #1F1710;
  border: 1px solid #4A3A1A;
  border-radius: 8px;
  padding: 8px 10px;
  color: #FFB74D;
  font-size: 11px;
  line-height: 1.4;
}
/* Umbenennen-Modal (PromptAsync-Aequivalent, GameDialog.cs Entry-Feld :140-151,
   Settings.razor) - eigene Umsetzung, bestehende Token statt GameDialog.cs'
   eigener Entry-Palette. */
.modal-input {
  height: 46px;
  border-radius: 10px;
  border: 1px solid var(--trap-border);
  background: var(--trap-bg);
  color: #fff;
  font-size: 14px;
  padding: 0 12px;
  outline: none;
}
.modal-option {
  height: 44px;
  border-radius: 10px;
  border: 1px solid var(--trap-border);
  background: var(--trap-surface-alt);
  color: #fff;
  font-size: 13px; font-weight: 700;
  cursor: pointer;
}
/* ── Optionsliste von Components/AuswahlDialog.razor (Welle C/Task 2) ────────
   GameDialog.ChooseAsync stapelt seine Optionen mit Spacing 8 und deckelt sie
   erst ab sechs Eintraegen per ScrollView (GameDialog.cs:190/214-226). Der
   Grund fuer die Schwelle steht dort woertlich - WinUIs ScrollViewer misst
   unbegrenzt breit und schnitt kurze Listen ab. CSS hat dieses Problem nicht
   (die Breite bleibt die des Elternelements), deshalb gilt die Deckelung hier
   IMMER statt erst ab einer Anzahl; die 320 px sind die MaximumHeightRequest
   aus GameDialog.cs:223. */
.modal-optionen {
  display: flex; flex-direction: column; gap: 8px;
  max-height: 320px;
  overflow-y: auto;
}
/* Titel links, Detail rechts - das ist WOERTLICH GameDialog.cs:194s Raster
   (Spalte 0 = Star, Spalte 1 = Auto, ColumnSpacing 8): der Titel nimmt den
   Rest, das Detail nur seine eigene Breite am rechten Rand. Eigene Klasse
   statt .modal-option umzubauen, damit Settings.razor's Sprachauswahl (die
   .modal-option ohne Kinder-Spans nutzt, Welle A2/Task 2) ihre zentrierte
   Beschriftung behaelt. */
.modal-option-zeile {
  display: flex; align-items: center; gap: 8px;
  padding: 0 14px;                                  /* Thickness(14, 12), GameDialog.cs:205 - Hoehe kommt aus .modal-option */
  text-align: left;
}
.modal-option-titel  { flex: 1 1 auto; }            /* Spalte 0, Star */
.modal-option-detail {
  flex: 0 0 auto;                                   /* Spalte 1, Auto */
  color: var(--trap-accent);                        /* GameDialog.cs:197 - Detail traegt die Akzentfarbe (DefaultAccent #00E676) */
}
.modal-cancel {
  height: 44px;
  border-radius: 10px;
  border: 0;
  background: var(--trap-border);
  /* Eigene Modal-Umsetzung (nicht aus SettingsPage.xaml uebernommen) -
     bestehendes Token statt eines aus GameDialog.cs uebernommenen Extra-Werts. */
  color: var(--trap-text-muted);
  font-size: 13px;
  cursor: pointer;
}
/* "Primary" (Hausregel oben, Punkt 2) - JEDER Bestaetigen/OK-Knopf eines
   Modals mit Standard- (nicht Gefahr-) Akzent traegt diese eine Klasse, ob er
   allein steht (AlertAsync) oder neben einem Cancel-Knopf (PromptAsync).
   --modal-akzent (Fix-Runde 1, Welle C/Task 4, CSS-Kaskaden-Befund): ein
   Dialog mit einer VOM Standard abweichenden Akzentfarbe (ConfirmDialog.razor's
   Akzent-Parameter, Store.razor's eigene _hinweis-Modals) setzt diese Variable
   per Inline-style auf seinem Wurzelelement (.modal) - der var()-Fallback
   --trap-accent greift unveraendert fuer jeden Aufrufer, der nichts setzt.
   EIGENS UNABHAENGIG VON DER @import-REIHENFOLGE: eine seiten-eigene CSS-
   KLASSE (der fruehere Versuch) verliert hier immer gegen diese Regel, weil
   @import-Inhalte im finalen Stylesheet-Strom vor dem Hauptteil DIESER Datei
   stehen - eine geerbte CSS-VARIABLE kennt dieses Problem nicht. */
.modal-primary {
  height: 44px;
  border-radius: 10px;
  border: 0;
  background: var(--modal-akzent, var(--trap-accent));  /* GameDialog.cs DefaultAccent, #00E676 */
  color: var(--modal-confirm-text-dark);             /* siehe Token-Kommentar oben */
  font-size: 13px; font-weight: 700;
  cursor: pointer;
}
/* Sichtbare Aenderung fuer JEDEN zeitgesperrten/gerade-nicht-klickbaren
   Bestaetigen-Knopf (Hausregel oben, Punkt 3) - trifft .modal-primary und
   .modal-confirm-danger, wenn ConfirmDialog.razor's SperreMs gerade laeuft
   (siehe DialogSperre.cs, TrapPhone.Core/Services). Vorher gab es KEINE
   einzige :disabled-Regel fuer diese Klassen - ein gesperrter Knopf sah
   exakt wie ein aktiver aus und verschluckte Klicks kommentarlos. */
.modal-primary:disabled,
.modal-confirm-danger:disabled {
  opacity: .5;
  cursor: not-allowed;
}

/* ── Zuruecksetzen-Bestaetigungen (GameDialog.ConfirmAsync-Aequivalent) UND
     Umbenennen (PromptAsync-Aequivalent) - BEIDE gehen im Ist-Code durch
     GameDialog.BuildButtonRow (GameDialog.cs:298-339): Abbrechen links /
     Bestaetigen rechts, zwei gleich breite Spalten. .modal-cancel oben wird
     fuer die linke Spalte wiederverwendet, rechts steht je nach Akzent
     .modal-primary (Umbenennen, Standardakzent) oder .modal-confirm-danger
     (Reset, dangerRed ueber ConfirmDialog's IstGefahr) - siehe Hausregel
     oben. */
.modal-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.modal-confirm-danger {
  height: 44px;
  border-radius: 10px;
  border: 0;
  background: var(--modal-danger-accent);           /* #FF5252, SettingsPage.xaml.cs:332 */
  color: var(--modal-confirm-text-dark);            /* siehe Token-Kommentar oben */
  font-size: 13px; font-weight: 700;
  cursor: pointer;
}

/* ── Wallpaper-Auswahl (Components/WallpaperAuswahl.razor, Welle B1/Task 6) ──
   Katalog aus TrapPhone.Core/Services/Hintergrundbild.cs (MainPage.xaml.cs:
   2977-3083, BuildWallpaperThumbs/BuildWallpaperThumb - selbst ein Zitat auf
   SettingsPage.Wallpapers/CurrentWallpaper()). ZWEITE Seite, die diese Kachel-
   Optik braucht (Settings.razor UND Home.razor's Hintergrundbild-Blatt via
   HomeHintergrundSheet.razor) - PLATZIERUNGSREGEL oben in dieser Datei greift
   hier woertlich ("mehr als eine Seite benutzt"). Vorher eine wortgleiche
   PRIVATE Kopie in settings.css (.settings-wallpaper-*, Welle A2/Task 2) -
   hierher gezogen, BEVOR eine zweite Kopie entsteht (siehe HexFarbe/Wetter,
   Task 3, fuer dasselbe Muster an anderer Stelle dieser Welle). Masse und Farben
   unveraendert aus der frueheren settings.css-Fassung uebernommen - die Zitate in
   diesem Block zeigen deshalb auf SettingsPage.xaml.cs, NICHT auf MainPage.xaml.cs.
   Das Blatt auf dem Homescreen sieht dadurch aus wie die Einstellungsseite und nicht
   wie im Ist-Code (dort gruen statt blau, andere Kachelmasse) - festgehalten als
   Schuldeneintrag, siehe docs/superpowers/PHASE-2-SCHULDEN.md. */
.wallpaper-scroll-row {
  display: flex;
  gap: 12px;
  padding: 4px 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.wallpaper-tile {
  position: relative;
  flex: 0 0 auto;
  width: 68px; height: 110px;
  border-radius: 12px;
  border: 2.5px solid var(--trap-border);          /* #232730 unselektiert - SettingsPage.xaml.cs:290, NICHT MainPage: dort ist der unselektierte Rand #282E39 (:3052) */
  background: var(--trap-surface-alt);             /* #161A20, :3057 */
  padding: 0;
  overflow: hidden;
  cursor: pointer;
}
.wallpaper-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wallpaper-tile-aktiv { border-color: var(--trap-accent-blue); }   /* #2AABEE selektiert - SettingsPage.xaml.cs:290. MainPage faerbt an derselben Stelle #00E676 (:3052); diese gemeinsame Komponente traegt die Settings-Optik, siehe Schuldeneintrag */
.wallpaper-default { display: grid; place-items: center; }
.wallpaper-default-label {
  white-space: pre-line;                            /* "Standard\nFarbe" / "Default\ncolor", :2997 */
  font-size: 11px; font-weight: 700; text-align: center;
  color: var(--trap-text-muted);                    /* #8F96AC, :2998 */
  padding: 0 6px;
}
.wallpaper-check {
  position: absolute; top: 5px; right: 5px;
  width: 20px; height: 20px;
  border-radius: 10px;
  background: var(--trap-accent-blue);              /* #2AABEE - SettingsPage.xaml.cs:253. MainPage.xaml.cs:3016 ist #00E676 */
  color: var(--trap-bg);                             /* #0B0D11, :3024 */
  font-size: 11px; font-weight: 700;
  display: grid; place-items: center;
}
.wallpaper-lock {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px;
  /* "#CC0B0D11" (BuildWallpaperTile:3033) - MAUI liest 8-stelliges Hex als AARRGGBB:
     0xCC=204/255=80% Deckkraft auf #0B0D11 == --trap-bg. Woertlich als CSS-Hex
     kopiert waere das RRGGBBAA (siehe .status-bar-Kommentar oben fuer dieselbe
     Falle) - deshalb ueber das --trap-bg-rgb-Begleittoken statt eines Hex-Literals. */
  background: rgba(var(--trap-bg-rgb), .8);
}
.wallpaper-lock-icon { font-size: 22px; }
.wallpaper-lvl { font-size: 10px; font-weight: 700; color: #fff; }

/* Homescreen-Fassung (MainPage.xaml.cs:3049-3061, BuildWallpaperThumb). Dasselbe
   Blatt sieht im Ist-Code an den zwei Stellen unterschiedlich aus, weil es dort
   ZWEI getrennte Bauroutinen sind (SettingsPage.xaml.cs:287-296 und die hier
   zitierte) - eine Komponente mit einem Modifikator statt einer zweiten Kopie.
   Die Zitate dieses Blocks zeigen deshalb auf MainPage.xaml.cs, die des Blocks
   darueber auf SettingsPage.xaml.cs.
   Die Reihenfolge traegt: .wallpaper-home .wallpaper-tile-aktiv steht NACH
   .wallpaper-home .wallpaper-tile und hat dieselbe Spezifitaet - das schlichte
   .wallpaper-tile-aktiv darueber wuerde sonst verlieren. */
.wallpaper-home .wallpaper-tile {
  width: 78px; height: 126px;                      /* :3054-3055 (Einstellungen: 68 x 110) */
  border-radius: 16px;                             /* :3053 (12) */
  border-width: 1px;                               /* :3051 - unselektiert duenner; die Einstellungen nehmen immer 2.5 */
  border-color: #282E39;                           /* :3052 (#232730) */
}
.wallpaper-home .wallpaper-tile-aktiv {
  border-width: 2.5px;                             /* :3051 */
  border-color: var(--trap-accent);                /* #00E676, :3052 (#2AABEE) */
  /* Shadow{ Brush #00E676, Radius 14, Opacity .5 } (:3060-3061) - auf der
     Einstellungsseite gibt es diesen Schein gar nicht. */
  box-shadow: 0 0 14px rgba(var(--trap-accent-rgb), .5);
}
.wallpaper-home .wallpaper-check { background: var(--trap-accent); }  /* :3016 (#2AABEE) */
.wallpaper-home .wallpaper-lock-icon { font-size: 16px; }             /* :3042 (22) */

/* ── Benachrichtigungsleiste (MainPage.xaml:898-1004, "NOTIFICATION SHADE")
   Components/Benachrichtigungsleiste.razor (Welle B1/Task 5) - Schale, die GameHost
   wie PhoneStatusBar/PhoneDock/AppTabs auf JEDER Nicht-Vollbild-Route einhaengt
   (SCHALE-REGEL, GameHost.razor; siehe Komponentenkopfkommentar dort fuer die
   Begruendung, warum das trotz Ist-Code-Bindung an nur die Homescreen-Seite
   richtig ist), deshalb hier statt in einer eigenen, per @import eingehaengten
   Datei (PLATZIERUNGSREGEL oben in dieser Datei). */
.shade-root {
  position: absolute;
  inset: 0;
  /* Ueber .overlay-layer (Banner/Toasts, DOM-Reihenfolge waere sonst darunter) -
     .modal-backdrop (Zeile ~421 dieser Datei) steht seit Welle B1/Task 6 auf 50 (ueber den Homescreen-Ueberlagerungen auf 40); der frueher hier vermerkte offene Punkt ist damit erledigt. Alte Notiz: die
     Sperre gegen "Leiste + Dialog gleichzeitig offen" ist noch nicht verdrahtet
     (PhoneStatusBar.razor's Kommentar zu HomeMenuOverlay/WallpaperSheetOverlay -
     kommt mit Task 6). */
  z-index: 35;
}

/* ShadeDim (:902-907): BoxView Color="Black". rgba() ueber --shadow-black-rgb
   (tokens.css) statt eines Dezimal-Literals - CssGuardTests verlangt das fuer
   jede rgba()/rgb() ausserhalb von tokens.css. Basisregel = GESCHLOSSEN (0,
   180ms - dieselbe Dauer wie CloseShadeAsync's FadeToAsync(0,180), :3620);
   .shade-dim-offen = GEOEFFNET (.55, 200ms - OpenShadeAsync's FadeToAsync(0.55,200),
   :3610). Easing UNSPEZIFIZIERT an beiden FadeToAsync-Aufrufen -> MAUI-Standard
   Easing.Linear (per Microsoft-Doku verifiziert, siehe phone.css's
   .phone-detail-Kommentar fuer die Fundstelle/Zitat), deshalb "linear" statt
   "ease". UiAnim.RunAsync setzt .shade-dim-offen beim Oeffnen, UiAnim.SwapAsync
   entfernt sie beim Schliessen (Klassenkommentar UiAnim.cs, "Rueckweg"-Fall). */
.shade-dim {
  position: absolute;
  inset: 0;
  /* Reines Schwarz (--shadow-black, tokens.css) OHNE rgba()/-rgb-Begleittoken:
     MAUIs "Opacity" ist eine eigene VisualElement-Eigenschaft, multipliziert
     UEBER die Farbe (Color="Black" bleibt konstant undurchsichtiges Schwarz,
     Opacity blendet das GESAMTE Element ein/aus) - kein Alphakanal IN der
     Farbe selbst, deshalb genuegt hier die CSS-"opacity"-Eigenschaft auf einem
     schlicht deckenden Hintergrund statt einer rgba()-Deckkraft. */
  background: var(--shadow-black);
  opacity: 0;
  transition: opacity 180ms linear;
}
.shade-dim.shade-dim-offen {
  opacity: .55;
  transition: opacity 200ms linear;
}

/* ShadePanel (:909-923): TranslationY -680 (:3607/:3621, woertlich uebernommen -
   ein beliebig grosser Wert, der das Panel bei jeder Bildschirmhoehe dieses
   Projekts sicher aus dem sichtbaren Bereich schiebt, keine Bezugsgroesse zur
   tatsaechlichen Panel-Hoehe). Basisregel = GESCHLOSSEN (-680px, 240ms/CubicIn -
   CloseShadeAsync's TranslateToAsync(0,-680,240,Easing.CubicIn), :3621);
   .shade-panel-offen = GEOEFFNET (0, 280ms/CubicOut - OpenShadeAsync's
   TranslateToAsync(0,0,280,Easing.CubicOut), :3611). --ease-cubic-out/-in aus
   anim.css (muss mit UiAnim.Easing() uebereinstimmen, AnimCssTokensTests). */
.shade-panel {
  position: absolute;
  top: 0; left: 0; right: 0;
  transform: translateY(-680px);
  transition: transform 240ms var(--ease-cubic-in);
  border: 1px solid #2A3140;                                    /* :910 */
  /* :911, StrokeShape="RoundRectangle 0,0,26,26" - eckige Oberkante, runde Unterkante.
     Die genaue Reihenfolge von MAUIs vierwertiger CornerRadius (welche der beiden
     Nullen TopLeft/TopRight ist, welche der beiden 26 BottomLeft/BottomRight) aendert
     hier nichts am Ergebnis: beide Eckenpaare tragen ohnehin denselben Wert. */
  border-radius: 0 0 26px 26px;
  padding: 42px 18px 12px;                                      /* :911, Padding="18,42,18,12" (L,T,R,B) */
  background: linear-gradient(180deg, #151B26 0%, #0C1016 100%); /* :913-916 */
  /* Shadow Brush="Black" Radius="26" Opacity="0.55" Offset="0,10" (:918-919).
     Radius als CSS-Weichzeichnungsradius angenommen (kein dokumentierter 1:1-
     Umrechnungsfaktor in diesem Projekt bekannt - dieselbe Annahme wie
     phone.css's .phone-avatar-Kommentar, dort zuerst so vermerkt). */
  box-shadow: 0 10px 26px rgba(var(--shadow-black-rgb), .55);
  display: flex;
  flex-direction: column;
  gap: 14px;                                                    /* :924, VerticalStackLayout Spacing="14" */
}
.shade-panel.shade-panel-offen {
  transform: translateY(0);
  transition: transform 280ms var(--ease-cubic-out);
}

@media (prefers-reduced-motion: reduce) {
  .shade-dim, .shade-dim.shade-dim-offen,
  .shade-panel, .shade-panel.shade-panel-offen,
  .shade-gear, .shade-gear-drueckt {
    transition-duration: 1ms;
  }
}

/* Kopf (:926-954): Uhr/Datum, Akku, Zahnrad. */
.shade-header { display: flex; align-items: flex-start; gap: 10px; }
.shade-clockcol { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }  /* :928, Spacing="1" */
.shade-clock { color: #fff; font-size: 36px; font-weight: 700; }                                    /* :929 */
.shade-daterow { display: flex; align-items: center; gap: 6px; }                                     /* :930, Spacing="6" */
.shade-date { color: #8F96AC; font-size: 13px; }                                                     /* :931 */
.shade-dot  { color: #3E4757; font-size: 13px; }                                                      /* :932 */
.shade-net  { color: #5E6673; font-size: 13px; }                                                      /* :933, "TrapNet 5G" unlokalisiert */

.shade-battery { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; margin-top: 10px; }  /* :937, Spacing="4" Margin="0,10,0,0" */
/* #C6CCDA ist derselbe Literalwert wie .status-battery-pct (app.css oben, MainPage.xaml:81) -
   ZWEI eigene XAML-Label mit demselben Hex, keine gemeinsame Quelle: 11px/Bold hier (:938)
   gegen die 10px der Statusleiste (.status-battery, :80) - deshalb eine eigene Klasse statt
   der geteilten. */
.shade-battery-pct { color: #C6CCDA; font-size: 11px; font-weight: 700; }

.shade-gear {
  flex: 0 0 auto;
  width: 34px; height: 34px; border-radius: 17px;               /* :949 */
  background: #1A1F2A; border: 1px solid #242B38;                /* :949 */
  display: flex; align-items: center; justify-content: center;
  font-size: 15px;                                                /* :952 */
  cursor: pointer;
  transform: scale(1);
  transition: transform 70ms var(--ease-cubic-in);                /* Rueckweg (ScaleToAsync 1.0, CubicIn, 70ms) */
}
.shade-gear-drueckt {
  transform: scale(.85);                                          /* ScaleToAsync 0.85 */
  transition: transform 70ms var(--ease-cubic-out);                /* Hinweg (CubicOut, 70ms) */
}

/* Schnelleinstellungen (:956-986). */
.shade-quicksettings { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }  /* :957, ColumnSpacing="10" */
.shade-qs {
  background: #1A1F2A; border: 1px solid #242B38; border-radius: 16px;   /* Standardfarbe (Flug/Torch, :958/:979) */
  padding: 12px 0;                                                        /* Padding="0,12" */
  display: flex; flex-direction: column; align-items: center; gap: 4px;   /* Spacing="4" */
  cursor: pointer;
}
.shade-qs-an { background: #00A152; }   /* :965 (WLAN-Standard) / UpdateVpnQuickTile "verbunden" (:3677) */
.shade-qs-icon { font-size: 19px; }      /* :961/:968/:975/:982 */
.shade-qs-name { color: #fff; font-size: 9px; font-weight: 700; }  /* :962/:969/:976/:983 */

/* Benachrichtigungen (:988-1001). */
.shade-notif-head { display: flex; align-items: center; gap: 8px; margin-top: 2px; }  /* :989, ColumnSpacing="8" Margin="0,2,0,0" */
.shade-notif-title { color: #6B7280; font-size: 10px; font-weight: 700; letter-spacing: 1.5px; }  /* :990 */
.shade-notif-pill {
  background: #1B2E22; color: #00E676;                            /* :991-992 */
  font-size: 9px; font-weight: 700;
  border-radius: 8px; padding: 1px 6px;
}
.shade-notif-line { flex: 1; height: 1px; background: #232B3C; }   /* :994 */

.shade-notif-list { display: flex; flex-direction: column; gap: 8px; }  /* :996, Spacing="8" */
.shade-notif {
  display: flex; align-items: center; gap: 12px;                   /* :3799, ColumnSpacing="12" */
  background: #171D28;                                             /* :3848 */
  border: 1px solid #232730;                                        /* :3849, onTap == null */
  border-radius: 14px;                                              /* :3851 */
  padding: 10px 12px;                                                /* :3852, Padding="12,10" */
  cursor: default;
}
/* #2E3A4D statt #232730, wenn der Ist-Code hierfuer ein onTap haette (xaml.cs:3849).
   Der zweite Selektor ".shade-notif.trap-spaeter" ist mit dem Wicker-Fund entfallen:
   er galt der einen Zeile mit onTap-aber-ohne-portiertes-Ziel (Wicker), und die hat
   seither ihr Ziel - siehe Benachrichtigungsleiste.razor's ZeilenKlasse. */
.shade-notif.shade-notif-klickbar { border-color: #2E3A4D; }
.shade-notif-klickbar { cursor: pointer; }
.shade-notif-chip {
  flex: 0 0 auto;
  width: 38px; height: 38px; border-radius: 11px;                   /* :3821-3822 */
  background: #1E2633;                                              /* :3818 */
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.shade-notif-chip img { width: 100%; height: 100%; object-fit: cover; }
.shade-notif-chip span { font-size: 18px; }                          /* :3814 */
.shade-notif-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }  /* :3833, Spacing="2" */
.shade-notif-titlerow { display: flex; align-items: center; gap: 6px; }  /* :3829, Spacing="6" */
.shade-notif-titel { color: #fff; font-weight: 700; font-size: 13px; }   /* :3830 */
.shade-notif-jetzt { color: #5E6673; font-size: 11px; }                  /* :3831 */
.shade-notif-text { color: #9CA3AF; font-size: 11px; overflow-wrap: break-word; }  /* :3835 */
.shade-notif-chevron { flex: 0 0 auto; color: #6B7280; font-size: 22px; font-weight: 700; }  /* :3841 */

/* Griff zum Schliessen (:999-1001). */
.shade-handle {
  width: 44px; height: 4px; border-radius: 2px;                     /* :999 */
  background: #4A5468;
  margin: 4px auto 0;                                                 /* Margin="0,4,0,0", HorizontalOptions="Center" */
  cursor: pointer;
}

/* ── Abrechnungswarnung / Abrechnungsbericht (Components/Tagesereignisse.razor,
     Welle B2/Task 2) ────────────────────────────────────────────────────────
   MainPage.xaml:801-841 (BILLING WARNING OVERLAY) und :843-896 (BILLING REPORT
   TOAST), gefuellt in MainPage.xaml.cs:3443-3491 bzw. :3495-3557, Kostenzeilen
   aus :3559-3589 (BuildBillingRow). Schale, die GameHost auf JEDER Nicht-
   Vollbild-Route einhaengt - deshalb hier statt in einer Seiten-Datei
   (PLATZIERUNGSREGEL oben in dieser Datei).

   MAUI-Thickness-Lesart, die unten mehrfach vorkommt: zwei Werte sind
   (horizontal, vertikal), vier Werte (links, oben, rechts, unten) - CSS dreht
   die Zweierform um (oben/unten zuerst). */
:root {
  /* MainPage.xaml:803 (Stroke der Warnkarte), :813 (Titel), :836 (Knopf) und
     MainPage.xaml.cs:3461/:3471 (Kontostand/Hinweis, wenn die Abrechnung NICHT
     gedeckt ist) - derselbe Orangeton an fuenf Stellen. Eigenes Token-Paar statt
     eines Literals, weil der Kartenschatten (:807, Shadow Brush="#FF9800"
     Opacity="0.25") ihn mit Deckkraft braucht und CssGuardTests jedes rgba()
     ausserhalb tokens.css an ein "-rgb"-Token bindet. NICHT in tokens.css: der
     Ton hat keine Entsprechung in Resources/Styles/Colors.xaml (dessen einziger
     Daseinsgrund laut Kopfkommentar dort) - --trap-warning ist #FFC107, ein
     anderer Wert. Gleiche Ablage-Entscheidung wie --modal-danger-accent oben. */
  --abrechnung-warn-akzent: #FF9800;
  --abrechnung-warn-akzent-rgb: 255, 152, 0;
}

/* Gemeinsam genutzt von Warnung und Bericht. */
.abrechnung-kopf { display: flex; align-items: center; justify-content: center; gap: 12px; }
.abrechnung-kopf-text { display: flex; flex-direction: column; gap: 2px; }

/* Eine Kostenzeile: Name links (abgeschnitten, LineBreakMode="TailTruncation",
   xaml.cs:3575), Betrag rechts (xaml.cs:3577-3584). */
.abrechnung-zeile { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.abrechnung-zeile-name {
  color: #9CA3AF;                                  /* xaml.cs:3572 */
  font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.abrechnung-zeile-betrag {
  flex: 0 0 auto;
  color: var(--trap-danger);                       /* #FF4444, xaml.cs:3580 */
  font-size: 12px; font-weight: 700;
  margin-left: 10px;                               /* Margin="10,0,0,0", xaml.cs:3584 */
}

/* Drei Zustandsfarben, die BEIDE Bloecke aus dem Spielerzustand ableiten
   (xaml.cs:3460-3471 bzw. :3513-3525). Setzen ausschliesslich "color" und
   ueberschreiben damit keine Groesse/Staerke der Basisklasse. */
.abrechnung-gruen  { color: var(--trap-accent); }                 /* #00E676 */
.abrechnung-orange { color: var(--abrechnung-warn-akzent); }      /* #FF9800 */
.abrechnung-rot    { color: var(--trap-danger); }                 /* #FF4444 */

/* ── Warnung (MainPage.xaml:801-841) ─────────────────────────────────────── */
/* :802, BackgroundColor="#CC000000". ACHTSTELLIGES MAUI-HEX = AARRGGBB:
   #CC = 204/255 = 80 % Deckkraft auf reinem Schwarz. Woertlich als CSS-Hex
   waere das RRGGBBAA - rgb(204,0,0) bei 0 % Alpha, also gar nichts. Deshalb
   ueber --shadow-black-rgb (tokens.css), wie CssGuardTests es verlangt. */
.abrechnung-warn-root {
  position: absolute;
  inset: 0;
  background: rgba(var(--shadow-black-rgb), .8);
  align-items: center; justify-content: center;    /* VerticalOptions="Center", :805 */
  padding: 60px 20px;                              /* Margin="20,60" der Karte, :803 */
  z-index: 50;                                     /* wie .modal-backdrop - ein Modal liegt ueber allem */
  /* :3475-3479 - Verdunkler blendet 0->1 in 200ms ein. FadeToAsync OHNE
     Easing-Parameter -> MAUI-Standard ist Easing.Linear, in diesem Projekt
     bereits per Microsoft-Doku belegt (phone.css, Kopfkommentar von
     .phone-detail-offen) - NICHT "ease". */
  opacity: 0;
  transition: opacity 200ms linear;
}
.abrechnung-warn-root.abrechnung-warn-offen { opacity: 1; }

.abrechnung-warn-card {
  width: 100%;
  background: #131C17;                             /* :803 */
  border: 1.5px solid var(--abrechnung-warn-akzent);  /* :803-:804, StrokeThickness="1.5" */
  border-radius: 20px;                             /* :804, StrokeShape="RoundRectangle 20" */
  padding: 18px 20px;                              /* :805, Padding="20,18" */
  display: flex; flex-direction: column; gap: 12px; /* :809, Spacing="12" */
  /* :806-808, Shadow Brush="#FF9800" Radius="24" Opacity="0.25". Radius als
     CSS-Weichzeichnungsradius angenommen - dieselbe Annahme wie .shade-panel
     oben (kein dokumentierter 1:1-Umrechnungsfaktor in diesem Projekt bekannt). */
  box-shadow: 0 0 24px rgba(var(--abrechnung-warn-akzent-rgb), .25);
  /* :3476-3480 - Karte skaliert 0.9 -> 1.0 in 260ms CubicOut. */
  transform: scale(.9);
  transition: transform 260ms var(--ease-cubic-out);
}
.abrechnung-warn-card.abrechnung-warn-karte-offen { transform: scale(1); }

.abrechnung-warn-emoji { font-size: 28px; }                                              /* :811 */
.abrechnung-warn-titel {
  color: var(--abrechnung-warn-akzent);
  font-size: 12px; font-weight: 700; letter-spacing: 1.5px;                              /* :813-814 */
}
.abrechnung-warn-datum { color: #6B7280; font-size: 11px; }                              /* :815 */
.abrechnung-warn-linie { height: 1px; background: #22332A; }                             /* :818/:820 */
.abrechnung-warn-posten { display: flex; flex-direction: column; gap: 6px; }             /* :819, Spacing="6" */
.abrechnung-warn-gesamt-label { color: #9CA3AF; font-size: 12px; font-weight: 700; }     /* :822-823 */
.abrechnung-warn-gesamt { color: var(--trap-danger); font-size: 16px; font-weight: 700; }/* :824-825 */
.abrechnung-warn-kontolabel { color: #6B7280; font-size: 12px; }                         /* :828-829 */
.abrechnung-warn-konto { font-size: 13px; font-weight: 700; }                            /* :830-831 */
.abrechnung-warn-hinweis {
  font-size: 11px; font-weight: 700; text-align: center; margin-top: 2px;                /* :833-834 */
}
.abrechnung-warn-knopf {
  height: 46px;                                                                          /* :838 */
  border: 0; border-radius: 12px;                                                        /* :838, CornerRadius="12" */
  background: var(--abrechnung-warn-akzent);                                             /* :836 */
  color: #120900;                                                                        /* :836, TextColor */
  font-size: 14px; font-weight: 700;                                                     /* :837 */
  margin-top: 4px;                                                                       /* :838, Margin="0,4,0,0" */
  cursor: pointer;
}

/* ── Bericht (MainPage.xaml:843-896) ─────────────────────────────────────── */
/* Der Ist-Code hat hier KEINEN Verdunkler - die Border liegt einfach ueber der
   Seite und faengt nur Tipps auf sich selbst ab. pointer-events:none auf der
   Wurzel bildet das nach (gleiches Muster wie .overlay-layer oben), der Toast
   selbst holt es sich zurueck. */
.abrechnung-bericht-root {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center; justify-content: center;    /* VerticalOptions="Center", :847 */
  padding: 0 16px;                                 /* Margin="16,0", :847 */
  pointer-events: none;
  z-index: 50;
}
.abrechnung-bericht-toast {
  pointer-events: auto;
  width: 100%;                                     /* HorizontalOptions="Fill", :847 */
  background: #0D1C13;                             /* :845 */
  border: 2px solid var(--trap-accent);            /* :845, Stroke="#00E676" StrokeThickness="2" */
  border-radius: 18px;                             /* :846 */
  padding: 14px 16px;                              /* :846, Padding="16,14" */
  display: flex; flex-direction: column; gap: 10px;/* :854, Spacing="10" */
  cursor: pointer;                                 /* :848-850, TapGestureRecognizer */
  /* :851-853, Shadow Brush="#00E676" Radius="22" Opacity="0.4" - Radius-Annahme
     wie bei der Warnkarte oben. */
  box-shadow: 0 0 22px rgba(var(--trap-accent-rgb), .4);
  /* :3535-3540 - Toast faehrt 0.82 -> 1.0 in 350ms CubicOut hoch und blendet
     0 -> 1 in 280ms ein. Der Fade hat keinen Easing-Parameter -> Easing.Linear,
     siehe Beleg am Verdunkler oben. */
  transform: scale(.82);
  opacity: 0;
  transition: transform 350ms var(--ease-cubic-out), opacity 280ms linear;
}
.abrechnung-bericht-toast.abrechnung-bericht-offen { transform: scale(1); opacity: 1; }
/* :3548-3551 - der Rueckweg endet auf 0.88, NICHT auf den 0.82 des Hinwegs, und
   laeuft mit anderen Dauern/Easing (280ms CubicIn / 260ms). Deshalb eine eigene
   Zustandsklasse statt eines blossen Entfernens von -offen. */
.abrechnung-bericht-toast.abrechnung-bericht-zu {
  transform: scale(.88);
  opacity: 0;
  transition: transform 280ms var(--ease-cubic-in), opacity 260ms linear;
}

.abrechnung-bericht-emoji { font-size: 26px; }                                            /* :856 */
.abrechnung-bericht-titel {
  color: var(--trap-accent);
  font-size: 10px; font-weight: 700; letter-spacing: 1.5px;                               /* :858-859 */
}
.abrechnung-bericht-monat { color: #6B7280; font-size: 11px; }                            /* :860 */
.abrechnung-bericht-linie { height: 1px; background: #1A3324; }                           /* :863/:865 */
.abrechnung-bericht-posten { display: flex; flex-direction: column; gap: 5px; }           /* :864, Spacing="5" */
.abrechnung-bericht-gesamt-label { color: #9CA3AF; font-size: 12px; font-weight: 700; }   /* :867-868 */
.abrechnung-bericht-gesamt { color: var(--trap-danger); font-size: 17px; font-weight: 700; } /* :869-870 */
.abrechnung-bericht-label { color: #6B7280; font-size: 11px; }                            /* :873/:879/:885 */
.abrechnung-bericht-einnahmen { color: var(--trap-accent); font-size: 12px; font-weight: 700; } /* :875-876 */
.abrechnung-bericht-bilanz { font-size: 12px; font-weight: 700; }                         /* :881-882 */
.abrechnung-bericht-danach { font-size: 13px; font-weight: 700; }                          /* :887-888 */
.abrechnung-bericht-warnung {
  color: var(--trap-danger);
  font-size: 11px; font-weight: 700; text-align: center;                                  /* :890-892 */
}
.abrechnung-bericht-tipphinweis {
  color: #3D5247;                                                                          /* :893 */
  font-size: 9px; letter-spacing: 1.5px; text-align: center; margin-top: 2px;              /* :893-894 */
}

/* Klasse 1 der UiAnim-Konvention (einmalige Uebergaenge) - unter reduzierter
   Bewegung auf 1ms, nicht auf 0s (Begruendung in anim.css). */
@media (prefers-reduced-motion: reduce) {
  .abrechnung-warn-root, .abrechnung-warn-root.abrechnung-warn-offen,
  .abrechnung-warn-card, .abrechnung-warn-card.abrechnung-warn-karte-offen,
  .abrechnung-bericht-toast,
  .abrechnung-bericht-toast.abrechnung-bericht-offen,
  .abrechnung-bericht-toast.abrechnung-bericht-zu {
    transition-duration: 1ms;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   GEMEINSAME GRUNDLAGE DER DREI EREIGNIS-VOLLBILDSEITEN
   (Pages/LevelUp.razor, Comeback.razor, Bust.razor - Welle B2/Task 9)

   DRITTE WIEDERHOLUNG, SIEHE PLATZIERUNGSREGEL OBEN: alle drei .xaml-Vorlagen
   (LevelUpPage.xaml, ComebackPage.xaml, BustPage.xaml) tragen dasselbe
   Grid-Skelett - "RowDefinitions=*, Auto" bzw. "Auto, *, Auto": scrollbarer
   Inhalt oben, eine feste Knopf-Leiste unten, die erst nach der Einblend-
   Choreografie erscheint. Diese zwei Klassen bilden das ab; Farben/Animationen
   bleiben Sache der jeweils EIGENEN <seitenname>.css (settings.css-Muster) -
   nur das Skelett gehoert hierher, nicht die Optik.

   Kein gemeinsames C#-Basisblatt (keine gemeinsame Razor-Basisklasse/-komponente):
   die drei Seiten teilen sich AUSSER dem Skelett nichts - Bust/Comeback haengen
   an je einem Tageswechsel-Delegaten mit eigenem Daten-Record, LevelUp an
   einer Perk-Auswahl mit eigenem Zustandsautomaten (Kartenwahl -> Bestaetigen).
   Eine erzwungene Basisklasse fuer drei Zeilen Gemeinsamkeit waere reine
   Kopplung ohne Gegenwert - derselbe Massstab wie bei --shadow-black (tokens.css,
   "keine erzwungene Kopplung ohne Gegenwert"). */
.ereignis-vollbild {
  position: fixed;
  inset: 0;
  z-index: 58;                 /* unter .anruf-overlay (60, "ein Anruf gewinnt immer"), ueber .modal-backdrop/.abrechnung-*-root (beide 50) */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ereignis-vollbild-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.ereignis-vollbild-buttons {
  flex: 0 0 auto;
  padding: 20px 24px 30px;
}

/* Eine Icon+Text-Zeile in der Bilanzkarte (Bust.razor's Zeilen()/Comeback.razor's
   DealZeilen()/OpsZeilen()) - BEIDE Seiten bauen dieselbe Struktur
   (BustPage.xaml.cs:80-106/ComebackPage.xaml.cs:174-200, AddSummaryLine/AddLine
   sind im Ist-Code wortgleiche Kopien). Hierher statt zweimal in bust.css/
   comeback.css. */
.ereignis-zeile {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.ereignis-zeile-icon { flex: 0 0 22px; font-size: 15px; text-align: center; }
.ereignis-zeile-text { flex: 1 1 auto; font-size: 14px; color: #D5D9DF; line-height: 1.4; }

/* ══════════════════════════════════════════════════════════════════════
   ERFOLGS-TOAST (Components/Erfolgstoast.razor, Welle B2/Task 9)
   Quelle: MainPage.xaml.cs:3383-3441 (DrainAchievementQueue/ShowAchievementToast)
   und Services/AchievementToastHost.cs (der tatsaechlich lebende Pfad, siehe
   Services/Erfolge.cs). HIER, NICHT IN EINER EIGENEN <name>.css: geteilte
   Schale-Komponente aus Components/ (PLATZIERUNGSREGEL oben), wie
   .abrechnung-* und .anruf-* weiter unten.

   POINTER-EVENTS WIE .abrechnung-bericht-root: der Ist-Code haengt den Toast
   ohne Ruecksprache mit Eingaben in den Baum (kein eigener Backdrop, keine
   Knoepfe) - die Wurzel lässt Taps durch, nur die Karte selbst faengt sie ab
   (hier ohnehin ohne Wirkung, aber konsistent mit dem Abrechnungsbericht-Muster).

   EIGENES TOKEN STATT --trap-warning: MainPage.xaml:1008/1012/1018 tragen
   #FFD700 (reines Gold) - #FFC107 (--trap-warning) ist ein anderer, naeher an
   Amber liegender Farbwert. Zwei XAML-Dateien, die zufaellig aehnliche Farben
   waehlen, bekommen laut Projektkonvention TROTZDEM eigene Token (siehe
   --anruf-grad-oben/--phone-hangup-shadow weiter unten fuer dieselbe
   Entscheidung) - ein gemeinsames Token wuerde behaupten, sie muessten sich
   gemeinsam aendern. */
:root {
  --erfolg-gold: #FFD700;
  --erfolg-gold-rgb: 255, 215, 0;
}
.erfolg-toast-root {
  position: fixed;
  inset: 0;
  z-index: 59;                 /* ueber den Ereignis-Vollbildseiten (58), unter .anruf-overlay (60) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 24px;
  pointer-events: none;
}
.erfolg-toast-karte {
  /* Welle D/Task 7 (Nebenfund): Der Toast hat keinen Handler und wuerde mit
     pointer-events:auto 2,8 s lang jeden Tipp in der Bildmitte schlucken - im
     Ist-Code (AchievementToastHost.cs:155-156, Border ohne Geste, nicht
     InputTransparent) passiert genau das; hier bewusst durchlaessig, weil der
     Toast nichts entgegennimmt. Schuldeneintrag fuer das MAUI-Spiel. */
  pointer-events: none;
  width: 100%;
  max-width: 420px;
  display: flex;
  align-items: center;
  gap: 14px;
  background: #1A1400;                                          /* MAUI BackgroundColor="#1A1400" */
  border: 2px solid var(--erfolg-gold);                           /* Stroke="#FFD700" */
  border-radius: 16px;
  padding: 12px 14px;
  box-shadow: 0 0 20px rgba(var(--erfolg-gold-rgb), .45);
  /* Scale 0.82->1.0 CubicOut UND Fade 0->1, EIN Element -> Klasse 1 der
     UiAnim-Konvention (RunAsync/SwapAsync), siehe Erfolgstoast.razor. */
  transform: scale(.82);
  opacity: 0;
  transition: transform 350ms var(--ease-cubic-out), opacity 280ms linear;
}
.erfolg-toast-root.erfolg-toast-offen .erfolg-toast-karte { transform: scale(1); opacity: 1; }
.erfolg-toast-root.erfolg-toast-zu .erfolg-toast-karte {
  transform: scale(.88);
  opacity: 0;
  transition: transform 280ms var(--ease-cubic-in), opacity 260ms linear;
}
.erfolg-toast-icon { flex: 0 0 auto; font-size: 30px; }
.erfolg-toast-text { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.erfolg-toast-label {
  color: var(--erfolg-gold);                                      /* TextColor="#FFD700" */
  font-size: 9px; font-weight: 700; letter-spacing: 1.5px;
}
.erfolg-toast-name { color: #fff; font-size: 15px; font-weight: 700; }
.erfolg-toast-desc {
  color: #B8960A; font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.erfolg-toast-schloss { flex: 0 0 auto; font-size: 22px; }

@media (prefers-reduced-motion: reduce) {
  .erfolg-toast-karte { transition-duration: 1ms; }
}
/* ══════════════════════════════════════════════════════════════════════
   CHAT-BANNER (Components/Chatbanner.razor, Welle D/Task 1)
   Quelle: TrapPhone/Services/TelegramBannerHost.cs:128-168 (BuildBanner) und
   WickerBannerHost.cs:97-137 - ZWEI Absender, EINE Huelle.

   HIER UND NICHT IN messenger.css: PLATZIERUNGSREGEL oben - der Wirt ist eine
   Schale-Komponente aus Components/, die GameHost auf JEDER Route einhaengt,
   keine Seite aus Pages/. Gleiche Ablage wie .erfolg-toast-* und .anruf-*.

   KEIN ACHTSTELLIGES HEX in beiden Quelldateien (nachgezaehlt: 4 Literale im
   TelegramBannerHost, 5 im WickerBannerHost, alle sechsstellig) - die
   MAUI/CSS-Falle stellt sich hier nicht. Die Deckkraft des Schattens
   (Opacity="0.5") steckt unten in rgba(var(--shadow-black-rgb), .5).

   Z-INDEX 59, ABSICHTLICH DERSELBE WIE .erfolg-toast-root: beide sind kurze
   Einblendungen oben ueber dem Spiel, und beide sollen unter dem Anruf-Overlay
   (60, "ein Anruf gewinnt immer") bleiben. Gleichzeitig koennen sie nicht
   erscheinen - Chatmeldungen unterdrueckt jedes Banner, solange ein Anruf
   laeuft (siehe Chatbanner.IstUnterdrueckt), und der Erfolgs-Toast liegt in
   einer eigenen Warteschlange. */
:root {
  --chatbanner-bg:          #1E242B;  /* TelegramBannerHost.cs:160 = WickerBannerHost.cs:120 */
  --chatbanner-teletrap:    #2AABEE;  /* TelegramBannerHost.cs:139/:152/:161 (Icon, Marke, Rahmen) */
  --chatbanner-wicker:      #00E676;  /* WickerBannerHost.cs:129/:121 (Marke, Rahmen) */
  --chatbanner-wicker-icon: #00C853;  /* WickerBannerHost.cs:105 (Icon-Kreis) */
}
.chatbanner-wurzel {
  position: fixed;
  inset: 0;
  z-index: 59;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 45px 12px 0;        /* Margin="0,45,0,0" + VerticalOptions="Start" (:157-158) */
  pointer-events: none;
}
.chatbanner {
  pointer-events: auto;
  width: 100%;
  max-width: 340px;            /* WidthRequest="340" (:159) */
  display: flex;
  align-items: center;
  gap: 15px;                   /* Icon-Margin="0,0,15,0" (:144) */
  text-align: left;
  background: var(--chatbanner-bg);
  border: 1px solid var(--chatbanner-teletrap);   /* Stroke + StrokeThickness="1" (:161-162) */
  border-radius: 25px;         /* CornerRadius="25" (:164) */
  padding: 12px 20px;          /* Padding="20,12" (:163) */
  box-shadow: 0 6px 15px rgba(var(--shadow-black-rgb), .5);   /* Shadow (:165) */
  font-family: inherit;
  cursor: pointer;
  /* TranslationY -150 -> 0 in 400ms SpringOut (:120-121); der Rueckweg endet
     wieder bei -150, laeuft aber 300ms CubicIn (:125) - deshalb eine EIGENE
     Zustandsklasse statt eines blossen Entfernens von -offen (Klasse 1 der
     UiAnim-Konvention, Muster .abrechnung-bericht-toast oben). */
  transform: translateY(-150px);
  opacity: 0;
  transition: transform 400ms var(--ease-spring-out), opacity 200ms linear;
}
.chatbanner-wurzel.chatbanner-offen .chatbanner { transform: translateY(0); opacity: 1; }
.chatbanner-wurzel.chatbanner-zu .chatbanner {
  transform: translateY(-150px);
  opacity: 0;
  transition: transform 300ms var(--ease-cubic-in), opacity 200ms linear;
}
.chatbanner-wicker { border-color: var(--chatbanner-wicker); }

.chatbanner-icon {
  flex: 0 0 auto;
  width: 40px; height: 40px; border-radius: 20px;   /* :140-143 */
  background: var(--chatbanner-teletrap);
  overflow: hidden;
}
.chatbanner-wicker .chatbanner-icon { background: var(--chatbanner-wicker-icon); }
.chatbanner-icon img { width: 100%; height: 100%; object-fit: cover; }   /* Aspect="AspectFill" */

.chatbanner-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.chatbanner-marke {
  color: var(--chatbanner-teletrap);
  font-size: 11px; font-weight: 700; letter-spacing: 1px;   /* :152 */
}
.chatbanner-wicker .chatbanner-marke { color: var(--chatbanner-wicker); }
.chatbanner-nachricht { color: #fff; font-size: 13px; }     /* :153, LineBreakMode="WordWrap" */
/* WickerBannerHost.cs:130 - 14px UND fett, anders als der Teletrap-Text. */
.chatbanner-nachricht-fett { font-size: 14px; font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .chatbanner { transition-duration: 1ms; }
}


/* ══════════════════════════════════════════════════════════════════════
   EINGEHENDER ANRUF (Components/Anrufoverlay.razor, Welle B2/Task 8)
   Quelle: MainPage.xaml:674-790 (IncomingCallOverlay) und
   MainPage.xaml.cs:3086-3381 (Region "Call System UI").

   HIER UND NICHT IN EINER SEITEN-DATEI: PLATZIERUNGSREGEL oben - das Overlay
   ist eine Schale-Komponente aus Components/, die GameHost einhaengt, keine
   Seite aus Pages/. Gleiche Ablage wie .abrechnung-* (Welle B2/Task 2).

   SECHS ACHTSTELLIGE HEX-LITERALE im Quell-XAML - die dichteste Stelle dieser
   Art seit dem Dashboard. MAUI liest achtstelliges Hex als AARRGGBB, CSS als
   RRGGBBAA; woertlich kopiert ergaebe jedes davon eine andere Farbe, ohne
   jeden Fehler (CssGuardTests verbietet sie deshalb ganz). Umrechnung, jede
   mit ihrer Quellzeile:
     :676 #F20A1220 -> AA=F2=242/255≈.949   RGB=0A1220 (Verlauf oben)
     :677 #F8080D18 -> AA=F8=248/255≈.973   RGB=080D18 (Verlauf unten)
     :702 #66778899 -> AA=66=102/255=.4     RGB=778899 (Untertitel)
     :783 #66778899 -> dasselbe Literal, zweite Stelle ("ANRUF BEENDET")
     :755 #10FFFFFF -> AA=10=16/255≈.063    RGB=FFFFFF (Dialogkarte, Fuellung)
     :755 #1EFFFFFF -> AA=1E=30/255≈.118    RGB=FFFFFF (Dialogkarte, Rahmen)
     :774 #2AFFFFFF -> AA=2A=42/255≈.165    RGB=FFFFFF (Effektkarte, Rahmen)
   Die drei Weiss-Deckkraefte laufen ueber --divider-white-rgb (tokens.css),
   das genau dafuer existiert. Fuer die vier uebrigen stehen unten eigene
   Token-Paare.

   DREI WERTE TEILT DIESES OVERLAY MIT phone.css (#0A1220/#080D18 Verlauf und
   #778899), UND SIE BEKOMMEN TROTZDEM EIGENE TOKEN: die Quellen sind zwei
   verschiedene XAML-Dateien (MainPage.xaml hier, PhonePage.xaml dort) fuer
   zwei verschiedene Bildschirme, die sich zufaellig auf denselben Farbwert
   geeinigt haben. Ein gemeinsames Token wuerde behaupten, sie muessten sich
   gemeinsam aendern. Dieselbe Entscheidung hat phone.css bereits fuer
   --phone-hangup-shadow gegenueber --trap-danger getroffen.
   ══════════════════════════════════════════════════════════════════════ */
:root {
  /* MainPage.xaml:675-678 (LinearGradientBrush des Overlay-Hintergrunds). */
  --anruf-grad-oben: #0A1220;
  --anruf-grad-oben-rgb: 10, 18, 32;
  --anruf-grad-unten: #080D18;
  --anruf-grad-unten-rgb: 8, 13, 24;

  /* :702 (LblCallerSubtitle) und :783 ("ANRUF BEENDET") - beide #66778899. */
  --anruf-gedaempft: #778899;
  --anruf-gedaempft-rgb: 119, 136, 153;

  --anruf-avatar-bg: #0E1420;      /* :690 (CallerAvatarContainer) */
  --anruf-annehmen-bg: #008833;    /* :747 (BtnAcceptCall) */
  --anruf-ablehnen-bg: #BB2020;    /* :735 (Ablehnen) und :758 (Auflegen) */
  --anruf-effektkarte-bg: #141C2A; /* :772 (CallEffectCard) */

  /* :737/:760 (Button.Shadow Brush="#FF3333" Opacity="0.55") - eigener Rotton,
     weder --trap-danger (#FF4444) noch --modal-danger-accent (#FF5252). */
  --anruf-ablehnen-schatten: #FF3333;
  --anruf-ablehnen-schatten-rgb: 255, 51, 51;

  /* Dauern der Ringel-Schleifen, aus MainPage.xaml.cs abgelesen. Eigene
     --loop-*-Namen statt der --anim-*-Token (anim.css, Kopfkommentar): eine
     Dauerschleife, die prefers-reduced-motion auf 1ms zusammenfaltet, ist ein
     Stroboskop statt einer Reduktion. Sie wird unten stattdessen GANZ
     abgeschaltet. */
  --loop-anruf-schuetteln: 1970ms;  /* :3155-3169, 55+55+45+45+35+35 + 1700 Pause */
  --loop-anruf-ring:       1800ms;  /* :3183-3187, 1600 Bewegung + 200 Pause */
  --loop-anruf-status:      800ms;  /* :3199-3200, je 800ms hin und zurueck */
  --loop-anruf-annehmen:    500ms;  /* :3212-3214, je 500ms hin und zurueck */

  /* Ab wann die Schleifen loslaufen: der Ist-Code startet sie erst NACH dem
     Einblenden (:3138-3141, Task.WhenAll aus 300ms Overlay-Blende und 450ms
     Avatar-Wachsen). In CSS ist das ein animation-delay statt eines awaits -
     die Schleifen sind deklarativ (Klasse 2 der UiAnim-Konvention) und haben
     keinen C#-Aufrufer, der auf sie warten koennte. */
  --anruf-schleifen-start: 450ms;
  /* :3174 - der ZWEITE Ring startet 900ms versetzt; der Versatz IST der Effekt. */
  --anruf-ring2-versatz: 1350ms;
}

/* Ueber allem, wie Grid.RowSpan="10" (:674) im Ist-Code: z-index 60 liegt eine
   Stufe ueber .modal-backdrop/.abrechnung-*-root (beide 50). Im Regelfall kann
   sich dort ohnehin nichts ueberlagern - Spieltakt haelt SpielSchleifeTick an,
   solange ein Rettungsangebot offen ist (Gegenstueck zu :288) -, aber ein
   Anruf-Overlay unter einem Abrechnungsmodal waere ein stiller Totalausfall
   der Bedienbarkeit, und 60 kostet nichts.

   --anruf-blende traegt die Ausblenddauer, die der Ist-Code je nach Ausgang
   verschieden waehlt: 300ms rein (:3139), 500ms raus MIT Effektkarte (:3320),
   300ms raus OHNE (:3324), 250ms raus bei Ablehnung (:3375). Anrufoverlay.razor
   setzt sie als Inline-Custom-Property, BEVOR es die Offen-Klasse wegnimmt -
   Klasse 3 der UiAnim-Konvention (variablengetrieben), hier nur fuer die Dauer
   statt fuer den Wert. FadeToAsync ohne Easing-Parameter ist in MAUI
   Easing.Linear (in diesem Projekt bereits per Doku belegt, siehe
   .phone-detail-offen) - also linear, NICHT ease. */
.anruf-overlay {
  position: absolute;
  inset: 0;
  z-index: 60;
  opacity: 0;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg,
    rgba(var(--anruf-grad-oben-rgb), .949) 0%,
    rgba(var(--anruf-grad-unten-rgb), .973) 100%);   /* :675-678 */
  transition: opacity var(--anruf-blende, 300ms) linear;
}
.anruf-overlay-offen { opacity: 1; pointer-events: auto; }

/* :680, RowDefinitions="*, Auto, Auto" Padding="24,36,24,20" (L,O,R,U). */
.anruf-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 36px 24px 20px;
}

/* :683, VerticalOptions="Start" Margin="0,32,0,0" - Zeile "*" des Ist-Grids. */
.anruf-oben {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 32px;
}

/* :686, HeightRequest/WidthRequest 160, Margin="0,0,0,14". */
.anruf-avatar-feld {
  position: relative;
  width: 160px; height: 160px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 14px;
  flex: none;
}

/* Der Skalierer traegt NUR das Wachsen (:3140, ScaleToAsync(1.0, 450,
   SpringOut)), der Avatar darin NUR das Wackeln (:3153). Zwei Elemente, weil
   CSS beide Bewegungen in DIESELBE transform-Eigenschaft schreiben wuerde -
   in MAUI sind Scale und TranslationX zwei unabhaengige Eigenschaften, in CSS
   waere die zweite ein stilles Ueberschreiben der ersten. */
.anruf-avatar-skalierer {
  position: absolute;
  display: flex; align-items: center; justify-content: center;
  transform: scale(.6);                                   /* :3133 */
  transition: transform 450ms var(--ease-spring-out);     /* :3140 */
}
.anruf-overlay-offen .anruf-avatar-skalierer { transform: scale(1); }

/* :690-694 - 118x118, Radius 59, #0E1420, Rahmen #00E676 2.5px,
   Shadow Brush="#00E676" Radius="22" Opacity="0.45". */
.anruf-avatar {
  width: 118px; height: 118px;
  border-radius: 59px;
  background: var(--anruf-avatar-bg);
  border: 2.5px solid var(--trap-accent);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 22px rgba(var(--trap-accent-rgb), .45);
}
.anruf-avatar-zeichen { font-size: 56px; line-height: 1; }   /* :695 */

/* :688-689 - beide Ringe 122x122, Radius 61, Rahmen #00E676; Ring 1 mit 1.5px,
   Ring 2 mit 1px. Grundzustand ist unsichtbar (Opacity="0"), genau wie
   StopCallAnimations ihn wiederherstellt (:3237-3240). */
.anruf-ring {
  position: absolute;
  width: 122px; height: 122px;
  border-radius: 61px;
  border: 1px solid var(--trap-accent);
  opacity: 0;
  pointer-events: none;
}
.anruf-ring-1 { border-width: 1.5px; }

/* :700, Spacing="4" Margin="0,0,0,14". */
.anruf-titel {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  margin-bottom: 14px;
  flex: none;
}
.anruf-name      { color: #fff; font-size: 26px; font-weight: 700; }                    /* :701 */
.anruf-untertitel{ color: rgba(var(--anruf-gedaempft-rgb), .4); font-size: 13px; }      /* :702 */
/* :703 - #00E676, 13px, CharacterSpacing="1.5", Margin="0,10,0,0". */
.anruf-status {
  color: var(--trap-accent);
  font-size: 13px;
  letter-spacing: 1.5px;
  margin-top: 10px;
}

/* :707-718 - HeightRequest="36" Spacing="5" Margin="0,0,0,10"; sieben Balken
   je 4px breit, CornerRadius 2, #00E676. */
.anruf-welle {
  height: 36px;
  display: flex; align-items: center; justify-content: center; gap: 5px;
  margin-bottom: 10px;
  flex: none;
}
.anruf-welle-balken {
  width: 4px;
  height: var(--welle-min, 8px);
  border-radius: 2px;
  background: var(--trap-accent);
}

/* :753-757 - Zeile "Auto" des Ist-Grids, Margin="0,0,0,56", Fuellung
   #10FFFFFF, Rahmen #1EFFFFFF, Radius 16, Padding="18,14" (H,V).
   :3280-3282/:3287-3290 - blendet mit Deckkraft 0 und 14px Versatz ein,
   300ms, TranslateTo mit CubicOut. */
.anruf-dialog {
  margin-bottom: 56px;
  padding: 14px 18px;
  border-radius: 16px;
  background: rgba(var(--divider-white-rgb), .063);
  border: 1px solid rgba(var(--divider-white-rgb), .118);
  color: #fff;
  font-size: 14px;
  line-height: 1.4;
  text-align: center;
  flex: none;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 300ms linear, transform 300ms var(--ease-cubic-out);
}
.anruf-dialog-offen { opacity: 1; transform: translateY(0); }

/* :730-765 - Zeile "Auto" ganz unten. Klingel-Knoepfe links/rechts
   (ColumnDefinitions="*, *", HorizontalOptions Start/End), der Auflegen-Knopf
   des laufenden Gespraechs mittig. */
.anruf-knopfreihe { display: flex; justify-content: space-between; flex: none; }
.anruf-knopfreihe-mitte { justify-content: center; }
.anruf-knopf-saeule { display: flex; flex-direction: column; align-items: center; gap: 8px; }

.anruf-knopf {
  width: 70px; height: 70px;
  border-radius: 35px;
  border: 0;
  color: #fff;
  font-size: 26px;
  cursor: pointer;
  padding: 0;
}
.anruf-knopf-ablehnen {
  background: var(--anruf-ablehnen-bg);
  box-shadow: 0 0 18px rgba(var(--anruf-ablehnen-schatten-rgb), .55);   /* :736-738 */
}
.anruf-knopf-annehmen {
  background: var(--anruf-annehmen-bg);
  box-shadow: 0 0 18px rgba(var(--trap-accent-rgb), .55);               /* :748-750 */
}
.anruf-knopf-label { color: #AAAAAA; font-size: 12px; }                 /* :739/:751/:763 */

/* :768-790 - eigenes Kind des Overlay-Grids, mittig, Margin="30,0",
   TranslationY="70", Fuellung #141C2A, Rahmen #2AFFFFFF 1px, Radius 22,
   Padding="36,22" (H,V), Shadow Black Radius 24 Opacity 0.55 Offset 0,8.
   :3308-3315 - blendet mit Deckkraft 0 (300ms) und Groesse 0.85 (400ms
   SpringOut) ein; der Versatz von 70px bleibt dabei stehen. */
.anruf-effekt-huelle {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 0 30px;
  pointer-events: none;
}
.anruf-effekt {
  background: var(--anruf-effektkarte-bg);
  border: 1px solid rgba(var(--divider-white-rgb), .165);
  border-radius: 22px;
  padding: 22px 36px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  text-align: center;
  box-shadow: 0 8px 24px rgba(var(--shadow-black-rgb), .55);
  opacity: 0;
  transform: translateY(70px) scale(.85);
  transition: opacity 300ms linear, transform 400ms var(--ease-spring-out);
}
.anruf-effekt-offen { opacity: 1; transform: translateY(70px) scale(1); }

/* :783 - #66778899, 11px, CharacterSpacing="2". */
.anruf-effekt-kopf {
  color: rgba(var(--anruf-gedaempft-rgb), .4);
  font-size: 11px;
  letter-spacing: 2px;
}
/* :3353 - FontSize 26, fett; LineHeight 1.3 (:788). */
.anruf-effekt-zeilen { display: flex; flex-direction: column; line-height: 1.3; }
.anruf-effekt-zeile { font-size: 26px; font-weight: 700; color: #fff; }
.anruf-effekt-gewinn  { color: var(--trap-accent); }          /* #00E676, :3351 */
.anruf-effekt-verlust { color: var(--modal-danger-accent); }  /* #FF5252, :3351 */

/* ── Die fuenf CSS-Dauerschleifen des Klingelns ────────────────────────────
   Klasse 2 der UiAnim-Konvention: rein deklarativ, kein RunAsync. Der Ist-Code
   fuehrt sie als sechs abbrechbare async-Schleifen (:3153-3229); fuenf davon
   sind reine Bewegung und gehoeren damit in @keyframes. Die sechste
   (VibrateRingLoop, :3219-3229) ist Haptik und kein Bild - sie bleibt C# und
   liegt in Anrufoverlay.razor.

   ABBRUCH: der Ist-Code braucht dafuer ein CancellationTokenSource
   (StopCallAnimations, :3231-3242). Hier genuegt das Entfernen der Klasse
   .anruf-klingelt - damit enden alle fuenf Schleifen SOFORT und die
   Basisregeln oben sind exakt die Zielwerte, die :3237-3241 von Hand
   wiederherstellt (Ringe: Deckkraft 0 und Groesse 1; Annehmen-Knopf: Groesse 1;
   Statuszeile: Deckkraft 1). */

/* :3155-3169 - Avatar ±7/±5/±3 px, je 55/45/35ms, dann 1700ms Pause.
   TranslateToAsync ohne Easing-Parameter ist in MAUI Easing.Linear.
   Prozentwerte = Zeitpunkt / 1970ms. */
@keyframes anruf-schuetteln {
  0%       { transform: translateX(0); }
  2.792%   { transform: translateX(-7px); }
  5.584%   { transform: translateX(7px); }
  7.868%   { transform: translateX(-5px); }
  10.152%  { transform: translateX(5px); }
  11.929%  { transform: translateX(-3px); }
  13.706%  { transform: translateX(0); }
  100%     { transform: translateX(0); }
}

/* :3183-3187 - Groesse 1 -> 1.65 (CubicOut) UND Deckkraft 0.6 -> 0 (CubicIn),
   beide ueber 1600ms, danach 200ms Pause im Ausgangszustand. ZWEI Animationen
   statt einer: eine einzelne @keyframes-Regel kann nur EINE
   animation-timing-function je Abschnitt tragen, der Ist-Code benutzt hier aber
   ausdruecklich zwei verschiedene Kurven. 1600/1800 = 88.889%. */
@keyframes anruf-ring-groesse {
  0%       { transform: scale(1); }
  88.889%  { transform: scale(1.65); }
  88.89%   { transform: scale(1); }
  100%     { transform: scale(1); }
}
@keyframes anruf-ring-blende {
  0%       { opacity: .6; }
  88.889%  { opacity: 0; }
  100%     { opacity: 0; }
}

/* :3199-3200 - Statuszeile 1 -> 0.2 -> 1, je 800ms, ohne Easing (Linear). */
@keyframes anruf-status-puls {
  from { opacity: 1; }
  to   { opacity: .2; }
}

/* :3212-3214 - Annehmen-Knopf 1 -> 1.09 -> 1, je 500ms SinInOut. */
@keyframes anruf-annehmen-puls {
  from { transform: scale(1); }
  to   { transform: scale(1.09); }
}

/* :3249-3255/:3258-3270 - sieben Balken, jeder mit eigener Hoehe, eigener
   Periode und eigenem Startversatz; auf und ab mit SinInOut. Die Werte stehen
   als Inline-Custom-Properties an jedem Balken (Anrufoverlay.razor), weil sie
   sich je Balken unterscheiden. animation-direction:alternate ersetzt den
   Rueckweg: eine Halbperiode hin, eine zurueck - deshalb steht in
   --welle-dauer die HALBE Ist-Code-Periode.
   Der Ist-Code haengt an jede Halbperiode 10ms Schlupf (Task.Delay(period/2+10),
   :3264/:3268), damit die MAUI-Animation garantiert fertig ist; das ist
   Ablaufsteuerung, keine gestalterische Absicht, und faellt hier weg. */
@keyframes anruf-welle {
  from { height: var(--welle-min, 8px); }
  to   { height: var(--welle-max, 22px); }
}

.anruf-klingelt .anruf-avatar {
  animation: anruf-schuetteln var(--loop-anruf-schuetteln) linear var(--anruf-schleifen-start) infinite;
}
.anruf-klingelt .anruf-ring {
  animation: anruf-ring-groesse var(--loop-anruf-ring) var(--ease-cubic-out) var(--anruf-schleifen-start) infinite,
             anruf-ring-blende  var(--loop-anruf-ring) var(--ease-cubic-in)  var(--anruf-schleifen-start) infinite;
}
.anruf-klingelt .anruf-ring-2 {
  animation-delay: var(--anruf-ring2-versatz), var(--anruf-ring2-versatz);
}
.anruf-klingelt .anruf-status {
  animation: anruf-status-puls var(--loop-anruf-status) linear var(--anruf-schleifen-start) infinite alternate;
}
.anruf-klingelt .anruf-knopf-annehmen {
  animation: anruf-annehmen-puls var(--loop-anruf-annehmen) var(--ease-sin-in-out) var(--anruf-schleifen-start) infinite alternate;
}
.anruf-welle-balken {
  animation: anruf-welle var(--welle-dauer, 240ms) var(--ease-sin-in-out) var(--welle-versatz, 0ms) infinite alternate;
}

/* Bewegungsreduzierung: die Dauerschleifen werden GANZ ABGESCHALTET, nicht
   verkuerzt (anim.css, Klasse-2-Beispiel: eine auf 1ms gekuerzte Schleife ist
   ein Stroboskop). Die Basisregeln oben bleiben die sichtbaren Werte - nichts
   wackelt, nichts pulsiert, alles bleibt lesbar und bedienbar.

   DIE SELEKTOREN MUESSEN DIE OBIGEN EXAKT SPIEGELN, nicht nur den letzten
   Klassennamen nennen: ".anruf-klingelt .anruf-ring" ist ein Zwei-Klassen-
   Selektor und schlaegt ein blosses ".anruf-ring { animation: none }" IMMER,
   auch aus einer @@media-Regel heraus - genau dieser Fehler ist in Welle
   A4/Task 4 bereits einmal passiert (siehe phone.css, gleicher Abschnitt).
   .anruf-welle-balken ist die Ausnahme: seine Schleife steht selbst auf einem
   Ein-Klassen-Selektor, hier genuegt derselbe. */
@media (prefers-reduced-motion: reduce) {
  .anruf-klingelt .anruf-avatar,
  .anruf-klingelt .anruf-ring,
  .anruf-klingelt .anruf-ring-2,
  .anruf-klingelt .anruf-status,
  .anruf-klingelt .anruf-knopf-annehmen,
  .anruf-welle-balken {
    animation: none;
  }
  /* Klasse 1 (einmalige Uebergaenge) auf 1ms, wie .abrechnung-* oben. */
  .anruf-overlay, .anruf-overlay-offen,
  .anruf-avatar-skalierer, .anruf-overlay-offen .anruf-avatar-skalierer,
  .anruf-dialog, .anruf-dialog-offen,
  .anruf-effekt, .anruf-effekt-offen {
    transition-duration: 1ms;
  }
}
