/* ============================================================
   Chat Tower — Companion-Components-CSS (web-240, Frontend-Neubau Phase 6,
   P6-Kette Glied 5/8, "style.css-Schnitt I: base + components")

   Geteilte UI-Bausteine, die auf MEHREREN Companion-Seiten/-Tabs UND in
   beiden Konsumenten-Kontexten (Standalone /dashboard, /highscore,
   /u/:login … UND Viewer-Embed-Fragment) wiederverwendet werden —
   extrahiert aus dem damals noch monolithischen web/public/style.css
   (10.882 Z. vor diesem Ticket, seit web-241 komplett in
   client/css/companion/pages/*.css aufgeteilt und gelöscht). Architektur-
   Lock ui-456-Regime, analog auf Companion übertragen: reine Verschiebung,
   kein Redesign — Konsolidierung nur bei Byte-Gleichheit, Regel-Änderungen
   jeder Art sind NICHT Teil dieses Tickets.

   Inhalt (Datei-Reihenfolge = Fundort-Reihenfolge im damaligen style.css):
   - Icons: SVG-Ausrichtungs-Utilities (.credit-icon, svg.draw-icon,
     svg.hexa-icon-Familie, svg.stat-icon-Familie, svg.weapon-slot-icon-
     Familie) — NUR die bare Selektoren; seiten-gescopte Overrides (z.B.
     .catalog-symbol .credit-icon, .draw-btn svg.draw-icon) bleiben in den
     jeweiligen pages/*.css, weil ihr Träger-Element seiten-spezifisch ist.
   - Stat-Pill-Familie (.stat-pill/.stat-num/.stat-ic/.stat-label) —
     bestätigt geteilt Dashboard+Profil. Die umgebende .dash-stats/
     .dash-header-Layout-Hülle bleibt Dashboard-seitenspezifisch in
     pages/dashboard.css.
   - dash-tabs (.dash-tabs/.dash-tab/.tab-icon/.tab-label/.tab-badge--*,
     .ref-cluster/.reflink) — Ticket-AK-Beispiel, geteilte Tab-Leiste
     (Standalone UND Viewer-Embed-Fragment, P6-Kette "EIN PJAX").
   - Panels (.panel/.panel-title/.panel-hint/.panel-empty/.panel-title-sub) —
     bestätigt geteilt Dashboard+Profil.
   - Modal: Drop-Chancen-Trigger + -Modal (web-204, .cs-modal-backdrop/
     .cs-drop-odds/.cs-do-*) — Ticket-AK-Beispiel "Modals", JS-Gegenstück
     companion/util.js createDropOddsModal().
   - Modal: Geschenk-Benachrichtigungspopup (ui-601, .gift-*, nutzt
     .cs-modal-backdrop) — JS-Gegenstück companion/gift-notice.js
     (createBackdropModal(), util.js). Akzent fest Cyan (Fifty-Entscheid F3).
   - Modal: Kenntnisnahme-Dialog Nutzungsbedingungen + Datenschutz (ui-610,
     #legal-ack-backdrop .la-*, nutzt .cs-modal-backdrop) — JS-Gegenstück
     companion/legal-ack.js (createBackdropModal(), util.js). Alle `.la-*`-
     Regeln sind ID-gescoped (`#legal-ack-backdrop` als Vorfahre, ui-602-Guard).
   - Mobile Sub-Navigation "Segment/Auswahl-Sheet" (ui-628, .subnav/
     .subnav-seg-x/.subnav-pick-x/.subnav-sheet-x-Familien, Datei-Ende) —
     geteiltes Mobile-only-Muster fuer Erfolge/Shop/Tower/Rang/Sockel;
     JS-Gegenstück companion/subnav.js (Sheet auf/zu), SSR-Bausteine
     web/src/views/subnav.js. Jede Seite fuegt ihrem BESTEHENDEN Container/
     Buttons nur die `.subnav`-Klassen hinzu (kein neuer State-Pfad, Lock 5).
   - Ranking-Akzente (.ct-rank-name/-pos/-1/-2/-3, ui-058-Familie,
     web-240-Re-Spawn/Review cd1cd70f nachgezogen) — bare, eigenständige
     Regeln OHNE Co-Selektor. Ihre drei Geschwister (.ct-rank/
     .ct-rank-head/.ct-rank-num) bleiben korrekt in pages/highscore.css
     (dort ECHT mit .hs-table/.hs-participant-table in JEDER Regel
     verschmolzen) — diese fünf waren ursprünglich fälschlich derselben
     "untrennbar"-Kategorie zugeordnet worden (Erst-Review-Fund).
   - Card Tile (@property --pulse-angle, .cs-card-Kern + Tier-Farben +
     Surfaces/Body/Badges/Chevron/Click-Hint/Touch-Preview + alle
     Varianten-Overlays is-group/is-weapon-exclusive/is-pact/is-bosslure/
     is-hunt) — Ticket-AK-Beispiel "Karten-Tiles", wiederverwendet in
     Inventar-Grid, Sockel, Draw-Flip-Reveal UND Drop-Odds-Modal. Die
     Merge-Animation (.cs-upgrading/.just-upgraded/.cs-merge-label/
     .cs-upgrade-spark) + die umgebende Grid-/Upgrade-Bar-/Sort-Bar-Layout-
     Hülle (.cs-layout/.cs-panel/.cs-inv-grid/.cs-socket-grid/
     .cs-upgrade-bar) bleiben bewusst in pages/collection.css: sie sind eine
     an EINE Dashboard-Tab-Feature-Choreographie gekoppelte Interaktions-
     Sequenz bzw. Tab-Layout, kein Tile-Aussehen selbst.
   - Tooltips: .glyph-tip (CSS-only, "Tooltip-Komponente" laut eigenem
     Kommentar) + CTTip-System (.tipwrap/.tip-i/.ct-tip-self/.ct-tip-hover/
     #ct-tip, EIN fixed-Portal an document.body, konsumiert von build.js/
     techtree-tab.js/dashboard.js/app.js quer über die Companion-Seiten).
     .tt-pop (Tech-Tree-Bestätigungs-Popover) ist ein EIGENES, nicht mit
     CTTip verwandtes System und bleibt in pages/techtree.css (eigener
     Kopfkommentar dort).
   - Chip + Tier-Chip (.chip/.chip.sub, .tchip+.tn/.tv+6 Tier-Modifier
     t1-t6, web-240-Re-Spawn/Review cd1cd70f nachgezogen) — BEIDE haben
     ECHTE Produktions-Konsumenten in web/src/views/profile.js (Sub-
     Monate-/„Dabei seit"-/Runden-/Fokus-Chips, Tier-Badges) plus Demo-
     Nutzung in styleguide.js. Der ursprüngliche Kopfkommentar an ihrer
     alten style.css-Fundstelle ("existiert im Bestand nicht, nur Guide-
     Proposal") war selbst stale, ungeprüft übernommen (Erst-Review-Fund).
     Die seiten-gescopte .wb-focus .chip.sub-Override-Regel bleibt korrekt
     in pages/profile.css.

   Bewusst NICHT extrahiert (im Ticket begründet, s. `.claude/sprint/
   tickets/web-240.md` ## Notes): `.ct-rank`/`.ct-rank-head`/`.ct-rank-num`
   (ui-058-Familie, s. "Ranking-Akzente" oben für ihre fünf jetzt hier
   liegenden Geschwister) sind ECHTE Co-Selektoren mit .hs-table/
   .hs-participant-table (JEDE der drei Regeln verschmilzt beide Klassen-
   Familien in EINER Deklaration) — ein Aufsplitten wäre eine Regel-
   Änderung (Out-of-Scope), bleiben deshalb komplett in pages/highscore.css.
   `.embed-surface.ct-embed-mount` (ui-402) wäre ebenfalls ein Kandidat
   gewesen, hätte aber seinen 19-zeiligen Erklär-Header von der direkt
   folgenden, seitenspezifischen Tech-Tree-`:has()`-Verfeinerung getrennt
   (Kommentar-Fragmentierungs-Risiko) — bewusst NICHT bewegt, liegt seit
   web-241 in pages/dashboard.css.

   Ladereihenfolge (Kaskade-Lock, Ticket web-240):
     /static/tokens.css → /client/css/companion/base.css →
     /client/css/companion/components.css (DIESE DATEI) → die Companion-
     pages/*.css (Seiten-Rest, seit web-241 lazy nachgeladen statt eines
     einzelnen style.css). @media-Blöcke (Responsive-Design, web-241-Scope)
     bleiben bewusst VOLLSTÄNDIG in den pages/*.css — auch dort, wo sie NUR
     Selektoren betreffen, die hier ihre Basis-Deklaration haben (z.B.
     .cs-card.cs-can-upgrade/.cs-socket-box.is-empty teilen sich einen
     @media(prefers-reduced-motion:reduce)-Block in pages/collection.css,
     obwohl .cs-card.cs-can-upgrade hier in components.css liegt). Das ist
     sicher: die pages/*.css laden NACH dieser Datei, ein @media-Override
     dort gewinnt bei gleicher Spezifität unabhängig davon, in welcher
     physischen Datei die überschriebene Basis-Regel liegt (Kaskade ist
     dokumentreihenfolgen-, nicht dateibasiert) — live verifiziert (s.
     Ticket `## Notes` Kaskaden-Beleg).

   no-store-Mount (web/src/app.js '/client/*'-Route, identisch zu jeder
   anderen /client/*-Datei; s. base.css-Kopfkommentar für Details).
   ============================================================ */

/* Credits-Icon — Inline-SVG-Coin als Ersatz fürs textuelle Ⓒ. Wird via
   effectSymbolHTML() (catalog.js) bzw. renderSymbolHTML() (public/app.js)
   überall eingesetzt, wo der Income-Effekt symbolisch dargestellt wird.
   Identisches Design wie game-seitig (desktop.css/mobile.css .credit-icon) —
   cyan Glow-Ring + dunkler Coin-Body + golden C-Bogen. */
.credit-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.18em;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10.5' fill='none' stroke='%2338e1ff' stroke-opacity='0.4' stroke-width='1.5'/><circle cx='12' cy='12' r='8.5' fill='%230d1322' stroke='%2338e1ff' stroke-width='1.4'/><path d='M16.4 8.6 A5.2 5.2 0 1 0 16.4 15.4' fill='none' stroke='%23ffd84a' stroke-width='2.3' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  filter: drop-shadow(0 0 2px rgba(56, 225, 255, 0.55));
}

/* ui-333: Draw-Icon (gefächerte Karten + Hexagon-Rücken) — löst die ✦-Doppelbelegung
   (Draw/Crit-Chance) auf. currentColor: erbt Kontextfarbe (cyan im HUD/Reward-Text,
   dunkel auf dem cyan-gefüllten .draw-btn). Größe kommt als width/height-Attribut vom
   Helper (draw-icon.js / app.js drawIconSvg) — vertical-align hier nur für Baseline-Tuning
   im Text-/Inline-Fluss (glyph-tip-Reward-Zeilen, Draw-Button); in Flex-Kontexten
   (.stat-num) übernimmt align-items die Zentrierung, vertical-align ist dort wirkungslos. */
svg.draw-icon {
  vertical-align: -0.15em;
  flex-shrink: 0;
  /* ui-623 (AK2/DL-3): Währungsfarbe als Default — jede Aufrufstelle ohne eigenen
     Farb-Kontext zeigt jetzt Orange statt zufällig geerbtem Cyan/Magenta/Muted.
     Einzige dokumentierte Ausnahme: das dunkle Icon auf dem Draw-Button
     (.draw-btn svg.draw-icon weiter unten, höhere Spezifität gewinnt). */
  color: var(--cur-draw);
}
/* ui-633: byte-identisch nach web/client/css/viewer-base.css gespiegelt (Kommentar
   dort) — der Viewer lädt diese Datei erst mit der ersten Companion-Fläche nach
   (surface-host.js loadMissingStylesheets()) und braucht die Regel deshalb schon
   VOR diesem Zeitpunkt selbst. Änderung hier IMMER in beiden Dateien nachziehen. */

/* ui-374: Währungs-Kanon-Icons (⬢ Hexa / ⬡ Tech-Tree / ◆◇ Sockel / Sub-Monate-
   Kalender) — SVG statt Unicode (src/render/currency-icons.js; Client-Spiegel
   nur fürs Hexa-Icon in app.js). currentColor: erbt exakt die Kontextfarbe, die
   das Unicode-Zeichen vorher trug. Baseline-Tuning analog svg.draw-icon oben;
   in Flex-Kontexten (.stat-num, .build-dot, …) zentriert align-items,
   vertical-align ist dort wirkungslos. Bewusst NUR eigene Klassen (kein
   Selector auf geteilte Klassen) — /styleguide lädt die Companion-CSS
   (base.css/components.css/styleguide.css) mit und darf computed unverändert
   bleiben (Leak-Lock ui-361/367, web-240: Datei-Umzug, Aussage unverändert
   gültig). */
svg.hexa-icon,
svg.techtree-icon,
svg.sockel-icon,
svg.sub-months-icon {
  vertical-align: -0.15em;
  flex-shrink: 0;
}
/* ui-623 (AK2/DL-3): Hexa-Default bleibt Magenta (--cur-hexa == --magenta,
   Wert unverändert) — jetzt über den Währungs-Token statt impliziter
   Ancestor-Vererbung, robust gegen künftige Aufrufstellen ohne eigenen
   Farb-Kontext. ⬡ Tech-Tree UND Sockel bleiben hier bewusst UNVERÄNDERT:
   ⬡ ist ein Nav-/Tab-Glyph (keine Währungsrolle), ◆/◇ Sockel hat einen
   "gefüllt = belegt/wertvoll, Outline = leer" Kontrast, den ein pauschaler
   Mint-Default zerstören würde (leere Sockel-Plätze sollen NICHT wie ein
   Währungsbetrag leuchten) — Sockel-Mint kommt gezielt je Aufrufstelle,
   wo tatsächlich eine Sockel-ANZAHL angezeigt wird (s. .stat-pill[data-role]
   unten), nicht als SVG-Klassen-Default. */
svg.hexa-icon { color: var(--cur-hexa); }
/* ui-633: byte-identisch nach web/client/css/viewer-base.css gespiegelt (Kommentar
   dort) — Bereichs-Cluster/App-Nav-Slot „Hexa" (#nc-hexa u.a.) rendern VOR dem
   ersten Flächen-Mount, also vor dieser Datei; sprang vorher sichtbar von der
   ambienten Kontextfarbe (z.B. Cyan) auf Magenta um. Änderung hier IMMER in
   beiden Dateien nachziehen — svg.techtree-icon/svg.sockel-icon/svg.sub-months-icon
   bleiben bewusst NUR hier (Viewer nutzt sie nicht vor dem Flächen-Mount). */
/* Fließtext-Modus: 1em-Kantenlänge — überstimmt die width/height-Attribute des
   Helpers, das Icon skaliert dann mit der Kontext-Schriftgröße (Prosa, Panel-
   Titel, Tabellenzellen, Tab-Leiste). */
svg.cur-ic--text {
  width: 1em;
  height: 1em;
}


/* ui-375: Stat-/Attribut-Icons (⚔ ⏱ … + ◉ Heavy-Caliber) — SVG statt Unicode
   (src/render/stat-icons.js; Client-Spiegel in app.js, via CTUtil von build.js
   + techtree-tab.js genutzt). currentColor: erbt exakt die Kontextfarbe, die
   das Unicode-Zeichen vorher trug (magenta im Invest-Grid, muted/cyan in den
   Endmode-Chips, Node-Farbe im Tech-Tree). Baseline-Tuning analog
   svg.draw-icon/svg.hexa-icon oben; in Flex-Kontexten zentriert align-items.
   Bewusst NUR eigene Klassen (kein Selector auf geteilte Klassen) —
   /styleguide lädt die Companion-CSS (base.css/components.css/styleguide.css)
   mit und darf computed unverändert bleiben (Leak-Lock ui-361/367, web-240:
   Datei-Umzug, Aussage unverändert gültig). stat-ic--text: 1em-Modus analog
   cur-ic--text. */
svg.stat-icon {
  vertical-align: -0.15em;
  flex-shrink: 0;
}
svg.stat-ic--text {
  width: 1em;
  height: 1em;
}
/* ui-547: gemeinsame "groß"-Größenklasse für die Tower-Tab-Build-Sektion
   (Invest-Grid/Template-Zeilen/Endstate/Fokus-Set-Chips, companion/collection/
   templates.js statIconHtml() + der SSR-Spiegel investCell() in
   views/profile.js "Builds pro Tower") — Fiftys PTR-Feedback "deutlich
   größer" (Richtwert ~1.5-2x der vormaligen 1em-Kante, ~13-16px effektiv in
   diesen Kontexten). Fixe rem-Kante statt der bisherigen ambienten em-Kette:
   robust gegen den 13px-body-Override im Desktop-Viewer-Embed (ui-519-Lehre,
   s. pages/build.css .build-invest-cell-lvl-Kommentar) — <html> bleibt in
   Standalone UND Viewer-Embed gleichermaßen 15px, 1.7rem ist damit in BEIDEN
   Kontexten identisch ~25.5px, nicht kontextabhängig 13.65px/15.75px wie ein
   em-Wert es wäre. EINE Klasse für alle Konsumenten — die im Ticket
   geforderte Größen-Stellschraube.
   Nachzügler (Review should-fix, 2. Runde): der Credit-Icon-Fallback
   (`.credit-icon`, für die 2 Income-Stats income_wave_flat/income_pct_bonus,
   die statIconSvg() strukturell nicht abdeckt) blieb in genau den DREI
   Icon-Slot-Konsumenten Template-Zeile/Endstate/Fokus-Set-Chip zunächst auf
   der alten 1em-Kante stehen, während sein SVG-Geschwister direkt daneben
   schon 1.7rem trug — sichtbare Diskrepanz. `.credit-icon.stat-ic--lg`
   (Selektor-Zusatz unten) schließt genau diese Lücke, dieselbe Klasse/
   derselbe Wert, kein neuer Sonderweg. Gesetzt wird die Klasse gezielt über
   `creditIcon(true)` in templates.js (nur die drei Icon-Slot-Aufrufstellen)
   — die übrigen `.credit-icon`-Verbraucher (Kosten-/Delta-Anzeige derselben
   Datei, Toasts/Status ui-545, alle anderen Ports) rufen `creditIcon()`
   ohne Argument, tragen die Klasse also nie und bleiben unverändert
   ambient-groß (1em). */
svg.stat-ic--lg,
.credit-icon.stat-ic--lg {
  width: 1.7rem;
  height: 1.7rem;
}


/* ui-377: Achievement-/Punkte-/Rang-/Nav-Icons (⌖ ◈ ★ ❖ Spitzenreiter 👑 👤 🛒 ➕)
   — SVG statt Unicode/Emoji (src/render/meta-icons.js; Client-Spiegel nur für
   weapon-slot/weapon-point/skill-point in app.js, via CTUtil von
   techtree-tab.js + dash-onboarding.js genutzt). currentColor: erbt exakt die
   Kontextfarbe, die das Zeichen vorher trug (gold in Panel-Titeln/.tt-pts-weapon,
   magenta in .tt-pts-global, muted/cyan in der Tab-Leiste). Baseline-Tuning
   analog svg.draw-icon/svg.hexa-icon oben; in Flex-Kontexten zentriert
   align-items. Bewusst NUR eigene Klassen (kein Selector auf geteilte Klassen)
   — /styleguide lädt die Companion-CSS (base.css/components.css/styleguide.css)
   mit und darf computed unverändert bleiben (Leak-Lock ui-361/367, web-240:
   Datei-Umzug, Aussage unverändert gültig). 1em-Modus: geteilte
   cur-ic--text-Klasse (s. oben). ui-567: + svg.shop-icon (🛒). ui-582: +
   svg.invite-icon (➕, "Einladen"-Tab) — beide Nachzügler in DIESER Regel
   (kein Sonderweg, Muster/Review-Lehre ui-567 (i): Baukasten-Selector-Liste
   bleibt zusammen, sonst fehlt die Baseline-Behandlung trotz visuell
   identischem Icon-Stil). */
svg.weapon-slot-icon,
svg.achievement-icon,
svg.weapon-point-icon,
svg.skill-point-icon,
svg.frontrunner-icon,
svg.crown-icon,
svg.profile-icon,
svg.shop-icon,
svg.invite-icon {
  vertical-align: -0.15em;
  flex-shrink: 0;
}
/* ui-623 (AK2/DL-3): Skillpunkt-Default — Violett (--cur-skill) statt des
   bisherigen Magenta (das war identisch zu Hexa, Fifty-Bug-Report "Skillpunkt
   ist auch pink"). Die übrigen Icons dieser Liste tragen keine Währungsrolle
   (★ Waffen-Punkt bleibt gold je Aufrufstelle, s. .tt-pts-weapon/.g-wp) und
   bleiben unverändert ancestor-gefärbt. */
svg.skill-point-icon { color: var(--cur-skill); }

/* .stat-pill padding: IST 0.5em/1em=7.5px/15px vs. --sp-s/--sp-l-max 8px/16px —
   bewusste Skalen-Angleichung (ui-265) */
.stat-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--sp-s) var(--sp-l);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  min-width: 5em;
}
.stat-num { font-size: var(--fs-h2); font-weight: 600; color: var(--cyan); display: inline-flex; align-items: center; gap: 0.28em; }
/* ui-134: Icon-Span in Header-Pillen — gedämpft, kleiner als die Zahl */
.stat-ic { font-size: 0.78em; opacity: 0.85; }
.stat-num.weapon-glyph {
  color: var(--yellow);
  text-shadow: 0 0 8px rgba(255, 216, 74, 0.4);
  display: inline-flex; align-items: center; justify-content: center;
}
/* ui-040: Glyph-<img> (Canvas-Render, hydriert via app.js). Fallback-Symbol bleibt via
   CSS color; nach Hydration ersetzt das <img> den Text (textContent = ''). */
.stat-num.weapon-glyph img.wg-img {
  display: block;
  filter: drop-shadow(0 0 4px rgba(255, 216, 74, 0.5));
}
/* ui-623 (AK4): Konto-Leisten-Pillen (dash-account-bar.js) — Draws-/Sockel-Pille
   trugen bislang unterschiedslos das generische .stat-num-Cyan (Draws) bzw.
   dasselbe Cyan (Sockel, NICHT das im Ticket-Inventar vermutete Magenta — IST-
   Check ui-623). Beide bekommen jetzt ihre Währungsfarbe (Glyph UND Zahl, DL-2)
   über das bestehende `data-role`-Attribut auf `.stat-pill` — kein JS-/Markup-
   Touch nötig (dash-account-bar.js setzt data-role bereits seit ui-371).
   Sub-Monate-Pille (data-role="sub") ist keine der vier Währungen und bleibt
   unverändert Cyan (Basisregel .stat-num oben). */
.stat-pill[data-role="draws"] .stat-num  { color: var(--cur-draw); }
.stat-pill[data-role="sockel"] .stat-num { color: var(--cur-socket); }
/* ui-342: fixe 13px statt --fs-caption (clamp deckelt bei 10.5px, Audit-Fund
   „Kopf-Pillen-Label 10,5px" — Kontrast löst ui-341 über den --muted-Token,
   hier nur die Größe). Bewusst literal statt Skalen-Token: kein --fs-*-Wert
   liegt bei/über 13px (--fs-small max 12,75px), ein neuer Token wäre
   Designer-/Token-Land (tokens.css). Gilt Desktop UND Mobile — die einzige
   Mobile-Sonderregel ist `.dashboard .stat-label{display:none}` bei ≤600px
   (Kontext-Pillen-Kompaktstrip, s. ui-318-Mobil-Block), keine eigene Mobile-Font-Size-Regel. */
.stat-label { font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }


.dash-tabs {
  display: flex;
  gap: 0;
  margin: 1.5em 0 1em;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.dash-tab {
  display: flex;
  align-items: center;
  gap: 0.5em;
  /* ui-265: var(--sp-s) vertikal fiel im Nachmessen @768px+Touch (iPad-Preset,
     kein pointer:coarse-Media unter 600px) effektiv unter 44px (39.5625px) —
     vertikales Padding auf var(--sp-m) gehoben haelt >=44px ueber alle
     Proof-Breiten (Dev-Prompt-Vorgabe). Horizontal IST 1.3em=19.5px vs.
     --sp-l-max 16px — bewusste Skalen-Angleichung (ui-265). */
  padding: var(--sp-m) var(--sp-l);
  color: var(--muted);
  border-bottom: 2px solid transparent;
  transition: color 0.15s, border-color 0.15s;
}
.dash-tab:hover { color: var(--text); text-decoration: none; }
.dash-tab.active {
  color: var(--cyan);
  border-bottom-color: var(--cyan);
  text-shadow: 0 0 8px rgba(56, 225, 255, 0.3);
}
.tab-icon { font-size: 1.1em; }
.tab-label { font-weight: 500; }
/* ui-318: zwei Label-Varianten pro Tab (DASH_TABS-Array `label`/`mobileLabel`,
   seit ui-368 im geteilten Leaf-Modul views/dash-nav.js) — Desktop-Basis zeigt
   NUR die volle Form (.tab-label--full), .tab-label--mobile ist hier versteckt.
   Der ≤600px-Sub-Nav-Block (seit ui-389 Sticky-Top-Strip, vormals ui-318-
   Bottom-Nav) dreht das um (Kurzformen „Tech"/„Erfolge"/„Rang" —
   Tech-Tree/Achievements/Highscore sind auf Desktop-Top-Strip-Breite gedacht,
   im Quer-Scroll-Strip spart die Kurzform Scroll-Distanz (ui-389-Prototyp);
   Waffe/Sockel sind bereits kurz genug, `mobileLabel` bleibt dort undefiniert
   → Fallback auf `t.label`, identischer Text in beiden Spans). Desktop bleibt
   dadurch visuell byte-gleich (nur EIN sichtbarer Label-Text wie vor ui-318).
   ui-586 (B2, ≤600px): im Standalone-Sub-Nav-Strip (`.dashboard`/
   `.profile-page`, pages/dashboard.css) ist `.tab-label--mobile` seither
   NICHT mehr fuer jeden Tab sichtbarer Fliesstext — nur der AKTIVE Tab zeigt
   es, inaktive Tabs clippen es auf 1x1px (Icon-only-Strip). `display:inline`
   hier bleibt trotzdem die richtige Basis: das Clip-Pattern arbeitet ueber
   `position:absolute` unabhaengig vom `display`-Wert (Muster mobile.css
   ≤640px `#nav > .dash-tabs .dash-tab .tab-label--mobile`, identische
   Kombination dort). */
.tab-label--mobile { display: none; }

/* ui-101/web-331: Tech-Tree-Tab-Badge — neue Kauf-Entscheidungen seit dem
   letzten Besuch (vormals Dauer-Marker "ausgebbare Skillpunkte").
   Pill in Violett (❖-Skillpunkt-Rolle, --cur-skill — ui-623 löst DL-A aus
   ui-085 ab, das Magenta vorschrieb und damit identisch zu Hexa war).
   Puls: 1,4 s, Pending-Dot-Standard (ui-081), reduced-motion deaktiviert Puls.
   ui-381: Form/Größe/Puls-Geometrie geteilt mit dem Sockel-Tab-Draws-Badge
   (.tab-badge--draw, Design-Lock „1:1 wie .tab-badge--sp, nur Farbtokens
   tauschen") — EINE Werte-Quelle, Farben stehen je Klasse darunter.
   ui-378: dito für das ◈-Achievement-Claimable-Badge (.tab-badge--ach, Gold).
   ui-585 (AK0): dito für das 🛒-Shop-Badge (.tab-badge--shop, Orange) — Markup
   existierte seit ui-557, hatte aber nie eine Regel (nackte Zahl statt Pill,
   Prototyp-Nebenfund ui-586). --orange-Token war bereits belegt
   (pages/shop.css .shop-subtab-badge, thematisch passend).
   ui-471: vertikale Mitte kommt NICHT von einer eigenen Regel hier, sondern
   vom Elternteil .dash-tab (weiter oben in dieser Datei: display:flex;
   align-items:center) — die Badge ist als Flex-Item automatisch mittig zu
   .tab-icon/.tab-label ausgerichtet. Ein vormals hier gesetztes
   `vertical-align:middle` war seit .dash-tab zu Flex wurde totes Markup
   (vertical-align wirkt laut Spec nur in Inline-/Table-Cell-Kontexten, nie
   auf Flex-Items) — entfernt statt fälschlich als Zentrierungs-Mechanismus
   stehen zu lassen. Gemessen (getBoundingClientRect Badge vs. .tab-label,
   Desktop-Standalone/Viewer-Popin/Mobile-Fragment, alle drei Farben, „9+"
   UND Einzelziffer): Δ 0px, siehe Ticket-Notes fuer Messreihe. */
.tab-badge--sp,
.tab-badge--draw,
.tab-badge--ach,
.tab-badge--shop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.35em;
  height: 1.35em;
  padding: 0 0.35em;
  /* ui-342: dunkler Text auf Magenta statt weiß (3,10:1 AA-Fail) — folgt dem
     Tier-Badge-Muster (.cs-card-tier-badge), ~6,4:1. font-size +0.05em
     (optionaler +1px-Hinweis im Ticket) — Badge bleibt eine kleine Pill,
     kein Layout-Risiko. Auf Cyan (--draw), Gold (--ach) und Orange (--shop,
     ui-585) ist derselbe dunkle Text erst recht AA-sicher (hellere Gründe).
     ui-623: --sp ist seither Violett (--cur-skill), --draw ist seither Orange
     (--cur-draw) statt Cyan — beide heller als Magenta, der dunkle Text bleibt
     also erst recht AA-sicher (unveränderte Begründung, nur die Ausgangsfarben
     verschoben). */
  color: var(--text-on-accent);
  font-size: 0.75em;
  font-weight: 700;
  border-radius: 999px;
  line-height: 1;
  margin-left: 0.35em;
  pointer-events: none;
}
/* Farbwelten (Bubble-Bedeutungen unterscheidbar, Design-Lock ui-381/ui-378;
   ui-623 (2026-09-09): Skillpunkte + Draws auf die neuen Währungs-Tokens
   umgestellt — DL-4 "Cyan bleibt reine UI-Farbe", Skillpunkt teilte sich vorher
   Magenta mit Hexa (Fifty-Bug-Report)):
   violett (--cur-skill) = Skillpunkte (⬡-Tab, vormals Magenta),
   orange (--cur-draw) = Draws (◆-Sockel-Tab, vormals Cyan),
   gold = claimbare Achievements (◈-Tab, Fifty-Entscheid 2026-07-25, unverändert),
   orange (--orange) = unaufgedeckte Shop-Angebote (🛒-Tab, ui-585 AK0 — "Badge-
   Quelle = Aufdecken, nie Kauf", Lock aus ui-557, unverändert). Beobachtung
   ui-623: --cur-draw (#ffb347) und --orange (#ffb347) sind derselbe Hex-Wert —
   die Draws- und die Shop-Bubble sehen jetzt zufällig gleich aus (verschiedene
   Tabs, verschiedene Bedeutung). Fifty-Design-Lock DL-1 legt --cur-draw exakt
   auf diesen Wert fest; kein Fix in diesem Ticket, s. Notes. */
.tab-badge--sp   { background: var(--cur-skill); }
.tab-badge--draw { background: var(--cur-draw); }
.tab-badge--ach  { background: var(--gold); }
.tab-badge--shop { background: var(--orange); }
.tab-badge--hidden {
  display: none;
}

/* ui-623: rgba von --cur-skill (#b48cff = rgb 180,140,255) — vormals Magenta
   (255,69,181), s. Farbwelten-Kommentar oben. */
@keyframes tab-badge-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(180, 140, 255, 0.7); }
  50%       { box-shadow: 0 0 0 5px rgba(180, 140, 255, 0); }
}
/* ui-623: rgba von --cur-draw (#ffb347 = rgb 255,179,71) — vormals Cyan
   (56,225,255), s. Farbwelten-Kommentar oben (deckungsgleich mit
   tab-badge-pulse-shop unten — bewusst als eigene Keyframe belassen, weil
   Draws und Shop-Angebote konzeptuell verschiedene Rollen sind, s. Notes). */
@keyframes tab-badge-pulse-draw {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 179, 71, 0.7); }
  50%       { box-shadow: 0 0 0 5px rgba(255, 179, 71, 0); }
}
/* ui-378: Puls-Geometrie 1:1 wie tab-badge-pulse, rgba von --gold (#f0b350). */
@keyframes tab-badge-pulse-ach {
  0%, 100% { box-shadow: 0 0 0 0 rgba(240, 179, 80, 0.7); }
  50%       { box-shadow: 0 0 0 5px rgba(240, 179, 80, 0); }
}
/* ui-585 (AK0): Puls-Geometrie 1:1 wie tab-badge-pulse, rgba von --orange (#ffb347). */
@keyframes tab-badge-pulse-shop {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 179, 71, 0.7); }
  50%       { box-shadow: 0 0 0 5px rgba(255, 179, 71, 0); }
}

/* ui-087: Referenz-Cluster — rechtsbündig in der Tab-Zeile */
.ref-cluster {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0;
  padding-left: 0.5em;
  flex-shrink: 0;
}
.reflink {
  display: flex;
  align-items: center;
  gap: 0.25em;
  padding: 0.5em 0.75em;
  color: var(--muted);
  font-size: 0.8em;
  white-space: nowrap;
  text-decoration: none;
  transition: color 0.15s;
}
.reflink:hover { color: var(--cyan); text-decoration: none; }
/* ui-503 (M1): 0.8em (~9.6px effektiv) -> 11px-Boden (K1). Direkter px-Wert statt
   em-Kette, damit die Groesse unabhaengig vom .reflink-Ancestor-Kontext greift. */
.reflink .ext { font-size: 11px; opacity: 0.7; }


/* ===== Panels ===== */

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1.2em 1.4em;
  margin-bottom: 1em;
}
.panel-title {
  margin: 0 0 0.4em;
  color: var(--cyan);
  font-size: 1.1em;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.panel-hint {
  margin: 0 0 1em;
  color: var(--muted);
  font-size: 0.9em;
  line-height: 1.5;
}
.panel-empty {
  color: var(--muted);
  font-style: italic;
  padding: 1em 0;
}


/* ===== web-204: Drop-Chancen-Trigger in der Draw-Leiste (DL-1) ===== */
.cs-drop-odds-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  background: none;
  border: none;
  padding: 0.25em 0;
  margin-top: 0.15em;
  color: var(--cyan);
  font: inherit;
  font-size: 1em;
  cursor: pointer;
}
.cs-drop-odds-link:hover {
  text-decoration: underline;
}

/* ===== web-204: Drop-Chancen-Modal (R2: raumgreifend, DL-2) ===== */
.cs-modal-backdrop {
  position: fixed;
  inset: 0;
  /* ui-448: 100→260. .cs-modal-backdrop haengt — wie #ct-tip — als
     document.body-Kind NEBEN #popin-scrim (z-200, desktop.css): beide
     position:fixed am Root-Stacking-Context, der zahlenmaessig hoehere
     gewinnt unabhaengig von der DOM-Reihenfolge. Bei z-100 oeffnete das
     Drop-Chancen-Modal deshalb strukturell HINTER dem Popin, sobald
     „Drop-Chancen ansehen" (#drop-odds-open, Sockel-Tab-Draw-Leiste) aus
     der Popin-Hexa-Flaeche geklickt wurde — Prod-Spieler-Meldung 2026-08-10
     ("Fenster mit den Auflistungen im Hintergrund"), seit v0.8.2 live
     erreichbar (meta-036 holte die Hexa-Flaeche ins Popin; das Modal selbst
     ist seit web-204 unveraendert bei z-100 geblieben). Dasselbe Muster wie
     ui-415 (#ct-tip 80→250) — "Popin absenken" (Alternative b) ist dort
     bereits mathematisch ausgeschlossen worden (Popin muesste unter 80
     fallen, damit auch unter Room-Switcher 130 — bricht dessen Overlay-
     Rolle) und gilt identisch hier. 260 liegt bewusst UEBER #ct-tip (250,
     ui-415): ein echtes blockierendes role="dialog"-Modal soll nicht unter
     einem kontextuellen Tooltip liegen. Heute kein realer Ueberschneidungs-
     fall (renderDropChanceTile() traegt keine .tipwrap/.tip-i-Trigger, und
     der #drop-odds-open-Klick schliesst ueber denselben document-Click-
     Handler jeden offenen Tip automatisch mit, s. app.js tipCloseAll()-Pfad
     via "Klick ausserhalb jedes Triggers") — die Ordnung ist Vorsorge fuer
     kuenftigen Modal-Inhalt. Klar UNTER allem, was NUR lokal in Popins
     eigenem Stacking-Context liegt (.ob-wt/Setup-Walkthrough, lokal z-300,
     s. #ct-tip-Kommentar) sowie #toast-bar/#pwa-install-bar (9999). Kein
     Konflikt mit Room-Switcher/Endscreen/Onboarding (120/121/130): wie
     #ct-tip traegt das Modal ausserhalb des Popins nie einen Trigger
     (#drop-odds-open lebt nur im `/dashboard?embed=1`-Fragment). */
  z-index: 260; /* über #ct-tip (250)/#popin-scrim (200), unter .tt-toasts (270, ui-630)/.ob-wt (300, lokal)/#toast-bar (9999) */
  background: rgba(3, 5, 12, 0.72);
  display: none;
  align-items: center;
  justify-content: center;
  /* ui-388 (viewport-fit=cover): Gutter mindestens so gross wie die Safe-Area-
     Insets, damit die Modal-Kante nicht unter Notch/Home-Indikator liegt —
     max()-Muster wie die Viewer-Overlays (desktop.css/mobile.css). Desktop: max(20px, 0) = 20px. */
  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));
}
.cs-modal-backdrop.open {
  display: flex;
}
.cs-drop-odds {
  width: min(94vw, 1060px);
  max-height: min(90svh, 900px);
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--cyan);
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5), 0 0 22px rgba(56, 225, 255, 0.15);
}
/* #drop-odds-content ist der Client-seitige Fetch-Ziel-Container (app.js) zwischen
   .cs-drop-odds (Flex-Column) und den Server-Fragment-Kindern (.cs-do-split/.cs-do-body/
   .cs-do-foot). Ohne diese Regel ist er ein normaler Block-Flex-Item, das zu seiner
   VOLLEN Content-Höhe wächst (die flex:1 1 auto + min-height:0 auf .cs-do-body wirkungslos
   macht, weil DESSEN Elternhöhe nie begrenzt wird) — die klassische Flex-Falle wandert
   dann nur eine Ebene höher. Muss selbst Flex-Column sein und den Fragment-Kindern die
   Höhen-Weitergabe erlauben (web-204 Fifty-Nachtrag: Scroll-Fix im Prototyp UND hier). */
#drop-odds-content {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
.cs-do-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 10px;
  flex-shrink: 0;
}
.cs-do-title {
  font-size: 0.82em;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  font-weight: 700;
}
.cs-do-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.4em;
  cursor: pointer;
  padding: 4px 10px;
  line-height: 1;
  border-radius: 4px;
  transition: color 0.15s;
}
.cs-do-close:hover {
  color: var(--cyan);
}

/* Verteilungs-Summary: Split-Meter + Labels (DL-3) — fix über dem Scroll-Bereich */
.cs-do-split {
  padding: 0 20px 14px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.cs-do-split-labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.84em;
  margin-bottom: 6px;
}
.cs-do-split-labels .eff {
  color: var(--cyan);
  font-weight: 600;
}
.cs-do-split-labels .wpn {
  color: var(--yellow);
  font-weight: 600;
}
.cs-do-split-labels .wpn.none {
  color: var(--muted);
  font-weight: 400;
}
.cs-do-meter {
  display: flex;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--muted-a10);
}
.cs-do-meter .m-eff {
  background: var(--cyan);
  transition: width 0.22s;
}
.cs-do-meter .m-wpn {
  background: var(--yellow);
  transition: width 0.22s;
}

/* Scroll-Bereich mit Karten-Grids (DL-4).
   flex:1 1 auto + min-height:0 sind PFLICHT in diesem Flex-Column-Container —
   overflow-y:auto allein reicht NICHT (klassische Flex-Falle: ohne min-height:0
   wächst der Body über seinen Flex-Anteil hinaus und overflowt das Modal statt
   intern zu scrollen). Verifiziert web-204 Browser-Smoke: scrollHeight > clientHeight. */
.cs-do-body {
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
  padding: 6px 20px 14px;
}
.cs-do-sect {
  font-size: 0.74em;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-dim);
  margin: 16px 0 10px;
  display: flex;
  justify-content: space-between;
}
.cs-do-sect .cnt {
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
}
/* ui-332: Section-Kopf-Hinweis "Tier-Upgrade bis T<N>", nur bei Admin-Max-Tier > 1 */
.cs-do-sect .tier-note {
  color: var(--cyan);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 700;
}

.cs-do-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(122px, 1fr));
  gap: 14px;
}

/* Ein Grid-Item = echte Karte (.cs-card, display-only) + Chance-Chip drunter */
.cs-do-item {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.cs-do-card-slot {
  position: relative;
  aspect-ratio: 3 / 4;
}
.cs-do-pct {
  text-align: center;
  font-size: 0.95em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.cs-do-pct .pct-lbl {
  display: block;
  font-size: 0.8em; /* ui-344: 0.68em→0.8em, war ~9.69px */
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ui-332: Per-Karte-Tier-Aufschlüsselung unter dem Chance-Chip (nur bei Admin-Max-Tier
   > 1 — s. renderTierRow() in views/dashboard.js). Design-Lock-Referenz: Prototyp v2
   .claude/sprint/prototypes/ui-332.html. Volle Ausprägung: tier-farbige Mini-Chips
   (.tt), kompakt, tabular-nums. Tierlos/gecappt/Waffen: eine gedämpft-kursive Zeile
   (.tierless) statt Chips — kein Split. */
.cs-do-tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 7px;
  justify-content: center;
  padding-top: 1px;
  border-top: 1px dashed var(--line);
}
.tt {
  /* ui-503 (M1): 0.64em (~9.6px effektiv) -> 11px-Boden (K1), direkter px-Wert
     statt em-Kette (Ancestor-unabhaengig). */
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-dim);
  display: inline-flex;
  gap: 0.28em;
  align-items: baseline;
}
.tt .tn {
  font-weight: 800;
}
/* Tier-Farben — identisch zum Sammlung-Kanon (.cs-card[data-tier="N"] weiter unten):
   T1 cyan / T2 magenta / T3 gold(-gelb) / T4 grün / T5+ Silber (ui-611: dieses
   Widget rendert die Tier-ZAHL exakt (renderTierRow(), dashboard.js), bucketet
   die FARBE aber weiterhin bei "5" (Math.min(t.tier, 5)) — bewusst NICHT auf die
   volle T6-T9-Farbleiter erweitert (Out-of-Scope für ui-611, kompakte
   Drop-Chance-Aufschlüsselung, kein Live-Container; s. Ticket ## Notes). Der
   Bucket zeigt jetzt automatisch Silber statt Weiß, weil --card-color-t5 seinen
   Wert geändert hat — kein Code-Unterschied nötig. */
.tt.t1 .tn { color: var(--cyan); }
.tt.t2 .tn { color: var(--magenta); }
.tt.t3 .tn { color: var(--yellow); }
.tt.t4 .tn { color: var(--green); }
.tt.t5 .tn { color: var(--card-color-t5); }
/* web-320 AK12/DL-9: „temporär"-Label hinter dem Prozentwert eines Tier-Chips,
   nur bei aktivem Happy-Draw-Hour-Event fuer Tier-Zeilen > base_tier
   (renderTierRow(), views/dashboard.js). Direkter px-Wert (kein em, ui-344/K1-
   Lehre "11px-Boden nicht unterschreiten") — DL-9 gibt 11px explizit vor.
   Nested unter .tt (2 Compounds) -- Ratchet-Guard-scoped ohne neuen Praefix.
   Farbe: dieselbe Tier-Farbe wie .tn (Geschwister-Element), Bucket-Regel
   identisch zu .tt.tN .tn oben (Math.min(t.tier,5) -- ui-611-Lock, s. dortiger
   Kommentar "bewusst NICHT auf T6-T9 erweitert"). */
.tt .tt-temp {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
}
.tt.t1 .tt-temp { color: var(--cyan); }
.tt.t2 .tt-temp { color: var(--magenta); }
.tt.t3 .tt-temp { color: var(--yellow); }
.tt.t4 .tt-temp { color: var(--green); }
.tt.t5 .tt-temp { color: var(--card-color-t5); }
.cs-do-tiers.tierless {
  color: var(--muted);
  font-size: 0.75em; /* ui-344: 0.66em→0.75em, war ~9.9px */
  font-style: italic;
  border-top-color: var(--line-2);
}

/* web-320 AK12/DL-9: Hinweiszeile "Happy Draw Hour · Max-Tier N · temporäre
   Chance, endet in …" zwischen Split-Meter und Karten-Tabelle (nur bei
   aktivem Event, renderDropChancesModalBody()). Tier-Farbe ueber dieselben
   Tokens wie .hdh-banner[data-tier]/.cs-card[data-tier] (Lock 4 — Paritaetstest
   web-320-css-parity.test.js). */
.cs-do-event-hint {
  --hdh-color: var(--cyan);
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 20px 0;
  padding: 8px 12px;
  border: 1px solid color-mix(in srgb, var(--hdh-color) 45%, var(--line));
  border-radius: 6px;
  font-size: 0.86em;
}
.cs-do-event-hint[data-tier="2"] { --hdh-color: var(--magenta); }
.cs-do-event-hint[data-tier="3"] { --hdh-color: var(--yellow); }
.cs-do-event-hint[data-tier="4"] { --hdh-color: var(--green); }
.cs-do-event-hint[data-tier="5"] { --hdh-color: var(--card-color-t5); }
.cs-do-event-hint[data-tier="6"] { --hdh-color: var(--card-color-t6); }
.cs-do-event-hint[data-tier="7"] { --hdh-color: var(--card-color-t7); }
.cs-do-event-hint[data-tier="8"] { --hdh-color: var(--card-color-t8); }
.cs-do-event-hint[data-tier="9"] { --hdh-color: var(--card-color-t9); }
.cs-do-event-hint .cs-do-eh-badge {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  border-radius: 4px;
  background: var(--hdh-color);
  color: var(--text-on-accent);
  /* ui-505/K1-Sweep-Lock: kein neues literales Sub-11px-font-size — --fs-micro
     (10px SSOT-Token) statt eines literalen 9px-Werts (s. web/spec/frontend.md
     "Companion-Sweep"-Regel, test/services/ui-505-companion-rest-fs-m3.test.js
     Sektion L). */
  font-size: var(--fs-micro);
  font-weight: 800;
}
.cs-do-event-hint .cs-do-eh-text { color: var(--text); }
.cs-do-event-hint .hdh-countdown { color: var(--hdh-color); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Hinweis-Fläche „alle Waffen im Bestand" (DL-5) — volle Grid-Breite */
.cs-do-allowned {
  grid-column: 1 / -1;
  border: 1px dashed var(--line-2);
  border-radius: 10px;
  padding: 18px 16px;
  color: var(--muted);
  font-size: 0.88em;
  line-height: 1.5;
}
.cs-do-allowned b {
  color: var(--green);
  font-weight: 600;
}

/* Fußzeile (DL-6) */
.cs-do-foot {
  padding: 10px 20px 14px;
  border-top: 1px solid var(--line);
  font-size: 0.76em;
  color: var(--muted);
  flex-shrink: 0;
}

/* Loading / Error States (DL-8) */
.cs-do-state {
  padding: 44px 20px;
  text-align: center;
  color: var(--muted);
  font-size: 0.92em;
}
.cs-do-state .retry {
  color: var(--cyan);
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  padding: 0;
}
.cs-do-state .retry:hover {
  text-decoration: underline;
}

/* =====================================================================
   ui-601: Geschenk-Benachrichtigungspopup ("🎁 Geschenk von Fifty") —
   nutzt den GETEILTEN `.cs-modal-backdrop`-Baustein oben (web-204/ui-448,
   z-260) — kein neuer Overlay-Mechanismus (DL-1). Modal-Karte `.gift-modal`
   im Muster `.club-buy-modal` (achievements.css: Panel, 14px-Radius, ✕ oben
   rechts mit 44px-Ziel). JS-Gegenstueck: companion/gift-notice.js
   (createBackdropModal(), util.js).

   Design-Quelle: `.claude/sprint/prototypes/ui-601.html` — CSS 1:1
   uebernommen, mit zwei Anpassungen ggue. dem Prototyp:
   (a) Akzent ist FEST Cyan (Fifty-Entscheid F3, 2026-09-05) — die
       Prototyp-Toggle-Selektoren `.frame[data-accent="cyan|gold"]` sind
       deshalb entfallen, ihre Cyan-Werte direkt in die Basis-Regeln
       gewandert (Gold existiert hier nicht mehr).
   (b) `.btn-ok`/`.btn-link` sind unter `.gift-modal` gescoped (Prototyp
       hatte sie bare) — Fallen-Register-Lehre ui-600 (PTR-Fund 2026-09-05,
       s. web/client/README.md): components.css laedt auf JEDER Companion-
       Seite, ein bare, generisch klingender Klassenname wie `.btn-ok`
       koennte mit einem kuenftigen, unabhaengigen Baustein kollidieren.
   `.frame[data-w="384"]`-Overrides (Prototyp-Breiten-Toggle) sind als
   echte `@media`-Regel unten uebernommen (bei `min(94vw,480px)` OHNE
   Breiten-Override wuerde die Modal-Karte bei sehr schmalen Viewports
   zusammen mit dem 20px-Backdrop-Gutter je Seite ueberlaufen).
   ===================================================================== */
.gift-modal {
  width: min(94vw, 480px);
  max-height: min(90svh, 720px);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--cyan);
  border-radius: 14px;
  padding: 1.1em 1.2em 1.2em;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-m);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5), 0 0 22px rgba(56, 225, 255, 0.15);
}
.gift-close {
  position: absolute;
  top: 0.5em;
  right: 0.6em;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.1em;
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
}
.gift-close:hover {
  color: var(--text);
}
.gift-head {
  display: flex;
  align-items: center;
  gap: var(--sp-m);
  padding-right: 44px;
}
.gift-icon {
  font-size: 2rem;
  line-height: 1;
}
.gift-title {
  margin: 0;
  font-size: var(--fs-h2);
  line-height: 1.2;
}
.gift-sub {
  color: var(--muted);
  font-size: var(--fs-small);
  margin-top: 0.15em;
}
.gift-reward {
  display: flex;
  align-items: center;
  gap: 0.5em;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 10px;
  padding: var(--sp-s) var(--sp-m);
  font-weight: 800;
  font-size: var(--fs-h3);
}
.gift-reward b {
  color: var(--cyan);
}
.gift-reward small {
  color: var(--muted);
  font-weight: 400;
  font-size: var(--fs-caption);
  margin-left: auto;
}
.gift-msg {
  white-space: pre-line;
  overflow-wrap: anywhere;
  line-height: 1.5;
  font-size: var(--fs-body);
  border-left: 3px solid var(--cyan);
  padding: 0.1em 0 0.1em 0.8em;
  color: var(--text);
}
.gift-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-m);
  flex-wrap: wrap;
}
.gift-count {
  color: var(--muted);
  font-size: var(--fs-small);
}
.gift-actions {
  margin-left: auto;
  display: flex;
  gap: var(--sp-s);
  align-items: center;
}
.gift-modal .btn-ok {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border-radius: 9px;
  padding: 0.6em 1.3em;
  cursor: pointer;
  font-family: inherit;
  font-weight: 800;
  font-size: var(--fs-small);
  border: 1px solid transparent;
  background: var(--cyan);
  color: var(--text-on-accent);
}
.gift-modal .btn-ok:hover {
  filter: brightness(1.08);
}
.gift-modal .btn-link {
  background: none;
  border: none;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-small);
  cursor: pointer;
  min-height: 44px;
  padding: 0 0.4em;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.gift-modal .btn-link:hover {
  color: var(--text);
}
.gift-hint {
  font-size: var(--fs-caption);
  color: var(--muted);
}
/* Mobile (Prototyp `[data-w="384"]`-Toggle als echte Media-Query, s.
   Block-Kommentar oben) — Muster ui-396 ≤600px-Konvention dieser Datei.
   ID-Override `#gift-notice-backdrop` (Live-CDP-Fund dieses Tickets, s.u.)
   MUSS vor `.gift-modal` stehen -- reine Lesbarkeits-Reihenfolge, an der
   Kaskade aendert die Position hier nichts (ID-Spezifitaet gewinnt gegen
   die Klassen-Regel unten UNABHAENGIG von der Deklarationsreihenfolge
   innerhalb dieser Datei). */
@media (max-width: 600px) {
  /* pages/collection.css hat eine EIGENE `@media (max-width: 520px)`-Regel
     auf der GETEILTEN `.cs-modal-backdrop`-Klasse ("Mobile: Vollbild (DL-2)"
     fuer das Drop-Chancen-Modal: `padding: 0; align-items: stretch;`,
     Datei laedt NACH components.css in der Kaskade). Ohne diesen ID-Override
     erbt JEDER `.cs-modal-backdrop`-Konsument dieses Vollbild-Verhalten
     unbeabsichtigt (Live-CDP-Fund: das Geschenk-Popup haengte bei ≤520px
     oben an der Viewport-Kante statt zentriert zu sein, KEIN 20px-Gutter
     mehr — verletzt DL-6 woertlich "Mobile: Backdrop-Padding 20 px"). Die
     ID gewinnt gegen die Klassen-Regel per Spezifitaet (0,1,0,0 < 1,0,0,0),
     unabhaengig von der 520-vs-600px-Schwellen-Differenz oder der
     Datei-Ladereihenfolge — kein `!important` noetig, kein Touch an
     collection.css (das Drop-Chancen-Modal behaelt sein eigenes,
     bewusst gewaehltes Vollbild-Verhalten unveraendert). */
  #gift-notice-backdrop {
    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));
    align-items: center;
  }
  .gift-modal {
    width: 100%;
    padding: 1em 0.9em 1em;
  }
  .gift-actions {
    margin-left: 0;
    width: 100%;
  }
  .gift-modal .btn-ok {
    flex: 1 1 auto;
  }
}

/* =====================================================================
   ui-610: Kenntnisnahme-Dialog (Nutzungsbedingungen + Datenschutz) — nutzt
   den GETEILTEN `.cs-modal-backdrop`-Baustein oben (web-204/ui-448, z-260)
   fuer Position/Sichtbarkeits-Toggle/Safe-Area-Padding (kein neuer Overlay-
   Mechanismus, Muster .gift-* direkt darueber). Alle `.la-*`-Kind-Selektoren
   sind ID-gescoped (`#legal-ack-backdrop` als Vorfahre) — kein bare-Klassen-
   Leak in die 19-Datei-Kaskade (ui-602-Guard).

   ACHTUNG Zweitport (Live-CDP-Fund dieses Tickets — Muster `.sh-skel*` in
   web/client/css/viewer-base.css, "bewusst dupliziert statt importiert"):
   diese ganze Sektion existiert ZUSAETZLICH, SELBSTSTAENDIG (kein `.cs-
   modal-backdrop`-Bezug, volle `#legal-ack-backdrop`-Basis inklusive) in
   `viewer-base.css` — companion/legal-ack.js laedt UNBEDINGT am initialen
   Viewer-Boot (views/viewer-shell.js), BEVOR surface-host.js diese Datei
   hier je injiziert (erst beim ERSTEN Setup-Popin-Oeffnen). Die Fassung
   HIER bedient ausschliesslich Mobile-Home (`/dashboard`, Companion-Seite
   ohne Viewer-Timing-Problem) UND den Fall, dass der Popin auf einem
   Viewer spaeter doch geoeffnet wird (dann laeuft diese Fassung parallel
   zur viewer-base.css-Fassung — deklarationsgleich, kein sichtbarer
   Unterschied, nur harmlos doppelt). Aenderungen an Farben/Massen/Wortlaut-
   Struktur MUESSEN in BEIDEN Dateien nachgezogen werden — ab hier
   automatisiert geprueft: der Block zwischen den `ui-610 legal-ack — START/
   END`-Markern unten ist WORTGLEICH (Kommentare ausgenommen, s. dortiger
   Guard) in `viewer-base.css` gespiegelt, 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).
   ===================================================================== */
/* ui-610 legal-ack — START (Guard-Test: test/services/ui-610-legal-ack-css-guard.test.js) */
#legal-ack-backdrop .la-card {
  /* DL-1: 560px Karte. */
  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 {
  /* DL-1: Eyebrow cyan Versalien. */
  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 {
  /* DL-1: Lead --text-dim. */
  margin: 0;
  color: var(--text-dim);
  line-height: 1.55;
  font-size: var(--fs-small);
}
#legal-ack-backdrop .la-links {
  /* DL-1: zwei Link-Kacheln nebeneinander (Mobile: eine Spalte, s.u.). */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 16px 0 0;
}
#legal-ack-backdrop .la-link {
  display: block;
  /* DL-4: Grid-Items brauchen min-width:0 + overflow-wrap:anywhere, sonst
     schieben lange Wörter die Kachel über den Kartenrand (Fifty-Fund
     Prototyp 2026-09-06). */
  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 {
  /* DL-3: Update-Zustand — Bernstein-gestrichelter Kasten. */
  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 {
  /* DL-2: Primär-Button Cyan-Verlauf 46px. */
  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 {
  /* DL-2: Ablehnen als leiser Textlink. */
  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) {
  /* DL-4: Mobile Bottom-Sheet, Safe-Area unten, eine Spalte. DL-6: diese
     Media Query greift im Produkt-CSS bei echten ≤600px-Viewports (der
     Prototyp-Rahmen war NUR dort ein Container, keine Viewport-Breite —
     Headless-Beleg bei echten 390px Viewport, s. Ticket ## Notes). */
  #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) {
  /* DL-4: kein Einblend-/Hochschieb-Animation. */
  #legal-ack-backdrop .la-card {
    animation: none;
  }
}
/* ui-610 legal-ack — END */

.panel-title-sub {
  color: var(--muted);
  font-weight: 400;
  font-size: 0.7em;
  letter-spacing: 0.05em;
  text-transform: none;
  margin-left: 0.6em;
}

/* ---- Ranking-Akzente (ui-058-Familie, web-240-Re-Spawn/Review cd1cd70f
   nachgezogen) — .ct-rank-name/-pos/-1/-2/-3 sind bare, eigenständige
   Regeln OHNE Co-Selektor. Anders ihre drei Geschwister
   .ct-rank/.ct-rank-head/.ct-rank-num (pages/highscore.css, "Highscore / Runs"):
   die sind dort ECHT mit .hs-table/.hs-participant-table in JEDER Regel
   zu einer Deklaration verschmolzen — ein Aufsplitten wäre eine
   Regel-Änderung (Out-of-Scope), bleiben deshalb dort. Diese fünf hier
   waren ursprünglich fälschlich in dieselbe "untrennbar"-Kategorie
   eingeordnet worden (Erst-Review-Fund, s. Ticket `## Review`). ---- */
.ct-rank-name { color: var(--cyan); font-weight: 600; }
.ct-rank-pos  { color: var(--yellow); font-weight: 700; }
.ct-rank-1 { color: var(--rank-1); font-weight: 700; text-shadow: 0 0 10px rgba(94, 184, 255, 0.7); }
.ct-rank-2 { color: var(--rank-2); font-weight: 700; text-shadow: 0 0 8px rgba(192, 200, 212, 0.6); }
.ct-rank-3 { color: var(--rank-3); font-weight: 700; text-shadow: 0 0 8px rgba(205, 127, 50, 0.6); }

@property --pulse-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}


/* --- Card Tile --- */
.cs-card {
  position: relative;
  width: 100%;
  height: 100%;
  background: linear-gradient(155deg, #131a44 0%, #0c1230 100%);
  border: 1px solid var(--card-color, var(--cyan));
  border-radius: 10px;
  cursor: grab;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.25s ease;
  user-select: none;
  overflow: hidden;
}
.cs-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 0%, var(--card-color, var(--cyan)) 0%, transparent 60%);
  opacity: 0.08;
  pointer-events: none;
  z-index: 0;
}
.cs-card:hover {
  transform: translateY(-3px);
  box-shadow:
    0 0 0 1px var(--card-color, var(--cyan)),
    0 6px 24px rgba(0, 0, 0, 0.4),
    0 0 30px var(--card-glow, rgba(56, 225, 255, 0.3));
}
.cs-card:active { cursor: grabbing; }
.cs-card.cs-dragging { opacity: 0.4; transform: scale(0.95); }

/* Tier-Farben */
.cs-card[data-tier="1"] {
  --card-color: var(--cyan);
  --card-glow: rgba(56, 225, 255, 0.5);
  --upgrade-color: var(--magenta);
  --upgrade-glow: rgba(255, 69, 181, 0.55);
}
.cs-card[data-tier="2"] {
  --card-color: var(--magenta);
  --card-glow: rgba(255, 69, 181, 0.5);
  --upgrade-color: var(--yellow);
  --upgrade-glow: rgba(255, 216, 74, 0.55);
}
.cs-card[data-tier="3"] {
  --card-color: var(--yellow);
  --card-glow: rgba(255, 216, 74, 0.55);
  --upgrade-color: var(--green);
  --upgrade-glow: rgba(94, 255, 138, 0.55);
}
.cs-card[data-tier="4"] {
  --card-color: var(--green);
  --card-glow: rgba(94, 255, 138, 0.5);
  --upgrade-color: var(--card-color-t5);
  --upgrade-glow: rgba(244, 247, 255, 0.6);
  /* ui-611: T4 selbst bleibt pixelidentisch (kein --badge-bg/--badge-text —
     die eigene Badge fällt weiter auf die alte Flachfarbe zurück). Die
     PREVIEW-Badge (zeigt "T5", wenn eine T4-Karte upgradebar ist) MUSS aber
     bereits wie eine echte T5-Badge aussehen (Verlauf, DL-4) — deshalb
     --upgrade-badge-*, identischer Wert wie .cs-card[data-tier="5"]s
     --badge-bg/--badge-text unten (Geschwister-Muster zu --upgrade-color). */
  --upgrade-badge-bg: linear-gradient(135deg, #f4f7ff, #b9c4dc);
  --upgrade-badge-text: #0b1020;
}
/* ui-611 (2026-09-06): T5-T9 hatten bis hierhin EINE gemeinsame (weiße)
   Farbe (Bucket-Regel, web-138) — Fifty: "T5, T6+ karten ... ein shiny
   effekt system". Jede Stufe bekommt jetzt Farbe + Ziel der Upgrade-Kette
   (T5→T6→…→T9, T9 zeigt auf sich selbst — höchste Stufe, kein weiterer
   Ziel-Farbwechsel). --badge-bg/--badge-text (DL-4: Verlauf statt Flachfarbe,
   Text dunkel) tragen dieselbe Kette wie --card-color/--upgrade-color — die
   BEIDEN Konsumenten-Regeln (.cs-card-tier-badge/.cs-card-surface-preview
   .cs-card-tier-badge) leben unverändert oben bei "Tier-Badge bottom-right"
   (Custom-Property-Kanal statt einer eigenen Nachfahren-Regel je Tier, s.
   dortiger Kommentar — vermeidet einen Spezifitäts-Konflikt mit der
   Preview-Badge, die den NÄCHSTEN Tier-Text zeigt). Shiny-Layer direkt
   darunter.
   Kontrast-Nachweis (WCAG-Relativluminanz-Formel, worst-case = jeweils
   DUNKELSTER Verlauf-Stop, per Node-Script gemessen, s. Ticket ## Notes):
   T5 #0b1020-auf-#b9c4dc = 10.81:1, T6 #0b1020-auf-#ffc6f5 = 13.21:1 (Stops
   #9ff5ff 15.31:1 / #c9ffb0 16.60:1), T7 #0b1020-auf-#9a6bff = 5.34:1 (engste
   Marge der fünf Stufen, immer noch über dem Boden), T8 #0b1020-auf-#4fd6ff
   = 11.17:1, T9 #1a1200-auf-#ffb84d = 10.81:1 — alle ≥ 4.5:1 (AK1-Boden). */
.cs-card[data-tier="5"] {
  --card-color: var(--card-color-t5); --card-glow: var(--card-glow-t5);
  --upgrade-color: var(--card-color-t6); --upgrade-glow: var(--card-glow-t6);
  --badge-bg: linear-gradient(135deg, #f4f7ff, #b9c4dc); --badge-text: #0b1020;
  --upgrade-badge-bg: linear-gradient(120deg, #9ff5ff, #ffc6f5 50%, #c9ffb0); --upgrade-badge-text: #0b1020;
}
.cs-card[data-tier="6"] {
  --card-color: var(--card-color-t6); --card-glow: var(--card-glow-t6);
  --upgrade-color: var(--card-color-t7); --upgrade-glow: var(--card-glow-t7);
  --badge-bg: linear-gradient(120deg, #9ff5ff, #ffc6f5 50%, #c9ffb0); --badge-text: #0b1020;
  --upgrade-badge-bg: linear-gradient(135deg, #e0c8ff, #9a6bff); --upgrade-badge-text: #0b1020;
}
.cs-card[data-tier="7"] {
  --card-color: var(--card-color-t7); --card-glow: var(--card-glow-t7);
  --upgrade-color: var(--card-color-t8); --upgrade-glow: var(--card-glow-t8);
  --badge-bg: linear-gradient(135deg, #e0c8ff, #9a6bff); --badge-text: #0b1020;
  --upgrade-badge-bg: linear-gradient(135deg, #b2ffe6, #4fd6ff); --upgrade-badge-text: #0b1020;
}
.cs-card[data-tier="8"] {
  --card-color: var(--card-color-t8); --card-glow: var(--card-glow-t8);
  --upgrade-color: var(--card-color-t9); --upgrade-glow: var(--card-glow-t9);
  --badge-bg: linear-gradient(135deg, #b2ffe6, #4fd6ff); --badge-text: #0b1020;
  --upgrade-badge-bg: linear-gradient(135deg, #fff3c4, #ffb84d); --upgrade-badge-text: #1a1200;
}
.cs-card[data-tier="9"] {
  --card-color: var(--card-color-t9); --card-glow: var(--card-glow-t9);
  --upgrade-color: var(--card-color-t9); --upgrade-glow: var(--card-glow-t9);
  --badge-bg: linear-gradient(135deg, #fff3c4, #ffb84d); --badge-text: #1a1200;
  --upgrade-badge-bg: linear-gradient(135deg, #fff3c4, #ffb84d); --upgrade-badge-text: #1a1200;
}

/* --- Shiny-Layer: ein zusätzliches ::after über der Karte (z-index 2, unter den
   Badges z-3, über den Surfaces z-1) — Prototyp .claude/sprint/prototypes/ui-611.html
   Abschnitt "SOLL", 1:1 übernommen. Bewegung ist eine Profil-Einstellung
   (Fifty-Entscheid 2026-09-06, users.shiny_mode, SSR als data-shiny-mode="always"|"hover"
   auf <html> — auth/session.js, views/layout.js, views/viewer-shell.js): Modus "always"
   (Default) animiert JEDE T5+-Karte überall, Modus "hover" hält die Folie ruhig und
   animiert nur bei :hover/:focus-visible der Karte selbst — der Prototyp-Marker
   [data-shiny="live"] (Container-Idee, VERWORFEN) ist deshalb html[data-shiny-mode="always"].
   Nur transform/opacity/box-shadow (Compositor) — nie background-position/filter-Animation
   (Architektur-Lock: kein Repaint-Sturm im 250-Karten-Raster). */
.cs-card[data-tier="5"]::after, .cs-card[data-tier="6"]::after, .cs-card[data-tier="7"]::after, .cs-card[data-tier="8"]::after, .cs-card[data-tier="9"]::after {
  content: ""; position: absolute; inset: -40% -60%; pointer-events: none; z-index: 2; border-radius: inherit;
  will-change: transform; transform: translate3d(0,0,0);
}
/* T5 Silber: eine weiche Glanzkante ("Folie"), die einmal über die Karte läuft */
.cs-card[data-tier="5"]::after {
  background: linear-gradient(115deg, transparent 38%, rgba(255,255,255,.22) 48%, rgba(255,255,255,.05) 52%, transparent 62%);
  transform: translate3d(-35%, 0, 0);
}
/* T6 Holo: Regenbogen-Folie (screen) + Glanzkante; ruht schräg, wandert bei Bewegung */
.cs-card[data-tier="6"]::after {
  background:
    linear-gradient(115deg, transparent 40%, rgba(255,255,255,.18) 50%, transparent 60%),
    linear-gradient(100deg, rgba(255,80,180,.22) 0%, rgba(255,210,80,.22) 20%, rgba(90,255,160,.22) 40%, rgba(80,200,255,.22) 60%, rgba(180,100,255,.22) 80%, rgba(255,80,180,.22) 100%);
  mix-blend-mode: screen; transform: translate3d(-20%, 0, 0);
}
/* T7 Prisma: schärfere, dichtere Spektralstreifen + Funken-Punkte */
.cs-card[data-tier="7"]::after {
  background:
    radial-gradient(circle at 20% 30%, rgba(255,255,255,.9) 0 1px, transparent 2px),
    radial-gradient(circle at 70% 20%, rgba(255,255,255,.8) 0 1px, transparent 2px),
    radial-gradient(circle at 55% 75%, rgba(255,255,255,.8) 0 1px, transparent 2px),
    radial-gradient(circle at 85% 60%, rgba(255,255,255,.7) 0 1px, transparent 2px),
    repeating-linear-gradient(115deg, rgba(255,90,200,.20) 0 6%, rgba(255,220,90,.20) 6% 12%, rgba(120,255,170,.20) 12% 18%, rgba(90,190,255,.20) 18% 24%, rgba(200,120,255,.20) 24% 30%);
  mix-blend-mode: screen; transform: translate3d(-25%, 0, 0);
}
/* T8 Aurora: weiche, große Farbbahnen, die langsam durchziehen (mint/blau/violett) */
.cs-card[data-tier="8"]::after {
  background:
    linear-gradient(115deg, transparent 42%, rgba(255,255,255,.16) 50%, transparent 58%),
    radial-gradient(ellipse 60% 40% at 30% 40%, rgba(125,255,210,.35), transparent 70%),
    radial-gradient(ellipse 50% 45% at 70% 60%, rgba(79,214,255,.30), transparent 70%),
    radial-gradient(ellipse 60% 35% at 50% 85%, rgba(180,120,255,.28), transparent 70%);
  mix-blend-mode: screen; transform: translate3d(-15%, 0, 0);
}
/* T9 Kosmos: Gold-Folie + Sternfeld + pulsierender Glow (einzige Stufe mit Glow-Puls) */
.cs-card[data-tier="9"]::after {
  background:
    radial-gradient(circle at 15% 25%, #fff 0 1.2px, transparent 2.4px),
    radial-gradient(circle at 40% 70%, #fff 0 1px, transparent 2px),
    radial-gradient(circle at 65% 15%, #fff 0 1.4px, transparent 2.6px),
    radial-gradient(circle at 80% 45%, #fff 0 1px, transparent 2px),
    radial-gradient(circle at 30% 90%, #fff 0 1px, transparent 2px),
    radial-gradient(circle at 90% 80%, #fff 0 1.2px, transparent 2.4px),
    linear-gradient(115deg, transparent 38%, rgba(255,240,180,.35) 48%, rgba(255,255,255,.12) 52%, transparent 62%),
    linear-gradient(160deg, rgba(255,200,90,.18), rgba(255,240,200,.06) 50%, rgba(255,180,60,.18));
  mix-blend-mode: screen; transform: translate3d(-30%, 0, 0);
}
.cs-card[data-tier="9"] { box-shadow: 0 0 18px var(--card-glow-t9); }
/* Bewegung: "always" animiert überall, "hover" nur bei Hover/Fokus — Dauer je Stufe
   (höher = gemächlicher, edler, DL-5: Sweep 2,2s T5, Drift 3,2-6s T6-T9, Puls 2,6s). */
@keyframes shiny-sweep { from { transform: translate3d(-35%, 0, 0); } to { transform: translate3d(35%, 0, 0); } }
@keyframes shiny-drift { 0% { transform: translate3d(-20%, -4%, 0); } 50% { transform: translate3d(20%, 4%, 0); } 100% { transform: translate3d(-20%, -4%, 0); } }
@keyframes shiny-pulse { 0%, 100% { box-shadow: 0 0 18px var(--card-glow-t9); } 50% { box-shadow: 0 0 34px var(--card-glow-t9), 0 0 60px rgba(255,224,138,.25); } }
.cs-card[data-tier="5"]:hover::after, .cs-card[data-tier="5"]:focus-visible::after, html[data-shiny-mode="always"] .cs-card[data-tier="5"]::after { animation: shiny-sweep 2.2s ease-in-out infinite alternate; }
.cs-card[data-tier="6"]:hover::after, .cs-card[data-tier="6"]:focus-visible::after, html[data-shiny-mode="always"] .cs-card[data-tier="6"]::after { animation: shiny-drift 4s ease-in-out infinite; }
.cs-card[data-tier="7"]:hover::after, .cs-card[data-tier="7"]:focus-visible::after, html[data-shiny-mode="always"] .cs-card[data-tier="7"]::after { animation: shiny-drift 3.2s ease-in-out infinite; }
.cs-card[data-tier="8"]:hover::after, .cs-card[data-tier="8"]:focus-visible::after, html[data-shiny-mode="always"] .cs-card[data-tier="8"]::after { animation: shiny-drift 6s ease-in-out infinite; }
.cs-card[data-tier="9"]:hover::after, .cs-card[data-tier="9"]:focus-visible::after, html[data-shiny-mode="always"] .cs-card[data-tier="9"]::after { animation: shiny-drift 5s ease-in-out infinite; }
.cs-card[data-tier="9"]:hover, .cs-card[data-tier="9"]:focus-visible, html[data-shiny-mode="always"] .cs-card[data-tier="9"] { animation: shiny-pulse 2.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .cs-card[data-tier]::after, .cs-card[data-tier="9"] { animation: none !important; }
}

/* Pulse-Border via @property --pulse-angle (upgrade-ready) */
.cs-card.cs-can-upgrade {
  border: 2px solid transparent;
  background:
    linear-gradient(155deg, #131a44 0%, #0c1230 100%) padding-box,
    conic-gradient(from var(--pulse-angle),
      var(--card-color)     0deg,
      var(--upgrade-color) 90deg,
      var(--card-color)   180deg,
      var(--upgrade-color) 270deg,
      var(--card-color)   360deg
    ) border-box;
  animation: cs-pulse-rotate 3.2s linear infinite;
}
@keyframes cs-pulse-rotate {
  to { --pulse-angle: 360deg; }
}
.cs-card.cs-can-upgrade:hover {
  animation: cs-pulse-rotate 1.4s linear infinite;
  box-shadow:
    0 6px 24px rgba(0, 0, 0, 0.5),
    0 0 24px var(--card-glow, rgba(56, 225, 255, 0.5)),
    0 0 40px var(--upgrade-glow, rgba(255, 69, 181, 0.6));
}

/* Card surfaces: rest + preview (Crossfade) */
.cs-card-surface {
  position: absolute;
  inset: 0;
  padding: 12px 10px 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transition: opacity 0.22s ease, transform 0.22s ease;
  z-index: 1;
}
.cs-card-surface-preview {
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(circle at 50% 50%, rgba(255, 69, 181, 0.12) 0%, transparent 70%);
}
.cs-card.cs-can-upgrade:hover .cs-card-surface-rest {
  opacity: 0;
  transform: scale(0.96);
}
.cs-card.cs-can-upgrade:hover .cs-card-surface-preview {
  opacity: 1;
  transform: scale(1);
}

/* Card icon */
.cs-card-icon-area {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
}
.cs-card-icon {
  font-size: 54px;
  line-height: 1;
  filter: drop-shadow(0 0 14px var(--card-color, var(--cyan)));
}

/* Card body */
.cs-card-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: center;
  padding-right: 30px; /* web-065: 32→30px — Sublabel braucht weniger Freiraum (Badge bottom-right) */
}
.cs-card-name {
  font-size: 11px;
  font-weight: 600;
  /* ui-611: war var(--card-color-t5) (damals generisches Off-White) — dieser
     Token ist jetzt eine ECHTE Tier-5-Farbe (Silber). --group ist der Fifty-
     entschiedene Kanon für die "Shared/Group"-Off-White-Rolle (identischer
     #f4f7ff-Wert, Glossar-Dev.md "CSS-Token --white / --group") — Kartenname
     gilt für ALLE Tiers, T1-T4 müssen pixelidentisch bleiben (Lock). */
  color: var(--group);
  line-height: 1.2;
}
.cs-card-bonus {
  font-size: 16px;
  font-weight: 700;
  color: var(--card-color, var(--cyan));
  text-shadow: 0 0 8px var(--card-glow, rgba(56, 225, 255, 0.5));
  line-height: 1;
}
.cs-card-surface-preview .cs-card-bonus {
  color: var(--upgrade-color, var(--magenta));
  text-shadow: 0 0 10px var(--upgrade-glow, rgba(255, 69, 181, 0.6));
}

/* ui-116: Per-Tier-Effekt-Text auf waffen-gebundenen Karten (.cs-card-effect).
   Sitzt zwischen .cs-card-name und .cs-card-sub (DL-1).
   Styling: abgeleitet von .cs-card-bonus — gleiche Tier-Farbe + Glow-Rolle (DL-2).
   Unterschiede: 13px statt 16px (trägt Wort+Zahl, nicht nur "%"), 2-zeilig erlaubt
   mit fester min-height = 2 Zeilen für Grid-Höhengleichheit (DL-3). */
.cs-card-effect {
  font-size: 13px;
  font-weight: 700;
  color: var(--card-color, var(--cyan));
  text-shadow: 0 0 8px var(--card-glow, rgba(56, 225, 255, 0.5));
  line-height: 1.12;
  white-space: normal;
  min-height: calc(2 * 1.12em); /* 2 Zeilen feste Höhe — 1- und 2-zeilige Karten gleich hoch */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
/* Preview-Surface: Effekt-Text in Upgrade-Farbe (nächster Tier) — analog .cs-card-bonus */
.cs-card-surface-preview .cs-card-effect {
  color: var(--upgrade-color, var(--magenta));
  text-shadow: 0 0 10px var(--upgrade-glow, rgba(255, 69, 181, 0.6));
}
/* ui-244 DL-4: .cs-socket-filled .cs-card-effect Shrink entfernt — erbt Basis 13px */

/* Tier-Badge bottom-right */
.cs-card-tier-badge {
  position: absolute;
  bottom: 7px;
  right: 7px;
  /* ui-503 (M1): 9px -> 11px-Boden (K1) — Badge ist auto-breit (Padding traegt
     das Wachstum), kein Layout-Risiko (Live-CDP-Messung s. Ticket ## Notes). */
  font-size: 11px;
  font-weight: 700;
  padding: 3px 6px;
  border-radius: 4px;
  /* ui-611: --badge-bg/--badge-text sind NUR auf .cs-card[data-tier="5"]..["9"]
     gesetzt (Verlauf, DL-4) — T1-T4 haben sie nicht, fallen also unverändert
     auf die alte Flachfarbe zurück (Pixel-Lock). Custom-Property-Kanal statt
     einer zusätzlichen `.cs-card[data-tier="N"] .cs-card-tier-badge`-Regel:
     das Tier-Badge existiert ZWEIMAL pro upgradebarer Karte (Rest- UND
     Preview-Surface, beide Nachfahren desselben .cs-card[data-tier="N"]) —
     eine direkte Nachfahren-Regel würde die Preview-Badge (zeigt den NÄCHSTEN
     Tier-Text, z.B. "T8" auf einer T7-Karte) fälschlich mit der AKTUELLEN
     Tier-Farbe einfärben (höhere Spezifität als die bestehende
     `.cs-card-surface-preview .cs-card-tier-badge`-Regel darunter — Fund beim
     Bauen dieses Tickets, s. Ticket ## Notes). Der Custom-Property-Kanal
     umgeht das: --badge-bg/--badge-text (Rest-Surface) vs.
     --upgrade-badge-bg/--upgrade-badge-text (Preview-Surface) sind exakt das
     Geschwister-Muster zu --card-color/--upgrade-color. */
  background: var(--badge-bg, var(--card-color, var(--cyan)));
  color: var(--badge-text, var(--text-on-accent));
  letter-spacing: 0.08em;
  white-space: nowrap;
  z-index: 3;
}
.cs-card-surface-preview .cs-card-tier-badge {
  background: var(--upgrade-badge-bg, var(--upgrade-color, var(--magenta)));
  color: var(--upgrade-badge-text, var(--text-on-accent));
}

/* Count-Badge top-right */
.cs-card-count {
  position: absolute;
  top: 6px;
  right: 6px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  background: var(--text-on-accent);
  border: 1px solid var(--card-color, var(--cyan));
  border-radius: 10px;
  font-size: var(--fs-caption); /* ui-505 (M3): 10px -> --fs-caption (K1) — "×N"-Stapelzahl, einzige Anzeige des Karten-Bestands */
  font-weight: 700;
  color: var(--card-color, var(--cyan));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  box-shadow: 0 0 8px var(--card-glow, rgba(56, 225, 255, 0.5));
}
.cs-card.cs-can-upgrade .cs-card-count {
  border-color: var(--upgrade-color, var(--magenta));
  color: var(--upgrade-color, var(--magenta));
}

/* Upgrade-Chevron top-left */
.cs-upgrade-chevron {
  position: absolute;
  top: 4px;
  left: 7px;
  font-size: 22px;
  line-height: 1;
  color: var(--upgrade-color, var(--magenta));
  text-shadow: 0 0 14px var(--upgrade-glow, rgba(255, 69, 181, 0.85));
  animation: cs-chev-pulse 1.6s ease-in-out infinite;
  z-index: 3;
  pointer-events: none;
}
@keyframes cs-chev-pulse {
  0%, 100% { opacity: 0.55; transform: translateY(0) scale(1); }
  50%       { opacity: 1;    transform: translateY(-3px) scale(1.1); }
}
.cs-card:not(.cs-can-upgrade) .cs-upgrade-chevron { display: none; }

/* Upgrade-CTA in Preview */
.cs-upgrade-cta {
  /* ui-503 (M1): 9px -> 11px-Boden (K1) — Live-CDP-Messpflicht (Wortlaut-Fit,
     s. Ticket ## Notes) wegen 0.16em-Letter-Spacing + margin-right:36px. */
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  /* ui-611: war var(--card-color-t5) (generisches Off-White) — s. .cs-card-name-
     Kommentar oben, gleicher Grund: die CTA erscheint auf JEDEM upgradebaren
     Tier, nicht nur T5. */
  color: var(--group);
  text-transform: uppercase;
  text-align: center;
  text-shadow: 0 0 8px var(--upgrade-glow, rgba(255, 69, 181, 0.8));
  padding: 4px 6px;
  border: 1px solid var(--upgrade-color, var(--magenta));
  border-radius: 4px;
  background: rgba(255, 69, 181, 0.15);
  margin-top: auto;
  margin-right: 36px;
}

/* Click-Hint on hover (for clickable non-upgrade cards) */
.cs-click-hint {
  position: absolute;
  bottom: 4px;
  left: 0;
  right: 0;
  /* ui-503 (M1): 8px -> 11px-Boden (K1) — position:absolute; left:0; right:0;
     text-align:center spannt die volle Kartenbreite, kein Clipping-Risiko. */
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  color: var(--cyan);
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 2;
  pointer-events: none;
}
.cs-card.cs-clickable:hover .cs-click-hint { opacity: 0.7; }


/* === web-065: Gruppen-Karten (.is-group) — additiver Layer, tier-farb-kompatibel ===
   Identität OHNE Text-Badge (DL-2): Herz-Wasserzeichen (DL-3) + Außen-Halo (DL-4)
   + weiß-strahlender Glyph (DL-5) + weißes Scope-Sublabel (DL-6).
   Pattern identisch zu .is-weapon: liegt additiv auf der Tier-Farbe.
   Draw-Flip-Karte (.draw-flip-inner .cs-card) erbt das Treatment via Klasse. */

/* Scope-Sublabel (.cs-card-sub) auf jeder Effekt-Karte (DL-6).
   Solo = muted, Gruppe = weiß (override unten).
   Auf Waffen-Karten wird .cs-card-sub nicht gerendert (DL-7).
   ui-184: display:flex + gap:3px damit Text + ⓘ-Marker sauber nebeneinander sitzen;
   white-space auf nowrap erhalten (kein Zeilenumbruch).
   ui-546: justify-content:center — Zeile war linksbündig (Fifty-Fund, Crit-
   Chance-Karten); Icon+Text (wo ein Icon existiert) sitzen als EINE zentrierte
   Gruppe, (i) steht im Markup jetzt VOR dem Text (dashboard.js/app.js). */
.cs-card-sub {
  /* ui-503 (M1): 9px -> 11px-Boden (K1) — "nur <Waffe>"/"alle Tower"/"dein Tower"
     ist Lese-Text (Scope-Sublabel), kein Deko-Kandidat. */
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
  white-space: nowrap;
  line-height: 1.1;
  margin-top: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}
/* ui-546: die globale .tipwrap-margin-left (Abstand zu VORAUSGEHENDEM Text,
   weiter unten in dieser Datei) passt nicht mehr — das (i) steht hier jetzt
   VOR dem Text, .cs-card-sub gap:3px traegt den Abstand zum NACHFOLGENDEN
   Text bereits. Ohne den Reset saesse das Icon 0.35em (~3.85px bei 11px)
   ausserhalb der zentrierten Text+Icon-Gruppe. */
.cs-card-sub .tipwrap {
  margin-left: 0;
}

/* ui-184: kleiner kreisrunder i-Marker; erbt via currentColor die Textfarbe.
   Seit ui-395 nur noch dekorativ im „Drop-Chancen ansehen"-Link (dashboard.js,
   aria-hidden, kein title). Der ursprüngliche Einsatzort — ⓘ am „alle Tower"-
   Sublabel der Gruppen-Karte mit title=-Hover — nutzt jetzt das Tap-Tooltip-
   Muster (.tipwrap/.tip-i, CTTip ui-395). */
.cs-sub-info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 11px;
  height: 11px;
  border-radius: 999px;
  border: 1px solid currentColor;
  /* K2: reines Deko-/Redundanz-Element (ui-503/M1) — seit ui-395 nur noch der
     aria-hidden="true"-i-Marker vor "Drop-Chancen ansehen" (dashboard.js), die
     Info steht bereits vollstaendig im Link-Text daneben. 8px -> 10px-Mikro-
     Boden (K2, nie darunter); Box bleibt 11x11px (Glyph "i" passt, live geprueft). */
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  cursor: help;
  opacity: 0.85;
  font-style: normal;
  flex-shrink: 0;
}
.cs-sub-info:hover {
  opacity: 1;
}


/* DL-5: Glyph weiß + Glow auf Gruppen-Karten.
   ui-611: war var(--card-color-t5) — s. .cs-card-name-Kommentar oben, gleicher
   Grund (Gruppen-Karten existieren auf JEDEM Tier, nicht nur T5). */
.cs-card.is-group .cs-card-icon {
  color: var(--group);
  text-shadow: 0 0 12px rgba(244, 247, 255, 0.55);
}

/* DL-6: Sublabel weiß auf Gruppen-Karten (ui-611: --card-color-t5 -> --group, s.o.) */
.cs-card.is-group .cs-card-sub {
  color: var(--group);
  opacity: 0.72;
}

/* DL-4: Außen-Halo (liegt additiv zum Tier-Glow) */
.cs-card.is-group {
  box-shadow: 0 0 0 1px var(--card-color), 0 0 22px rgba(244, 247, 255, 0.18);
}
.cs-card.is-group:hover {
  box-shadow:
    0 0 0 1px var(--card-color),
    0 6px 24px rgba(0, 0, 0, 0.4),
    0 0 30px var(--card-glow),
    0 0 26px rgba(244, 247, 255, 0.28);
}

/* DL-3: Herz-Wasserzeichen oben links — hinter Content (z-index 0), angeschnitten
   von overflow:hidden der Karte. Untere Spitze ragt ins Tile, rechter Bogen
   sitzt halb über der Oberkante, linke Hälfte abgeschnitten.
   Finale Werte (mit Fifty getunt): top:-4px; left:-16px; width:52px; height:52px; opacity:.24
   Fill #f4f7ff (= --group seit ui-611, vormals --card-color-t5, s. .cs-card-name-
   Kommentar oben; SVG-URL kann kein CSS-Var → Literal bleibt). Pfad: Material-"favorite"-Herz. */
.cs-card.is-group::after {
  content: '';
  position: absolute;
  top: -4px;
  left: -16px;
  width: 52px;
  height: 52px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23f4f7ff' d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  opacity: 0.24;
  z-index: 0;
  pointer-events: none;
}


/* === ui-106: Waffen-gebundene Effekt-Karten (.is-weapon-exclusive) ===
   Dritte Klasse (egoistisch / Gruppe / waffen-gebunden) — additiver Layer,
   tier-farb-kompatibel. Pattern identisch zu .is-group:
   Wasserzeichen + Außen-Halo + weiß-strahlender Glyph + Sublabel „nur <Waffe>".
   Unterschied: Wasserzeichen ist ein DOM-Element (.cs-weapon-watermark) statt
   CSS ::after — weil das Symbol pro Waffe variiert (9 Waffen, kein fix SVG).
   Draw-Flip-Karte erbt das Treatment via Klasse. */

/* ui-110: Waffen-Glyph-Wasserzeichen — Hydration-Host (data-glyph-role="card").
   In-card-Position (kein Klippen über den Rand), 30px-Box passend zum hydrierten <img>.
   Vor Hydration: Unicode-SSR-Fallback zentriert in der Box.
   Nach Hydration: <img.wg-img> (30×30, Hex-Token + drawWeaponGlyph, muted) tritt an die Stelle.
   DL-2: top:8px left:8px; DL-2: opacity .55 (ui-112); DL-4: z-index 0 (hinter Karten-Content). */
.cs-weapon-watermark {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 30px;
  height: 30px;
  font-size: 18px;
  line-height: 1;
  overflow: hidden;
  color: var(--muted, #6b7a96);
  opacity: 0.55;
  z-index: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* DL-4: Außen-Halo analog .is-group */
.cs-card.is-weapon-exclusive {
  box-shadow: 0 0 0 1px var(--card-color), 0 0 22px rgba(244, 247, 255, 0.18);
}
.cs-card.is-weapon-exclusive:hover {
  box-shadow:
    0 0 0 1px var(--card-color),
    0 6px 24px rgba(0, 0, 0, 0.4),
    0 0 30px var(--card-glow),
    0 0 26px rgba(244, 247, 255, 0.28);
}

/* DL-5: Glyph weiß + Glow analog .is-group (ui-611: --card-color-t5 -> --group, s.o.) */
.cs-card.is-weapon-exclusive .cs-card-icon {
  color: var(--group);
  text-shadow: 0 0 12px rgba(244, 247, 255, 0.55);
}

/* DL-6: Sublabel weiß analog .is-group (ui-611: --card-color-t5 -> --group, s.o.) */
.cs-card.is-weapon-exclusive .cs-card-sub {
  color: var(--group);
  opacity: 0.72;
}


/* ===== Pakt-Karten-Chrome (ui-125, A-Layout + C-Icons) =====
   A-Layout: horizontale Kopfzeile (SVG-Icon links, Name daneben) →
   magenta→rot Akzent-Hairline → Reward/Penalty als zwei Blöcke (große Zahl + Mini-Label).
   Nur var(--*)-Tokens (DL5). Hover + is-pact-Border: --magenta (web-096-Kanon).
   ============================================================ */

.cs-card.is-pact {
  box-shadow: 0 0 0 1px var(--magenta), 0 0 22px rgba(255, 69, 181, 0.14);
}
.cs-card.is-pact:hover {
  border-color: var(--magenta);
  box-shadow:
    0 0 0 1px var(--magenta),
    0 6px 24px rgba(0, 0, 0, 0.4),
    0 0 30px rgba(255, 69, 181, 0.28);
}

/* ===== ui-511: Pakt-Karten-Front = Cover (großes Icon + Name + Kurzzeile).
   Ersetzt das alte 3-Block-Layout (Kopfzeile/Akzent/Reward-Split/Penalty-Box)
   auf der Kachel — die Detail-Infos (Reward-Split, Penalty, Stapel-Hinweis)
   wandern in den Tap-Tooltip (Details-Trigger unten links seit ui-546 —
   vormals unten mittig, s. .cs-pact-det.tipwrap weiter unten; Inhalt weiter
   unten in diesem Abschnitt, Mechanik companion/app.js CTTip unverändert). */
.cs-pact-cover {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 2px 4px 0;
  text-align: center;
}
.cs-pact-cover-ico {
  width: 62px;
  height: 62px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 69, 181, 0.22) 0%, rgba(255, 69, 181, 0) 70%);
  color: var(--magenta-light);
  filter: drop-shadow(0 0 10px rgba(255, 69, 181, 0.45));
}
.cs-pact-cover-ico svg {
  width: 44px;
  height: 44px;
}
.cs-pact-cover-name {
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.15;
  max-width: 100%;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* ui-546-Fund (Root-Cause der Fifty-"Aufgebot ist abgeschnitten"-Meldung, live per
     CDP-Ancestry-Diff verifiziert, s. Ticket ## Notes): overflow:hidden entzieht einem
     Flex-Kind seine automatische Content-Minimum-Groesse (CSS Flexbox §4.5) — ohne
     explizites min-height darf der Flex-Algorithmus dieses Kind bis auf 0px schrumpfen,
     WAEHREND die Geschwister (.cs-pact-cover-ico/-acc, .cs-pact-kpi — kein overflow:hidden)
     ihre Content-Minimums behalten. Trifft .cs-pact-cover (flex:1) ein Deficit (Summe der
     Kind-Hoehen > verfuegbarer Platz — auch NACH dem ui-546-Platzgewinn durch den
     entfernten in-flow-Details-Trigger bei sehr langen Namen weiterhin moeglich), erbte
     ALLEIN der Name-Block das gesamte Deficit und kollabierte auf 0px (unsichtbar) statt
     sauber auf 2 Zeilen zu clampen — reproduziert mit "Aufgebot: Schützen" (laengster
     Pakt-Name) VOR UND NACH dem reinen Layout-Fix. min-height fixiert die 2-Zeilen-
     Content-Groesse als harten Floor (identisches Muster zu .cs-card-effect min-height
     weiter oben in dieser Datei) — der Rest der Cover-Kinder weicht dem stattdessen aus. */
  min-height: calc(2 * 1.15em);
}
.cs-pact-cover-acc {
  width: 40px;
  height: 2px;
  flex-shrink: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--magenta), var(--red));
  opacity: 0.6;
}
/* Kurzzeile (DL-1, Default AN — Fifty-Entscheid 2026-08-22: die Option "Cover
   pur" entfaellt, die Front traegt immer Kartenwert + Penalty (game-160: kein
   Double-Dip mehr — der Kartenwert selbst statt eines verdoppelten "du"-Werts).
   Traegt auch die Upgrade-Preview-Surface mit den T+1-Werten. */
.cs-pact-kpi {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: var(--fs-caption);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cs-pact-kpi b {
  color: var(--magenta-light);
  font-weight: 800;
}
.cs-pact-kpi-pen {
  color: var(--red-light);
  font-weight: 800;
}

/* Details-Trigger (ui-546): einziges Tooltip-Icon, jetzt unten LINKS —
   position:absolute, symmetrisch zu .cs-card-tier-badge unten rechts
   (identische bottom/Inset-Werte). Vormals (ui-511 DL-3) in normalem
   Flex-Fluss unten MITTIG mit sichtbarem "Details"-Text (tip-i--label-
   Modifier) — der Text ist raus (Fifty: "Aufgebot"-Karten-Name lief dadurch
   ab, s. Ticket ## Notes), der frei werdende Platz in .cs-pact-cover geht an
   den Namen. Reine Basis-Variante A (.tip-i weiter unten in dieser Datei,
   17px-Icon) — kein --label-Modifier mehr; aria-label traegt die "Details"-
   Semantik weiter (Markup, dashboard.js pactBody()). `.tipwrap` traegt
   weiterhin den Klick-Guard in cards.js (nur .tipwrap-Klicks gehen zum
   Tooltip, sonst bleibt Karten-Klick Sockeln/Drag).
   Die generische 45px-::after-Hitbox (ui-214-Kanon, weiter unten in dieser
   Datei) reicht HIER NICHT — Review-Fund (Re-Spawn must-fix): an dieser
   Ecken-Position klippt .cs-card (overflow:hidden) einen Teil der -14px-
   Erweiterung weg, die reale (per document.elementFromPoint live gescannte,
   nicht nur CSS-arithmetisch angenommene) Trefferflaeche fiel dadurch auf
   ~874-1634px² statt der vorherigen (Text-Trigger-)Flaeche von ~5717px²
   (107.86×53px, Review-Messung). Asymmetrischer Override weiter unten
   (.cs-pact-det.tipwrap .tip-i::after) kompensiert das.
   margin-left:0 (analog .cs-card-sub .tipwrap weiter oben in dieser Datei):
   ohne den Reset sass der Trigger durch die globale .tipwrap-margin-left
   (0.35em, weiter unten) ~13-15px statt der beabsichtigten 7-8px von der
   Kartenkante entfernt (Review-Fund, should-fix) — sichtbar asymmetrisch zur
   Tier-Badge auf der Gegenseite (dort exakt 7-8px, kein Margin-Reset noetig
   da position:absolute rechts ohne davorstehenden Flex-Text).
   z-index:3 wie .cs-card-tier-badge (haelt den Trigger innerhalb SEINES
   EIGENEN Stacking-Contexts — .cs-card-surface weiter oben in dieser Datei,
   position:absolute + z-index:1 — ueber .cs-pact-cover/.cs-click-hint).
   Reicht aber NICHT gegen den Sockel-"Karte entfernen"-✕-Button
   (.cs-card-remove, collection.css, bottom:4px/left:4px, z-index:4): der
   ✕-Button ist ein SIBLING von .cs-card selbst (nicht von .cs-card-surface),
   ausserhalb von dessen Stacking-Context — ein z-index auf .tipwrap kann
   diesen Container nach aussen NIE "durchbrechen" (live per Ancestor-Chain-
   Dump verifiziert, nicht nur vermutet: ein Test-Bump auf .tipwrap allein,
   bis z-index:999, aenderte am Ergebnis nichts). Mit margin-left:0 rueckt
   das sichtbare 17px-Icon zudem so nah an die Kartenecke, dass seine EIGENE
   Box (nicht nur die ::after-Erweiterung) den ✕-Button geometrisch
   ueberlappt — der (unsichtbare, opacity:0 bis :hover) ✕-Button gewann dort
   und schluckte den Klick auf dem SICHTBAREN (i)-Glyphen komplett (live per
   Raster-Scan verifiziert: die Mitte des Icons war nicht mehr klickbar, ein
   reiner Ecken-Wedge blieb übrig) — eine staerkere Regression als die
   urspruengliche Flaechen-Schrumpfung, klar AK1-Lock-widrig ("Details-
   Funktionalitaet unveraendert"). Der eigentliche Fix sitzt eine Ebene
   hoeher: .cs-card.is-pact .cs-card-surface weiter unten in dieser Datei. */
.cs-pact-det.tipwrap {
  position: absolute;
  bottom: 7px;
  left: 7px;
  z-index: 3;
  margin-left: 0;
}
/* Asymmetrischer Hitbox-Override NUR fuer den Pakt-Ecken-Trigger (Review-
   Vorschlag: "mehr Puffer nach oben/rechts als nach unten/links, da die
   Karten-Ecke nur 7px Abstand zum Rand laesst"). Alle vier Richtungen live
   per document.elementFromPoint-Scan verifiziert (Karten-overflow:hidden-
   Clipping + Nachbar-Elemente eingerechnet, nicht nur CSS-Geometrie):
     - unten/links (0px, keine Erweiterung): die eigene 17px-Box des Triggers
       ueberlappt den Sockel-"Karte entfernen"-✕-Button (.cs-card-remove,
       bottom:4px/left:4px) bereits OHNE jede ::after-Erweiterung (beide
       Buttons sitzen faktisch in derselben Ecke, s. Kommentar bei .cs-pact-
       det.tipwrap oben) — jedes zusaetzliche Minus haette dort NUR den vom
       ✕-Button beanspruchten Bereich vergroessert, ohne echten Zugewinn (die
       Kartenkante selbst liesse zwar noch ~7px zu, aber der ✕-Button ist der
       engere Nachbar). 0px haelt den Fussabdruck auf das Minimum, das die
       Ueberlappung ohnehin erzwingt.
     - oben/rechts (-68px): unkritische Richtungen (keine Nachbar-Kontrolle,
       kein Karten-Rand) — geben die noetige Flaeche zurueck UND puffern den
       Flaechenverlust ab, den der Trigger WAEHREND des ✕-Button-:hover
       (s. Kommentar bei .cs-pact-det.tipwrap oben — dort gewinnt bewusst der
       jetzt sichtbare ✕-Button die geteilte Ecke zurueck) auf der unten/
       links-Seite hinnimmt: live gescannt sinkt die Trigger-Flaeche
       WAEHREND des Hovers auf ~5472px² bei -62px — mit -68px liegt sie auch
       in diesem Moment wieder ueber dem Ziel. Default (keine Hover-
       Interaktion, der weit ueberwiegende Fall — auf Touch gibt es gar kein
       echtes :hover) liegt bei ~6320-6396px², >5717px² (altes Ziel, +11-12%).
       Rechts bleibt reichlich Abstand zur Tier-Badge (live gemessen ≥18px
       Luecke in JEDEM getesteten Kontext/Breakpoint) — keine Ueberlappung
       der beiden Klickziele. */
.cs-pact-det.tipwrap .tip-i::after {
  inset: -68px -68px 0px 0px;
}
/* Stacking-Context-Fix fuer den Pakt-Ecken-Trigger (Review-Fund, Re-Spawn
   must-fix — s. Kommentar bei .cs-pact-det.tipwrap oben): .cs-card-surface
   (Basis-Regel weiter oben in dieser Datei, position:absolute + z-index:1)
   ist ein EIGENER Stacking-Context — von AUSSEN zaehlt fuer alle Kinder
   (inkl. .tipwrap egal welchen eigenen z-index-Werts) nur die 1 dieses
   Containers. .cs-card-remove (Sockel-"Karte entfernen"-✕, collection.css,
   z-index:4) ist ein SIBLING von .cs-card, nicht von .cs-card-surface, und
   dadurch AUSSERHALB von dessen Stacking-Context — 1 < 4, der ✕-Button
   gewinnt jede Ueberlappung, macht Teile des sichtbaren (i)-Icons (nicht nur
   die unsichtbare Hitbox-Erweiterung) unklickbar. z-index:5 auf .cs-card-
   surface SELBST (> 4) behebt das dort, wo es tatsaechlich wirkt — bewusst
   nur fuer .is-pact (einzige Karten-Art mit einem Ecken-Trigger in dieser
   Zone) statt global, um bestehende Stapel-Layer (.cs-card-count z-index:3,
   .cs-card-touch-preview z-index:4, beide SIBLINGS von .cs-card-surface)
   nicht ueberall zu verschieben. Kollisionsfrei live verifiziert: die neue
   ::after-Erweiterung (oben, bis zu 55px) erreicht weder .cs-card-count
   (oben rechts, eigener enger Bereich) noch die persistente Touch-Vorschau-
   Leiste (unten, ui-511-Ausweichregel bottom:46px bleibt fuer den Abstand
   zustaendig, hier unveraendert). Gegenstueck: waehrend der ✕-Button per
   :hover sichtbar ist, gewinnt ER die Ueberlappung zurueck (eigene
   :hover-Regel bei .cs-card-remove, collection.css, z-index:6 dort) — sonst
   waere er zwar sichtbar aber unklickbar, sobald man ihn anvisiert. */
.cs-card.is-pact .cs-card-surface {
  z-index: 5;
}

/* ===== ui-511: Pakt-Detail-Tooltip-Inhalt (im CTTip-Portal #ct-tip, DL-2) =====
   Kopf (Icon 28px + Name + Tier-Badge in Tier-Farbe) → Hexa-Bonus-Block
   (game-160: EINE Zeile Titel links/Wert rechts + Raum-Durchschnitt-Satz,
   vormals andere|du-Split + Double-Dip-Satz) → Penalty-Block (Überschrift
   eigene Zeile, darunter Achsen-Label links/Wert rechts) → Raum-Satz →
   optionaler Stapel-Hinweis. Eigene .cs-pact-tip-*-Klassen — bewusst KEIN
   .tt-*-Präfix (das ist Tech-Tree-Namensraum, s. techtree.js/dev-techtree.js/
   #tt-tip) und keine Wiederverwendung der alten Front-Klassen (oben entfernt).
   Portal kopiert nur innerHTML (ui-509-Befund) — Markup mit <br>/<b>/kleinen
   Blöcken ist ok, alle Schriften ≥11px (Token/direkter px-Wert). */
.cs-pact-tip-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.cs-pact-tip-head-ico {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--magenta-light);
}
.cs-pact-tip-head-ico svg {
  width: 24px;
  height: 24px;
}
.cs-pact-tip-head-nm {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}
.cs-pact-tip-tier {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--fs-caption);
  font-weight: 800;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--tc, var(--cyan));
  color: var(--text-on-accent);
  white-space: nowrap;
}
/* Tier-Bucket-Färbung — dieselbe Konvention wie .tchip.tN weiter unten
   (T1 cyan/T2 magenta/T3 yellow/T4 grün, T5 exakt/T6 Bucket ≥6 — ui-611:
   vormals teilten sich t5/t6 dieselbe card-color-t5-weiß; T5-T9 haben jetzt
   eigene Farben, s. components.css Tier-Farben oben, .tchip.t6-Kommentar
   weiter unten). */
.cs-pact-tip-head.t1 { --tc: var(--cyan); }
.cs-pact-tip-head.t2 { --tc: var(--magenta); }
.cs-pact-tip-head.t3 { --tc: var(--yellow); }
.cs-pact-tip-head.t4 { --tc: var(--green); }
.cs-pact-tip-head.t5 { --tc: var(--card-color-t5); }
.cs-pact-tip-head.t6 { --tc: var(--card-color-t6); }

.cs-pact-tip-blk {
  border-radius: 6px;
  padding: 7px 9px;
  margin-top: 6px;
}
.cs-pact-tip-blk.r { background: rgba(255, 69, 181, 0.10); }
.cs-pact-tip-blk.p { background: rgba(255, 77, 110, 0.10); }
.cs-pact-tip-title {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-caption);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 4px;
}
/* game-160: Zeile mit Titel/Label links + Wert rechts (loest die alte
   andere|du-Spalten-Box ab). In der Zeile verliert der Titel seinen Abstand
   nach unten, das Label seinen linken Einzug — Werte 1:1 aus dem
   abgenommenen Prototyp (.claude/sprint/prototypes/pakt-bonus-raumschnitt.html). */
.cs-pact-tip-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.cs-pact-tip-row .cs-pact-tip-title { margin-bottom: 0; }
.cs-pact-tip-row .lbl { margin-left: 0; }
.cs-pact-tip-row .num { flex: 0 0 auto; text-align: right; }
.cs-pact-tip-blk .num {
  font-size: 16px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--magenta-light);
}
.cs-pact-tip-blk .lbl {
  font-size: var(--fs-caption);
  color: var(--muted);
  margin-left: 3px;
}
.cs-pact-tip-blk.p .num { color: var(--red-light); }
.cs-pact-tip-note {
  font-size: var(--fs-caption);
  color: var(--muted);
  margin-top: 8px;
  line-height: 1.5;
}

/* ui-244 DL-4: Pakt-Shrink-Satz entfernt — .cs-socket-filled .cs-pact-* Overrides gelöscht.
   Pakt-Karten erben 1:1 die Basis-Pakt-Chrome-Styles (ui-511: jetzt .cs-pact-cover/-kpi/-det). */


/* ===== Köder-Karten-Chrome (web-169) =====
   Gespiegelt aus .is-pact, aber farbig (Boss-Art-Farbe via --bl).
   Kein Hexa-Reward/Penalty — neutrale Steuerungskarte.
   Border + Glow + Akzent folgen der Boss-Art-Farbe (--bl).
   Nur var(--*)-Tokens und --bl/--bl-light/--bl-glow (per Inline-Style gesetzt,
   da Boss-Art-Farbe je Karte unterschiedlich ist).
   ========================================= */

.cs-card.is-bosslure {
  box-shadow: 0 0 0 1px var(--bl), 0 0 20px var(--bl-glow, rgba(255, 69, 181, 0.14));
}
.cs-card.is-bosslure:hover {
  border-color: var(--bl);
  box-shadow:
    0 0 0 1px var(--bl),
    0 6px 24px rgba(0, 0, 0, 0.4),
    0 0 28px var(--bl-glow, rgba(255, 69, 181, 0.22));
}
.cs-card.is-bosslure::before {
  background: radial-gradient(circle at 50% 0%, var(--bl) 0%, transparent 60%);
  opacity: 0.10;
}

/* Kopfzeile: farbiges Icon links (30px) + Name daneben */
.cs-bl-hdr {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 9px 0;
  position: relative;
  z-index: 1;
}
.cs-bl-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bl);
}
.cs-bl-icon svg {
  width: 28px;
  height: 28px;
}
.cs-bl-name {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.15;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Akzent-Hairline in Boss-Art-Farbe (opacity .6) */
.cs-bl-accent {
  height: 2px;
  margin: 7px 9px 0;
  border-radius: 2px;
  background: var(--bl);
  opacity: 0.6;
  position: relative;
  z-index: 1;
}

/* Karten-Body: Effekt-Zeile + run-global-Badge */
.cs-bl-body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 9px;
  flex: 1;
  position: relative;
  z-index: 1;
}
.cs-bl-effect {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text);
}
.cs-bl-arrow {
  color: var(--bl);
  font-weight: 800;
  font-size: 14px;
  line-height: 1;
}
.cs-bl-effect b {
  color: var(--bl-light, var(--bl));
}
.cs-bl-runglobal {
  margin-top: auto;
  /* ui-503 (M1): 9.5px -> 11px-Boden (K1). */
  font-size: 11px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 5px;
}
.cs-bl-hex {
  color: var(--text-dim);
}


/* =========================================
   Jagd-Karten-Tiles (web-186, DL-1..DL-6)
   .is-hunt Klasse + .cs-ht-* Elemente.
   Farb-Token --ht (Gegner-Archetyp-Farbe), --ht-glow (Glow).
   Kein Tier-Badge, kein Merge-Surface, kein count.
   ========================================= */

.cs-card.is-hunt {
  border-color: color-mix(in srgb, var(--ht) 55%, var(--panel-border, #1e2735));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--ht) 20%, transparent) inset,
    0 6px 18px -10px var(--ht-glow);
}
.cs-card.is-hunt:hover {
  transform: translateY(-2px);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--ht) 45%, transparent) inset,
    0 10px 26px -10px var(--ht-glow);
}
.cs-card.is-hunt::before {
  background: radial-gradient(circle at 50% 0%, var(--ht) 0%, transparent 65%);
  opacity: 0.08;
}

/* Kopfzeile: Icon (38px Reticle) + Name */
.cs-ht-hdr {
  display: flex;
  align-items: center;
  gap: 9px;
  position: relative;
  z-index: 1;
}
.cs-ht-icon {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  display: grid;
  place-items: center;
  border-radius: 6px;
  background: radial-gradient(circle at 50% 42%,
    color-mix(in srgb, var(--ht) 22%, transparent),
    transparent 70%);
}
.cs-ht-icon svg {
  width: 38px;
  height: 38px;
  display: block;
}
.cs-ht-name {
  font-weight: 700;
  font-size: 14.5px;
  color: var(--text, #e6edf6);
  letter-spacing: .01em;
}

/* Akzent-Linie */
.cs-ht-accent {
  height: 2px;
  margin: 10px 0 9px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--ht), transparent);
}

/* Effekt-Zeile */
.cs-ht-effect {
  display: flex;
  gap: 7px;
  align-items: flex-start;
  color: var(--text-dim, #9fb0c4);
  font-size: 12.6px;
}
.cs-ht-effect b {
  color: var(--text, #e6edf6);
}
.cs-ht-arrow {
  color: var(--ht);
  font-weight: 700;
}

/* Scope-Zeile */
.cs-ht-scope {
  margin-top: 9px;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11.4px;
  /* ui-346: --text-faint war nirgends in tokens.css definiert → fiel still auf
     den Fallback-Hex #65788f zurück (~4,05:1 auf Karten-BG, unter AA 4,5:1).
     Auf --text-dim umgestellt (Lock: "wenn definiert, dann ≥ --text-dim" —
     dann lieber gleich --text-dim nutzen statt ein zweites Token einführen). */
  color: var(--text-dim);
}
.cs-ht-rt {
  color: var(--cyan, #36e0e0);
}

/* Tech-Tree-Pill (top-right, einmalig-Badge) */
.cs-ht-once {
  position: absolute;
  top: 8px;
  right: 9px;
  font-size: 11px; /* ui-346: war 10px (klein + kontrastarm gleichzeitig, AK-Vorgabe ≥11px) */
  letter-spacing: .06em;
  text-transform: uppercase;
  /* ui-346: --text-faint war nirgends in tokens.css definiert → still auf
     #65788f (~4,05:1, unter AA). Auf --text-dim umgestellt, s. .cs-ht-scope. */
  color: var(--text-dim);
  border: 1px solid var(--panel-border, #1e2735);
  border-radius: 999px;
  padding: 1px 7px;
  background: #0c1119;
}


/* ui-133: Glyph-Tooltip-Komponente (.glyph-tip)
 * CSS-only, trigger=<strong class="glyph-tip" data-tip="...">. Aktuell aktiv
 * für die Belohnungs-Chunks (rewardGlyph()/rewardChipHtml(), dashboard.js/src/render/reward-line.js) —
 * die inline im Textfluss stehen, nicht in einem clippenden Vorfahren.
 * Modifier .glyph-tip--down: Bubble öffnet nach unten (für Trigger nahe der
 * Viewport-Oberkante). ui-414: die Konto-Pillen (dash-account-bar.js) nutzten
 * --down bis dahin auch, sind aber auf das #ct-tip-Portal migriert (dortiger
 * Kommentar) — .dash-header/.pf-account-bar clippten die absolut positionierte
 * Bubble bei ≤600px. --down bleibt als generischer Modifier für künftige
 * .glyph-tip-Einsätze nahe der Oberkante.
 * Tokens: --panel, --line-2, --text.
 * ui-204: tabindex="0" am Trigger → :focus öffnet Bubble auf Touch/Tastatur.
 *   Wichtig: :focus (nicht :focus-visible) — :focus-visible matcht auf Touch-Tap meist nicht.
 *   Sichtbarer Fokus-Ring separat via :focus-visible (Tastatur-Only-Heuristik).
 */
.glyph-tip {
  position: relative;
  cursor: help;
  border-bottom: 1px dotted transparent;
}
/* ui-623 (AK3): Reward-Chunk-Farbe je Währung statt pauschal Gelb. rewardGlyph()/
   dailyRewardHtml() (dashboard.js) setzen eine dieser vier Modifier-Klassen
   zusätzlich zu .glyph-tip auf denselben <strong> — Glyph UND Zahl stehen im
   selben Element, tragen also automatisch dieselbe Farbe (DL-2). Ersetzt die
   vormalige `.achievement-reward-hint strong { color: var(--yellow) }`-Regel
   (achievements.css), die JEDE Reward-Zeile (Erfolge, Saison-Pass, Täglich)
   unterschiedslos gelb einfärbte. */
.glyph-tip--hexa       { color: var(--cur-hexa); }
.glyph-tip--draw       { color: var(--cur-draw); }
.glyph-tip--socket     { color: var(--cur-socket); }
.glyph-tip--skillpoint { color: var(--cur-skill); }
.glyph-tip:hover,
.glyph-tip:focus {
  border-bottom-color: currentColor;
  outline: none;
}
/* ui-204: Fokus-Ring nur für Tastatur-Navigation (nicht bei Touch-Tap oder Maus-Klick) */
.glyph-tip:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
  border-radius: 3px;
}
.glyph-tip[data-tip]:hover::after,
.glyph-tip[data-tip]:focus::after {
  content: attr(data-tip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line-2);
  border-radius: 6px;
  padding: 6px 9px;
  font-size: 11px;
  font-weight: 400;
  line-height: 1.35;
  white-space: nowrap;
  letter-spacing: 0.01em;
  z-index: 50;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
  opacity: 0;
  animation: tipIn 0.15s ease forwards;
}
.glyph-tip[data-tip]:hover::before,
.glyph-tip[data-tip]:focus::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: calc(100% + 3px);
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--line-2);
  z-index: 50;
}
@keyframes tipIn {
  from { opacity: 0; transform: translate(-50%, 3px); }
  to   { opacity: 1; transform: translateX(-50%); }
}
/* Modifier --down: Bubble öffnet nach unten (ui-134 Header-Pillen, sitzt am Viewport-Rand) */
.glyph-tip--down[data-tip]:hover::after,
.glyph-tip--down[data-tip]:focus::after {
  bottom: auto;
  top: calc(100% + 8px);
  animation: tipInDown 0.15s ease forwards;
}
.glyph-tip--down[data-tip]:hover::before,
.glyph-tip--down[data-tip]:focus::before {
  bottom: auto;
  top: calc(100% + 3px);
  border-top-color: transparent;
  border-bottom-color: var(--line-2);
}
@keyframes tipInDown {
  from { opacity: 0; transform: translate(-50%, -3px); }
  to   { opacity: 1; transform: translateX(-50%); }
}


/* ui-641: Kontostand-Chip -- geteilter Baustein (views/balance-chip.js
   renderBalanceChip()), umgezogen aus pages/achievements.css (Lock 1 "ein
   Baustein, eine Quelle"). Konsumenten: Erfolge-Kopf (vier Chips in
   .ach-balance, ui-621 DL-3, Reward-Flug-/Bump-Ziel) UND der einzelne
   Hexa-Budget-Chip im Tower-Tab (OHNE .ach-balance-Wrapper, pages/build.css
   .build-tower-chips). Farb-Tokens aus tokens.css (global vor base.css). */
.ach-balance {
  display: flex;
  gap: 0.5em;
  justify-content: flex-end;
  flex-wrap: wrap;
  margin: 0 0 0.7em;
}
.ach-balance-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.28em 0.7em;
  border-radius: 999px;
  border: 1px solid;
  font-size: 0.8em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.ach-balance-glyph { display: inline-flex; align-items: center; }
.ach-balance-glyph svg { display: block; }
.ach-balance-chip--hexa       { background: var(--cur-hexa-a12);   border-color: var(--cur-hexa-a45);   color: var(--cur-hexa); }
.ach-balance-chip--draw       { background: var(--cur-draw-a12);   border-color: var(--cur-draw-a45);   color: var(--cur-draw); }
.ach-balance-chip--socket     { background: var(--cur-socket-a12); border-color: var(--cur-socket-a45); color: var(--cur-socket); }
.ach-balance-chip--skillpoint { background: var(--cur-skill-a12);  border-color: var(--cur-skill-a45);  color: var(--cur-skill); }
/* DL-1 (670ms, ui-621): Chip-Bump beim Ankommen des Reward-Flugs (app.js
   bumpBalanceChip()) -- nur der Erfolge-Kopf loest diese Klasse aus (s.
   Kopfkommentar), reduced-motion-Abschaltung bleibt bewusst in
   pages/achievements.css (dortiger @media-Block deckt alle Claim-Sequenz-
   Animationen gemeinsam ab). */
.ach-balance-chip--bump { animation: achBalanceBump .45s cubic-bezier(.2, 1.6, .4, 1); }
@keyframes achBalanceBump {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.14); }
  100% { transform: scale(1); }
}


/* ui-204: Persistente Touch-Vorschau-Leiste (.cs-card-touch-preview)
 * Magenta (Upgrade-Rolle, --upgrade-color), Bottom-Leiste der Karte.
 * Zeigt nächstes Tier + Bonus persistent — kein Hover nötig.
 * Am Desktop display:none (Basis); Touch-Media-Block weiter unten schaltet auf display:flex.
 * WICHTIG: Reihenfolge-sensitiv — diese Basis-Regel MUSS vor dem @media(hover:none)-Block stehen,
 * damit display:flex im Media-Block (gleiche Spezifizität) gewinnt. DL-5: Cyan = Karte, Magenta = Upgrade.
 */
.cs-card-touch-preview {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  padding: 5px 7px;
  gap: 6px;
  align-items: center;
  justify-content: center;
  background: linear-gradient(0deg, rgba(255, 69, 181, 0.22), rgba(255, 69, 181, 0.04));
  border-top: 1px solid var(--upgrade-color, var(--magenta));
  font-size: var(--fs-caption); /* ui-505 (M3): 10px -> --fs-caption (K1) — Upgrade-Vorschau-Text, echte Info */
  font-weight: 700;
  color: var(--upgrade-color, var(--magenta));
}


/* =====================================================================
   ui-395: Tap-Tooltips + Tech-Tree-Bestätigungs-Popover (web-221 P3.3)

   Rollout des Waffe-Tab-Tooltip-Musters (.info/.tip-open, ui-285ff) auf
   alle spieler-sichtbaren Erklärungen — Mechanik: CTTip in app.js.
   Markup-Varianten (DL-2; D seit ui-400): A) .tipwrap > .tip-i (i-Icon) +
   .tip[hidden], B) .ct-tip-self (Element selbst tappbar), C) .ct-tip-hover
   (nur Hover — Träger, deren Klick schon eine Aktion hat, z.B. locked
   Waffen-Sub-Tabs), D) .tipwrap.build-tipwrap > .info (Waffe-Tab-Faktoren-
   Tooltips: SVG-ⓘ-Trigger + .build-tip-body-Content-Styles im Build-Block
   weiter oben, seit ui-400 — damit ist CTTip die EINZIGE Tooltip-Mechanik
   im Companion).

   ANZEIGE läuft über das EINE fixed-Portal #ct-tip an document.body,
   NICHT über das Einblenden des .tip-Kinds: die Einsatzorte liegen in
   overflow-Containern (.hs-scroll) und in .cs-card (overflow:hidden +
   :hover-transform) — ein Kind-Tooltip würde dort geclippt bzw. vom
   transformierten containing block eingefangen. Die Prototyp-Regel
   `.tip-open > .tip { display:block }` entfällt deshalb bewusst; die
   DL-1-Scope-Warnung (nicht auf Wrapper-Klasse scopen) ist damit
   strukturell erledigt. .tip-open bleibt der Zustands-Marker am Träger
   (Icon-Highlight, Ein-offen-Invariante — geteilt mit build.js).

   Definiert nur NEUE Klassen (+#ct-tip/.tt-pop) — keine Überschneidung mit
   dem ui-394-Touch-Block oder ui-390 (disjunkte Selektor-Mengen; web-240:
   die frühere "Position am Dateiende"-Begründung ist mit dem CSS-Umzug
   nach components.css hinfällig — die Datei lädt jetzt VOR den pages/*.css
   statt textuell an deren Ende zu stehen, die Überschneidungsfreiheit selbst
   bleibt aber unverändert wahr). .tt-pop bleibt in pages/techtree.css
   (Tech-Tree-spezifisch, eigener Kopfkommentar dort). */

/* --- Variante A: i-Icon (DL-2) — 17px sichtbarer Kreis, 44px-Hitbox.
   Hitbox via ::after (ui-214-Kanon wie .info ui-300) statt des negativen
   Margins aus dem Prototyp: verschiebt kein Layout in dichten Zeilen. */
.tipwrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: 0.35em;
}
.tip-i {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  padding: 0;
  border: none;
  background: none;
  cursor: help;
  color: var(--muted);
  font-family: inherit;
}
.tip-i::before {
  content: 'i';
  width: 17px;
  height: 17px;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  color: inherit;
  font-size: 11px;
  font-style: italic;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s, border-color 0.15s;
}
/* 17px + 2×14px Inset = 45px Hitbox (≥44, ui-214-Kanon) */
.tip-i::after { content: ''; position: absolute; inset: -14px; }
.tip-i:hover, .tip-i:focus-visible,
.tipwrap.tip-open .tip-i { color: var(--cyan); }
.tip-i:hover::before, .tip-i:focus-visible::before,
.tipwrap.tip-open .tip-i::before { border-color: var(--cyan); }
.tip-i:focus-visible { outline: none; } /* Fokus-Indikator = cyan Kreis (s.o.) */

/* --- Variante B/C: Träger-Element selbst als Trigger --- */
.ct-tip-self { cursor: pointer; }
/* Als <button> gesetzte B-Träger (Pakt-Chips in Tabellenzellen): auf
   Text-Optik zurücksetzen — die Identitäts-Farbe kommt von den
   bestehenden Regeln (.es-breakdown .bd-pact etc.). */
button.ct-tip-self {
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.ct-tip-self.tip-open, .ct-tip-hover.tip-open { color: var(--cyan); }

/* ui-414: Konto-Pillen (dash-account-bar.js Draws/Sockel/Sub) sind jetzt
   Variante-B-Träger (<button class="stat-num ct-tip-self">) statt des
   CSS-only .glyph-tip--down-Wegs — .dash-header/.pf-account-bar clippten die
   Sprechblase bei ≤600px (overflow-x:auto koppelt overflow-y, s. dortiger
   Kommentar) UND fingen ihr z-index:50 in einem eigenen backdrop-filter-
   Stacking-Context (Ticket-Befund, CDP-gemessen). button.ct-tip-self (oben)
   resettet auf color:inherit — die Pillen-Zahl behält hier bewusst ihre
   eigene Farbe (.stat-num oben, Bestandsoptik unverändert), deshalb erneut
   gesetzt (Selektor-Spezifität schlägt die generische Regel). Kein
   font-Shorthand-Reset (würde .stat-num font-weight:600 auf inherit
   reissen) — nur die button-UA-Defaults. Fokus-Ring identisch zum
   vorherigen .glyph-tip:focus-visible-Wert (optisch keine Änderung). */
.stat-pill button.stat-num.ct-tip-self {
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font-family: inherit;
  cursor: pointer;
  color: var(--cyan);
}
.stat-pill button.stat-num.ct-tip-self:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Die .tip-QUELLE wird nie direkt angezeigt (Portal, s. Kopf) —
   [hidden] deckt das ab, die Regel hier ist Backstop + Doku. */
.tipwrap .tip, .ct-tip-self .tip, .ct-tip-hover .tip { display: none; }

/* --- Das Portal-Panel (Optik DL-3) --- */
#ct-tip {
  position: fixed;
  /* ui-415: 80→250. #ct-tip haengt als document.body-Kind NEBEN #popin-scrim
     (z-200, desktop.css) — beide sind position:fixed am Root-Stacking-Context,
     der zahlenmaessig hoehere gewinnt unabhaengig von der DOM-Reihenfolge.
     Bei z-80 verlor das Portal deshalb strukturell gegen JEDEN Tooltip, der
     fuer Inhalt INNERHALB des Popins geoeffnet wurde (Rangliste/Profil/Hexa-
     Bereich seit ui-407/408/413 — der Grossteil des Portal-Traffics). 250 legt
     bewusst Abstand nach beiden Seiten: klar ueber Popin (200) UND ueber alles,
     was NUR lokal in Popins eigenem Stacking-Context liegt (z.B. .ob-wt/
     Setup-Walkthrough, lokal z-300 — durch #popin-scrims backdrop-filter als
     Containing-Block fuer dessen fixed-Kinder gefangen, siehe dortiger
     Kommentar; ihr Wert ist NICHT root-vergleichbar mit diesem hier), klar
     unter #toast-bar/#pwa-install-bar (9999, s. .site-header:has(.site-more
     [open])-Kommentar weiter unten fuer die Gesamt-Uebersicht — kein fixer
     Zeilenverweis, der drifted sonst wieder wie dieser hier vor ui-448).
     Kein Konflikt mit Room-Switcher/Endscreen/Onboarding (120/121/130):
     #ct-tip traegt ausserhalb des Popins nie einen Trigger (0 .tipwrap/
     .tip-i/.ct-tip-self/.ct-tip-hover im Viewer-Markup, 2026-08-08
     verifiziert) — die Neuordnung hat dort nichts zu vergleichen. Alternative
     "Popin absenken" verworfen: Popin muesste dafuer UNTER 80, laege damit
     auch unter Room-Switcher (130) — bricht dessen Overlay-Rolle statt CTTip
     zu heilen (mathematisch kein gemeinsamer gueltiger Wert; ui-448 hat aus
     demselben Grund fuer .cs-modal-backdrop ebenfalls Richtung (a) gewaehlt).
     ui-448: seit .cs-modal-backdrop (Drop-Chancen-Modal) auf 260 liegt,
     rankt #ct-tip klar UNTER diesem einen echten Dialog-Modal — s. dortiger
     Kommentar (.cs-modal-backdrop, weiter oben in dieser Datei — web-240:
     beide liegen seit dem CSS-Umzug gemeinsam in components.css). */
  z-index: 250; /* über .tt-tip (50)/#popin-scrim (200), unter .cs-modal-backdrop (260, ui-448)/.tt-toasts (270, ui-630)/.ob-wt (300, lokal)/#toast-bar (9999) */
  width: max-content;
  /* ui-461 Design-Lock 5 (web-241, P6-Kette): 250px -> min(340px, ...).
     Fiftys Symptom (c): der Faktoren-Tooltip (Waffe-Tab, buildFactorTooltipHTML,
     .build-tip-body-Inhalt) lief mit seiner Werte-Spalte rechts aus dem
     schmalen Embed-Viewport (Zahlen "53.7.../x1,3.../69.8..." abgeschnitten) —
     der alte 250px-Cap war schon viewport-geklemmt (calc(100vw - 16px) griff),
     aber 250px selbst war fuer eine 2-Spalten-Tabelle (Label + tabular-nums
     Zahl) zu schmal, sobald die Label-Spalte laenger als ein paar Zeichen ist.
     340px gibt der Werte-Spalte genug Raum, OHNE dass sie umbricht (bleibt
     white-space:nowrap + tabular-nums, s. pages/build.css .build-tip-body
     td:last-child) — nur die Label-Spalte darf noetigenfalls umbrechen. */
  max-width: min(340px, calc(100vw - 16px));
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 9px 11px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.55);
  /* Lese-Panel ohne interaktive Kinder — Klicks gehen durch (ui-287-Muster,
     stammt aus dem frueheren .build-stat-tip; seit ui-400 rendert auch der
     Waffe-Tab hier). */
  pointer-events: none;
}
#ct-tip b { color: var(--text); }

/* ui-658: kanonisches CTTip-Tabellen-Muster (Bezeichnung | Zahl, „Gesamt"
   abgesetzt) für NEUE Tooltip-Tabellen — generalisiert aus .build-tip-body
   (pages/build.css), das als Waffe-Tab-Vorbild bewusst UNVERÄNDERT bleibt
   (eigene Typografie/Farbe, ui-400-Optik-Lock) statt hier mitgezogen zu
   werden. Kein eigener font-size/line-height-Override — erbt bewusst die
   #ct-tip-Fließtext-Werte (12.5px/1.5, s.o.). Erster Konsument: Profil-Hero-
   (i) „Hexas gesamt"/„Karten" (views/profile.js tipTable()). */
.tip-table { width: 100%; border-collapse: collapse; }
.tip-table th, .tip-table td { padding: 2.5px 0; vertical-align: baseline; font-weight: 400; }
/* Label-Spalte darf umbrechen (Sicherheitsnetz gegen lange Labels, Muster
   ui-461 Design-Lock 5 bei .build-tip-body td:first-child); mind. 12px
   Abstand zur Werte-Spalte über padding-right. */
.tip-table th { text-align: left; color: var(--text-dim); padding-right: 14px; overflow-wrap: anywhere; }
.tip-table td { text-align: right; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tip-table tr.total th, .tip-table tr.total td {
  border-top: 1px solid var(--line);
  padding-top: 5px;
  color: var(--cyan);
  font-weight: 700;
}

/* ---- Chip (generisch — Sub-Monate-/„Dabei seit"-/Runden-/Fokus-Pillen,
   web-240-Re-Spawn/Review cd1cd70f nachgezogen) ----
   Ursprünglich (an der alten style.css-Fundstelle) fälschlich als
   "bislang nur Guide-Proposal, kein realer Konsument" eingestuft — jener
   Kommentar war selbst stale. ECHTE Produktions-Konsumenten:
   web/src/views/profile.js (Sub-Monate-Chip, „Dabei seit ${since}"-Chip,
   Runden-Chip, Fokus-Stat-Chips via .chip.sub) plus Demo-Nutzung in
   styleguide.js/.swatch .chip (Farbkarten-Vorschau, scoped Override dort
   überschreibt nur height — Basisregeln kommen von hier, Memory
   styleguide-shared-class-collision geprüft: kein Leak-Risiko). Die
   seiten-gescopte .wb-focus .chip.sub-Override-Regel bleibt korrekt in
   pages/profile.css (Player-Profil-Sektion). ---- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: .35em;
  font-size: .8em;
  font-weight: 600;
  padding: .3em .7em;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  color: var(--text-dim);
}
.chip.sub { color: var(--cyan); }

/* ---- Tier-Chip (.tchip + 6 Tier-Modifier, web-240-Re-Spawn/Review
   cd1cd70f nachgezogen) — Farben spiegeln die bestehende
   .cs-card[data-tier]-Konvention (T1 cyan/T2 magenta/T3 yellow/T4 grün,
   T5 Silber, T6-Bucket Holo-Eis — ui-611: T5-T9 hatten vormals dieselbe
   card-color-t5-weiß, jetzt eigene Farben je Stufe, s. Tier-Farben-Abschnitt
   oben). Bucket T6 fasst Tier ≥6 zusammen (s. profile-stats.js
   getCollectionSummary) — zeigt seit ui-611 die T6-Farbe (nicht mehr T5), der
   Bucket selbst bleibt bestehen (Design-Lock ui-326 "Tier-Chips T1-T6").
   Echter Konsument: web/src/views/profile.js
   Tier-Badge (Sammlung-Sektion). Layout-Wrapper .tiers + Summary
   .maxtier bleiben seiten-spezifisch in pages/profile.css (Player-Profil-
   Sektion, dort Fundort-Nachbarschaft zu .tiers). ---- */
.tchip {
  flex: 1 1 auto;
  min-width: 70px;
  text-align: center;
  border-radius: 6px;
  padding: 8px;
  border: 1px solid var(--tc, var(--line-2));
  background: color-mix(in srgb, var(--tc, #fff) 8%, transparent);
}
.tchip .tn { font-size: var(--fs-caption); font-weight: 700; color: var(--tc, var(--text)); } /* ui-505 (M3): 0.72em (~10.8px, 0.72em-Treppe) -> --fs-caption (K1) */
.tchip .tv { font-size: 1.25em; font-weight: 800; margin-top: 2px; }
.tchip.t1 { --tc: var(--cyan); }
.tchip.t2 { --tc: var(--magenta); }
.tchip.t3 { --tc: var(--yellow); }
.tchip.t4 { --tc: var(--green); }
.tchip.t5 { --tc: var(--card-color-t5); }
.tchip.t6 { --tc: var(--card-color-t6); }

/* =====================================================================
   web-291 AK1: Checkout-Zustimmungs-Block (§ 356 Abs. 5 BGB) — geteilter
   Baustein, sitzt unter dem Preis in allen drei Kauf-CTA-Zeilen (Club-/
   Saison-Popin `.buy-cta`, Saison-Langweg `.sp-shop-cta-row`, Angebot-des-
   Tages `.pack-cta`, s. views/checkout-consent.js Markup-Vertrag). Design-
   Lock (kein Designer-Durchgang, Ticket-Wortlaut): "im bestehenden Popin-
   Stil unter dem Preis, Checkboxen mit --line-Rahmen, Links cyan". Die
   umgebenden `.buy-cta`/`.sp-shop-cta-row`/`.pack-cta`-Regeln (achievements.css/
   shop.css) sind SPALTEN-Layouts (Preis → Zustimmung → Button gestapelt) —
   dieser Block selbst braucht dafür kein eigenes Flex/Grid, nur die beiden
   Checkbox-Zeilen untereinander.
   ===================================================================== */
.checkout-consent {
  display: flex;
  flex-direction: column;
  gap: 0.5em;
  margin: 0.7em 0;
  width: 100%;
}
.checkout-consent .cc-row {
  display: flex;
  align-items: flex-start;
  gap: 0.55em;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.6em 0.75em;
  font-size: 0.78em;
  line-height: 1.45;
  color: var(--text-dim);
  cursor: pointer;
  /* ui-600 Defense-in-Depth: explizit nowrap statt dem Default zu vertrauen.
     Grund: `.cc-row` ist ein geteilter Klassenname mit der Hexa-Club-
     Gutschrift-Zeile (achievements.css `.club-credit .cc-row`, dort bewusst
     `flex-wrap: wrap`) — weil views/layout.js ALLE pages/*.css auf jeder
     Companion-Seite laedt, wuerde ein kuenftiger erneut bare `.cc-row`-
     Selektor sonst wieder unbemerkt hierher leaken (Checkbox+Text brechen
     in zwei Zeilen). Guard-Test: web/test/client/ui-600-css-class-scope.test.js. */
  flex-wrap: nowrap;
}
.checkout-consent .cc-row input[type="checkbox"] {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 0.2em;
  accent-color: var(--cyan);
  cursor: pointer;
}
.checkout-consent .cc-row a { color: var(--cyan); }
.checkout-consent .cc-note {
  font-size: 0.72em;
  color: var(--muted);
  line-height: 1.4;
}

/* =====================================================================
   ui-628: Mobile Sub-Navigation ("Segment / Auswahl-Sheet") — GETEILTES
   Muster fuer alle fuenf Mobile-Sub-Navigationen (Erfolge-Bereich +
   -Waffenfilter, Shop-Sub-Tab, Rang-Board, Sockel/Inventar-Toggle,
   Tower-Typ-Wahl; Lock 1 "ein Muster statt fuenf"). Formregel (Lock 6,
   DL-7): ≤3 Eintraege = Segment in voller Breite, > 3 Eintraege = Pick-
   Knopf, der ein Bottom-Sheet mit der Liste oeffnet. Mobile-only (Lock 3,
   ≤600px) — die Basis-Regel hier ist deshalb `display:none`, jede Seite
   zeigt daneben ihr unveraendertes Desktop-Markup (Pillen-Leiste/Kachel-
   Grid); Sichtbarkeit toggelt ausschliesslich der `@media`-Block unten,
   analog dem bestehenden `.cs-subtoggle`-Muster (pages/dashboard.css).

   Zwei-Klassen-Compounds mit Absicht (Spezifitaets-Kalkuel): `.subnav.
   subnav-seg`/`.subnav-seg .subnav-seg-btn` haben Spezifitaet (0,2,0) und
   ueberstimmen damit JEDE bestehende seiten-lokale Ein-Klassen-Basisregel
   (`.ach-scope-tab`/`.shop-subtab`/`.cs-subtoggle-btn`, je (0,1,0)) —
   UNABHAENGIG von der Datei-Ladereihenfolge (components.css laedt VOR
   pages/*.css, s. Datei-Kopf "Ladereihenfolge"). Jede Seite fuegt ihrem
   BESTEHENDEN Container/Buttons nur zusaetzlich `subnav`/`subnav-seg`/
   `subnav-seg-btn` hinzu (Lock 5 "nur das Markup darunter wird
   vereinheitlicht") — kein zweiter, paralleler State-Pfad.

   Sheet-Zeilen sind aus demselben Grund die WIEDERVERWENDETEN Desktop-
   Buttons (`.hs-board-tab`/`.achievement-weapon-tab`/`.weapon-card`, je
   zusaetzlich mit der Marker-Klasse `subnav-sheet-row`) — ihre Zustands-
   Klassen (`--active`/`.equipped`/`aria-selected`) werden bereits von den
   BESTEHENDEN Handlern gepflegt (switchBoard()/achSwitchTab()/Waffen-Equip-
   Delegation, alle in `document.querySelectorAll('.<klasse>')` UNGESCOPT
   auf die Klasse, treffen also automatisch auch die Sheet-Kopie). Details
   + Client-Controller: web/src/views/subnav.js, web/client/companion/
   subnav.js. Spec: web/spec/frontend.md "Mobile Sub-Navigation". */
.subnav { display: none; }
.subnav-label-short { display: none; }
/* Sheet + Backdrop tragen (bewusst, s. renderSubnavSheet()) KEINE `subnav`-
   Basisklasse (sie sind kein Segment/Pick-Element) -- brauchen deshalb ihre
   EIGENE Default-Hidden-Regel, sonst rendern sie auf Desktop als unstyled,
   sichtbarer Block (Live-CDP-Fund dieses Tickets: Erfolge-Waffenfilter-
   Sheet erschien @1440px als zweite, kaputte Tab-Zeile unter der echten). */
.subnav-sheet-backdrop,
.subnav-sheet { display: none; }

@media (max-width: 600px) {
  .subnav { display: block; }

  /* --- Segment (DL-2): so viele gleich breite Felder wie Eintraege --- */
  .subnav.subnav-seg {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    background: var(--bg-2);
    border: 1px solid var(--line-2);
    border-radius: 10px;
    padding: 3px;
    gap: 3px;
    /* Sticky (AK3/DL-4) — Top-Offset ist bedingt (Review-Fix ui-628,
       changes-requested-Runde 2026-09-11), NICHT mehr ein unbedingter
       57px-Default: der vormalige Default nahm UNBEDINGT ein 57px hohes
       `.dash-tabs` direkt darueber an — stimmt nur auf der Standalone-
       `/dashboard`-Seite. Im ECHTEN Mobile-Viewer-Popin verschiebt
       syncTabsHome() (web/client/surfaces/surface-host.js) `.dash-tabs`
       waehrend `#screen-setup` aktiv ist LIVE aus dem Fragment-Mount in die
       Bottom-Nav (`#nav`) — die Flaeche darueber ist dann leer, s.
       web/spec/frontend.md "Setup-Surface im Viewer". Fallback deshalb
       jetzt 0px ("nichts oberhalb"); der `.dashboard:has(.dash-tabs)`-Block
       direkt unten (nach der Pick-Knopf-Definition) traegt 57px nach,
       GENAU dann wenn `.dash-tabs` tatsaechlich (noch) Nachfahre von
       `.dashboard` ist — dasselbe `:has()`-Praesenz-Muster wie
       pages/dashboard.css `.embed-surface:has(.dash-header) .dash-tabs`
       (ui-402), reagiert live auf syncTabsHome()s appendChild()/
       insertBefore() ohne eigenen JS-Hook oder Timing-Fenster. Erfolge
       ueberschreibt den WERT fuer den L2-Waffenfilter-Pick zusaetzlich
       additiv und ebenso praesenz-abhaengig (`:has(.ach-scope-nav)`), s.
       pages/achievements.css. */
    position: sticky;
    top: var(--subnav-top, 0px);
    z-index: 6;
  }
  .subnav-seg .subnav-seg-btn {
    min-height: 44px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
    font-weight: 700;
    font-family: inherit;
    font-size: var(--fs-small);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.35em;
    padding: 0 0.4em;
    margin: 0;
    cursor: pointer;
  }
  /* DL-2: aktiv gefuellt, Teal wie im Prototyp (.claude/sprint/prototypes/
     ui-628.html .seg button.on) — ein eigener, dunklerer Ton als das
     helle var(--cyan), weil er eine ganze 44px-Flaeche fuellt statt nur
     Rand/Text (var(--cyan) auf dieser Flaeche wirkte im Prototyp-Vergleich
     zu grell). `[aria-selected="true"]` wird von den BESTEHENDEN Handlern
     bereits gepflegt (achSwitchScope()/shopSwitchSub()/cs-subtoggle-Klick),
     kein neuer Zustands-Pfad. */
  .subnav-seg .subnav-seg-btn[aria-selected="true"] {
    background: #1b9aa8;
    color: #fff;
  }
  .subnav-seg .subnav-label-full { display: none; }
  .subnav-seg .subnav-label-short { display: inline; }

  /* --- Pick-Knopf (DL-3): aktueller Eintrag + Caret, oeffnet das Sheet --- */
  .subnav.subnav-pick {
    width: 100%;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    border: 1px solid var(--line-2);
    border-radius: 10px;
    background: var(--bg-2);
    color: var(--text);
    font-weight: 700;
    font-family: inherit;
    font-size: var(--fs-small);
    padding: 0 12px;
    margin: 0;
    cursor: pointer;
    position: sticky;
    top: var(--subnav-top, 0px);
    z-index: 6;
  }
  .subnav-pick-current {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .subnav-pick-suffix {
    color: var(--yellow);
    font-weight: 700;
    font-size: 0.85em;
    white-space: nowrap;
  }
  .subnav-pick-caret {
    color: var(--cyan);
    font-weight: 700;
    font-size: 0.85em;
    white-space: nowrap;
    flex: none;
  }

  /* ui-628 Review-Fix (changes-requested must-fix #1, 2026-09-11): 57px
     gilt NUR, wenn `.dash-tabs` tatsaechlich (noch) Nachfahre von
     `.dashboard` ist — s. Sticky-Kommentar am `.subnav.subnav-seg`-Block
     oben fuer die volle Begruendung (der echte Mobile-Viewer-Popin
     verschiebt `.dash-tabs` per syncTabsHome() aus dem Fragment-Mount weg,
     sobald `#screen-setup` aktiv ist — die Standalone-`/dashboard`-Seite
     bewegt `.dash-tabs` nie). `:has()` reagiert live auf jeden
     appendChild()/insertBefore()-Reparent, kein eigener JS-Hook noetig.
     Zwei Compounds (`.dashboard:has(.dash-tabs)` + `.subnav.subnav-*`)
     halten den Selektor "scoped" fuer den ui-602-Namensraum-Guard,
     unabhaengig von der `.dashboard`-Praefix-Frage (s. web/scripts/
     css-selector-inventory.mjs Klassifikations-Grenze "≥2 Compounds gilt
     immer als scoped"). */
  .dashboard:has(.dash-tabs) .subnav.subnav-seg,
  .dashboard:has(.dash-tabs) .subnav.subnav-pick {
    --subnav-top: 57px;
  }

  /* Segment + Pick-Knopf im selben Block (Erfolge: L1-Segment + L2-Pick,
     nur unter Bereich Persistent, DL-3 "sitzt 8px unter dem Segment"). */
  .subnav + .subnav { margin-top: 8px; }

  /* --- Auswahl-Sheet (DL-5) --- */
  .subnav-sheet-backdrop {
    position: fixed;
    inset: 0;
    /* Echtes role="dialog"-Modal — dieselbe Ebene wie .cs-modal-backdrop
       (260, s. dortiger Kommentar weiter oben in dieser Datei fuer die
       volle z-index-Uebersicht); beide koennen strukturell nie gleichzeitig
       offen sein (Sheet ist mobile-only, Drop-Odds-Trigger sitzt im
       Sockel-Tab), Reihenfolge ist reine Vorsorge. */
    z-index: 260;
    background: rgba(3, 5, 12, 0.6); /* DL-5: Backdrop 60% --bg */
    display: none;
  }
  .subnav-sheet-backdrop.open { display: block; }

  .subnav-sheet {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 261;
    max-height: 78vh;
    overflow-y: auto;
    background: var(--panel);
    border-top: 1px solid var(--line-2);
    border-radius: 16px 16px 0 0;
    padding: 10px 14px calc(18px + env(safe-area-inset-bottom, 0px));
    /* AK6/DL-5: Slide-up ohne den `display:none`-Transition-Bruch (Sheet
       bleibt IMMER gerendert, nur ausserhalb des Viewports transformiert;
       `visibility` folgt der `transform`-Dauer nur beim Schliessen —
       schliesst also erst NACH dem Slide, oeffnet aber sofort). */
    transform: translateY(100%);
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0s linear 0.2s;
  }
  .subnav-sheet.open {
    transform: translateY(0);
    visibility: visible;
    transition: transform 0.2s ease, visibility 0s linear 0s;
  }
  @media (prefers-reduced-motion: reduce) {
    /* AK6: "ohne Einblend-Animation" — Sheet erscheint/verschwindet instant. */
    .subnav-sheet { transition: none; }
  }
  .subnav-sheet-grab {
    width: 36px;
    height: 4px;
    border-radius: 99px;
    background: var(--line-2);
    margin: 0 auto 10px;
  }
  .subnav-sheet-title {
    margin: 0 0 8px;
    font-size: var(--fs-caption);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
  }
  .subnav-sheet-rows { display: grid; gap: 6px; }
  /* AK2: ab fuenf Eintraegen zweispaltig (Aufrufer entscheidet via `twoCol`,
     s. renderSubnavSheet()). */
  .subnav-sheet-rows--grid { grid-template-columns: repeat(2, 1fr); }
  /* Spezifitaets-Kalkuel (wie oben am Segment): `.subnav-sheet-rows
     .subnav-sheet-row` ist (0,2,0) und ueberstimmt damit JEDE bestehende
     Ein-Klassen-Basisregel der wiederverwendeten Buttons (`.achievement-
     weapon-tab`/`.hs-board-tab`, je (0,1,0), beide seiten-lokal in
     pages/*.css mit eigenem Padding/Radius/Gap fuer ihre Pillen-/Tab-Optik)
     — bare `.subnav-sheet-row` allein (0,1,0) würde bei gleicher
     Spezifitaet gegen die SPAETER ladende Seiten-Datei verlieren (Kaskade,
     s. Datei-Kopf "Ladereihenfolge"). Tower (`.tower-pick-row`, ui-635)
     braucht denselben Kniff nicht extra, weil es KEINE seiten-lokale
     Basisregel mehr mitbringt (kein `.weapon-card`-Zeilen-Override mehr,
     s. Kommentar weiter unten "die vormalige ... Zeilen-Optik ... ist
     ENTFERNT"). */
  .subnav-sheet-rows .subnav-sheet-row {
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: var(--bg-2);
    color: var(--text);
    font-weight: 600;
    font-family: inherit;
    font-size: var(--fs-small);
    text-align: left;
    cursor: pointer;
  }
  /* Aktiv-Markierung wiederverwendet die BESTEHENDEN Zustands-Hooks der
     jeweiligen Klassenfamilie (kein neuer State) — `aria-selected` deckt
     Rang (.hs-board-tab), Erfolge-Waffenfilter (.achievement-weapon-tab)
     UND seit ui-635 Tower (.tower-pick-row); das weiterhin unveraendert per
     CSS ausgeblendete Kachel-Grid nutzt `.equipped` separat weiter
     (eigene Farbe/Glow, pages/build.css). */
  .subnav-sheet-rows .subnav-sheet-row[aria-selected="true"] {
    border-color: var(--cyan);
    color: var(--cyan);
  }
  .subnav-sheet-rows .subnav-sheet-row:disabled { opacity: 0.35; cursor: not-allowed; }
  .subnav-row-status {
    margin-left: auto;
    font-size: 0.75em;
    color: var(--muted);
    font-weight: 600;
    white-space: nowrap;
    flex: none;
  }
  .subnav-sheet-hint {
    font-size: 0.8em;
    color: var(--muted);
    margin: 10px 0 0;
  }

  /* ui-635: die vormalige `.subnav-sheet-rows .weapon-card`-Zeilen-Optik
     (Kachel-Buttons auf Zeilen-Layout umgeschaltet) ist ENTFERNT — Tower-
     Sheet-Zeilen sind seither `.tower-pick-row` (renderSubnavRow(), views/
     subnav.js), tragen die generische `.subnav-sheet-row`-Basisoptik direkt
     oben (keine eigene Zeilen-Deklaration mehr noetig). Tower-Kachel-CSS
     (`.weapon-card`/`.wc-*`, pages/build.css) bleibt fuer das weiterhin
     unveraendert per CSS ausgeblendete `.weapon-grid` bestehen. */
}

/* ui-634 (Fifty 14.09.): generischer inline-Glyph-Wrapper fuer Pick-Chip-
   Inhalte (`renderSubnavPick()`/`renderDeskPick()` `currentHtml`) --
   bewusst EIGENE Klasse statt `.wc-glyph` (bleibt unveraendert fuer die
   Tower-Karten-Kacheln, pages/build.css — seit ui-635 nur noch im
   Desktop-Grid im Einsatz):
   `.wc-glyph` ist `display:flex` und damit block-level, sobald der
   umgebende `.subnav-pick-current` selbst kein Flex-Container ist (der
   Desktop-Chip-Kontext ausserhalb dieser ≤600px-Media-Query oben hat KEINE
   eigene `.subnav-pick-current`-Display-Regel) -- das Symbol stand dadurch
   als eigener Block UEBER dem Label statt daneben (Fiftys Screenshot
   14.09., bisher nur am Tower-Chip beobachtet). `inline-flex` bleibt
   inline-level nach aussen (reiht sich neben den folgenden Text-Knoten
   ein) unabhaengig vom Display des Elternelements -- unscoped (keine
   Media-Query), damit Mobile-Pick UND Desktop-Chip identisch profitieren.
   Unbenutzt bei Konsumenten, deren currentHtml den Glyph roh (ohne
   Wrapper-Span) rendert (Erfolge-Filter/Rang/Baum-Chip -- die zeigten das
   Layout-Problem nie, s. ui-634 Ticket-Notes). */
.subnav-pick-glyph { display: inline-flex; align-items: center; vertical-align: middle; }

/* ui-635: generischer Glyph-Slot + Label-Text fuer renderSubnavRow()
   (views/subnav.js) -- fester 22px-Slot (Icon selbst 20px, zentriert),
   Label mit Ellipsis bei zu langem Text. EINE unscoped Regel statt
   Dopplung je Breakpoint (anders als `.subnav-row-status` direkt oben, das
   sich seine Optik je Kontext TRAEGT, s. Kopfkommentar am ui-630-Block
   weiter unten) -- Glyph-Groesse/Ellipsis sind laut DL-1/DL-4/DL-5
   identisch in Sheet (44px-Zeile) UND Popover (40px-Zeile), nur die
   Zeilenhoehe selbst unterscheidet sich (kommt bereits aus der geteilten
   `.subnav-sheet-row`-Basis). Erster Verwender: Tower-Typ
   (`.tower-pick-row`, views/dashboard.js renderWeaponSlot()). */
.subnav-row-glyph {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.subnav-row-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* =====================================================================
   ui-630 Lock 5: Desktop-Pendant der Mobile Sub-Navigation (Block oben) —
   ein Auswahl-Knopf als Pille + ein daran verankertes Popover
   (role="listbox") statt einer langen Chip-/Pillen-Leiste. Anlass: Tech-
   Tree-Baum-Wechsel bei ≥ 2 aktiven Bäumen (Fifty 2026-09-13: "die Desktop
   Chipleiste ist zu lang"). Generischer Baustein (Lock 5) — kein
   Konsumenten-spezifisches Markup hier, ein Konsument liefert nur die
   Zeilen (dieselben `.subnav-sheet-row`-Buttons/-Links wie im Mobile-Sheet
   oben) + eigene Modifier-Klassen in seiner eigenen pages/*.css.

   Bruch bewusst KOMPLEMENTÄR zum Mobile-Sheet-Bruch oben (max-width:600px)
   statt eines eigenen 768px-Bruchs: der Mobile-Block oben ist komplett IN
   der 600px-Media-Query eingebettet (Sichtbarkeit UND Optik in einer
   Deklaration) — ein Anheben auf 768px NUR für einen einzelnen Konsumenten
   hätte entweder alle fünf bestehenden ui-628-Konsumenten mitverändert
   (out of scope) oder ~120 Zeilen Dopplung für die 601–768px-Lücke gekostet
   (Wartungslast/Drift-Risiko). min-width:601px schließt lückenlos an
   max-width:600px an — Details + Trade-off in ui-630 ## Notes.

   Anders als beim Sheet TRÄGT dieser Block seine Zeilen-Optik selbst
   (`.subnav-desk-pop .subnav-sheet-row` unten, volle Deklaration statt
   Vererbung von der 600px-gescopten `.subnav-sheet-rows .subnav-sheet-row`
   oben) — dieselbe Klasse wird nur für den JS-Hook/die Konsistenz
   wiederverwendet, nicht für automatisch geerbtes CSS (das gäbe es hier
   nicht, s.o.). Sichtbarkeit läuft komplett über den `.subnav-desk`-
   Wrapper — die Popover-/Zeilen-Regeln selbst brauchen deshalb KEINE eigene
   Media-Query (unsichtbar, solange der Wrapper unsichtbar ist).
   JS-Gegenstück: companion/subnav.js (openDeskPopover()/closeDeskPopover()).
   SSR-Bausteine: web/src/views/subnav.js renderDeskPick()/renderDeskPopover(). */
.subnav-desk { position: relative; display: none; }
@media (min-width: 601px) {
  .subnav-desk { display: inline-flex; }
}
.subnav-desk-pick {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 6px 12px 6px 10px;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  background: var(--panel);
  color: var(--text);
  font-family: inherit;
  font-weight: 700;
  font-size: var(--fs-small);
  cursor: pointer;
}
.subnav-desk-pick:hover,
.subnav-desk-pick[aria-expanded="true"] { border-color: var(--cyan); }
.subnav-desk-pop {
  display: none;
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 20;
  width: 340px;
  max-width: calc(100vw - 32px);
  max-height: 60vh;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  padding: 10px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
}
.subnav-desk-pop.open { display: block; }
.subnav-desk-pop .subnav-sheet-title { margin: 2px 2px 8px; }
.subnav-desk-pop .subnav-sheet-rows { display: grid; gap: 4px; }
.subnav-desk-pop .subnav-sheet-row {
  min-height: 40px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--bg-2);
  color: var(--text);
  font-weight: 600;
  font-family: inherit;
  font-size: var(--fs-small);
  text-align: left;
  cursor: pointer;
}
.subnav-desk-pop .subnav-sheet-row[aria-selected="true"] {
  border-color: var(--cyan);
  color: var(--cyan);
}
.subnav-desk-pop .subnav-sheet-row:disabled,
.subnav-desk-pop .subnav-sheet-row[aria-disabled="true"] {
  opacity: 0.42;
  cursor: not-allowed;
}
.subnav-desk-pop .subnav-row-status {
  margin-left: auto;
  font-size: 0.75em;
  color: var(--muted);
  font-weight: 600;
  white-space: nowrap;
  flex: none;
}
.subnav-desk-pop .subnav-sheet-hint {
  font-size: 0.8em;
  color: var(--muted);
  margin: 10px 0 0;
}

/* =====================================================================
   ui-631 Lock 1: erster tatsaechlicher Wiederverwender des ui-630-Desktop-
   Bausteins (Block oben) -- wendet Auswahl-Chip + Popover auf Rang (7
   Boards, `.hs-boardbar`), den Erfolge-Tower-Typ-Filter (10,
   `.achievement-weapon-tabs`) und die Tower-Typ-Wahl (9, `.weapon-grid`)
   an. Formregel `pillsOrPick()` (views/subnav.js) ist seitenunabhaengig --
   Shop (3) und der Profil-Run-Bereich (2) bleiben ueber denselben Aufruf
   automatisch Pillen, ohne dass irgendeine Seite hier eine eigene Regel
   braucht (Lock 1 "eine Regel, keine Seiten-Ausnahmen").

   Drei generische Ergaenzungen zum ui-630-Baustein:
   1. `.subnav-desk-pop .subnav-sheet-rows--grid` -- AK2 "ab 8 Eintraegen
      zweispaltig" (renderDeskPopover() `twoCol`-Param, s. Kopfkommentar
      dort) -- dieselbe Modifier-Klasse wie das Mobile-Sheet oben, hier
      unter `.subnav-desk-pop` erneut deklariert (der Popover-Block traegt
      seine Optik komplett selbst, s. Kopfkommentar am ui-630-Block --
      keine Vererbung von der 600px-gescopten Sheet-Regel).
   2. (ui-638, s. eigener Block unten: `.subnav-desk-head`/`.subnav-desk-
      head-text` ersetzen den vormaligen `.subnav-desk-row`-Zeilen-Wrapper --
      der Chip sitzt seither IM Kopf der Flaeche statt in einer eigenen
      Zeile darunter.)
   3. `.subnav-desk-pick .subnav-pick-level` -- Review-must-fix 2026-09-13
      (AK2/DL-2 "Ebenen-Label klein links"): Optik 1:1 aus dem abgenommenen
      Prototyp (`.claude/sprint/prototypes/ui-631.html` `.pick .lvl`).
      Kompatibel mit `renderDeskPick()`s opt-in `levelLabelVisible` (Default
      `false`, s. dortiger Kommentar) -- seit ui-636 (2026-09-14) setzt auch
      der techtree.js-Aufruf das Flag (`true`, Ebenen-Label "Baum"), diese
      Regel gilt seither unveraendert auch fuer den Baum-Chip. Bei genau
      einem aktiven Baum bleibt der Baum-Chip weiterhin ungerendert (Lock 4
      "byte-gleich" haengt an dieser Nicht-Ausfuehrung, s. subnav.js).

   Die drei Konsumenten selbst lassen ihre bestehende Pillen-/Kachel-Leiste
   (`.hs-boardbar`/`.achievement-weapon-tabs`/`.weapon-grid`) unveraendert im
   Markup -- ausgeblendet wird sie NUR am Desktop-Breakpoint (jeweils in der
   eigenen Seiten-CSS-Datei, exaktes Pendant zum bestehenden Mobile-Hide
   ≤600px), UNBEDINGT je Konsument, weil die Eintrags-Anzahl je Konsument
   ein fester, seitenlokaler Wert ist (7/10/9, nie < SUBNAV_DESK_PICK_MIN)
   -- exakt dieselbe Begruendung wie das bestehende Mobile-Hide (ebenfalls
   unbedingt, weil dort >3 Eintraege ebenso konstant sind). */
.subnav-desk-pop .subnav-sheet-rows--grid { grid-template-columns: repeat(2, 1fr); }

.subnav-desk-pick .subnav-pick-level {
  font-size: 0.72em;
  color: var(--muted);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ui-638 (Fifty 14.09., Sichtcheck ui-634-Screenshot "Chip laesst Platz fuer
   den Text, gehoert oben rechts in die Ecke"): Kopf-Container fuer
   renderDeskHead() (views/subnav.js) -- ersetzt den vormaligen eigenen
   `.subnav-desk-row`-Zeilen-Wrapper UNTER Titel/Hinweis. `.subnav-desk-head`
   ist nur ab 601px ein Flex-Kopf (Titel links, Chip rechts, Oberkante auf
   Titelhoehe); darunter bleibt es ein normaler Block -- `.subnav-desk`
   (Chip-Wrapper, s. Block oben) blendet sich dort bereits SELBST aus
   (display:none), kein eigener Mobile-Sonderfall noetig hier.
   `.subnav-desk-head-text` buendelt Titel+Hinweis zu EINEM Flex-Item (sonst
   wuerde `justify-content:space-between` bei drei Kindern [Titel/Hinweis/
   Chip] falsch gleichmaessig verteilen statt "Text links, Chip rechts"). */
.subnav-desk-head-text { min-width: 0; }
@media (min-width: 601px) {
  .subnav-desk-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    /* ui-638 Re-Spawn (Review-must-fix): width:100% verhindert, dass der
       Kopf als einziges Flex-Kind eines Row-Flex-Elternteils (Rang-
       Standalone `.dash-header`) auf Inhaltsbreite schrumpft. */
    width: 100%;
  }
}

