/* Chat Tower Companion -- pages/achievements.css
 * P6-Kette Glied 6/8 (web-241). Reine Verschiebung aus web/public/style.css.
 * Scope: Dashboard-Tab "Achievements" (web-021) inkl. eines physisch hier
 * sitzenden, aber SITE-WEITEN Touch-Fixes (.nav-link/.nav-logout/.site-nav,
 * ui-204) -- bewusst NICHT nach shared-mobile.css umgezogen (wuerde einen
 * Kaskaden-Zyklus erzeugen, s. web-241-Ticket Cross-File-Sweep: dieser Block
 * muss vor pages/dashboard.css laden, mehrere andere shared-mobile.css-
 * Kandidaten aber NACH dashboard.css -- unaufloesbar in einer linearen
 * Link-Reihenfolge ohne diesen einen Block an seiner Originalstelle zu
 * belassen).
 *
 * Kaskaden-Lock: MUSS vor pages/dashboard.css UND pages/shared-mobile.css
 * laden (.achievement-claim-btn/.account-delete-btn/.nav-logout werden dort
 * per spaeterer Datei-Position ueberschrieben/ergaenzt). */


/* ui-214 Breakpoint-Kanon -- volle Doku in pages/shared-mobile.css. */
@media (max-width: 600px) {
  /* Achievement-Tab — Mobile (Design-Lock D10) */
  .achievement-grid { grid-template-columns: 1fr; }
  .achievement-weapon-tabs { gap: 0.35em; }
  .achievement-weapon-tab { padding: 0.4em 0.65em; font-size: 0.82em; }
  .achievement-card { padding: 0.85em 0.9em; }
}

/* ===== Achievement-Tab (web-021, 2026-06-01) =====
   Eigene Klassen (achievement-*) — bewusst getrennt von hexa-/templates-Klassen
   (Isolierungs-Lock). Active-State in var(--yellow) statt var(--magenta) (Design-Lock D2).
   Privacy-Notice-Klasse ENTFÄLLT (Fifty-Feedback 2026-06-01).
   ================================================== */

/* ui-523: der vormalige .ath-hint*-Block (ui-517 DL-2 Achievement-Tab-
   Info-Block) ist restlos entfernt — derselbe Wortlaut läuft jetzt als
   1-Schritt-Floating-Coachmark-Sequenz `achievements_tab_intro` über die
   geteilte .ob-*-Box-Chrome (web/client/css/companion/pages/dashboard.css,
   web/public/dash-onboarding.js), verankert auf dem Champion-Achievement-
   Card (`[data-achievement-id="placement_first"]`) — keine eigene
   CSS-Klasse mehr nötig. */

.achievement-panel { padding: 1.5em; }
.achievement-panel .panel-title { display: flex; align-items: baseline; gap: 0.6em; }
/* ui-362: .panel-title-sub bringt ein eigenes margin-left:0.6em mit (Default-
   Fall: einfacher Inline-Abstand im Block-Flow, ~6.93px, s. .panel-title-sub-
   Regel). Im Flex-Fall hier ist das ZUSAETZLICH zum gap:0.6em oben (9.9px) —
   beide sind unabhaengige Abstandsmechanismen, sie kollabieren nicht ineinander
   und addieren sich auf ~16.8px (gemessen). gap traegt die Distanz zum Sub-Label
   hier bereits allein — die margin-left neutralisieren, sonst wirkt der Abstand
   sichtbar groesser als ueberall sonst auf der Seite (Player-Profil A3-Fund).
   Scoped auf .achievement-panel (nicht generisch), damit .templates-panel
   (dashboard.js, exakt derselbe flex+gap-Aufbau) unangetastet bleibt — dort
   nicht Teil dieses Tickets. */
.achievement-panel .panel-title .panel-title-sub { margin-left: 0; }

/* Sub-Tab-Strip */
.achievement-weapon-tabs {
  display: flex;
  gap: 0.5em;
  flex-wrap: wrap;
  margin: 1em 0 1.2em;
  padding-bottom: 0.5em;
  border-bottom: 1px solid var(--line);
}
.achievement-weapon-tab {
  position: relative; /* ui-041: Anker für den Claimable-Dot-Marker */
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.4em 0.9em;
  min-height: 44px; /* ui-354: Touch-Target-Minimum, Spiegel zu .hs-board-tab (ui-215) */
  background: transparent;
  border: 1px solid var(--line-2);
  color: var(--muted);
  font-size: 0.88em;
  font-weight: 600;
  letter-spacing: 0.03em;
  cursor: pointer;
  border-radius: 2px;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.achievement-weapon-tab:hover:not(.achievement-weapon-tab--locked) {
  border-color: var(--cyan);
  color: var(--text);
  background: rgba(56, 225, 255, 0.08);
}
/* Active-State: var(--cyan) — ui-045 (Kanon: Cyan = aktiv, Gold = Kategorie/Wert) */
.achievement-weapon-tab--active {
  border-color: var(--cyan);
  color: var(--cyan);
  background: rgba(56, 225, 255, 0.10);
  text-shadow: 0 0 8px rgba(56, 225, 255, 0.3);
}
.achievement-weapon-tab--active:hover {
  border-color: var(--cyan);
  color: var(--cyan);
  background: rgba(56, 225, 255, 0.14);
}
.achievement-weapon-tab--locked {
  opacity: 0.55;
  border-color: var(--line);
  color: var(--line-2);
  cursor: pointer;
}
.achievement-weapon-tab--locked:hover { opacity: 0.75; border-color: var(--line-2); }
.achievement-weapon-tab-glyph { font-size: 1.1em; line-height: 1; display: inline-flex; align-items: center; }
/* ui-040: Glyph-<img> für Achievement-Sub-Tab. */
.achievement-weapon-tab-glyph img.wg-img { display: block; }
.achievement-weapon-tab-label { font-size: 0.9em; }
/* ui-344: 0.8em→0.9em, war ~10.56px. Höher als die übrigen Locks der Familie (0.85em),
   weil .achievement-weapon-tab bei ≤600px auf 0.82em schrumpft (Design-Lock D10,
   Block oben in dieser Datei) — 0.85em wäre dort wieder unter 11px gefallen (0.85 x 0.82 x 15 = 10.455px).
   0.9em x 0.82em x 15 = 11.07px (Mobile) / 0.9em x 0.88em x 15 = 11.88px (Desktop). */
.achievement-weapon-tab-lock  { font-size: 0.9em; opacity: 0.7; }
/* ui-360: eigene Klasse fuer den Tab-Zaehler (Items je Kategorie, profile.js) statt
   Wiederverwendung von .achievement-weapon-tab-lock — die heisst hier "Schloss"
   (dashboard.js 🔒-Icon), im Profil war's zweckentfremdet als Zaehler-Inline-Style
   (war .72em, schlug die SSOT-Klasse per Spezifitaet ≈ 9.50px, unter dem Boden).
   Ambient ist konstant 13.2px (.achievement-weapon-tab 0.88em x 15px) auf ALLEN
   Breiten — die ≤600px-Regel fuer .achievement-weapon-tab (Block oben in dieser
   Datei) ist tot (spaetere gleichspezifische Basisregel oben gewinnt per
   Source-Order, s. Nebenbefund in .claude/sprint/followups.md), kein separater
   Mobil-Fall noetig. 0.84em x 13.2 = 11.09px — ueber dem Boden, unter den
   11.88px des Label-Geschwisters (.achievement-weapon-tab-label, oben in dieser Datei). */
.achievement-weapon-tab-count { font-size: 0.84em; opacity: 0.6; font-weight: 400; }
/* ui-041: Claimable-Highlight — Cyan-Außen-Glow + Corner-Dot (Design-Locks DL-1..DL-7) */
.achievement-weapon-tab--has-claimable {
  box-shadow: 0 0 14px rgba(56, 225, 255, 0.28);
}
.achievement-weapon-tab--has-claimable::after {
  content: "";
  position: absolute;
  top: -4px;
  right: -4px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--cyan);
  border: 1.5px solid var(--panel);
  box-shadow: 0 0 7px rgba(56, 225, 255, 0.55);
  animation: subtle-pulse 2.4s ease-in-out infinite;
}
/* DL-6: Locked-Tabs nie markiert — defensiv hart aus */
.achievement-weapon-tab--locked.achievement-weapon-tab--has-claimable {
  box-shadow: none;
}
.achievement-weapon-tab--locked.achievement-weapon-tab--has-claimable::after {
  content: none;
}
@keyframes subtle-pulse {
  0%, 100% { transform: scale(1);    box-shadow: 0 0 6px rgba(56, 225, 255, 0.45); }
  50%       { transform: scale(1.18); box-shadow: 0 0 10px rgba(56, 225, 255, 0.85); }
}
/* DL-4: Reduced-Motion — Puls abschalten, Dot + Glow bleiben statisch */
@media (prefers-reduced-motion: reduce) {
  .achievement-weapon-tab--has-claimable::after {
    animation: none;
  }
}

/* Panel-Container (CSS-Toggle wie Hexa/Templates) */
.achievement-weapon-panels { display: block; }
.achievement-weapon-panel  { display: none; }
.achievement-weapon-panel.is-active { display: block; }

/* Card-Grid (Design-Lock D3)
   ui-401: min(300px, 100%) statt hartem 300px-Floor — bei 320px-Viewports
   (iPhone SE) ist der Container schmaler als 300px, ein nackter
   minmax(300px, 1fr)-Track erzwingt dann trotzdem 300px und schiebt die
   Seite auf 339px scrollWidth (Messbefund ui-401: div.achievement-card
   right=339 @320px; die .dash-tab-Offender aus der PO-Vormessung waren
   dagegen sauber im Scroll-Strip geclippt). Ab Container ≥300px ist
   min(300px, 100%) == 300px → 390px/768px byte-identisch.
   WARUM der aeltere ≤600px-Override nicht half: der generische Block am
   Datei-Anfang setzt zwar .achievement-grid auf 1fr, wird aber
   von DIESER textuell spaeter stehenden unconditional Regel uebersteuert
   (gleiche Spezifitaet 0,1,0, letzte gewinnt — dasselbe Kaskadenmuster
   wie der .cs-socket-grid-Root-Cause im web-085/ui-318-Block). Der Fix
   macht deshalb die Basis-Regel selbst container-sicher, statt auf die
   tote Mobile-Regel zu bauen. */
.achievement-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 0.8em;
  margin-top: 0.8em;
}

/* Einzelne Achievement-Card */
.achievement-card {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1em 1.1em;
  display: flex;
  flex-direction: column;
  gap: 0.6em;
  transition: border-color 0.15s;
}

/* State: claimable — Cyan-Rand + Glow (Design-Lock D4) */
.achievement-card--claimable {
  border-color: var(--cyan);
  box-shadow: 0 0 16px rgba(56, 225, 255, 0.18);
}

/* State: done — abgedunkelt (Design-Lock D4) */
.achievement-card--done {
  opacity: 0.5;
  border-color: var(--line);
}

/* Card-Header: Name + Tier-Badge */
.achievement-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5em;
}
.achievement-card-name {
  font-weight: 600;
  font-size: 1em;
  color: var(--text);
  line-height: 1.3;
}
/* web-298: Saison-Pool-Varianten-Glyph (Waffe/Gegner-Familie/Boss-Typ) neben
   dem Karten-Titel — Muster .kb-cell/.kb-cell svg (highscore.css): enemyIconSVG()
   traegt keine eigenen width/height-Attribute (100x100 viewBox), overflow:visible
   haelt den feDropShadow-Glow sichtbar. */
.season-variant-glyph {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: 0.3em;
}
.season-variant-glyph svg {
  display: block;
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  overflow: visible;
}
/* Tower-Varianten: nach der Client-Hydration (CTWeaponGlyphs._hydrateAll(),
   app.js GLYPH_BOX['season-card']=18) ersetzt <img.wg-img> das SSR-<svg> —
   Muster .ct-daily-task-glyph img.wg-img (dieselbe 18px-Groessenklasse). */
.season-variant-glyph img.wg-img {
  display: block;
}
.achievement-card--claimable .achievement-card-name {
  color: var(--cyan);
  text-shadow: 0 0 8px rgba(56, 225, 255, 0.25);
}
.achievement-tier-badge {
  flex-shrink: 0;
  font-size: 0.75em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.2em 0.6em;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line-2);
  color: var(--muted);
  white-space: nowrap;
}
.achievement-card--claimable .achievement-tier-badge {
  background: rgba(56, 225, 255, 0.10);
  border-color: var(--cyan);
  color: var(--cyan);
}
.achievement-card--done .achievement-tier-badge {
  background: rgba(94, 255, 138, 0.08);
  border-color: rgba(94, 255, 138, 0.3);
  color: var(--green);
}

/* Beschreibung */
.achievement-card-desc {
  font-size: 0.87em;
  color: var(--muted);
  line-height: 1.45;
  margin: 0;
}

/* Progress-Bar-Bereich (Design-Lock D5) */
.achievement-progress-area {
  display: flex;
  flex-direction: column;
  gap: 0.3em;
  margin-top: auto; /* ui-131: Progress + Reward an den unteren Card-Rand drücken */
}
/* ui-645 AK1: die neuen Schranken-Fortschrittstexte ("T3 · beste Runde seit
   der letzten Stufe X / Y") sind deutlich laenger als das vormalige "X / Y"
   -- auf schmalen Karten (Mobile 390px) brach das Prozent-Badge sonst MITTEN
   im Fliesstext um (ein einzelnes Wort der Ziel-Zahl landete optisch neben
   dem Badge, kaum lesbar). flex-wrap + eine Mindestbreite fuers Label
   erzwingen stattdessen einen sauberen Zeilenumbruch VOR dem Badge, sobald
   der Fliesstext nicht mehr neben ihm passt -- kurze Labels bleiben wie
   bisher einzeilig. */
.achievement-progress-label {
  display: flex;
  flex-wrap: wrap;
  column-gap: 0.5em;
  row-gap: 0.1em;
  align-items: baseline;
  font-size: 0.8em;
  color: var(--muted);
}
.achievement-progress-label > span:not(.achievement-progress-pct) {
  flex: 1 1 auto;
  min-width: 200px;
}
.achievement-progress-pct {
  flex: 0 0 auto;
  margin-left: auto;
  white-space: nowrap;
  font-weight: 600;
  color: var(--text);
}
.achievement-card--done .achievement-progress-pct     { color: var(--green); }
.achievement-card--claimable .achievement-progress-pct { color: var(--cyan); }

/* Progress-Track + Fill (Design-Lock D5: 6px, border-radius 3px) */
.achievement-progress-bar-track {
  height: 6px;
  background: var(--line);
  border-radius: 3px;
  overflow: hidden;
}
.achievement-progress-bar-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--line-2);
  transition: width 0.3s ease;
}
.achievement-card--claimable .achievement-progress-bar-fill {
  background: var(--cyan);
  box-shadow: 0 0 6px rgba(56, 225, 255, 0.5);
}
.achievement-card--done .achievement-progress-bar-fill {
  background: var(--green);
}

/* ui-619 DL-1/DL-3: Bestwert/Zaehler hat die naechste (noch nicht gecrosste)
   Stufe schon erreicht/ueberschritten (Stufen-Sperre web-289 laesst die
   Freischaltung trotzdem auf eine neue Auswertung warten) -- Balken/Label
   gruen statt cyan/neutral, UNABHAENGIG vom sonstigen State. Auf einer
   .achievement-card--claimable-Karte (DL-3) gewinnt sonst der cyan
   .achievement-card--claimable .achievement-progress-bar-fill-Override oben
   (gleiche Klassenanzahl, aber Vorfahren-Selektor > alleinstehender Modifier)
   -- !important loest genau diesen einen Konflikt auf. */
.achievement-progress-bar-fill--reached {
  background: var(--green) !important;
  box-shadow: none;
}
.achievement-progress-pct--reached {
  /* Gleicher Konflikt wie am Balken oben: .achievement-card--claimable
     .achievement-progress-pct (Zeile 301, Spezifitaet 0,2,0) gewinnt sonst
     gegen diesen alleinstehenden Modifier (0,1,0) -- !important loest ihn. */
  color: var(--green) !important;
  font-weight: 700;
}

/* "fehlt"-Zeile (ui-325): welche Effekte noch unter dem Ziel-Tier sind —
   Collection-Achievements (Vollsortiment/Kollektiv) only. Gemutet, klein,
   wrappt (kein white-space:nowrap) — Prototyp .claude/sprint/prototypes/ui-325.html. */
.ach-missing {
  font-size: 0.76em;
  color: var(--muted);
  line-height: 1.4;
}
.ach-missing .ml-lead { color: var(--muted); }
.ach-missing .ml-eff  { color: var(--text); opacity: 0.85; }
.ach-missing .sep     { opacity: 0.4; }

/* Reward-Zeile */
.achievement-reward-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5em;
  flex-wrap: wrap;
}
.achievement-reward-hint {
  font-size: 0.82em;
  color: var(--muted);
}
/* ui-623 (AK3): die vormalige pauschale "strong { color: var(--yellow) }" ist
   entfallen — jeder Reward-Chunk (rewardGlyph()/dailyRewardHtml(), dashboard.js)
   trägt jetzt zusätzlich zu .glyph-tip eine glyph-tip--Modifier-Klasse
   (hexa/draw/socket/skillpoint, components.css), die seine eigene Währungsfarbe
   setzt (Fifty-Bug-Report: "Hexa sind mal pink, mal gelb" — Reward-Zeilen waren
   die gelbe Quelle). Gilt identisch für Erfolge-, Saison-Pass- und Täglich-
   Reward-Zeilen (alle drei nutzen dieselbe .achievement-reward-hint-Klasse). */

@media (hover: none), (pointer: coarse) {
  /* web-202: Touch-Target ≥44px effektiv fuer den Header-Nav-Link (.nav-link
   * war reiner Text-Link ohne Padding/min-height, ~20px hoch — Geraete-
   * Smoke-Fund). Klickflaeche waechst per Padding/min-height, NICHT die
   * Schriftgroesse (Desktop/OBS bleiben unberuehrt, da dieser Block nur auf
   * Touch-Geraeten greift). inline-flex + align-items:center zentriert den
   * Text vertikal in der vergroesserten Box. */
  .nav-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 0.3em;
  }

  /* web-202: .nav-logout hatte bereits einen min-height:44px-Fix, aber nur im
   * @media (max-width:480px)-Block (pages/shared-mobile.css) — auf breiteren
   * Touch-Geraeten (z.B. iPad 768px) griff er nicht (Geraete-Smoke-Fund,
   * 26.6px hoch). pointer:coarse deckt alle Touch-Breiten ab, nicht nur
   * schmale Phones. */
  .nav-logout {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* web-202: Nav-Overflow bei schmalen Touch-Viewports (iPhone SE 320/390px) —
   * .site-nav war display:flex ohne flex-wrap (Default nowrap) → die 5
   * Nav-Links + Login/Logout liefen sichtbar rechts über den Viewport-Rand
   * hinaus (Geraete-Smoke-Fund). flex-wrap:wrap erlaubt Zweizeiligkeit
   * statt horizontalem Ueberlauf — row-gap haelt die Zeilen sauber getrennt. */
  .site-nav {
    flex-wrap: wrap;
    row-gap: 0.4em;
  }
}


/* Claim-Button (Design-Lock D6) */
.achievement-claim-btn {
  padding: 0.38em 1em;
  background: transparent;
  border: 1px solid var(--cyan);
  color: var(--cyan);
  font-size: 0.85em;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 2px;
  transition: background 0.12s, transform 0.08s;
  white-space: nowrap;
  min-height: 36px;
  font-family: inherit;
}
.achievement-claim-btn:hover:not(:disabled) {
  background: rgba(56, 225, 255, 0.12);
  transform: scale(1.02);
}
.achievement-claim-btn:disabled {
  border-color: var(--line);
  /* ui-342: --line-2 (1,48:1) am Stream aus Distanz nicht als „aus" lesbar —
     --muted (jetzt #838fa7 via ui-341) bleibt sichtbar deaktiviert-grau statt
     fast unsichtbar. WCAG-exempt (disabled), Fix ist bewusst über die Norm
     hinaus (Fifty-Kontext). Border bleibt --line (Lock). */
  color: var(--muted);
  cursor: not-allowed;
  opacity: 1;
  background: transparent;
}
/* Geclaimed-State: grüner disabled Button (Design-Lock D6).
   ui-349: an :disabled gekoppelt (Spezifität 0,2,0 = gleich wie .achievement-claim-btn:disabled
   oben), gewinnt per Source-Order da nachfolgend — sonst überstimmt das höher spezifische
   :disabled border-color/color grau (der eingesammelte Button traegt immer beide Klassen + disabled). */
.achievement-claim-btn--claimed:disabled {
  border-color: var(--green);
  color: var(--green);
  opacity: 0.5;
  cursor: not-allowed;
}
/* ui-619 DL-4: Wartezustand-Button klar gedimmt (heute -- ohne diese Regel --
   kaum vom aktiven Button zu unterscheiden, Fifty-Screenshot 2026-09-08).
   Spezifitaet 0,2,0 = gleich wie .achievement-claim-btn:disabled oben,
   gewinnt per Source-Order (Muster --claimed:disabled direkt darueber). Text
   bleibt --muted (bereits vom :disabled-Grundzustand geerbt); Rand zusaetzlich
   auf --muted (statt dem fast unsichtbaren --line) UND die ganze Flaeche auf
   ~40% Deckkraft -- zusammen klar erkennbar "gerade nicht klickbar". */
.achievement-claim-btn--waiting:disabled {
  border-color: var(--muted);
  opacity: 0.4;
}

/* Login-Prompt (Design-Lock D9) */
.achievement-login-prompt {
  padding: 2.5em 1.5em;
  text-align: center;
  border: 1px dashed var(--line-2);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.02);
  margin-top: 0.5em;
}
.achievement-login-prompt-icon {
  font-size: 2.2em;
  margin-bottom: 0.3em;
  line-height: 1;
}
.achievement-login-prompt h3 {
  margin: 0.2em 0 0.6em;
  font-size: 1.1em;
  color: var(--muted);
  font-weight: 600;
}
.achievement-login-prompt p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9em;
  line-height: 1.5;
}
.achievement-login-prompt a { color: var(--cyan); font-weight: 600; }

/* Locked-Panel (analog hexa-/templates-locked-box) */
.achievement-locked-box {
  padding: 2em 1.5em;
  border: 1px dashed var(--line-2);
  border-radius: 3px;
  text-align: center;
  background: rgba(255, 255, 255, 0.02);
}
.achievement-locked-glyph {
  font-size: 2.4em;
  color: var(--line-2);
  margin-bottom: 0.3em;
  line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
/* ui-040: Glyph-<img> für locked Achievement-Panel (--line-2, kein Glow, DL-3). */
.achievement-locked-glyph img.wg-img { display: block; }
.achievement-locked-title {
  margin: 0.2em 0 0.6em;
  font-size: 1.1em;
  color: var(--muted);
  font-weight: 600;
}
.achievement-locked-text {
  margin: 0.2em 0;
  color: var(--muted);
  font-size: 0.92em;
  line-height: 1.55;
}
.achievement-locked-text strong { color: var(--text); }
.achievement-locked-text a { color: var(--cyan); }

/* Account-Löschung (web-027, DSGVO) — dezenter Danger-Footer im Dashboard. */
.dash-footer { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--border, #2a3550); }
.account-danger > summary { cursor: pointer; color: var(--muted, #8aa0c0); font-size: 13px; list-style: none; user-select: none; }
.account-danger > summary::-webkit-details-marker { display: none; }
.account-danger > summary::before { content: '⚠ '; opacity: 0.7; }
.account-danger[open] > summary { color: var(--text); }
.account-danger-hint { color: var(--muted, #8aa0c0); font-size: 12px; line-height: 1.5; max-width: 52ch; margin: 10px 0; }
.account-delete-btn {
  background: transparent; color: #ff6b6b; border: 1px solid #ff6b6b;
  border-radius: 6px; padding: 7px 14px; font-size: 13px; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.account-delete-btn:hover:not(:disabled) { background: #ff6b6b; color: #1a1f2e; }
.account-delete-btn:disabled { opacity: 0.5; cursor: default; }

/* =====================================================================
   ui-554/ui-556: Achievement-Sub-Nav "◈ Persistent | Saison-Pass | 🎁
   Täglich". Sitzt EINE Ebene ÜBER dem Bestands-Sub-Tab-Strip
   (.achievement-weapon-tabs, Persistent-internal) — eigene ach-scope-*-
   Klassen statt Fork dieser Familie (andere Optik: runde Pillen statt
   rechteckiger Tabs). Optik 1:1 am Prototyp
   .claude/sprint/prototypes/season-pass.html `.shop-subtab` orientiert
   (Design-Quelle) — eigene Klassennamen, weil `.shop-subtab` konzeptionell
   zum (noch nicht gebauten) Shop-Feature gehört, keine Wiederverwendung über
   Feature-Grenzen (Kollisionsrisiko mit einer künftigen Shop-CSS-Datei,
   Memory "Styleguide-Klassen-Kollision"). Im DOM wenn ENABLE_DAILY_BONUS
   ODER ENABLE_SHOP (views/dashboard.js renderAchievements() — jede Pille
   zusätzlich EIGENSTÄNDIG gegated, kein DOM wenn aus, web-261-Muster).
   ===================================================================== */
.ach-scope-nav {
  display: flex;
  gap: 0.5em;
  margin-bottom: 1em;
}
.ach-scope-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.5em 1.1em;
  min-height: 44px; /* ui-396-Kanon: Tap-Ziele ≥44px (Ticket-AK4) — unconditional wie .achievement-weapon-tab */
  border-radius: 99px;
  border: 1px solid var(--line-2);
  background: transparent;
  color: var(--muted);
  font-size: 0.88em;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.ach-scope-tab:hover:not(.ach-scope-tab--active) {
  border-color: var(--cyan);
  color: var(--text);
  background: rgba(56, 225, 255, 0.08);
}
/* Active-State Cyan (ui-045-Kanon: Cyan = aktiv, Gold bleibt Kategorie/Wert —
   die Badge-Pille INNEN bleibt gold, s. .ach-scope-badge unten). */
.ach-scope-tab--active {
  color: var(--bg);
  background: var(--cyan);
  border-color: var(--cyan);
}
/* ui-574 (Zusatzfund aus ui-573): dieser Selektor bestand bis hierher
   ALLEIN aus `.ach-scope-tab--active:hover` mit denselben zwei
   Deklarationen wie unten (background: var(--cyan); color: var(--bg);)
   -- Spezifitaet (0,2,0) (2 Klassen/Pseudoklassen, 0 Elemente), schlaegt den
   globalen `button:hover:not(:disabled)` (base.css:105, Spezifitaet (0,2,1))
   NICHT (Klassen-Spalte gleich 2=2, Element-Spalte entscheidet 0<1, Kaskade
   vergleicht spaltenweise IDs->Klassen->Elemente, unabhaengig von
   Ladereihenfolge) -- toter Code. Per CDP bestaetigt (ui-573 ## Notes): Hover
   ueber den AKTIVEN Scope-Tab lieferte `color` weiterhin aus dem globalen
   Reset (var(--cyan)) auf permanent cyanem Hintergrund = unlesbar, derselbe
   Bug wie die Hexa-Club-Pille vor dem ui-573-Fix (nur hier noch ungemeldet,
   weil selten jemand die gerade angeklickte Pille laenger hovert). Fix
   identisch zum shop.css-Vorbild (shop.css:72-76): der zusaetzliche doppelte
   Klassen-Selektor `.ach-scope-tab.ach-scope-tab--active:hover` hebt die
   Klassen-Spalte auf 3 an (0,3,0) und gewinnt GARANTIERT, unabhaengig von
   Datei-Ladereihenfolge -- kein Wettlauf mit base.css. Die woertliche
   Vorbild-Form bleibt Teil der gruppierten Regel (identischer Werte-Satz,
   kein Verhaltensunterschied), traegt aber allein die Spezifitaet nicht. */
.ach-scope-tab--active:hover,
.ach-scope-tab.ach-scope-tab--active:hover {
  background: var(--cyan);
  color: var(--bg);
}
.ach-scope-tab-glyph { font-size: 1.05em; line-height: 1; display: inline-flex; align-items: center; }
.ach-scope-tab-label { font-size: 0.95em; }
.ach-scope-badge {
  display: inline-block;
  min-width: 1.3em;
  padding: 0.05em 0.45em;
  border-radius: 99px;
  background: var(--gold);
  color: var(--bg);
  font-size: 0.72em;
  font-weight: 800;
  text-align: center;
}
.ach-scope-tab--active .ach-scope-badge { background: var(--bg); color: var(--cyan); }
.ach-scope-badge--hidden { display: none; }

/* ui-638 AK6 (Fifty 14.09.): Ebene-1-Sub-Nav (.ach-scope-nav) und der
   Tower-Typ-Chip (Persistent-Scope, renderDeskHead()-Pendant fuer diese
   Zeile, dashboard.js) sitzen ab 601px in EINER Zeile, vertikal zentriert
   (beide sind einzeilige 40-44px-Elemente — anders als der Titel-Kopf oben
   im Panel braucht diese Zeile deshalb KEIN align-items:flex-start).
   `.subnav-desk` (der Chip-Wrapper, `views/subnav.js` renderDeskPick()/
   renderDeskPopover()) lebt hier als direktes Geschwister von
   `.ach-scope-nav`, NICHT mehr als Nachfahre von
   `.ach-scope-panel[persistent]` — er darf trotzdem nur sichtbar sein, wenn
   Persistent der aktive Scope ist (Saison-Pass/Taeglich kennen keinen
   Tower-Typ-Filter). ui-662: die Sichtbarkeit haengt deshalb am Attribut
   `data-ach-scope-active` auf `.ach-scope-row` selbst, geschrieben an allen
   drei Stellen, die auch `.ach-scope-panel--active` setzen (SSR,
   `achSwitchScope()`, `_prepareAchievementsFragmentActiveState()`, s.
   dortige Kommentare) — ein vormaliger `:has()`-Mechanismus verwarf die
   ganze Regel in Browsern ohne `:has()`-Support (Firefox 115), der Chip war
   dort nie sichtbar (Bug-Meldung McEs_). */
.ach-scope-row .subnav-desk { display: none; }
@media (min-width: 601px) {
  .ach-scope-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
  }
  .ach-scope-row[data-ach-scope-active="persistent"] .subnav-desk {
    display: inline-flex;
  }
}

/* Scope-Panels (CSS-Toggle, Muster .achievement-weapon-panel/.is-active) */
.ach-scope-panel { display: none; }
.ach-scope-panel--active { display: block; }

@media (max-width: 600px) {
  /* ui-628 (loest ui-389 fuer diese Sub-Nav ab): der vormalige Scroll-Strip
     (`flex-wrap:nowrap;overflow-x:auto`, KEINE Scroll-Affordance — genau
     das Fifty-Symptom, das ui-628 ausloeste) ist ERSATZLOS entfernt. Die
     `.ach-scope-nav`/`.ach-scope-tab`-Basisregeln (oben in dieser Datei)
     bleiben unveraendert (Desktop, Lock 3); die neuen `.subnav`/`.subnav-
     seg`/`.subnav-seg-btn`-Klassen (components.css) schalten sie ≤600px auf
     das Segment-Muster um — Spezifitaet (0,2,0) gewinnt unabhaengig von der
     Datei-Ladereihenfolge, s. dortiger Kopfkommentar.
     `.achievement-weapon-tabs` (Waffenfilter-Strip, INNERHALB Persistent):
     wird durch den neuen Pick+Sheet-Ersatz ersetzt (renderAchievements(),
     dashboard.js) — NUR im Dashboard-Kontext ausgeblendet. `.dashboard`
     scopen ist Pflicht: `.achievement-weapon-tabs`/`.achievement-weapon-
     tab` sind ein GETEILTER Klassenname mit dem rein lesbaren
     Achievement-Tab auf JEDEM Spieler-Profil (views/profile.js
     renderAchievementsPanel(), KEIN Pick+Sheet-Ersatz dort, Out-of-Scope
     dieses Tickets) — ein bare `.achievement-weapon-tabs{display:none}`
     wuerde die Profilseite mobil ohne jeden Filter zurücklassen. */
  .dashboard .achievement-weapon-tabs { display: none; }

  /* ui-628 AK3/DL-3: der L2-Waffenfilter-Pick-Knopf stapelt sich UNTER dem
     L1-Segment (beide sticky), ABER NUR WENN das L1-Segment (.ach-scope-nav,
     renderAchScopeSubnav()) ueberhaupt rendert. Review-Fix (changes-
     requested must-fix #2, 2026-09-11): die vormalige UNBEDINGTE
     117px-Konstante (57+52+8px) nahm an, `.ach-scope-nav` sei immer da --
     renderAchScopeSubnav() wird laut eigenem Kommentar an ihrer Aufrufstelle
     (dashboard.js renderAchievements()) aber NUR gerufen, wenn mindestens
     eines von ENABLE_SHOP/ENABLE_DAILY_BONUS an ist (heutiger Default:
     beide aus) -- ohne Segment blieb der Pick trotzdem 117px tief haengen,
     sichtbare Luecke mit durchscrollendem Karten-Inhalt darueber.
     `:has(.ach-scope-nav)` macht den Zuschlag praesenz-abhaengig (Muster
     components.css `.dashboard:has(.dash-tabs)`, s. dortiger Kommentar):
     nur wenn das Segment wirklich im DOM steht, kommt seine Hoehe (52px,
     live-gemessen) + 8px Abstand ON TOP des ererbten `--subnav-top` (0 im
     Popin ohne `.dash-tabs`, 57 sobald `.dash-tabs` Nachfahre von
     `.dashboard` ist) dazu; ohne Segment bleibt der Pick schlicht beim
     ererbten Basiswert stehen -- identisch zu jedem anderen
     Alleinsteher-Pick (Shop/Tower/Rang/Sockel). `top` (nicht `--subnav-top`)
     wird hier DIREKT gesetzt, um keine selbstreferenzielle Custom-Property
     zu erzeugen -- `calc(var(--subnav-top) + ...)` als neuer Wert VON
     `--subnav-top` selbst waere ein CSS-Zyklus (guaranteed-invalid, faellt
     auf reine Vererbung zurueck, der Zuschlag ginge verloren); der
     Pick-Knopf ist selbst schon ein `.subnav.subnav-pick` und traegt
     `--subnav-top` deshalb bereits korrekt aus components.css. Selektor per
     `data-subnav-pick`-Hook (stabil, unabhaengig vom generierten
     Sheet-Markup); `.dashboard`-Vorfahre ist weiterhin Pflicht fuer den
     ui-602-Namensraum-Guard (kein bare Attribut-Selektor) UND inhaltlich
     korrekt (derselbe Scope wie die Zeile darueber, `.ach-scope-nav`
     rendert ohnehin nur innerhalb `.dashboard`). */
  .dashboard:has(.ach-scope-nav) [data-subnav-pick="ach-weapon-filter"] {
    top: calc(var(--subnav-top, 0px) + 52px + 8px);
  }
}

/* ui-631 AK1/AK4: Desktop-Pendant zum Mobile-Hide oben (≤600px) — der
   Waffenfilter hat immer 10 Eintraege (Allgemein + 9 Tower-Typen, IMMER
   >= SUBNAV_DESK_PICK_MIN, views/subnav.js), `.achievement-weapon-tabs`
   weicht deshalb ab 601px unbedingt dem neuen Auswahl-Chip + Popover (seit
   ui-638 in `.ach-scope-row` NEBEN der Ebene-1-Sub-Nav statt eines eigenen
   `.subnav-desk-row`, views/dashboard.js renderAchievements()).
   `.dashboard` scopen ist Pflicht aus demselben Grund wie beim Mobile-Hide
   oben: `.achievement-weapon-tabs`/`.achievement-weapon-tab` sind ein
   GETEILTER Klassenname mit dem rein lesbaren Achievement-Tab auf jedem
   Spieler-Profil (views/profile.js renderAchievementsPanel(), KEIN
   Pick+Popover-Ersatz dort, Out-of-Scope dieses Tickets). */
@media (min-width: 601px) {
  .dashboard .achievement-weapon-tabs { display: none; }
}

/* =====================================================================
   ui-554: 🎁 Täglich-Panel — Kopf (Datum/"X von Y Aufgaben"/Countdown/
   Budget) + Gestern-Grace-Sektion. Optik 1:1 am Prototyp `.offer-head`/
   `.buy-section`/`.buy-list`/`.buy-row.earned`/`.offer-foot` orientiert —
   eigene ct-daily-*-Klassen aus demselben Feature-Grenzen-Grund wie beim
   Sub-Nav oben (kein Shop-Klassen-Fork). Die 5 Aufgaben-Karten + die Login-
   Karte selbst laufen über die BESTEHENDE `.achievement-card`/
   `.achievement-grid`-Familie unverändert (Design-Lock, Ticket-AK2) — hier
   nur das Kopf-/Fuß-Chrome drumherum, das es im Bestand noch nicht gab.
   ===================================================================== */
.ct-daily-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8em;
  align-items: center;
  justify-content: space-between;
  background: linear-gradient(135deg, #131a35 0%, #0d1322 70%);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  padding: 0.9em 1em;
  margin-bottom: 1em;
}
.ct-daily-head-main { flex: 1 1 auto; min-width: 0; }
.ct-daily-sub { color: var(--muted); font-size: 0.82em; }
.ct-daily-meta {
  display: flex;
  flex-direction: column;
  gap: 0.3em;
  align-items: flex-end;
}
.ct-daily-meta-line { font-size: 0.82em; color: var(--text-dim); white-space: nowrap; }
.ct-daily-countdown { font-variant-numeric: tabular-nums; color: var(--yellow); font-weight: 700; }
.ct-daily-budget { color: var(--yellow); }

/* Gestern-Grace-Sektion (Prototyp .buy-section/.buy-list/.buy-row.earned/.offer-foot) */
.ct-daily-yesterday { margin-top: 1em; }
.ct-daily-yesterday-title {
  margin: 0 0 0.5em;
  font-size: 0.85em;
  color: var(--text-dim);
  font-weight: 700;
}
.ct-daily-yesterday-list { display: flex; flex-direction: column; gap: 0.35em; }
.ct-daily-yesterday-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap; /* defensiv gegen Button-Overflow @375px — Prototyp-.buy-row hat kein Wrap */
  gap: 0.55em;
  background: var(--bg-2);
  border: 1px solid #4a3a12; /* 1:1 Prototyp-Wert (.buy-row.earned) — kein Token in tokens.css */
  border-radius: 8px;
  padding: 0.5em 0.7em;
  font-size: 0.85em;
}
.ct-daily-y-ic { font-size: 1.1em; line-height: 1; }
.ct-daily-y-name { flex: 1 1 auto; min-width: 8em; }
.ct-daily-y-note { color: var(--muted); font-size: 0.9em; }
.ct-daily-y-reward { white-space: nowrap; margin-left: auto; }
.ct-daily-yesterday-foot {
  margin-top: 0.5em;
  color: var(--muted);
  font-size: 0.78em;
  line-height: 1.5;
}

@media (max-width: 600px) {
  /* Kopf-Meta-Spalte rechtsbündig sieht auf schmalem Viewport nach dem
     flex-wrap-Umbruch falsch aus (volle Breite, rechts geklebt) — links-
     bündig liest sich dort natürlicher (Design-Ermessen, kein Prototyp-Lock
     für diesen Breakpoint). */
  .ct-daily-meta { align-items: flex-start; width: 100%; }
  /* Claim-Button in der Gestern-Zeile ≥44px (ui-396) — Basis-Regel für
     .achievement-claim-btn setzt das bereits selbst (pages/dashboard.css
     ≤600px-Block), hier nur die Zeile selbst absichern (kein Sonderfall nötig). */
}

/* ---------- ui-558: Taeglich-Kopf-CTA-Zeile (Muster ct-daily-meta-line
   direkt oberhalb — eigene Klasse nur fuer den kleinen Abstand zur
   Budget-Zeile, kein neues Layout). ---------- */
.ct-daily-club-cta { margin-top: 0.35em; }

/* ---------- ui-558-Beipack (web-272-Review): "Waffe des Tages"-Aufgaben-
   karte — Waffen-Glyph statt generischem Katalog-Emoji (Muster
   .achievement-weapon-tab-glyph oben in dieser Datei) + Leih-Hinweis, wenn
   die Waffe (noch) nicht regulaer freigeschaltet ist. ---------- */
.ct-daily-task-glyph { font-size: 1.1em; line-height: 1; display: inline-flex; align-items: center; }
.ct-daily-task-glyph img.wg-img { display: block; }
.ct-daily-loan-hint {
  margin: 0.35em 0 0;
  font-size: 0.78em;
  color: var(--cyan);
  font-style: italic;
}

/* =====================================================================
   ui-556: ◈-Saison-Bereich (Saison-Pass-Sub-Tab-Panel) — Kopf, Zeitleiste,
   Tier-Pillen-Reihe, Doppel-Status-Nachfrist-Banner, Kauf-Popin. Optik 1:1
   Prototyp `.claude/sprint/prototypes/season-pass.html` (`.sp-*`/`.stp*`/
   `.retro-*`/`.buy-*`/`.sum-*`/`.guest-gate`-Klassen dort übernommen).
   Karten selbst laufen über die BESTEHENDE `.achievement-card`-Familie
   (Design-Lock, s. views/dashboard.js renderSeasonCardHtml()) — hier nur
   das Kopf-/Zeitleiste-/Popin-Chrome drumherum, das es im Bestand noch
   nicht gab (Muster ct-daily-*-Block oberhalb dieser Sektion).
   ===================================================================== */

/* ---------- Saison-Kopf + Zeitleiste ---------- */
.sp-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8em;
  align-items: center;
  justify-content: space-between;
  background: linear-gradient(135deg, #131a35 0%, #0d1322 70%);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  padding: 0.9em 1em;
  margin-bottom: 1em;
}
.sp-head-title { margin: 0; font-size: 1.1em; letter-spacing: 0.02em; }
.sp-head-title .sp-num { color: var(--cyan); }
.sp-sub { color: var(--muted); font-size: 0.82em; margin-top: 0.2em; }
.sp-countdown { font-variant-numeric: tabular-nums; color: var(--yellow); font-weight: 700; }
.sp-head-right { display: flex; align-items: center; gap: 0.7em; flex-wrap: wrap; }
.sp-progress-mini { font-size: 0.82em; color: var(--text-dim); }
.sp-progress-mini b { color: var(--text); }

.prem-badge {
  display: inline-flex; align-items: center; gap: 0.35em;
  background: #2a1f0b; border: 1px solid var(--gold); color: var(--gold);
  border-radius: 99px; padding: 0.3em 0.75em; font-size: 0.8em; font-weight: 800;
}

/* Premium-Kennzeichnung in Reward-Hints (Prototyp `.achievement-reward-hint
   .prem-mark`) — EINE Quelle statt verstreuter Inline-Styles je Aufrufstelle
   (views/dashboard.js renderSeasonCardHtml() + client/companion/app.js
   Skip-Logik-Re-Arm nach einem reload-freien Claim — beide referenzieren
   diese Klasse statt eigener style=-Attribute, damit sie nie auseinanderdriften). */
.prem-mark { color: var(--gold); font-weight: 700; }

/* Saison-Zeitleiste: Tag-X/14-Balken mit Positionspunkt + Wochenwechsel-Strich. */
.sp-timeline { flex-basis: 100%; margin-top: 0.2em; }
.sp-tl-row {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 0.74em; color: var(--muted); margin-bottom: 0.3em;
}
.sp-tl-row .tl-date b { color: var(--text-dim); font-weight: 600; }
.sp-tl-row .tl-day { color: var(--yellow); font-weight: 800; font-variant-numeric: tabular-nums; }
.sp-tl-track { position: relative; height: 8px; background: var(--line); border-radius: 4px; overflow: visible; }
.sp-tl-fill { height: 100%; border-radius: 4px; background: linear-gradient(90deg, #2a6a7c, var(--cyan)); }
.sp-tl-dot {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  width: 14px; height: 14px; border-radius: 50%; background: var(--cyan);
  border: 2px solid var(--bg-2); box-shadow: 0 0 8px rgba(56, 225, 255, 0.6);
}
.sp-tl-weekmark { position: absolute; top: -3px; bottom: -3px; left: 50%; width: 1px; background: var(--line-2); }

/* ---------- ui-646: Saison-Balken (`.sp-progress`, zwischen .sp-head-right
   und .sp-timeline, gleiche Bauart wie die Zeitleiste) — Zahlen/Zustaende
   ausschliesslich aus `bar` (Lock 1, services/season-bar.js). Optik 1:1
   Prototyp `.claude/sprint/prototypes/saison-balken.html` bubble(). ---------- */
.sp-progress { flex-basis: 100%; margin-top: 0.4em; }
.sp-pg-row {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 0.74em; color: var(--muted); margin-bottom: 0.3em; gap: 0.6em;
}
.sp-pg-label b { color: var(--text); font-weight: 800; font-variant-numeric: tabular-nums; }
.sp-pg-cap { color: var(--gold); }
.sp-pg-track { position: relative; height: 10px; background: var(--line); border-radius: 5px; overflow: visible; margin-bottom: 0.2em; }
.sp-pg-fill { height: 100%; border-radius: 5px; background: linear-gradient(90deg, #2a6a7c, var(--cyan)); position: relative; z-index: 1; }
.sp-pg-ghost {
  position: absolute; top: 0; bottom: 0;
  background: repeating-linear-gradient(135deg, rgba(240, 179, 80, .55) 0 4px, rgba(240, 179, 80, .15) 4px 8px);
  border-radius: 0 5px 5px 0; z-index: 1;
}
.sp-pg-half { position: absolute; top: -4px; bottom: -4px; left: 50%; width: 2px; background: var(--gold); z-index: 2; }
.sp-pg-half::after { content: '🔒'; position: absolute; top: -18px; left: -7px; font-size: 11px; }
.sp-pg-half.open::after { content: ''; }
.sp-pg-premzone { position: absolute; top: 0; bottom: 0; left: 50%; right: 0; border-radius: 0 5px 5px 0; background: rgba(240, 179, 80, .08); }
.sp-pg-ms {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  width: 12px; height: 12px; border-radius: 50%; background: var(--bg-2); border: 2px solid var(--line-2); z-index: 3;
}
.sp-pg-ms.done { background: var(--cyan); border-color: var(--cyan); box-shadow: 0 0 8px rgba(56, 225, 255, .6); }
.sp-pg-ms.prem { border-color: var(--gold); }
.sp-pg-ms.prem.done { background: var(--gold); }
.sp-pg-ms.next { border-color: var(--text); }
.sp-pg-ms.locked { border-style: dashed; }
.sp-ms-row { position: relative; height: 34px; margin-top: 0.35em; }
.sp-ms { position: absolute; left: var(--ms-pos); transform: translateX(-50%); text-align: center; font-size: 0.72em; white-space: nowrap; color: var(--text-dim); }
.sp-ms:last-child { transform: translateX(-100%); text-align: right; }
.sp-ms-n { display: block; color: var(--muted); font-size: 0.9em; }
.sp-ms.done { color: var(--text); }
.sp-ms.done .sp-ms-chip::after { content: ' ✓'; color: var(--green); font-weight: 800; }
.sp-ms.locked { opacity: .7; }
.sp-ms.locked .sp-ms-chip::before { content: '🔒 '; font-size: .9em; }
.sp-pg-note { font-size: 0.74em; color: var(--muted); margin-top: 0.2em; }
.sp-pg-note b { color: var(--gold); }
/* ui-648: „Nächster Meilenstein"-Zeile (DL-6) ist nur mobil sichtbar. */
.sp-pg-next { display: none; }

/* Generischer Kauf-Button-Kanon (Gold-Gradient) — Saison-Pass/Nachfrist-CTA,
   Shop-Kaufsicht (shop-season-pass.js) nutzt dieselben Klassen. */
.btn {
  display: inline-block; border-radius: 9px; padding: 0.5em 1em; cursor: pointer;
  font-weight: 800; font-size: 0.9em; border: 1px solid transparent; letter-spacing: 0.02em;
  font-family: inherit;
}
.btn-buy { background: linear-gradient(135deg, var(--gold), #ffcf7d); color: #201502; box-shadow: 0 0 14px rgba(240, 179, 80, 0.33); }
.btn-buy:hover { filter: brightness(1.08); }
/* Sekundär-Stil (Outline) — Nachkauf-CTA im Doppel-Status NICHT als zweiter
   identischer Gold-Button neben dem frischen Saison-CTA (PM-R2 3.5). */
.btn-secure { background: none; border-color: var(--cyan); color: var(--cyan); }
.btn-secure:hover { background: rgba(56, 225, 255, 0.08); }

/* ---------- Stufen-Pillen-Reihe (tappbar, ui-396: ≥44px Tap-Ziel) ---------- */
.season-tier-pills { display: flex; gap: 0.35em; }
/* ui-580: zweizeilig (Zeile 1 T1/P2/✓ T1/🔒 P2 wie bisher, Zeile 2
   `.stp-goal` — Kurzformat-Ziel-Zahl aus dashboard.js fmtSeasonNumShort())
   + Segment-Fortschrittsbalken als Bottom-Fill (::after weiter unten,
   `--segfill` SSR-berechnet in renderSeasonPillHtml()). KEIN Wrapper-
   Element (Design-Entscheid, Ticket-Notes) — .stp bleibt direktes flex-Kind
   von .season-tier-pills, das bestehende Stretching ist unveraendert.
   Referenz: Prototyp `.claude/sprint/prototypes/ui-580-stufen-balken.html`,
   Variante B ("Balken in der Pille"). */
.stp {
  position: relative;
  flex: 1; min-width: 0; min-height: 44px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  text-align: center; font-size: 0.68em; font-weight: 700; letter-spacing: 0.03em;
  border: 1px solid var(--line-2); border-radius: 8px; padding: 0.22em 0.2em;
  color: var(--muted); background: rgba(131, 143, 167, 0.05);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; font-family: inherit;
}
.stp--premium { border-color: #4a3a12; color: var(--gold); background: rgba(240, 179, 80, 0.06); }
.stp--reached { border-color: var(--text-dim); color: var(--text); }
.stp--premium.stp--reached { border-color: var(--gold); }
.stp--claimed { border-color: rgba(94, 255, 138, 0.4); color: var(--green); background: rgba(94, 255, 138, 0.05); }

/* Segment-Fuellstand (ui-580 AK1/AK2): EIGENER Fortschritt DIESER Stufe
   (vorherige Schwelle -> eigene), NICHT der Gesamtfortschritt zur naechsten
   Stufe (der bleibt unveraendert in .achievement-progress-area, AK3).
   T-Stufen cyan, P-Stufen gold; claimed daempft den Fill (der Haken traegt
   den Zustand bereits). .stp-locked-dim (dashboard.js, gesetzt bei
   tier.locked_premium) daempft den Fill ebenso, OHNE ihn zu stoppen —
   Fortschritt laeuft weiter, nur das Einsammeln ist gesperrt. Basis-Regel
   VOR den Farb-/Dimm-Overrides (Kaskade: gleiche Spezifitaet, spaetere
   Regel gewinnt). */
.stp::after {
  content: "";
  position: absolute; left: 0; bottom: 0; height: 3px;
  width: calc(var(--segfill, 0) * 100%);
  background: var(--cyan);
  border-radius: 0 99px 99px 0;
}
.stp--premium::after { background: var(--gold); }
.stp--claimed::after { opacity: .45; }
.stp-locked-dim::after { opacity: .5; }

/* Zeile 2: Stufen-Ziel-Zahl im Kurzformat (dashboard.js fmtSeasonNumShort()),
   IMMER die Zahl, auch bei locked_premium (ui-650 -- die vormalige ui-645-
   AK3-Ausnahme "holbar mit Saison-Pass" lief hier ueber, der Satz sitzt
   seither nur noch im title/aria-label). 10px fix (kein Skalen-Token) —
   spiegelt den abgenommenen Prototyp-Wert 1:1 (Fifty-Iteration 2, "prototyp
   ist super"). align-self:stretch + eigenes overflow/ellipsis bleiben als
   defensive Absicherung (`.stp`s eigenes text-overflow:ellipsis greift
   NICHT auf Flex-Kinder durch). */
.stp .stp-goal {
  font-size: 10px; line-height: 1; font-weight: 500; letter-spacing: 0.02em;
  color: var(--muted); pointer-events: none;
  align-self: stretch; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stp--premium .stp-goal { color: color-mix(in srgb, var(--gold) 70%, var(--muted)); }
.stp--claimed .stp-goal { opacity: .55; }

/* =====================================================================
   ui-590 (DL-3/DL-4, komplett seit ui-645 retired): Sperr-Optik fuer Saison-
   Stufen. `.stp--claim-locked` (web-311, Claim-Sperre) ist seit ui-645 AK3
   entfernt, `.stp--seq-locked` (Reihenfolge-Sperr-Optik) seit ui-645 Review
   Runde 2 (must-fix, AK3/Lock 2) ebenfalls — Regel B (web-329) kennt keine
   Pillen-Sperr-Optik mehr; die Reihenfolge selbst (web-290) bleibt reines
   Server-/Knopf-Verhalten, keine Pillen-/Karten-Optik mehr.
   ===================================================================== */
/* die naechste sequenziell holbare UND jede weitere gleichzeitig erreichte,
   noch nicht eingesammelte Saison-Stufe — Cyan-Rahmen/Glow wie eine
   claimable-Karte (Bestands-Kanon .achievement-card--claimable). */
.stp--next-claimable { border-color: var(--cyan) !important; color: var(--cyan) !important; box-shadow: 0 0 8px rgba(56, 225, 255, 0.25); }
/* ui-645 Review Runde 2 (must-fix #2): ohne diese Regel gewinnt obiges
   `!important` gegen `.stp--premium` (Zeile 982, ohne `!important`) — eine
   holbare Premium-Stufe MIT Pass zeigte sonst faelschlich Cyan statt Gold. */
.stp--premium.stp--next-claimable { border-color: var(--gold) !important; color: var(--gold) !important; box-shadow: 0 0 8px rgba(240, 179, 80, 0.25); }

/* Sperr-/Meta-Hinweiszeile — eigene Zeile UEBER der unveraenderten
   .achievement-reward-row, NUR im holbaren Zustand (offene Claim-Zeile).
   Geteilt von renderAchievementCard() (◈-Karte, Lifetime) UND
   renderSeasonCardHtml() (Saison-Karte) — ein Namespace fuer beide. */
.ach-lock-line { font-size: .8em; color: var(--muted); display: flex; align-items: center; gap: .35em; margin-bottom: 0.4em; }
.ach-lock-line b { color: var(--text); font-weight: 600; }

/* ui-619 DL-2: Erklaerzeile im Wartezustand -- gleiche Optik wie .ach-lock-line
   (Groesse/Farbe/Abstand), aber der volle Satz ist laenger als "🔒 Stufe N
   gesperrt..." und darf auf schmalen Viewports zweizeilig umbrechen --
   align-items:flex-start statt center haelt eine zweite Zeile linksbuendig
   statt vertikal mittig zu haengen (ohne sichtbaren Unterschied bei EINER
   Zeile). Kein Schloss-Icon (Fifty-Entscheid) -- reiner Text-Node im Kind-Span. */
.ach-lock-line--explain { align-items: flex-start; line-height: 1.35; }

/* web-297: Anker-Hinweiszeile auf Saison-Karten ("Stufen sind auf dich
   zugeschnitten") — direkt unter der Beschreibung, VOR den Stufen-Pillen.
   Gleiche Groesse/Farbe wie .ach-lock-line (beides schmale Meta-Hinweise),
   eigene Klasse fuer semantische Klarheit (kein Sperr-Zustand). */
.ach-anchor-hint { font-size: .8em; color: var(--muted); margin: 0 0 0.5em; }

/* ui-645 AK2: Schranken-Hinweis ("In einem Run kann nur die nächste Stufe...")
   — inline im SELBEN Absatz wie die Beschreibung (kein eigener Block), nur
   gedaempfter als deren bereits muted Grundfarbe. */
.ach-rule-hint { font-size: .92em; opacity: .8; }

/* ui-645 AK8: Stufen-Vorschau-Zeile (Hover/Tipp auf eine Pille) — Persistent
   `.ltp-row` UND Saison `.season-tier-pills` teilen sich dieselbe Klasse +
   Position (direkt unter den Pillen, ueber der Fortschritts-Leiste). Min-
   height haelt die Kartenhoehe stabil, egal ob der Default-Text (kurz) oder
   eine Belohnungs-Zeile mit Icon-Chips (zweizeilig auf schmalen Karten)
   steht — kein Layout-Sprung beim Hover. */
.ach-tier-info { font-size: .8em; color: var(--muted); min-height: 1.6em; margin: -0.1em 0 0.5em; line-height: 1.4; }

/* ui-645 AK8: Pillen sind jetzt (auch) hover-/tap-interaktiv fuer die
   Stufen-Vorschau — Zeigercursor als Signal (Saison-Pillen sind bereits
   <button>, tragen den Cursor ohnehin; Persistent-Pillen sind <span>).
   `data-tier-peek` markiert die GEPINNTE Pille (ein Tipp pinnt, ein zweiter
   loest — geteiltes Attribut statt zweier Klassennamen fuer beide Karten). */
.ltp[data-ltp-idx] { cursor: pointer; }
.ltp[data-tier-peek], .stp[data-tier-peek] { outline: 1px solid var(--text-dim); outline-offset: 1px; }

/* =====================================================================
   ui-581: Lifetime-◈ Tier-Bubble-Reihe (3er-Fenster) — Design-Pendant zu
   der .stp-Familie oben (ui-580), aber EIGENER `.ltp-*`-Namespace (Ticket-
   Vorgabe „nicht die Saison-.stp-Regeln anfassen") — Lifetime-Achievements
   zeigen ein FENSTER (max. 3 Bubbles: letzte ✓ · aktuelle · nächste) statt
   der Saison-Voll-Reihe (immer 4), weil Lifetime-Reihen bis zu 16 Tiers
   haben können (seit web-326, vormals 12 Tiers — web-110; views/dashboard.js
   renderAchievementCard(), `windowOf()`-Formel). Referenz: Prototyp `.claude/sprint/prototypes/
   ui-581-lifetime-bubbles.html` (`.lt-pill`/`.lt-pills`/`.goal`/`.anim-out`/
   `.grow-start`/`.anim-complete` dort — hier 1:1 in den `.ltp-*`-Namespace
   übernommen: `.ltp`/`.ltp-row`/`.ltp-goal`/`.ltp-anim-out`/
   `.ltp-grow-start`/`.ltp-anim-complete`). Werte bleiben LITERALE px (nicht
   das em/15-Muster wie `.club-monthly` & Co) — deckungsgleich mit den
   Iteration-3/4-Mess-Pins des Prototyps (44px-Reihenhöhe 0-stabil über die
   komplette Claim-Animation, ±1-2px Breiten-Stabilität der mittleren
   Bubbles), gleiche Begründung wie `.stp-goal` oben ("spiegelt den
   abgenommenen Prototyp-Wert 1:1").
   ===================================================================== */
.ltp-row { display: flex; gap: 6px; margin-bottom: 10px; align-items: flex-start; }
.ltp {
  position: relative; overflow: hidden;
  flex: 1 1 0; min-width: 52px; min-height: 44px; align-self: stretch;
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  background: rgba(255, 255, 255, .03); border: 1px solid var(--line-2); border-radius: 9px;
  color: var(--text); font-size: 12px; font-weight: 700; cursor: default; padding: 4px 6px;
  transition: transform .28s ease, opacity .28s ease, flex-grow .28s ease, min-width .28s ease,
              padding .28s ease, margin .28s ease, border-color .2s ease;
}
/* Iteration 3 (Ticket-Pflicht-Detail): white-space:nowrap auf der Pille UND
   ihren beiden Kind-<span>s hält den Inhalt einzeilig (overflow:hidden
   clippt statt umzubrechen), während `.ltp-anim-out` die Breite gegen 0
   führt — ohne das bräche der Text um ("✓ 25 %" vertikal) und würde das
   schrumpfende Flex-Item hoch, was ueber `align-items:flex-start` (Gurt,
   s.o.) hinaus die ganze Reihe strecken würde. Mess-Beleg (Prototyp): 37
   Höhen-Samples über die komplette Claim-Animation, alle exakt 44px. */
.ltp, .ltp span { white-space: nowrap; }
.ltp .ltp-goal { font-size: 10px; font-weight: 500; color: var(--muted); letter-spacing: .02em; line-height: 1; }
.ltp::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 3px;
  width: calc(var(--segfill, 0) * 100%);
  background: var(--cyan); border-radius: 0 99px 99px 0;
  transition: width .25s ease;
}
.ltp--done { border-color: rgba(94, 255, 138, .55); }
.ltp--done .ltp-goal { opacity: .55; }
.ltp--done::after { opacity: .45; }
.ltp--current { border-color: rgba(56, 225, 255, .5); }
.ltp--next { opacity: .75; }
.ltp--next .ltp-goal { opacity: .8; }
/* ui-645 AK3: `.ltp--locked` (ui-590 DL-1, Claim-Sperre) entfernt — Regel B
   (web-329) kennt keine Claim-Sperre mehr, die Pille NACH der
   aktiven/holbaren ist seither ein normaler `.ltp--next` (Ziel/spaeter). */

/* Claim-Conveyor (Iteration 4, Fifty-Diktat wörtlich): links schrumpft UND
   rechts wächst die neue Einheit GLEICHZEITIG — die Summe der flex-grow-
   Faktoren bleibt dadurch ~konstant, die mittleren Bubbles behalten ihre
   Breite und wandern nur nach links (kein Breiten-Sprung). Client-Treiber:
   `web/client/companion/app.js` `_ltRunConveyor()`. Mess-Beleg (Prototyp):
   24 Layout-Breiten-Samples (offsetWidth, NICHT getBoundingClientRect — das
   misst den `.ltp-anim-complete`-Puls mit, der transform-only/layout-
   neutral ist) über die komplette Animation, max. 1px Abweichung. */
.ltp-anim-complete { animation: ltpPulse .45s ease; border-color: rgba(94, 255, 138, .7); }
@keyframes ltpPulse { 0% { transform: scale(1); } 40% { transform: scale(1.08); } 100% { transform: scale(1); } }
.ltp-anim-out { flex-grow: .0001; min-width: 0; padding-left: 0; padding-right: 0; margin-right: -6px; opacity: 0; transform: translateX(-12px) scale(.7); }
/* Start-Zustand der rechts hereinwachsenden Einheit — das ENTFERNEN der
   Klasse (app.js, nach einem erzwungenen Reflow) animiert über die
   `.ltp`-Transition aufs Normalmaß. */
.ltp-grow-start { flex-grow: .0001; min-width: 0; padding-left: 0; padding-right: 0; margin-left: -6px; opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .ltp { transition: none; }
  .ltp-anim-complete { animation: none; }
}

/* ---------- Doppel-Status: Nachfrist-Banner der Vorsaison ---------- */
.retro-banner {
  background: linear-gradient(135deg, #241a08, #12100a); border: 1px solid var(--gold);
  border-radius: 12px; padding: 0.9em 1em; margin-bottom: 1em;
}
.retro-banner h2 { margin: 0 0 0.25em; font-size: 1.1em; color: var(--gold); }
.retro-banner p { margin: 0.2em 0; font-size: 0.84em; color: var(--text-dim); }
.retro-stats { display: flex; gap: 1.4em; flex-wrap: wrap; margin-top: 0.6em; }
.retro-stat { font-size: 0.8em; color: var(--muted); }
.retro-stat b { display: block; font-size: 1.25em; color: var(--text); font-variant-numeric: tabular-nums; }
.retro-cta-row { display: flex; align-items: center; gap: 0.8em; margin-top: 0.7em; flex-wrap: wrap; }
.retro-frist { font-size: 0.78em; color: var(--muted); }

/* ---------- Kauf-Popin (Kurzweg) — Backdrop reuses components.css
   `.cs-modal-backdrop`/`.open` (geteilter Modal-Baustein, web-204). ---------- */
.sp-buy-modal {
  width: min(94vw, 620px); max-height: min(90svh, 900px); overflow-y: auto;
  /* ui-653 AK3/Lock 2: Schutz-Clip, NICHT der eigentliche Fix (der steckt im
     `.sum-lines`-Raster oben) — ohne ihn wuerde `overflow-y: auto` einen
     ungewollten horizontalen Scrollbalken erzwingen, falls doch mal Inhalt
     breiter als das Modal wird. */
  overflow-x: clip;
  background: var(--panel); border: 1px solid var(--line-2);
  border-radius: 14px; padding: 1.1em 1.2em 1.3em; position: relative;
}
.sp-buy-close {
  position: absolute; top: 0.6em; right: 0.8em; background: none; border: none;
  color: var(--muted); font-size: 1.1em; cursor: pointer; min-width: 44px; min-height: 44px;
}
.sp-buy-title { margin: 0 0 0.2em; font-size: 1.15em; }
.sp-buy-sub { color: var(--muted); font-size: 0.82em; margin-top: 0.2em; }
.buy-benefits { list-style: none; margin: 0.8em 0; padding: 0; display: flex; flex-direction: column; gap: 0.45em; }
.buy-benefits li { display: flex; gap: 0.55em; align-items: baseline; font-size: 0.88em; }
.buy-section h4 {
  margin: 1em 0 0.45em; font-size: 0.8em; letter-spacing: 0.05em;
  color: var(--text-dim); text-transform: uppercase;
}
.sum-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.5em; }
.sum-tile {
  border: 1px solid var(--line-2); border-radius: 10px; background: var(--bg-2);
  padding: 0.65em 0.5em 0.6em; text-align: center; display: flex; flex-direction: column; gap: 0.15em;
}
.sum-tile .st-ic { font-size: 1.3em; line-height: 1; display: flex; align-items: center; justify-content: center; }
.sum-tile .st-num { font-size: 1.35em; font-weight: 900; font-variant-numeric: tabular-nums; }
.sum-tile .st-lbl { font-size: 0.68em; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.sum-tiles--earned .sum-tile { border-color: #4a3a12; background: linear-gradient(180deg, #171207, #0d1322); }
.sum-tiles--earned .st-num { color: var(--gold); }
.buy-empty { color: var(--muted); font-size: 0.8em; font-style: italic; padding: 0.3em 0.1em; }

/* ui-592 (Kauf-Transparenz): Gratis/Premium-Split auf allen Kaufflaechen
   (Kurzweg-Popin + Shop-Karte) — ersetzt die vormalige Gesamt-Summen-Anzeige
   (.sum-tiles--open + die "aus:"-Icon-Zeile .sum-src, Fifty "aus: raus",
   beide ENTFERNT).
   .sum-tiles--free: bestehende .sum-tile-Optik UNVERAENDERT (kein Gold, kein
   Dimmen) — .st-num erbt einfach --text (body-Default), deshalb bewusst KEINE
   eigene Regel hier.
   .buy-section--premium: 1px --line-Trenner + Abstand ueber der Sektion
   (gestapelter Popin-/Shop-Fall — .shop-cols unten hebt das fuer die
   nebeneinander-Variante wieder auf). .h4--gold faerbt NUR die Farbe (Rest
   erbt .buy-section h4 oben) — Ueberschrift traegt bewusst KEINE Zahlen
   (Ticket-Design-Lock).
   .sum-lines: Textraster statt Kacheln fuer die personalisierten Premium-
   Zeilen (erspielt/erspielbar) — Spalte 1 Label, Spalten 2-4 je Waehrung
   "Zahl + Icon", rechtsbuendig (.sl-val), damit die Icons beider Zeilen
   exakt untereinander stehen. */
.buy-section--premium { margin-top: 1.1em; padding-top: 0.8em; border-top: 1px solid var(--line); }
.h4--gold { color: var(--gold); }
.sum-sub { font-size: 0.78em; color: var(--text-dim); margin-top: 0.4em; }
.sum-sub b { color: var(--text); font-weight: 600; }
/* ui-653: Spalte 1 `minmax(0, 1fr)` statt `max-content` — ein Label-only-
   Text (z.B. der Nachkauf-Erklaersatz) sprengte als `max-content` sonst
   ungebrochen die Werte-Spalten aus dem Modal. `.sl-lbl--full` spannt
   Label-only-Zeilen zusaetzlich ueber alle Spalten, auf allen Breiten. */
.sum-lines {
  margin-top: 0.6em; display: grid; grid-template-columns: minmax(0, 1fr) repeat(3, max-content);
  column-gap: 0.9em; row-gap: 0.3em; align-items: baseline; font-size: 0.82em; color: var(--text-dim);
}
.sum-lines .sl-val { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; justify-self: end; white-space: nowrap; }
.sum-lines .sl-val--earned { color: var(--gold); }
.sum-lines .sl-lbl--full { grid-column: 1 / -1; }
@media (max-width: 420px) {
  .sum-lines { column-gap: 0.5em; font-size: 0.76em; }
  .sum-lines .sl-lbl { grid-column: 1 / -1; }
}

/* ui-592 AK3: Shop-Karte (Langweg) legt die beiden Sektionen NEBENEINANDER
   statt gestapelt (Karte ist auf Desktop konstant 1108px breit, aus der
   Setup-Popin-Embed-Shell #popin{width:min(1180px,94vw)} in desktop.css —
   NICHT aus .site-main, das dort strukturell nie greift, s. ui-592-Fix-Runde
   + web/spec/monetarisierung.md) — Popin (.sp-buy-modal, 620px) bleibt
   gestapelt und nutzt .shop-cols nicht. */
.shop-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1.6em; align-items: start; }
.shop-cols .buy-section { margin-top: 0; }
.shop-cols .buy-section--premium { border-top: 0; padding-top: 0; border-left: 1px solid var(--line); padding-left: 1.4em; margin-top: 0; }
.shop-cols .buy-section h4 { margin-top: 0.2em; }
@media (max-width: 700px) {
  .shop-cols { grid-template-columns: 1fr; }
  .shop-cols .buy-section--premium { border-left: 0; padding-left: 0; border-top: 1px solid var(--line); padding-top: 0.8em; margin-top: 1.1em; }
}

/* ui-563 AK1/AK2: Kurzweg-Popin-Zahlen werden beim Oeffnen frisch nachgeladen
   (Club .club-today-list + Saison .sum-lines/.sum-sub seit ui-592, vormals
   .sum-tiles--earned/--open). Waehrend des Fetches gedimmt (aria-busy) statt
   der alten Zahlen als vermeintlich aktuell; .kw-loading ist der Lade-
   Platzhalter darin. .kw-stale-hint ist SSR mit dem nativen hidden-Attribut
   ausgeblendet, app.js schaltet ihn nur bei einem Fetch-Fehler sichtbar
   ("Stand vom Seitenaufruf"). */
.club-today-list[aria-busy="true"], .sum-lines[aria-busy="true"], .sum-sub[aria-busy="true"] { opacity: 0.55; }
.kw-loading { color: var(--muted); font-size: 0.85em; padding: 0.3em 0; }
.kw-stale-hint { color: var(--queued); font-size: 0.72em; margin: 0.5em 0 0; }

/* web-291 AK1: gestapeltes Layout (Preis -> Zustimmungs-Block -> Kauf-
   Button) statt der vormaligen Preis-links/Button-rechts-Zeile — die
   Checkbox-Zeilen (components.css .checkout-consent) brauchen die volle
   Breite "unter dem Preis" (Design-Lock, Ticket-Wortlaut). Vormals nur ab
   600px Breakpoint gestapelt (s. Media-Query weiter unten, seither
   redundant aber unschaedlich belassen). */
.buy-cta { display: flex; flex-direction: column; align-items: stretch; gap: 0.6em; margin-top: 1.1em; }
.buy-price { font-size: 1.5em; font-weight: 900; color: var(--gold); }
.buy-price small { color: var(--muted); font-size: 0.72em; font-weight: 400; display: block; }
.buy-note { color: var(--muted); font-size: 0.7em; margin-top: 0.7em; line-height: 1.4; }
.buy-note a { color: var(--cyan); }

/* Gast-Gate (Kauf-Einstiege — Ticket-AK6 "Muster Prototyp"). Eigene Klasse
   statt Wiederverwendung von .fd-banner (anderer Kontext: Kauf statt
   Gratis-Draws-Kampagne). */
.guest-gate {
  margin-top: 1em; border: 1px solid var(--cyan); border-radius: 10px; padding: 0.7em 0.8em;
  background: rgba(56, 225, 255, 0.05); font-size: 0.85em; line-height: 1.45;
}
.guest-gate b { color: var(--cyan); }

@media (max-width: 600px) {
  .sp-head { flex-direction: column; align-items: stretch; }
  .sp-head-right { justify-content: space-between; }
  .sum-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.35em; }
  .sum-tile .st-num { font-size: 1.1em; }
  .buy-cta { flex-direction: column; align-items: stretch; }
  /* ui-648: festes 2x4-Kachel-Raster statt Zufalls-Umbruch (ui-646-Flex-Wrap);
     Zustand als Eck-Abzeichen statt Inline-Zeichen, sonst sprengt „⬢ +280" die Kachel. */
  .sp-ms-row {
    position: static; height: auto; display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px 5px; margin-top: 0.7em;
  }
  .sp-ms, .sp-ms:last-child {
    position: relative; left: auto; transform: none; text-align: center; min-width: 0;
    border: 1px solid var(--line-2); border-radius: 6px; background: var(--bg-2);
    padding: 4px 2px 3px; opacity: 1;
  }
  .sp-ms-n { display: block; }
  .sp-ms.done .sp-ms-chip::after, .sp-ms.locked .sp-ms-chip::before { content: none; }
  .sp-ms.done::after, .sp-ms.locked::after {
    position: absolute; top: -7px; right: -4px; width: 15px; height: 15px; border-radius: 50%;
    background: var(--bg-2); border: 1px solid var(--line-2); font-size: 9px; line-height: 13px; text-align: center;
  }
  .sp-ms.done::after { content: '✓'; color: var(--green); font-weight: 900; border-color: rgba(94, 255, 138, .55); }
  .sp-ms.locked::after { content: '🔒'; font-size: 8px; border-color: rgba(240, 179, 80, .55); }
  .sp-ms.done { border-color: rgba(56, 225, 255, .5); background: rgba(56, 225, 255, .07); }
  .sp-ms.prem { border-color: rgba(240, 179, 80, .4); background: rgba(240, 179, 80, .05); }
  .sp-ms.prem.done { border-color: var(--gold); background: rgba(240, 179, 80, .13); }
  /* Desktop setzt opacity:.7 auf .sp-ms.locked (gleiche Spezifität) — hier
     explizit auf 1 zurück, gedimmt wird nur Chip/Stufenzahl (Zeile danach). */
  .sp-ms.locked { border-style: dashed; opacity: 1; }
  .sp-ms.locked .sp-ms-chip, .sp-ms.locked .sp-ms-n { opacity: .6; }
  .sp-ms.next:not(.locked) { border-color: var(--text); box-shadow: 0 0 0 1px rgba(220, 230, 247, .22); }
  .sp-pg-next { display: block; font-size: 0.74em; color: var(--text-dim); margin-top: 0.55em; }
  .sp-pg-next b { color: var(--text); }
  .sp-pg-next-short { display: none; }
}

/* ui-648 Runde 2 (Fifty 19.09.): auf schmalen Handys (Bubble-Innenbreite auf
   der echten Seite ~231–260px, schmaler als die Harness-Annahme) bricht das
   volle Label um — Kurz-Label statt Umbruch. `display:none` (nicht nur
   visuell versteckt) je Gegenstueck, damit Screenreader nur EIN Label lesen. */
@media (max-width: 400px) {
  .sp-pg-next-long { display: none; }
  .sp-pg-next-short { display: inline; }
}

/* =====================================================================
   ui-556: Shop-Kaufsicht (Langweg) — views/shop-season-pass.js Standalone-
   Partial. `.sp-shop-*`-Namensraum (eigenständig, NICHT `.club-hero` o.ä. —
   Kollisionsrisiko mit einer künftigen Shop-CSS-Datei, Memory "Styleguide-
   Klassen-Kollision", gleicher Grund wie beim `.ach-scope-*`-Namensraum
   oben). Optik 1:1 Prototyp `.club-hero` (Gold-Rand-Variante, `?view=spass`).
   `.buy-*`/`.sum-*`/`.guest-gate`/`.prem-badge`/`.btn*` sind bereits oben in
   dieser Datei definiert (geteilt mit dem ◈-Saison-Bereich, Kurzweg/Langweg-
   Lock: gleiche Bausteine, andere Rahmung) — hier nur das Hero-Chrome.
   Falls die Shop-Rahmen-Seite (ui-557) eine EIGENE CSS-Datei verlinkt statt
   achievements.css, muss dieser Block (oder ein Import davon) dorthin
   mitwandern — s. ui-556-Ticket ## Notes.
   ===================================================================== */
.sp-shop-hero {
  background: linear-gradient(135deg, #0e1f33 0%, #0d1322 65%);
  border: 1px solid var(--gold);
  border-radius: 14px;
  padding: 1.2em 1.3em;
  box-shadow: 0 0 24px rgba(240, 179, 80, 0.12);
}
.sp-shop-title { margin: 0 0 0.3em; font-size: 1.35em; }
.sp-shop-title .sp-num { color: var(--cyan); }
.sp-shop-sub { color: var(--muted); font-size: 0.85em; margin-bottom: 0.9em; }
.sp-shop-sub a { color: var(--cyan); }
.sp-shop-cta-wrap { margin-top: 1em; }
.sp-shop-cta-row { display: flex; align-items: center; justify-content: space-between; gap: 0.8em; flex-wrap: wrap; }
/* web-291 AK1: NUR der Kauf-Zweig (Preis+Zustimmung+Button) stapelt sich —
   der Premium-Badge-Zweig (kein Kauf-CTA, s. renderShopSeasonPassCard())
   teilt sich zwar dieselbe Basis-Klasse, bleibt aber bewusst die
   horizontale Zeile (Modifier-Klasse statt die Basis-Regel umzustellen). */
.sp-shop-cta-row--buy { flex-direction: column; align-items: stretch; }

@media (max-width: 600px) {
  .sp-shop-cta-row { flex-direction: column; align-items: stretch; }
}

/* =====================================================================
   ui-616: Pre-Season-Teaser (`views/shop-season-pass.js
   renderShopSeasonTeaserHtml()`) — ersetzt VOR Saison 1 die `.retro-banner`-
   Zeile an derselben Stelle innerhalb `.sp-shop-hero` oben (unter dem Titel
   "Saison-Pass", über `.shop-cols`). Eigener `.sp-teaser-*`-Namensraum
   (bereits als `sp-` in css-namespaces.mjs registriert, kein Guard-Update
   nötig). `.sp-countdown` (Basisregel oben, `font-variant-numeric`/`color`/
   `font-weight`) bleibt UNVERÄNDERT — `.sp-teaser-count-val` liefert nur die
   Ergänzungen (Größe, Leuchten), beide Klassen sitzen gemeinsam auf
   demselben Element (Design-Lock DL-2). Einheiten in `em` (Datei-
   Konvention), 1:1 aus dem Prototyp übernommen (`.claude/sprint/prototypes/
   ui-616.html`/`ui-616-mobile.html`, identischer `<style>`-Block in beiden).
   ===================================================================== */
.sp-teaser {
  position: relative; overflow: hidden; margin-bottom: 1em;
  padding: 1.05em 1.15em 1em; border-radius: 12px; border: 1px solid var(--gold);
  background:
    radial-gradient(120% 90% at 100% 0%, rgba(240, 179, 80, .16), transparent 55%),
    linear-gradient(135deg, #241a08, #12100a);
}
.sp-teaser-head { display: flex; align-items: center; gap: .7em; flex-wrap: wrap; }
.sp-teaser-pill {
  display: inline-block; padding: .16em .6em; border-radius: 999px; font-size: .74em; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: #12100a;
  background: linear-gradient(90deg, var(--gold), #ffd27a);
}
.sp-teaser-title { margin: 0; font-size: 1.1em; color: var(--gold); font-weight: 600; }
.sp-teaser-title b { color: var(--text); }
.sp-teaser-count { display: flex; align-items: baseline; gap: .4em; margin: .25em 0 .85em; }
.sp-teaser-count-lbl { color: var(--text-dim); font-size: .9em; }
.sp-teaser-count-val {
  font-size: 2em; text-shadow: 0 0 16px rgba(240, 179, 80, .35);
}
.sp-teaser-preview { list-style: none; margin: 0 0 .9em; padding: 0; display: grid; gap: .5em; }
.sp-teaser-preview li { display: grid; grid-template-columns: 1.5em 1fr; gap: .5em; align-items: start; line-height: 1.4; }
.sp-teaser-ico { text-align: center; color: var(--gold); font-size: 1em; }
.sp-teaser-preview b { display: block; color: var(--text); font-size: .95em; }
.sp-teaser-preview span:not(.sp-teaser-ico) { color: var(--text-dim); font-size: .84em; }
.sp-teaser-preview em { color: var(--yellow); font-style: normal; }
.sp-teaser-open {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4em;
  padding-top: .8em; border-top: 1px solid rgba(240, 179, 80, .25); font-size: .86em;
}
.sp-teaser-open-lbl { color: var(--text-dim); }
.sp-teaser-chip {
  display: inline-block; padding: .16em .6em; border-radius: 999px; text-decoration: none;
  border: 1px solid rgba(56, 225, 255, .35); color: var(--cyan); background: rgba(56, 225, 255, .06);
}
.sp-teaser-chip:hover { border-color: var(--cyan); background: rgba(56, 225, 255, .12); }
@media (max-width: 480px) {
  .sp-teaser { padding: .9em .9em .85em; }
  .sp-teaser-count-val { font-size: 1.6em; }
  .sp-teaser-open-lbl { width: 100%; }
}

/* =====================================================================
   ui-558: ⬡ Hexa-Club — Shop-Kaufsicht (Langweg, `views/shop-club.js`) +
   geteiltes Kurzweg-Kauf-Popin (`renderClubBuyPopinHtml()`, ZWEI Instanzen:
   Taeglich-Kopf + Shop-Club-Sub-Tab) + Taeglich-Kopf-CTA/×2-Zeilen/Nudge
   (`renderDailyBonusTab()`, ct-daily-*-Erweiterungen oben in dieser Datei).
   Optik 1:1 Prototyp `.claude/sprint/prototypes/season-pass.html` `.club-*`
   (Cyan-Rand-Variante — Analogon zur Gold-Rand-Saison-Pass-Kaufkarte
   `.sp-shop-hero` oben; geteilte Bausteine `.buy-*`/`.guest-gate`/`.btn*`
   sind bereits oben in dieser Datei definiert, Kurzweg/Langweg-Lock: gleiche
   Bausteine, andere Rahmung). Eigener `.club-*`-Namensraum — von der
   `.sp-shop-*`-Sektion oben bewusst freigehalten (s. dortiger Kommentar
   "NICHT `.club-hero` o.ä. — Kollisionsrisiko mit einer kuenftigen
   Shop-CSS-Datei"). Einheiten in `em` (Datei-Konvention), nicht `rem` wie im
   Prototyp — 1:1 dieselben Werte, nur die Einheit an den Rest dieser Datei
   angeglichen.
   ===================================================================== */
.club-hero {
  background: linear-gradient(135deg, #0e1f33 0%, #0d1322 65%);
  border: 1px solid var(--cyan);
  border-radius: 14px;
  padding: 1.2em 1.3em;
  box-shadow: 0 0 24px rgba(56, 225, 255, 0.12);
}
.club-hero-title { margin: 0 0 0.3em; font-size: 1.35em; }
.club-sub { color: var(--muted); font-size: 0.85em; margin-bottom: 0.9em; }
.club-rule {
  border: 1px dashed var(--line-2);
  border-radius: 9px;
  padding: 0.55em 0.7em;
  font-size: 0.8em;
  color: var(--text-dim);
  margin-bottom: 1em;
}
.club-cta-row { display: flex; align-items: center; justify-content: space-between; gap: 0.8em; flex-wrap: wrap; }
.club-status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  background: rgba(56, 225, 255, 0.08);
  border: 1px solid var(--cyan);
  color: var(--cyan);
  border-radius: 99px;
  padding: 0.3em 0.8em;
  font-size: 0.85em;
  font-weight: 800;
}
/* abgelaufen: derselbe queued/orange Ton wie anderswo im Bestand fuer
   "wartet auf Aktion"-Zustaende (nicht rot/error — kein Fehlerzustand). */
.club-status-chip--expired { border-color: var(--queued); color: var(--queued); background: rgba(255, 138, 60, 0.07); }

@media (max-width: 600px) {
  .club-cta-row { flex-direction: column; align-items: stretch; }
}

/* ---------- Kauf-Popin (Kurzweg) — Backdrop nutzt den GETEILTEN
   components.css `.cs-modal-backdrop`/`.open`-Baustein (web-204), Muster
   `.sp-buy-modal` oben (keine lokale Neudefinition des Backdrops). ---------- */
.club-buy-modal {
  width: min(94vw, 620px);
  max-height: min(90svh, 900px);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 14px;
  padding: 1.1em 1.2em 1.3em;
  position: relative;
}
.club-buy-close {
  position: absolute; top: 0.6em; right: 0.8em; background: none; border: none;
  color: var(--muted); font-size: 1.1em; cursor: pointer; min-width: 44px; min-height: 44px;
}
.club-buy-title { margin: 0 0 0.2em; font-size: 1.15em; }
.club-buy-sub { color: var(--muted); font-size: 0.82em; margin-top: 0.2em; }

.club-today-list { display: flex; flex-direction: column; gap: 0.4em; }
.club-today-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6em;
  flex-wrap: wrap;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 0.5em 0.7em;
  font-size: 0.85em;
}
.club-today-name { flex: 1 1 auto; min-width: 10em; }
.club-today-reward { white-space: nowrap; display: flex; align-items: center; gap: 0.3em; }

/* Doppelt-Markierung — geteilt zwischen den Popin-Zeilen oben UND den
   Taeglich-×2-Zeilen (ct-daily-*-Kartenreihen, dailyRewardHtml() in
   views/dashboard.js). */
.x2-base { color: var(--muted); text-decoration: line-through; font-weight: 400; margin-right: 0.25em; }
.x2-mark { color: var(--cyan); font-weight: 800; font-size: 0.9em; }

/* Nudge-Diaet (Fifty-Lock, 2026-08-31): der EINE Zeilen-Hinweis, der im
   ganzen Taeglich-Tab ueberhaupt vorkommen darf (Analogon zum `.prem-mark`-
   Nudge der Saison-Karten oben in dieser Datei). */
.club-nudge {
  color: var(--cyan);
  font-size: 0.78em;
  font-weight: 700;
  white-space: nowrap;
  border-bottom: 1px dotted var(--cyan);
  opacity: 0.8;
  text-decoration: none;
}
.club-nudge:hover { opacity: 1; }

@media (hover: none), (pointer: coarse) {
  /* ui-396: Touch-Ziel ≥44px effektiv — `.club-nudge` ist ein reiner Inline-
     Text-Link ohne eigenes Padding/min-height (Muster `.nav-link`-Fix oben in
     dieser Datei, web-202). Klickflaeche waechst per Padding/min-height,
     NICHT die Schriftgroesse (Desktop bleibt unberuehrt, da dieser Block nur
     auf Touch-Geraeten greift). */
  .club-nudge { display: inline-flex; align-items: center; min-height: 44px; padding: 0 0.3em; }
}

/* =====================================================================
   ui-579: Monats-Paket-Kachel (.club-monthly/.cm-*) + Konto-Gutschrift-Zeile
   (.club-credit/.cc-*) — Shop-Club-Karte (views/shop-club.js), direkt unter
   .club-hero oben (s. dortige EINHÄNG-Reihenfolge). Design-Quelle:
   `.claude/sprint/prototypes/ui-578-club-monatspaket.html` Iteration 2 —
   Einheiten in em (Datei-Konvention wie .club-hero oben), Werte = Prototyp-px
   ÷ 15 (Companion-Root-Basis, s. `companion-rem-is-15px`-Memory), gerundet;
   border-radius bleibt px (Datei-Konvention, s. .club-buy-modal/.club-buy-
   close oben). `.cm-claim` traegt zusaetzlich die geteilte `.btn`-Klasse
   (Radius/Padding/Font-Size/:disabled kommen von dort, Muster .btn-secure
   direkt oberhalb — nur Farb-/Hover-Abweichung wird hier ueberschrieben).
   ===================================================================== */
.club-monthly {
  border: 1px solid var(--gold);
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(240, 179, 80, 0.10), rgba(240, 179, 80, 0.03));
  padding: 0.8em 0.93em;
  margin: 0.93em 0;
}
/* locked (Nicht-Mitglied bzw. kein offenes Paket, Ticket-AK1 "gedimmt, Border neutral") */
.club-monthly[data-monthly-state="locked"] {
  border-color: var(--border);
  background: rgba(255, 255, 255, 0.02);
}
.cm-head { display: flex; align-items: center; justify-content: space-between; gap: 0.67em; }
.cm-title { font-weight: 700; color: var(--text); font-size: 0.93em; }
.cm-chip {
  font-size: 0.73em;
  color: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 999px;
  padding: 0.13em 0.53em;
}
.club-monthly[data-monthly-state="locked"] .cm-chip { color: var(--muted); border-color: var(--border); }
.cm-body { margin-top: 0.4em; }
.cm-amount { font-size: 1.33em; color: var(--text); }
.cm-amount b { color: var(--gold); font-size: 1.73em; }
.cm-sub { color: var(--muted); font-size: 0.77em; margin-top: 0.13em; }
.cm-cta-row { display: flex; align-items: center; gap: 0.8em; margin-top: 0.67em; flex-wrap: wrap; }
.cm-claim { background: rgba(56, 225, 255, 0.14); border-color: var(--cyan); color: var(--cyan); }
.cm-claim:hover:not(:disabled) { background: rgba(56, 225, 255, 0.22); }
.cm-note { color: var(--muted); font-size: 0.77em; }
.cm-claimed { color: var(--green); font-weight: 700; font-size: 0.87em; }
/* ui-600: .cc-count svg mit auf .club-credit gescoped (s. Kommentar an
   .club-credit unten) — dieselbe Klassen-Kollisions-Vorsicht wie der Rest
   der .cc-*-Familie, obwohl .cc-count svg selbst nie ausserhalb von
   .club-credit vorkam (kein aktiver Bug, nur Konsistenz). */
.cm-draw-ic svg, .cm-claim svg, .club-credit .cc-count svg { vertical-align: -2px; }

/* Konto-Gutschrift-Zeile (normales Draw-Konto, Sockel-Inventar). ui-600
   (PTR-Fund Fifty 2026-09-05): die fuenf .cc-*-Regeln (+ .cc-count b/
   .cc-link:hover) waren BARE — derselbe Klassenname wie im Checkout-
   Zustimmungs-Block (views/checkout-consent.js, .checkout-consent .cc-row
   in components.css). Weil views/layout.js ALLE pages/*.css auf JEDER
   Companion-Seite laedt, leakte das bare .cc-row { flex-wrap: wrap; } (kein
   Property-Konflikt noetig — .checkout-consent .cc-row setzt gar kein
   eigenes flex-wrap) in den Checkout-Block: die erste Zustimmungs-Checkbox
   brach in eine eigene Zeile um. Fix: alle .cc-*-Regeln unter .club-credit
   gescoped (s. web/spec/frontend.md § "Geteilte Klassennamen nur unter
   Seiten-Parent" fuer die volle Herleitung + web/test/client/
   ui-600-css-class-scope.test.js als struktureller Guard). Verhalten fuer
   die Club-Gutschrift-Zeile selbst unveraendert (nur Scope, keine
   Werte-Aenderung). */
.club-credit {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.8em 0.93em;
  margin: 0.93em 0;
  background: rgba(56, 225, 255, 0.05);
}
.club-credit .cc-row { display: flex; align-items: center; gap: 0.93em; flex-wrap: wrap; }
.club-credit .cc-label { color: var(--muted); font-size: 0.83em; }
.club-credit .cc-count { color: var(--text); font-size: 1em; display: inline-flex; align-items: center; gap: 0.4em; }
.club-credit .cc-count b { color: var(--cyan); font-size: 1.33em; }
.club-credit .cc-note { color: var(--muted); font-size: 0.77em; }
.club-credit .cc-link {
  margin-left: auto;
  color: var(--cyan);
  font-size: 0.83em;
  text-decoration: none;
  border: 1px solid var(--cyan);
  border-radius: 8px;
  padding: 0.33em 0.67em;
}
.club-credit .cc-link:hover { text-decoration: underline; }

/* ===== ui-621: Juicy Claim-Sequenz + Kontostand-Chips =====
   Prototyp `.claude/sprint/prototypes/ui-621.html` Spalte V2 ist die Referenz
   (Timing/Easing/Klassen). Reihenfolge unten folgt DL-1: Press -> Reward-Flug
   -> Pille (bestehendes .ltp-anim-complete/_ltRunConveyor, s. app.js -- KEINE
   eigene dritte Pillen-Animation hier) -> Balken-Blitz -> Badge-Flip -> Karte
   Glow -> Chip-Bump/Count-up. FLIP-Absinken (DL-2, .achievement-card--lift)
   ist reine Transform/box-shadow-Optik, keine Layout-Eigenschaft -- kollidiert
   nicht mit der bestehenden `.achievement-card { transition: border-color }`.
   Reduced Motion (DL-5, Pflicht-Lock): eigener @media-Block unten schaltet
   alle Keyframes/Transitions ab -- app.js faehrt den reduzierten Pfad
   zusaetzlich selbst (kein WAAPI-/setTimeout-Aufruf), dieser Block ist
   Defense-in-Depth (Muster .ltp-anim-complete oben in dieser Datei). */

/* DL-1 (0ms): Button-Press. Eigene Klasse statt eines Transition-Overrides an
   .achievement-claim-btn selbst (Zeile ~414 oben) -- die bestehende Regel dort
   bleibt unangetastet (kein Drift-Risiko fuer web-023-Bestandstests), diese
   Regel steht spaeter im Quelltext und gewinnt bei gleicher Spezifitaet 0,1,0
   per Source-Order. */
.achievement-claim-btn--press { transform: scale(.94); }

/* DL-3: Kontostand-Chips im Erfolge-Kopf (SSR views/dashboard.js
   renderAchBalanceChips()) -- rechtsbuendig ueber den Scope-Pillen.
   ui-641: Markup + Basis-CSS (.ach-balance/.ach-balance-chip*) sind seit
   diesem Ticket der geteilte Baustein renderBalanceChip() in
   components.css (zweiter Konsument: Hexa-Budget-Chip im Tower-Tab) --
   hier bleibt nur noch die reduced-motion-Abschaltung der Bump-Animation
   unten (Teil des gemeinsamen Claim-Sequenz-@media-Blocks dieser Datei). */

/* DL-1 (120ms, je Glyph +80ms versetzt): Reward-Glyphen fliegen als
   position:fixed-Klone (app.js flyRewardGlyph(), Web Animations API) vom
   Reward-Hint zum passenden Kontostand-Chip. Der Klon uebernimmt die
   .glyph-tip.glyph-tip--<waehrung>-Klasse der Quelle 1:1 (SVG-Icon + Farbe
   bleiben unveraendert, currentColor via components.css) -- ach-fly setzt
   NUR die fuer den Flug noetigen Zusatz-Properties. */
.ach-fly {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  pointer-events: none;
  will-change: transform, opacity;
}

/* DL-1 (350ms, 600ms): Balken-Blitz. */
.achievement-progress-bar-fill--flash { animation: achBarFlash .6s ease-out; }
@keyframes achBarFlash {
  0%   { filter: brightness(1); }
  30%  { filter: brightness(1.9); }
  100% { filter: brightness(1); }
}

/* DL-1 (450ms): Badge-Flip (rotateX, 180ms raus / 240ms rein). Basis-Regel
   (backface-visibility/transform-origin) gilt permanent -- kein Layout-
   Sprung, wenn keine der beiden Modifier-Klassen aktiv ist. */
.achievement-tier-badge {
  backface-visibility: hidden;
  transform-origin: 50% 50%;
}
.achievement-tier-badge--flip-out {
  transition: transform .18s ease-in, opacity .18s ease-in;
  transform: rotateX(90deg);
  opacity: .15;
}
.achievement-tier-badge--flip-in {
  transition: transform .24s cubic-bezier(.2, 1.4, .4, 1), opacity .24s ease-out;
  transform: rotateX(0deg);
  opacity: 1;
}

/* DL-1 (500ms, 900ms): Karte wechselt Zustand + kurzer Erfolgs-Glow. */
.achievement-card--claim-glow { animation: achClaimGlow .9s ease-out; }
@keyframes achClaimGlow {
  0%   { box-shadow: 0 0 0 0 rgba(94, 255, 138, 0); }
  25%  { box-shadow: 0 0 0 3px rgba(94, 255, 138, .55), 0 0 28px rgba(94, 255, 138, .35); }
  100% { box-shadow: 0 0 0 0 rgba(94, 255, 138, 0); }
}

/* DL-2 (ab 1.000ms): Karte hebt beim Absinken auf die Server-Reihenfolge kurz
   ab (Schatten + z-index ueber den Nachbarn) -- reines Transform/box-shadow-
   Overlay waehrend des FLIP-Transforms (app.js flipContainerToServerOrder()),
   kein Hoehen-Reflow (DL-6). */
.achievement-card--lift {
  position: relative;
  z-index: 5;
  box-shadow: 0 14px 30px rgba(0, 0, 0, .5), 0 0 0 1px rgba(94, 255, 138, .35);
}

/* DL-5 (Pflicht-Lock): kein Flug, keine Keyframes, Badge nur Opacity-Fade,
   Chip springt (keine Bump-Animation). app.js faehrt den reduzierten Pfad
   zusaetzlich selbst (kein WAAPI-Aufruf, kein FLIP-Rect-Sampling) -- dieser
   Block ist Defense-in-Depth, falls eine Klasse trotzdem gesetzt wird. */
@media (prefers-reduced-motion: reduce) {
  .achievement-claim-btn--press { transform: none; }
  .ach-fly { display: none; }
  /* ui-641: unter .ach-balance gescopt (statt der bare Klasse) -- vermeidet
     eine CSS-Namensraum-Duplikat-Meldung gegen die Basis-Regel in
     components.css (dieselben Elemente: --bump traegt NUR ein Chip
     innerhalb von .ach-balance, s. app.js bumpBalanceChip()-Scope-Kommentar
     in balance-chip.js -- keine Verhaltensaenderung). */
  .ach-balance .ach-balance-chip--bump { animation: none; }
  .achievement-progress-bar-fill--flash { animation: none; }
  .achievement-tier-badge--flip-out,
  .achievement-tier-badge--flip-in {
    transition: opacity .2s ease;
    transform: none;
  }
  .achievement-card--claim-glow { animation: none; }
}
