/* animations.css - Bewegung am Tisch (Heft #117).

   Verlinkt in internal/ui/views/layout.html nach app.css, theme-*.css und
   den mobile-*.css-Blaettern; sie darf Details der bisherigen Blaetter
   ueberschreiben, ohne ihre Regeln anzufassen. Grundsaetze aus dem Heft:

   - Bewegung ist Zusatz, nie Voraussetzung: ohne CSS und ohne JavaScript
     bleibt alles lesbar und bedienbar.
   - Keine Bewegung ueber 250 ms, kein Drehen, kein Balken ueber der Seite.
     Der einzige dauerhafte Takt ist das leise Atmen des aktiven Sitzes
     (und sein Pendant, die zwei Punkte am denkenden Bot-Sitz).
   - Keine Bewegung beim ersten echten Seitenaufruf: das Merkmal fuer
     "gerade eingetauscht" (data-pushed) setzt nur der SSE-Pfad in
     internal/web/pages.go; Animationen fuer Geaendertes haengen daran.
   - "Neue Karte auf dem Tisch" konnte #117 nicht leisten: der Renderpfad
     weiss aus dem Snapshot nicht ehrlich, welche Tischstelle zuletzt
     geaendert wurde (internal/game/actions.go: die Abwehr sticht die ERSTE
     offene Stelle), also waere jede Markierung an einer festen Stelle eine
     falsche Bewegung. Das holt Heft #122 nach - ohne neuen Rundenzustand,
     durch den Vergleich des Clients: app.js haelt die Kartencodes des
     Vorher-Fragments gegen die des Nachher-Fragments (data-slot-card an
     jedem Slot-Kartenelement) und setzt allein der Differenz .played. Der
     Takt steht unten, nicht am Merkmal des Servers.
   - Am Ende schaltet @media (prefers-reduced-motion: reduce) aus. */

/* -- 2: Die eigene Hand laeuft gestaffelt ein -----------------------------

   Der Handindex sitzt als data-deal an jeder Handkarte (der Renderpfad
   setzt ihn in table.html). Die Staffelung laeuft nur im eingetauschten
   Fragment ([data-pushed]) und nur, wenn der Snapshot einen Neuanfang
   meldet: data-stage="lead" steht beim Austeilen wie beim Auffuellen nach
   einer Runde. Die Verzoegerung zaehlt 40 ms je Position bis zur siebten
   Karte, ab der achten kommen alle gemeinsam - mit 0.28 s Verzoegerung
   plus 0.12 s Bewegung bleibt die Hand unter einer halben Sekunde. */
.game[data-pushed][data-stage="lead"] .hand .card-btn {
  animation: deal-in 0.12s ease-out both;
}
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(2) { animation-delay: 0.04s; }
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(3) { animation-delay: 0.08s; }
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(4) { animation-delay: 0.12s; }
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(5) { animation-delay: 0.16s; }
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(6) { animation-delay: 0.20s; }
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(7) { animation-delay: 0.24s; }
.game[data-pushed][data-stage="lead"] .hand .card-btn:nth-child(n+8) { animation-delay: 0.28s; }
@keyframes deal-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

/* -- 3: Der aktive Sitz atmet leise ----------------------------------------

   app.css grundiert den Sitz schon golden (Rand + box-shadow); hier atmet
   dieser Schein im ruhigen Zwei-Sekunden-Takt statt im bisherigen 1.6-s-
   Puls. "Hinweise: Aus" (body.no-hints) will das goldige Highlight nicht -
   dann atmet nichts; dass jemand am Zug ist, sagt weiter die Statuszeile. */
.opponent.has-turn {
  animation: seat-breathe 2s ease-in-out infinite;
}
@keyframes seat-breathe {
  0%, 100% { box-shadow: 0 0 0 2px var(--gold), 0 0 10px rgba(242, 193, 78, 0.35); }
  50%      { box-shadow: 0 0 0 2px var(--gold), 0 0 20px rgba(242, 193, 78, 0.60); }
}
body.no-hints .opponent.has-turn { animation: none; }

/* -- 10: Ein Bot-Sitz, dessen Denkzeit laeuft, fuehrt zwei leise Punkte ---

   Der Sitz traegt .has-turn und die Bot-Rolle schon im Markup; die Punkte
   sind sein Denkzeichen im selben ruhigen Takt wie das Atmen. */
.opponent.has-turn .role.bot::after {
  content: " ··";
  animation: bot-think 2s ease-in-out infinite;
}
@keyframes bot-think {
  0%, 100% { opacity: 0.2; }
  50%      { opacity: 1; }
}

/* -- 4: Bei einer Absage rueckt die eigene Hand ---------------------------

   app.js setzt .rejected an .hand, wenn der Dienst mit 4xx/5xx antwortet
   (htmx:responseError - derselbe Moment wie der Grund-Toast aus #113), und
   nimmt die Klasse nach rund 260 ms wieder weg. */
.hand.rejected {
  animation: hand-jerk 0.2s ease-in-out;
}
@keyframes hand-jerk {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-5px); }
  50%      { transform: translateX(4px); }
  75%      { transform: translateX(-2px); }
}

/* -- 5: Der Countdown-Chip warnt, bevor er pulst ---------------------------

   app.js setzt die Klassen im vorhandenen data-deadline-Chip (#87):
   .warn ab 10 s (Rand und Text warm), .urgent ab 5 s (Puls), .expired beim
   Ablauf. Das Zaehlen selbst bleibt unangetastet. */
.turn-timer.warn {
  color: #e8b04a;
  border-color: #e8b04a;
}
.turn-timer.urgent {
  animation: chip-pulse 1s ease-in-out infinite;
}
@keyframes chip-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(214, 68, 60, 0); }
  50%      { box-shadow: 0 0 0 3px rgba(214, 68, 60, 0.35); }
}
.turn-timer.expired {
  animation: chip-pop 0.2s ease-out;
}
@keyframes chip-pop {
  from { transform: scale(1.15); }
  to   { transform: none; }
}

/* -- 6: Das Ergebnisbanner gleitet ein, die Wertung folgt ------------------

   Das Fragment kennt seinen Zustand: das Banner (und mit ihm die Wertungs-
   zeilen) erscheint nur, wenn die Partie einen Endstand fuehrt. Die
   Animation laeuft wieder nur im eingetauschten Fragment ([data-pushed]),
   damit ein Seitenaufruf auf einem fertigen Blatt still dasteht. */
.game[data-pushed] .result-banner {
  animation: banner-in 0.2s ease-out;
}
@keyframes banner-in {
  from { opacity: 0; transform: translateY(-16px); }
  to   { opacity: 1; transform: none; }
}
.game[data-pushed] .result-banner .score-table tbody tr {
  animation: row-in 0.15s ease-out both;
}
.game[data-pushed] .result-banner .score-table tbody tr:nth-child(2) { animation-delay: 0.05s; }
.game[data-pushed] .result-banner .score-table tbody tr:nth-child(3) { animation-delay: 0.1s; }
.game[data-pushed] .result-banner .score-table tbody tr:nth-child(4) { animation-delay: 0.15s; }
.game[data-pushed] .result-banner .score-table tbody tr:nth-child(n+5) { animation-delay: 0.2s; }
@keyframes row-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* -- 12: Eine frische Karte auf dem Tisch legt sich hin (#122) ------------

   Nicht der Server markiert die frische Stelle (er kennt sie nicht ehrlich,
   siehe oben), der Client vergleicht beim Eintauschen die Kartencodes des
   alten gegen die des neuen Tischfragments und gibt allein der Differenz
   .played - app.js tragt die Klasse fuer gut 600 ms, die Bewegung selbst
   sind die 180 ms hier.

   Der Takt kommt von oben herein, ueberschiesst beim Landen einen Hauch und
   findet zur Ruhe; er laeuft einmal und nicht dauerhaft. Legt ein Sitz
   mehrere Karten auf einmal (Zerlegen, Zugaben), staffelt der Versatz von
   50 ms je weitere neuen Karte, ab der funften mit Kappe - so wie die Hand
   der Austeilung (Takt 2). Ohne .played steht der Tisch still: jeder andere
   Zustand und jeder erste echte Seitenaufruf bleibt unbewegt. */
.slots .card.played {
  animation: card-land 0.18s ease-out both;
}
.slots .card.played.step-2 { animation-delay: 0.05s; }
.slots .card.played.step-3 { animation-delay: 0.1s; }
.slots .card.played.step-4 { animation-delay: 0.15s; }
.slots .card.played.step-5 { animation-delay: 0.2s; }
@keyframes card-land {
  from { opacity: 0; transform: translateY(-14px) scale(1.06); }
  60%  { opacity: 1; transform: translateY(3px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

/* -- 8: Laeuft eine Anfrage, "denkt" der betroffene Bereich ----------------

   htmx setzt .htmx-request selbst an das ausloesende Element - kein Skript
   noetig. Kein Drehen, kein Balken: ein ruhiges Abdunkeln, das mit der
   Antwort zurueckgeht. */
.htmx-request {
  opacity: 0.55;
  transition: opacity 0.15s ease;
}

/* -- 9: Druckgefuehl der Knoepfe und ein Fokus, der weich nachzieht -------

   app.css laesst .btn:active bereits minimal nachgeben (0.08 s); das Heft
   nennt 60 ms. Hier sitzt die Ueberschreibung fuer alle Knoepfe, das
   Druckgefuehl der kleinen Schalter und ein Fokus, der nachzieht statt
   aufzuspringen. Die Markierung der eigenen Auswahl (.selected an
   .card-btn) bleibt unberuehrt - sie ist eine Zustandsaenderung ohne
   Bewegung. */
.btn, .hints-toggle, .theme-toggle, .lang-toggle {
  outline: 2px solid rgba(242, 193, 78, 0);
  outline-offset: 0;
  transition: transform 0.06s ease, filter 0.15s ease,
              outline-color 0.12s ease, outline-offset 0.12s ease;
}
.btn:active, .hints-toggle:active, .theme-toggle:active, .lang-toggle:active {
  transform: scale(0.97);
}
.btn:focus-visible, .hints-toggle:focus-visible,
.theme-toggle:focus-visible, .lang-toggle:focus-visible {
  outline-color: var(--gold);
  outline-offset: 2px;
}

/* -- Reduced Motion: ohne jede Bewegung -----------------------------------

   Vollstaendiger Ausschalter: jede Bewegung dieser Datei haelt hier an,
   Uebergange inklusive. Die Klassen bleiben sitzen (der Toast verschwindet
   per Skript, der Countdown zaehlt weiter) - Information geht keine
   verloren, es bewegt sich nur nichts mehr. */
@media (prefers-reduced-motion: reduce) {
  .game[data-pushed][data-stage="lead"] .hand .card-btn,
  .game[data-pushed] .result-banner,
  .game[data-pushed] .result-banner .score-table tbody tr,
  .opponent.has-turn,
  .opponent.has-turn .role.bot::after,
  .hand.rejected,
  .slots .card.played,
  .turn-timer.urgent,
  .turn-timer.expired,
  .htmx-request,
  .btn, .hints-toggle, .theme-toggle, .lang-toggle {
    animation: none;
    transition: none;
  }
}
