/* ============================================================
   Chat Tower — Viewer-Base-CSS (ui-456, Frontend-Neubau Phase 5,
   P5-Kette Glied 7/10, "Hüllen"-Block)

   Regeln, die BEIDE Kompositionen (Desktop + Mobile) byte-
   identisch nutzen — extrahiert aus deren vormals getrennten Inline-
   <style id="ct-viewer-style">-Blöcken (Architektur-Lock ui-456: reine
   Verschiebung, kein Redesign — Konsolidierung NUR bei Byte-Gleichheit,
   sonst bleibt der Bestand viewer-lokal in desktop.css/mobile.css).

   Ladereihenfolge (Kaskade-Lock, Ticket ui-456 + ui-454):
     tokens.css → <style id="ct-viewer-style"> (leerer Boot-Anker + Fragment-
       CSS-Einfügepunkt für surface-host.js — /static/style.css wird per
       insertBefore() IMMER unmittelbar VOR diesen Anker inseriert) →
       viewer-base.css (DIESE DATEI) → desktop.css/mobile.css (gewinnen
       zuletzt gegen ein evtl. injiziertes style.css-Fragment — Companion-CSS
       überschreibt nicht die Viewer-Chrome, s. ui-402/ui-454-Kommentar in
       den beiden Hüllen). WICHTIG: der Anker steht VOR dieser Datei, NICHT
       danach — stünde er danach, würde ein injiziertes style.css NACH
       viewer-base.css/desktop.css/mobile.css landen und deren Kaskade
       gewinnen (empirisch als echte Regression gefunden, Pixel-Beweis
       Ticket '## Notes': #popin erbte Companions Body-Font statt der
       eigenen Viewer-Font).
   Beide Viewer linken diese Datei als /client/css/viewer-base.css — no-store-
   Mount (web/src/app.js '/client/*'-Route, identisch zu allen anderen
   /client/*-Dateien; Mount-Begründung + Cache-Busting-Entscheid im Ticket
   '## Notes'). KEIN /static/*-Mount (Service-Worker cached /static/* stale-
   while-revalidate — hier bewusst vermieden, s. web/client/README.md).
   ============================================================ */

/* ---- Shared SVG-Icon-Basisregeln (statIconSvg/hexaIconSvg aus
   src/render/stat-icons.js + currency-icons.js, ui-382) — byte-identisch in
   beiden Viewern seit ui-382 (Begleitkommentar dort leicht unterschiedlich
   formuliert, Deklarationen 1:1 gleich). currentColor erbt die jeweilige
   Kontextfarbe (muted/cyan/magenta/…, je nach Aufrufstelle); *-ic--text:
   1em-Kantenlänge überstimmt die width/height-Attribute des Helpers, das
   Icon skaliert mit der Kontext-Schriftgröße. ---- */
svg.stat-icon,
svg.hexa-icon {
  vertical-align: -0.15em;
  flex-shrink: 0;
}
svg.stat-ic--text,
svg.cur-ic--text {
  width: 1em;
  height: 1em;
}
/* ui-633 (Fifty 13.09.: „die hexa-menü-anzeige wird erst sehr spät im laden
   pink" — Bereichs-Cluster #nc-hexa): Währungsfarbe ab erstem Paint statt
   erst nach dem Nachladen von companion/components.css (surface-host.js
   loadMissingStylesheets(), greift erst beim ersten Flächen-Mount) — vorher
   erbte svg.hexa-icon bis dahin die AMBIENTE Kontextfarbe seines jeweiligen
   Containers (Cyan im Cluster-/App-Nav-Slot „Hexa", Gelb in der Big-Run-
   Zeile, Grün in der Pool-Anteil-Zeile, Cyan im Club-Boost-Chip UND im
   Endscreen-Brücken-Kicker — volle Inventur im Ticket „## Notes") und sprang
   sichtbar auf Magenta um, sobald die erste Companion-Fläche geladen wurde.
   Byte-identische Kanon-Regel zu companion/components.css (Z. 178, ui-623)
   — bewusst KEINE Cluster-Sonderregel: eine auf #nc-hexa gescopte Regel
   hätte nur DIESEN einen Aufrufer korrigiert, das nächste Icon wäre weiter
   gesprungen (Lock 1 dieses Tickets: Viewer-Farben kommen aus Viewer-CSS,
   nicht aus nachgeladenem Companion-CSS). svg.techtree-icon/svg.sockel-icon/
   svg.sub-months-icon bleiben bewusst NICHT hierher migriert — sie rendern
   ausschließlich INNERHALB einer bereits geladenen Companion-Fläche
   (techtree.js/collection/templates.js), deren eigenes CSS im selben
   loadMissingStylesheets()-Schritt mitkommt (kein Timing-Fenster dort). */
svg.hexa-icon { color: var(--cur-hexa); }
/* ui-633: dito für svg.draw-icon (Endscreen-Reward-Zeilen, Login-Indicator-
   Draws-Zähler rendern ebenfalls vor dem ersten Flächen-Mount) — byte-
   identisch zu companion/components.css (Z. 137-145, ui-333/ui-623). Alle
   heutigen Viewer-Aufrufstellen setzen ihren Container bereits selbst auf
   var(--cur-draw) (kein sichtbarer Sprung heute), die Regel fehlte hier
   trotzdem komplett inkl. Baseline-Tuning — ergänzt für Konsistenz UND
   gegen eine künftige Aufrufstelle ohne eigenen Farb-Kontext. */
svg.draw-icon {
  vertical-align: -0.15em;
  flex-shrink: 0;
  color: var(--cur-draw);
}

/* ---- graph.js-Innenleben (.ge-*, ui-436, web/client/elements/graph.js) —
   Teilmenge, die byte-identisch war. Der Rest (.ge-sub/.ge-canvas/
   .ge-legend/.ge-legend-chip/.ge-legend-dot/.ge-legend-name) bleibt
   viewer-lokal (desktop.css/mobile.css), weil Mobile auf Touch-Maße
   abgestimmt ist (größere Chips/Dots/Truncation-Breite) — Bestand gewinnt
   bei Abweichung, kein Vereinheitlichungs-Zwang (P5-Lock). ---- */
.ge-canvas-wrap { position: relative; width: 100%; height: 120px; }
/* ▲ Skalen-Höchstwert oben links (DL-2) */
.ge-ymax {
  position: absolute; top: 1px; left: 2px;
  font-size: 11px; font-variant-numeric: tabular-nums;
  pointer-events: none; z-index: 1;
}
.ge-legend:empty { margin-top: 0; padding: 0; }
/* eigener/hervorgehobener Tower: Cyan-Kontur (identisch zu Mobiles .in-legend-chip.own) */
.ge-legend-chip.own { border-color: var(--cyan); }
.ge-legend-num { color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- Enemy-Intro-Karte (renderCardFromEvent, elements/panels/enemy-log.js,
   ui-169/ui-313) — Kern-Karte byte-identisch in beiden Viewern. .ei-trait/
   .ei-pips/.ei-special bleiben viewer-lokal (desktop.css nutzt --border,
   mobile.css --line — beides #1c2742 auf Desktop-Seite, aber ein
   ABWEICHENDER lokaler Wert auf Mobile-Seite, s. mobile.css Root-Block-
   Kommentar; echte Bestands-Abweichung, keine reine Namens-Alias-Frage).
   ui-514s .ei-explain (weiter unten) ist die EINE Ausnahme davon und lebt
   HIER geteilt — sie nutzt --cyan/--text-dim statt der abweichenden
   Border-Tokens, also keine der beiden Divergenz-Gründe. ---- */
.el-empty { color: var(--muted); font-size: 11.5px; }
.enemy-intro {
  width: 100%; box-sizing: border-box;
  color: var(--text); font-size: 13px;
  /* ui-227 DL-9: reiner Opacity-Fade statt translateY (war zu hart).
     opacity 0→1 via .show-Klasse (renderCard setzt sie nach reflow).
     translateY entfernt — kein Slide mehr. */
  opacity: 0;
  transition: opacity .32s ease;
}
.enemy-intro.show { opacity: 1; }
.enemy-intro .ei-head { display: flex; align-items: center; gap: 11px; }
/* ui-092: rahmenlos — kein border, kein Kaestchen-Hintergrund.
   Das echte Gegner-Icon (SVG) sitzt direkt auf dem Panel-Hintergrund. */
.enemy-intro .ei-glyph {
  width: 42px; height: 42px; flex: 0 0 42px;
  display: flex; align-items: center; justify-content: center;
}
.enemy-intro .ei-glyph svg { width: 100%; height: 100%; display: block; }
.enemy-intro .ei-newtag {
  font-size: 11px; letter-spacing: 1.5px; font-weight: 700;
  color: var(--ei, var(--red)); text-transform: uppercase;
}
.enemy-intro .ei-name { font-size: 17px; font-weight: 700; line-height: 1.1; }
.enemy-intro .ei-wave-note { font-size: 11px; color: var(--muted); margin: 6px 0 7px; }
.enemy-intro .ei-stats { display: grid; grid-template-columns: auto 1fr auto; gap: 5px 10px; align-items: center; }
.enemy-intro .ei-label { font-size: 11.5px; color: var(--muted); }
.enemy-intro .ei-bar { display: flex; gap: 3px; }
.enemy-intro .ei-seg { width: 100%; height: 7px; border-radius: 2px; background: #2a3550; }
.enemy-intro .ei-seg.on { background: var(--ei, var(--red)); box-shadow: 0 0 5px -1px var(--ei, var(--red)); }
.enemy-intro .ei-val { font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; min-width: 62px; }
.enemy-intro .ei-val.ei-val-dim { color: var(--muted); font-weight: 400; }
/* ui-514: Erstkontakt-Erklaerzeile (Schuetze/Tank) — nur beim allerersten
   Account-Kontakt gerendert (src/enemy-intro.js renderCard() liest
   enemy.firstContactExplain, von der Komposition VOR
   enemyLogEl.addEvent() gesetzt). Anders als .ei-trait/.ei-pips/.ei-special
   HIER geteilt statt viewer-lokal — nutzt --cyan/--text-dim, keine der
   beiden per-Plattform abweichenden Border-Tokens (--border/--line, s.
   Datei-Kommentar oben). Dashed Border + cyan grenzt sie optisch als
   Tutorial-Hinweis von der IMMER sichtbaren .ei-special-Zeile ab. */
.enemy-intro .ei-explain { font-size: 11.5px; line-height: 1.4; border-top: 1px dashed var(--cyan); padding-top: 8px; margin-top: 8px; color: var(--text-dim); }
.enemy-intro .ei-explain b { color: var(--cyan); }

/* ---- Pact-Banner (buildPactBannerInnerHTML, src/render/pact-banner.js,
   ui-212/ui-242/ui-258/ui-313) — Teilmenge, die byte-identisch war.
   .pact-warn-icon/.pact-pill nutzten in Mobiles Alt-Stand rohe rgba()-
   Literale statt der --red-a12/--red-a42/--red-a08-Tokens (Werte exakt
   identisch, s. tokens.css) — ui-456 stellt sie auf die Tokens um (Mobile
   linkt tokens.css seit diesem Ticket), dadurch wurden sie byte-identisch zu
   Desktop und ziehen mit hierher (Ticket-AK "harte Hex-Duplikate der
   Token-Werte auf var() umgestellt"). game-160: .pact-rw-personal (die
   "dein Pakt"-Pille) ist ersatzlos entfernt — kein persönlicher Anteil mehr.
   .pact-banner-head/.pact-banner-rewards/.pact-pill-label bleiben
   viewer-lokal (echte Layout-/Farb-Abweichungen, s. desktop.css/
   mobile.css). ---- */
.pact-banner-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; letter-spacing: 1.5px; font-weight: 700; text-transform: uppercase;
  color: var(--red); white-space: nowrap;
}
.pact-warn-icon {
  width: 24px; height: 24px; flex: 0 0 24px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center; font-size: 14px;
  background: var(--red-a12); border: 1px solid var(--red-a42); color: var(--red);
}
.pact-rw-shared {
  display: inline-flex; align-items: baseline; gap: 4px; font-variant-numeric: tabular-nums;
}
.pact-rw-glyph { color: var(--magenta); font-size: 14px; }
.pact-rw-val   { color: var(--magenta); font-weight: 700; font-size: 14px; }
.pact-rw-lbl   { color: var(--muted); font-size: 11px; }
/* Penalty-Pillen-Reihe (DL-5) */
.pact-pills {
  display: flex; flex-wrap: wrap; gap: 5px 6px; margin-top: 9px;
  padding-top: 9px; border-top: 1px dashed rgba(255,255,255,.08);
}
.pact-pill {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--red-a08); border: 1px solid var(--red-a42);
  border-radius: 999px; padding: 3px 9px; font-variant-numeric: tabular-nums;
}
.pact-pill-glyph { color: var(--red); font-size: 11px; opacity: .85; }
.pact-pill-type  { color: var(--text); font-size: 13px; line-height: 1; }
.pact-pill-val   { color: var(--red); font-weight: 700; font-size: 12px; }

/* ============================================================
   ui-482 (Roter Faden) — Onboarding-Sequenz-Fenster hook/c1/c2/c3/rearm/tpl.
   DL-8 "bestehende #ob-hook-Optik (…) für alle Fenster": Kicker-/CTA-/Punkte-
   Klassen SIND jetzt für alle sechs Fenster gemeinsam UND auf beiden
   Plattformen identisch (echte Neu-Konsolidierung, kein Bestand — anders als
   die ui-456-"nur bei Byte-Gleichheit"-Fälle oben: Mobile hatte diese Fenster
   vor diesem Ticket nicht). NUR Positionierung (position/left/top/right/
   bottom/transform/width/z-index) bleibt viewer-lokal (desktop.css: panel-nah,
   JS-berechnet; mobile.css: zentriert mit Scrim, DL-7) — s. dortige Blöcke.
   #ob-bridge/#mob-ob (ui-147/ui-392, Endscreen-Brücke) bleiben UNVERÄNDERT,
   eigene Klassen (.ob-kick/.ob-cta-primary/.mo-*), keine Überschneidung.
   ui-501 (Faden-Verzahnung privater Raum): +#ob-mates/#ob-unlock — dieselbe
   "ob-Optik-Bestand"-Chrome (AK1), macht die Familie acht statt sechs Fenster;
   Positionierung bleibt ebenfalls viewer-lokal (s. desktop.css/mobile.css).
   ============================================================ */
#ob-hook, #ob-c1, #ob-c2, #ob-c3, #ob-rearm, #ob-tpl, #ob-mates, #ob-unlock {
  background: rgba(13,19,34,.97);
  border: 1px solid var(--cyan);
  border-radius: 12px;
  padding: 16px 18px 14px;
  box-shadow: 0 12px 40px rgba(0,0,0,.6), 0 0 24px rgba(56,225,255,.12);
  animation: ob-pop .22s ease-out;
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
}
@keyframes ob-pop {
  from { opacity: 0; transform: translateY(6px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
/* Gemeinsame Schliessen-Affordance ("✕" = Skip, DL-1) — ein Rechteck oben
   rechts, unabhängig davon welches der acht Fenster gerade sichtbar ist
   (ui-501: +mates/unlock). */
.ob-x {
  position: absolute; top: 7px; right: 9px; width: 28px; height: 28px;
  border-radius: 6px; display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--muted); font-size: 15px; line-height: 1;
  background: transparent; border: none;
}
.ob-x:hover { color: var(--text); background: rgba(255,255,255,.06); }
.ob-kicker {
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--cyan); margin-bottom: 4px;
}
#ob-hook h2 { margin: 0 0 8px; font-size: 16px; color: var(--text); }
#ob-hook p, #ob-c1 p, #ob-c2 p, #ob-c3 p, #ob-rearm p, #ob-tpl p, #ob-mates p, #ob-unlock p {
  margin: 0 0 10px; font-size: 12.5px; line-height: 1.5; color: var(--text-dim);
}
/* ui-496 AK3 (PTR-Fund): `white-space:nowrap` auf .ob-cta/.ob-skip/.ob-back —
   ohne das erlaubt Flexbox' automatische Mindestgröße (min-width:auto) einem
   Button, bis auf die Breite seines LÄNGSTEN WORTS zu schrumpfen ("Weiter"
   ODER "›" statt "Weiter ›" als Ganzes) — genau der gemeldete Glyph-Umbruch
   bei schmalen .ob-nav-Zeilen (Mobile-Kartenbreite ~352-362px,
   `left:14px;right:14px` in mobile.css, minus 36px Karten-Padding ergibt
   ~316-326px für 3 Buttons + 2×10px Gap; die c2/c3-Zeile hat DREI Buttons
   -- Zurück+Weiter+Überspringen -- also den knappsten Fall). Kombiniert mit
   der automatischen Mindestgröße verhindert nowrap jedes Schrumpfen unter
   die volle Textbreite (Browser-Standardverhalten, kein Extra-min-width
   nötig). */
.ob-cta {
  font: inherit; cursor: pointer; border-radius: 8px; padding: 7px 14px;
  font-weight: 600; font-size: 12.5px; background: var(--cyan); border: none; color: #07091a;
  white-space: nowrap;
}
/* ui-484-Fix (CTA-Lesbarkeit, DL-8 "gilt auch hier"): color explizit auf
   Hover gesetzt, :not(:disabled) als Spezifitäts-Härtung — vorbeugend
   (der Viewer hat KEINEN globalen button:hover-Reset wie companion/base.css,
   s. ui-484-Root-Cause dort; hier keine live Regression, nur Konsistenz). */
.ob-cta:hover:not(:disabled) { box-shadow: 0 0 16px rgba(56,225,255,.4); color: #07091a; }
.ob-skip, .ob-back {
  font: inherit; cursor: pointer; background: none; border: none; color: var(--muted); font-size: 12px;
  white-space: nowrap;
}
.ob-skip:hover, .ob-back:hover { color: var(--text); }
/* ui-584 AK4 (Fund ui-496-Dev/followups.md): eigene Basisregel fuer
   .ob-cta-later — vormals NUR als `#ob-bridge .ob-cta-later` gescoped
   (desktop.css, Bridge-Popin "Setup öffnen →"/"Später"). Der Template-
   Coachmark-Button `tpl-goto` (#ob-tpl, "Zum <Tab>-Tab", seit ui-482) trägt
   dieselbe Klasse AUSSERHALB von #ob-bridge und rendert dadurch als
   unstyled Browser-Default-Button (weißer Hintergrund, schwarzer Text)
   statt im dunklen App-Theme. Werte analog .ob-skip/.ob-back (identisches
   „gedecktes, dunkles Theme"-Muster) — `#ob-bridge .ob-cta-later` bleibt
   UNVERÄNDERT als spezifischere Override bestehen (ID-Selektor schlägt
   diese Klassenregel ohnehin, s. dortiger Kommentar in desktop.css). */
.ob-cta-later {
  font: inherit; cursor: pointer; background: none; border: none; color: var(--muted); font-size: 12px;
  white-space: nowrap;
}
.ob-cta-later:hover { color: var(--text); }
/* flex-wrap:wrap (ui-496 AK3) — zweite Verteidigungslinie: sollten nowrap-
   Buttons trotz allem nicht mehr NEBENEINANDER passen (extrem schmale
   Zukunfts-Breakpoints), brechen sie als GANZE Elemente auf eine zweite
   Zeile um (row-gap identisch zu gap) statt die Zeile horizontal zu
   sprengen — nie mehr ein Umbruch MITTEN in einem Button (AK3-Kern). */
.ob-nav { display: flex; flex-wrap: wrap; gap: 10px; row-gap: 6px; align-items: center; margin-top: 2px; }
.ob-nav .ob-cta { flex: 1 0 auto; }
.ob-nav .ob-back, .ob-nav .ob-skip { flex: 0 0 auto; }
.ob-nav .ob-skip { margin-left: auto; text-decoration: underline; }
.ob-step {
  position: absolute; right: 34px; top: 14px; font-size: 11px; color: var(--muted);
}
/* "So steuerst du" (K1-K3) Text-Akzente — b-magenta existierte vor ui-482
   bereits ID-gescoped (#ob-coach p .b-magenta), jetzt global + drei weitere
   Farben (Ticket-Wortlaut Credits=gelb, Fokus-Set=cyan, Rearm-Häkchen=grün). */
.b-magenta { color: var(--magenta); }
.b-yellow  { color: var(--yellow); }
.b-cyan    { color: var(--cyan); }
.b-green   { color: var(--green); }

/* Willkommen-Punkteliste (DL-2) — feste 44px-Miniatur-Spalte (Fifty
   2026-08-20 "Bündigkeit"): Grid statt Flex garantiert, dass die
   Text-Anfänge aller drei Zeilen auf derselben x-Position beginnen,
   UNABHÄNGIG von 1 oder 2 Miniaturen in Spalte 1. */
.ob-pts { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.ob-pts li { display: grid; grid-template-columns: 44px 1fr; gap: 10px; align-items: center; font-size: 13.5px; color: var(--text-dim); }
.ob-mini { display: flex; align-items: center; justify-content: center; gap: 3px; }

/* Mini-Tower/Mini-Gegner (src/render/onboarding-minis.js towerMiniHtml()/
   enemyMiniHtml()) — echte Render-Bausteine statt PNG/handkopiertem SVG
   (Fifty-Lock Lösung B). currentColor: .ob-mt erbt die Onboarding-Chrome-
   Farbe (Cyan), .ob-me trägt seine Farbe bereits inline (CONFIG.ENEMY_TYPES.
   normal.color, s. dortiger Datei-Kopf) — kein zweites Hex-Literal hier. */
.ob-mt { color: var(--cyan); flex: 0 0 auto; }
.ob-mt.dimmed { opacity: .55; }
.ob-me { flex: 0 0 auto; filter: drop-shadow(0 0 3px currentColor); }

/* Live-Beispiel-Box (K2/K3, AK3) — umschließt echte .ip-strow-Zeilen
   (web/client/elements/controls/upgrades.js-Markup, s. viewer-shell.js
   _obExampleRowHtml()) mit statischen Illustrations-Werten. */
.ob-ex { border: 1px solid var(--border, #1c2742); border-radius: 8px; padding: 6px; margin: 0 0 12px; background: rgba(255,255,255,.02); }
/* ui-504 (M2, Audit-K1 -- nicht zu verwechseln mit dem "K2/K3" oben, das
   sind Onboarding-Coachmark-Schritte): 10.5px -> 11px Lese-Boden. Caption
   traegt echten Text ("Beispiel · …"), kein fixed-height-Container -- wraps
   frei bei Bedarf. */
.ob-ex-cap { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin: 0 0 4px; padding: 0 2px; }

/* ============================================================
   .ip-strow-Familie (ui-193 u.a., umgezogen aus desktop.css seit ui-482) —
   Stat-Zeilen-Grid (Icon/Label/Level/Wert/Divider/Checkbox-Zone) +
   in-set/active/locked/ip-disabled-Zustandsmodell. War bis ui-482
   Desktop-exklusiv (Live-Panel `#ip-upgrades-mount`, Read-only Hover-Panel);
   jetzt geteilt, weil die K2/K3-Live-Beispiele im Onboarding (#ob-c2/#ob-c3,
   AK3 "echte Klassen — Änderung an .ip-strow-Optik schlägt automatisch
   durch") dieselben Klassen auf BEIDEN Plattformen nutzen (DL-7 "identische
   Live-Beispiele"). Byte-identische Werte zur vormaligen desktop.css-Kopie
   — reine Verschiebung, kein Redesign (Muster ui-456). Panel-/Tabellen-
   Breiten-Kontext (.ip-stbl table-layout, .ip-readonly-Hover-Overrides)
   bleibt bewusst desktop-lokal (betrifft nur das echte Live-Panel, nicht
   diese Basis-Optik).
   ============================================================ */
.ip-strow {
  display: grid; grid-template-columns: 18px auto auto 1fr 1px 30px; align-items: center; gap: 7px;
  border-radius: 7px; border: 1px solid transparent;
  transition: background .1s, border-color .1s, opacity .1s;
  min-height: 34px;
}
/* ui-065: Attribut-Icon */
.ip-strow .ip-icon {
  color: var(--muted); font-size: 14px; line-height: 1;
  justify-self: center; text-align: center; transition: color .1s; padding-left: 7px;
}
.ip-icon .credit-icon { width: 15px; height: 15px; vertical-align: -3px; }
/* ui-481: `min-width: 0` hebt die automatische Grid-Mindestgröße auf (sonst
   = Min-Content = volle Textbreite, Track kann nie schrumpfen); `overflow:
   hidden` macht die Box zum "Scroll-Container" im Sinne der Grid-Spec
   (Voraussetzung für `min-width:0`s Wirkung) UND schneidet den Text hart am
   Box-Rand ab, `text-overflow: ellipsis` zeigt „…" statt eines harten
   Schnitts, `white-space: nowrap` verhindert Umbruch. `.ip-lvl`/`.ip-val`
   bleiben ohne `overflow:hidden` (volle Min-Content-Breite als Floor, nie
   gekürzt). `title` mit dem vollen Namen kommt aus dem Markup. */
.ip-strow .ip-label {
  color: var(--text);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ui-099: Level-Chip — sekundär, gedimmt, tabular */
.ip-strow .ip-lvl {
  color: var(--muted); font-size: 11px; white-space: nowrap;
  letter-spacing: .02em; font-variant-numeric: tabular-nums;
}
.ip-strow .ip-lvl .lq { opacity: .62; }
.ip-strow .ip-lvl .mq { color: var(--text-dim); }
.ip-strow .ip-val { color: var(--text); text-align: right; white-space: nowrap; }

/* ui-193: Vordere Klick-Zone (Fokus-auf-nur-dieses) */
.ip-front { display: contents; cursor: pointer; }

/* ui-193: Trenner zwischen Wert und Checkbox-Zone (DL-3) */
.ip-div { width: 1px; height: 18px; background: var(--border); justify-self: center; }

/* ui-193: Checkbox-Zone — eigene Hit-Area, ganze Zellenhöhe (DL-3) */
.ip-cbz {
  justify-self: stretch; align-self: stretch; display: flex;
  align-items: center; justify-content: center;
  cursor: pointer; border-radius: 0 7px 7px 0; transition: background .1s;
}
.ip-cbz:hover { background: rgba(56,225,255,.10); }

/* ui-193: Checkbox = Quadrat (DL-2) */
.ip-cb {
  width: 16px; height: 16px; border-radius: 4px; border: 2px solid #3a4a6e;
  position: relative; transition: background .12s, border-color .12s; flex: 0 0 auto;
}
.ip-cb::after {
  content: ""; position: absolute; left: 4px; top: 0px; width: 4px; height: 9px;
  border: solid var(--text-on-accent); border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0); transition: transform .12s;
}

/* ui-193: DL-1 — Drei-Schicht-Zustandsmodell */
/* Im Set (angehakt, aber nicht aktiv): volle Deckkraft, Checkbox gefüllt cyan */
.ip-strow.in-set .ip-cb { background: var(--cyan); border-color: var(--cyan); }
.ip-strow.in-set .ip-cb::after { transform: rotate(45deg) scale(1); }
/* Nicht im Set: gedimmt, leere Checkbox */
.ip-strow:not(.in-set):not(.ip-disabled) { opacity: .5; }
.ip-strow:hover:not(.ip-disabled) { background: #131c30; }

/* Aktiv = der gerade bediente Stat (günstigster im Set / displayedStat).
   Starker Cyan-Highlight — identisch zum alten .ip-focused (DL-1). */
.ip-strow.active { background: rgba(56,225,255,.12); border-color: var(--cyan); opacity: 1; }
.ip-strow.active .ip-icon  { color: var(--cyan); }
.ip-strow.active .ip-label { color: var(--cyan); font-weight: 600; }
.ip-strow.active .ip-val   { color: var(--cyan); }
.ip-strow.active .ip-lvl   { color: var(--text-dim); }

/* ui-193: DL-5 — Letztes Häkchen: stiller No-op */
.ip-strow.locked .ip-cbz { cursor: default; }
.ip-strow.locked .ip-cbz:hover { background: transparent; }

/* Disabled (Waffe nutzt Stat nicht) — keine klickbare Checkbox (DL-7) */
.ip-strow.ip-disabled { opacity: .34; }
.ip-strow.ip-disabled .ip-front { cursor: not-allowed; }
.ip-strow.ip-disabled:hover { background: transparent; }
.ip-strow.ip-disabled .ip-cbz { cursor: not-allowed; }
.ip-strow.ip-disabled .ip-cb { border-color: #2a3450; background: transparent; }
.ip-strow.ip-disabled .ip-cb::after { transform: rotate(45deg) scale(0); }

/* ============================================================
   FLÄCHEN-SKELETON (ui-570, First-Paint-Regel ui-569) — synchron
   eingeblendet über surface-host.js coverMountWithSkeleton(), wenn ein
   ensure()/ensureUrl()-Fetch tatsächlich einen neuen Fragment-Inhalt lädt
   (Desktop #popin-mount, Mobile #mob-setup-mount/#mob-surface-mount) —
   verhindert, dass der ALTE Tab-/Flächen-Inhalt für einen Frame sichtbar
   bleibt (First-Paint-Regel oben: „niemals der Zustand der ANDEREN
   Fläche"). NEUE, nicht migrierte Regel (anders als der Rest dieser Datei,
   s. Datei-Kopf „reine Verschiebung") — bewusst hier platziert, weil BEIDE
   Kompositionen sie byte-identisch brauchen (Datei-Zweck laut Kopfkommentar
   „Regeln, die BEIDE Kompositionen byte-identisch nutzen", nicht nur ein
   einmaliger Migrations-Schnappschuss).
   Eigene Klassen (sh-skel, „surface-host") statt Re-Nutzung von Companions
   sn-skel (companion/pages/shared-mobile.css) — jene Datei lädt NUR als
   Teil des GEFETCHTEN Fragment-Response (loadMissingStylesheets()) und
   stünde beim allerersten Öffnen dieser Session noch gar nicht zur
   Verfügung (zirkuläre Abhängigkeit). tokens.css ist dagegen VOR dieser
   Datei gelinkt (s. Datei-Kopf Ladereihenfolge) — var(--line)/
   var(--line-2) sind von Anfang an verfügbar. Gleiches Shimmer-Rezept wie
   companion/surface-nav.js skeletonHTML() (bewusst dupliziert statt
   importiert — Bestand-Muster dieser Codebase für parallele Fragment-
   Loader, s. surface-host.js Datei-Kopf).
   ============================================================ */
.sh-skel { padding: 20px; }
.sh-skel-row {
  height: 14px;
  border-radius: 4px;
  margin-bottom: 0.55em;
  background: linear-gradient(90deg, var(--line) 25%, var(--line-2) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: sh-skel-shimmer 1.1s ease-in-out infinite;
}
.sh-skel-row.sh-skel-w60 { width: 60%; }
.sh-skel-row.sh-skel-w80 { width: 80%; }
@keyframes sh-skel-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sh-skel-row { animation: none; }
}

/* ============================================================
   ui-610: Kenntnisnahme-Dialog (Nutzungsbedingungen + Datenschutz) —
   SELBSTSTAENDIGE Kopie der Backdrop-/`.la-*`-Regeln aus companion/
   components.css (`#legal-ack-backdrop`-Sektion dort). Bewusst dupliziert
   statt importiert -- IDENTISCHES Bestand-Muster wie `.sh-skel*` direkt
   darueber (s. dortiger Kommentar): companion/legal-ack.js laedt UNBEDINGT
   am initialen Viewer-Boot (views/viewer-shell.js, VOR jedem Setup-Popin-
   Oeffnen -- der "Spielen"-CTA ist nicht dahinter versteckt), also BEVOR
   surface-host.js#loadMissingStylesheets() die Companion-CSS-Dateien
   (inkl. components.css, wo `.cs-modal-backdrop`/die urspruengliche `.la-*`-
   Sektion leben) je injiziert hat -- eine Re-Nutzung von `.cs-modal-backdrop`
   waere hier zirkulaer (die Klasse existiert erst NACH dem ersten Setup-
   Popin-Oeffnen). tokens.css ist VOR dieser Datei gelinkt (s. Datei-Kopf
   Ladereihenfolge) -- alle var(--...)-Tokens sind von Anfang an verfuegbar.
   Oeffnet der Spieler SPAETER den Setup-Popin (components.css laedt nach),
   gewinnt dessen `#legal-ack-backdrop`-ID-Regel dort per Kaskade-Reihenfolge
   (spaeter injiziert) gegen diese hier -- beide Fassungen sind aber
   deklarationsgleich, kein sichtbarer Unterschied, nur doppelt (harmlos).
   Auf Mobile-Home (`/dashboard`, Companion-Seite, KEIN Viewer) laedt diese
   Datei nie -- dort greift ausschliesslich components.css' Fassung, die
   von Anfang an geladen ist (kein Timing-Problem dort).

   Guard-Test: der Block zwischen den `ui-610 legal-ack — START/END`-Markern
   unten (`.la-card` bis zum reduced-motion-Media-Query, OHNE den Basis-Block
   `#legal-ack-backdrop`/`.open` hier drueber -- der lebt NUR hier, components.css
   bezieht ihn stattdessen ueber `.cs-modal-backdrop`) wird automatisiert gegen
   companion/components.css verglichen (deklarationsgleich, Kommentare
   ausgenommen) -- s. test/services/ui-610-legal-ack-css-guard.test.js.

   Design-Quelle: `.claude/sprint/prototypes/ui-610.html` (DL-1..DL-6).
   JS-Gegenstueck: companion/legal-ack.js (createBackdropModal(), util.js).
   ============================================================ */
#legal-ack-backdrop {
  position: fixed;
  inset: 0;
  z-index: 260; /* Muster components.css .cs-modal-backdrop (web-204/ui-448) */
  background: rgba(3, 5, 12, 0.72);
  display: none;
  align-items: center;
  justify-content: center;
  padding: max(20px, env(safe-area-inset-top, 0px)) max(20px, env(safe-area-inset-right, 0px)) max(20px, env(safe-area-inset-bottom, 0px)) max(20px, env(safe-area-inset-left, 0px));
}
#legal-ack-backdrop.open {
  display: flex;
}
/* ui-610 legal-ack — START (Guard-Test: test/services/ui-610-legal-ack-css-guard.test.js) */
#legal-ack-backdrop .la-card {
  width: min(560px, 100%);
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 14px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(56, 225, 255, 0.06);
  padding: 22px 24px 20px;
  color: var(--text);
  animation: la-in 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes la-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to { opacity: 1; transform: none; }
}
#legal-ack-backdrop .la-eyebrow {
  color: var(--cyan);
  font-size: var(--fs-caption);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin: 0 0 0.5em;
}
#legal-ack-backdrop h2 {
  margin: 0 0 0.5em;
  font-size: var(--fs-h3);
}
#legal-ack-backdrop .la-lead {
  margin: 0;
  color: var(--text-dim);
  line-height: 1.55;
  font-size: var(--fs-small);
}
#legal-ack-backdrop .la-links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 16px 0 0;
}
#legal-ack-backdrop .la-link {
  display: block;
  min-width: 0;
  overflow-wrap: anywhere;
  text-decoration: none;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  padding: 12px 14px;
  background: rgba(255, 255, 255, 0.02);
  color: var(--text);
}
#legal-ack-backdrop .la-link:hover {
  border-color: var(--cyan);
}
#legal-ack-backdrop .la-link b {
  display: block;
  color: var(--cyan);
  font-size: var(--fs-small);
}
#legal-ack-backdrop .la-link span {
  display: block;
  color: var(--muted);
  font-size: var(--fs-caption);
  margin-top: 0.2em;
  line-height: 1.4;
}
#legal-ack-backdrop .la-new {
  margin: 14px 0 0;
  border: 1px dashed rgba(240, 179, 80, 0.55);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: var(--fs-small);
  color: var(--text-dim);
  line-height: 1.5;
}
#legal-ack-backdrop .la-new b {
  color: var(--gold);
}
#legal-ack-backdrop .la-version {
  margin: 14px 0 0;
  color: var(--muted);
  font-size: var(--fs-caption);
}
#legal-ack-backdrop .la-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 16px;
  flex-wrap: wrap;
}
#legal-ack-backdrop .la-primary {
  flex: 1 1 auto;
  min-height: 46px;
  border-radius: 9px;
  border: 1px solid transparent;
  font-family: inherit;
  font-weight: 800;
  font-size: var(--fs-small);
  background: linear-gradient(135deg, var(--cyan), #8cf1ff);
  color: #04121a;
  cursor: pointer;
}
#legal-ack-backdrop .la-primary:hover {
  filter: brightness(1.06);
}
#legal-ack-backdrop .la-primary:disabled {
  opacity: 0.6;
  cursor: default;
  filter: none;
}
#legal-ack-backdrop .la-logout {
  color: var(--muted);
  font-size: var(--fs-caption);
  text-decoration: none;
}
#legal-ack-backdrop .la-logout:hover {
  color: var(--text);
  text-decoration: underline;
}
@media (max-width: 600px) {
  #legal-ack-backdrop {
    align-items: flex-end;
    padding: 0;
  }
  #legal-ack-backdrop .la-card {
    border-radius: 16px 16px 0 0;
    padding: 18px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    animation-name: la-up;
  }
  @keyframes la-up {
    from { transform: translateY(24px); opacity: 0; }
    to { transform: none; opacity: 1; }
  }
  #legal-ack-backdrop .la-links {
    grid-template-columns: 1fr;
  }
  #legal-ack-backdrop .la-actions {
    flex-direction: column;
    align-items: stretch;
  }
  #legal-ack-backdrop .la-logout {
    text-align: center;
    padding: 0.6em 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  #legal-ack-backdrop .la-card {
    animation: none;
  }
}
/* ui-610 legal-ack — END */
