/* Career Intelligence / BewerberAssistent — shared/motion.css
   ================================================================
   Bewegung und Zustände für die ganze Anwendung. Präfix ms-.

   WARUM EINE EIGENE DATEI

   Die Dauern und die Kurve stehen im Unterbau (shared/design-tokens.css)
   und werden in jede Seite kopiert — Marken müssen dort stehen, wo eine
   Seite auch ohne Server aufgeht. Die REGELN dagegen sind überall
   dieselben, und dreiundzwanzig Kopien einer Regel sind nach dem ersten
   Umbau dreiundzwanzig verschiedene Regeln. Also eine Datei, per
   relativem <link> geladen, genau wie shared/werkbank.css.

   Sie steht neben werkbank.css und nicht darin, weil acht Seiten den
   Rahmen gar nicht tragen: die Startseite, die Unternehmensseite, das
   Anmelden, das Konto, die drei Editoren, der gesetzte Lebenslauf.
   Bewegung brauchen sie trotzdem.

   Reihenfolge im <head>: motion.css NACH werkbank.css, VOR dem <style>
   der Seite. Die Seite gewinnt damit bei Gleichstand — Absicht, siehe
   den Abschnitt zu den Knöpfen.

   WAS HIER NICHT STEHT

   Die Sperre unter prefers-reduced-motion. Sie steht im Unterbau und
   liegt damit auf jeder Seite:

     @media (prefers-reduced-motion: reduce) {
       * { animation-duration: .001ms !important;
           transition-duration: .001ms !important; }
     }

   Unten steht nur, was diese Regel NICHT erreicht: eine endlose
   Animation läuft mit .001ms nicht ruhig, sondern tausendmal je
   Millisekunde. Die drei Dauerläufer bekommen deshalb Abschnitt 10 —
   zwei stehen dort still, einer wird langsam.

   DIE REGEL ÜBER ALLEN REGELN

   Bewegung trägt nie eine Information allein. Wer sie abschaltet, muss
   jeden Zustand weiterhin sehen und jeden Weg zu Ende gehen können. Ein
   Zustand, den man nur an seiner Animation erkennt, ist ein Fehler. */

/* ================================================================
   1 — Die vier Bewegungen
   ================================================================ */

/* Der Ring am Ladeknopf. Das einzige linear in diesem Haus: Ein
   Fortschritt, der seine Länge nicht kennt, darf nicht beschleunigen —
   sonst behauptet er einen Verlauf, den es nicht gibt. */
@keyframes ms-dreh { to { transform: rotate(360deg); } }

/* "Hier passiert gerade etwas." Für die Marke einer Zeile, die noch
   arbeitet — und NUR dafür: Am Ende muss sie stehen bleiben, sonst
   liest sich fertig wie hängengeblieben. */
@keyframes ms-puls { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }

/* Der Schimmer über einem Skelett. Läuft in Leserichtung von links
   nach rechts und ist blau getönt, nicht grau: Er gehört zu dieser
   Anwendung, nicht zum Browser. */
@keyframes ms-schimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(220%); }
}

/* Der Auftritt: Dialog, Palette, Blatt. Kommt von unten und leicht aus
   der Tiefe — das ist die Richtung, aus der Aufgelegtes kommt. */
@keyframes ms-auftritt {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

/* Der Grund darunter. */
@keyframes ms-einblenden { from { opacity: 0; } to { opacity: 1; } }

/* ================================================================
   2 — Knöpfe: der Druck ist die Quittung
   ================================================================

   Ein Knopf hebt sich beim Überfahren um 1 px und sinkt beim Drücken
   auf +1 px mit 0.985 zurück, während der Schatten zusammenfällt. Er
   liest sich damit als in die Seite gedrückt. Eine reine Farbänderung
   tut das nicht: Sie sagt "ich habe dich bemerkt", nicht "ich habe
   deinen Druck genommen".

   WARUM ÜBER DIE ENDUNG UND NICHT ÜBER EINE NEUE KLASSE

   Dieselbe Überlegung wie bei der Schriftskala in werkbank.css: 184
   Knöpfe auf dreiundzwanzig Seiten heißen -btn, weil die Kürzel-Regel
   aus CLAUDE.md längst eine gemeinsame Sprache erzeugt hat. Die Endung
   IST die Rolle. Sie umzuschreiben hieße, dreiundzwanzig Dateien
   anzufassen und in zweiundzwanzig etwas zu übersehen.

   Beide Attributformen, und das ist kein Doppelt-genäht: [class$="-btn"]
   prüft das ganze class-ATTRIBUT auf sein Ende. Bei class="pr-btn
   pr-btn-primary" endet es auf "-primary" und die Regel griffe nicht.
   [class*="-btn "] fängt jeden Fall ab, in dem eine Zustandsklasse
   dahintersteht.

   WARUM DER SELEKTOR DOPPELT STEHT

   [class$="-btn"]:hover wiegt so viel wie eine Klasse plus eine
   Pseudoklasse; .we-btn:hover wiegt zwei Klassen und gewönne. Derselbe
   Selektor zweimal hintereinander zählt doppelt und entscheidet die
   Sache — ohne ein einziges !important. Der Trick steht so schon in
   werkbank.css (.wb-inhalt.wb-inhalt h1) und ist dort erklärt.

   Nur transform und box-shadow werden hier gesetzt. Farbe, Rand und
   Grund bleiben Sache der Seite: Vier Knopfsorten mit vier Paletten
   gehören dorthin, wo man sieht, wozu sie da sind. */

/* Auch der Grundzustand steht doppelt, und aus demselben Grund: Neun
   Seiten schrieben ihren Knöpfen eine eigene, kürzere Übergangsliste
   ("nur transform und opacity"). Bei Gleichstand gewinnt ihr <style>,
   und dann sprang der Schatten, während sich der Knopf hob — die halbe
   Quittung. Die Liste hier ist die Obermenge und enthält opacity, weil
   mehrere Seiten damit ihren gesperrten Knopf abblenden. */
[class$="-btn"][class$="-btn"],
[class*="-btn "][class*="-btn "],
[class$="-knopf"][class$="-knopf"],
[class*="-knopf "][class*="-knopf "] {
  transition: transform var(--ms-schnell) var(--ms-kurve),
              box-shadow var(--ms-schnell) var(--ms-kurve),
              background-color var(--ms-schnell) var(--ms-kurve),
              border-color var(--ms-schnell) var(--ms-kurve),
              color var(--ms-schnell) var(--ms-kurve),
              opacity var(--ms-schnell) var(--ms-kurve);
}

[class$="-btn"][class$="-btn"]:hover:not(:disabled):not([aria-disabled="true"]),
[class*="-btn "][class*="-btn "]:hover:not(:disabled):not([aria-disabled="true"]),
[class$="-knopf"][class$="-knopf"]:hover:not(:disabled):not([aria-disabled="true"]),
[class*="-knopf "][class*="-knopf "]:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(-1px);
}

[class$="-btn"][class$="-btn"]:active:not(:disabled):not([aria-disabled="true"]),
[class*="-btn "][class*="-btn "]:active:not(:disabled):not([aria-disabled="true"]),
[class$="-knopf"][class$="-knopf"]:active:not(:disabled):not([aria-disabled="true"]),
[class*="-knopf "][class*="-knopf "]:active:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(1px) scale(.985);
  box-shadow: none;
}

/* Ein gesperrter Knopf hebt nicht ab. Er ist keine Fläche, die etwas
   annimmt. */
[class$="-btn"]:disabled,
[class*="-btn "]:disabled,
[class$="-knopf"]:disabled,
[class*="-knopf "]:disabled { transform: none; }

/* ================================================================
   3 — Der Ladeknopf: gesperrt, nicht verschwunden
   ================================================================

   Die Beschriftung bleibt STEHEN und die Breite bleibt, sonst springt
   die ganze Zeile. Der Ring kommt daneben, der Knopf wird gesperrt.
   Ein Knopf, der beim Klicken verschwindet, nimmt die Rückmeldung mit:
   Man weiß nicht mehr, was man angestoßen hat.

   Angestoßen wird das aus shared/motion.js — BAMotion.ladeKnopf(). */

.ms-ring {
  width: 13px; height: 13px;
  flex: none;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: .55;
  animation: ms-dreh .7s linear infinite;
}

/* Auf dunklem Grund ist currentColor weiß und der Ring als Ganzes zu
   blass. Dort trägt der Rand die Durchsichtigkeit, nicht das Element —
   sonst blasst auch die laufende Spitze ab und der Ring steht still. */
.ms-ring[data-auf-dunkel] {
  opacity: 1;
  border-color: rgba(255, 255, 255, .28);
  border-top-color: var(--on-block);
}

[data-ms-laedt] {
  opacity: .72;
  cursor: progress;
}

/* ================================================================
   4 — Karten: 3 px heben, nicht skalieren
   ================================================================

   Niemals eine Karte skalieren, in der Text steht: Subpixel-Skalierung
   macht Schrift unscharf, und eine unscharfe Karte ist teurer als eine
   ruhige. 3 px, nicht 6 — das Heben soll am Rand des Blickfelds
   ankommen, nicht sich melden.

   NUR was wirklich WOANDERSHIN führt. Die meisten -karte und -card in
   diesem Haus sind <div> mit Inhalt: eine Zusammenfassung, ein Hinweis,
   ein Ergebnisblock. Die heben zu lassen behauptet eine Bedienbarkeit,
   die es nicht gibt — genau die Sorte Bewegung, die auf der
   Verbotsliste steht.

   Und auch nicht jedes <button>, das wie eine Karte aussieht. Die
   Karten des Boards sind Knöpfe, liegen dicht in einer Spalte und
   werden gezogen: Würden sie beim Überfahren aufspringen, wogte das
   halbe Board unter dem Zeiger. Sie haben ihre eigene, ruhige
   Rückmeldung (Rand und Schatten) und behalten sie.

   Bleibt: das <a>, das die Seite verlässt — dort ist das Abheben die
   Ankündigung des Sprungs — und alles, was ausdrücklich data-ms-heben
   trägt. Ein Attribut, das man setzen muss, ist hier die richtige
   Sperre: Wer eine Karte hebt, hat sich das überlegt. */

a[class$="-karte"], a[class*="-karte "],
a[class$="-card"],  a[class*="-card "],
[data-ms-heben] {
  transition: transform var(--ms-zustand) var(--ms-kurve),
              box-shadow var(--ms-zustand) var(--ms-kurve),
              border-color var(--ms-zustand) var(--ms-kurve);
}

a[class$="-karte"]:hover, a[class*="-karte "]:hover,
a[class$="-card"]:hover,  a[class*="-card "]:hover,
[data-ms-heben]:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 28px rgba(20, 20, 30, .09);
  border-color: var(--blue-border);
}

/* ================================================================
   5 — Auftritt und Schleier
   ================================================================

   Drei Dinge legen sich über die Seite, und alle drei kommen aus der
   Richtung, in die sie gehören: die Leiste von links (werkbank.css),
   die Palette von oben, der Dialog aus der Mitte nach vorn.

   Der Schleier blendet über --ms-zustand ein, der Kasten tritt über
   --ms-auftritt auf. Der Kasten ist etwas langsamer als sein Grund:
   Erst wird die Seite zurückgestellt, dann kommt das Neue. Andersherum
   sähe es aus, als hätte der Dialog auf den Schleier gewartet.

   BEIDE ALS ANIMATION, NICHT ALS ÜBERGANG — und das ist der Punkt, an
   dem eine Überlagerung sonst unsichtbar bleibt.

   Ein Übergang braucht einen Anfangszustand, der schon einen Anstrich
   erlebt hat: opacity 0 setzen, ein Bild abwarten, opacity 1 setzen.
   Das Warten hängt an einem requestAnimationFrame IM SKRIPT — und wenn
   dieser eine Rückruf ausbleibt oder zu früh läuft, steht der Schleier
   für immer auf 0: Der Dialog ist da und man sieht ihn nicht, ohne
   Fehler, ohne Hinweis. Genau die Sorte Fehler, die diese Datei
   vermeiden soll.

   Eine Animation braucht diesen Rückruf nicht. Sie beginnt, sobald das
   Element im Dokument steht, `both` hält den Endzustand, und fällt das
   Stylesheet aus, ist das Element schlicht sofort da — sichtbar, und
   das ist der richtige Ausfall. (Ruht die Zeitleiste des Dokuments,
   weil der Reiter im Hintergrund liegt, läuft nichts davon — dann sieht
   aber ohnehin niemand hin, und beim Zurückkommen spielt sie ab.) */

.ms-auftritt { animation: ms-auftritt var(--ms-auftritt) var(--ms-kurve) both; }

.ms-schleier { animation: ms-einblenden var(--ms-zustand) var(--ms-kurve) both; }

/* ================================================================
   6 — Skelett in der Form dessen, was kommt
   ================================================================

   Ein Skelett hat die FORM des Inhalts: eine Überschriftenzeile auf
   etwa 40 %, darunter Textzeilen auf 70 und 58 %, im echten Rahmen der
   Karte. Ein grauer Klotz sagt nur "es lädt"; diese Form sagt "es lädt
   eine Karte mit einer Überschrift und zwei Zeilen", und wenn der
   Inhalt kommt, springt nichts.

   overflow: hidden ist Pflicht — der Schimmer läuft sonst über die
   Kartenkante hinaus. */

.ms-skelett {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  padding: 14px 16px;
}

.ms-skelett-titel,
.ms-skelett-zeile {
  border-radius: 3px;
  background: var(--hairline);
  height: 8px;
}

.ms-skelett-titel {
  height: 10px;
  width: 44%;
  background: var(--line);
}

.ms-skelett-zeile { margin-top: 8px; width: 72%; }
.ms-skelett-zeile + .ms-skelett-zeile { margin-top: 6px; width: 58%; }

.ms-skelett::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 45%;
  background: linear-gradient(90deg, transparent, rgba(76, 91, 212, .09), transparent);
  animation: ms-schimmer 1.5s var(--ms-kurve) infinite;
}

/* Geschwister versetzt anlaufen lassen. Zwei Skelette im Gleichtakt
   sehen aus wie ein Muster, nicht wie eine Ladung. */
.ms-skelett + .ms-skelett::after { animation-delay: .2s; }

/* ================================================================
   7 — Warten mit Auskunft
   ================================================================

   Kein Kreisel im Dokumentenweg. Ein Kreisel sagt "warte", und nach
   zwanzig Sekunden sagt er "hängt". Fünf benannte Schritte sagen, wo
   es steht:

     Hochladen → Texterkennung → Auslesen → Vergleichen → Bestätigt

   Die Marke am Ende der Zeile trägt den Namen des Schritts, nicht
   seine Nummer. Farbe ist dabei nie das einzige Signal: Der Text sagt
   dasselbe wie der Ton. */

.ms-stufen { display: flex; gap: 6px; }

.ms-stufe { flex: 1 1 0; display: grid; gap: 5px; min-width: 0; }

.ms-stufe-balken {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: var(--blue-tint);
  overflow: hidden;
}

.ms-stufe-balken > i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--blue);
  transition: width var(--ms-erscheinen) var(--ms-kurve);
}

.ms-stufe[data-erreicht] .ms-stufe-balken > i { width: 100%; }

.ms-stufe-name {
  font-family: var(--font-ui);
  font-size: 9px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ms-stufe[data-erreicht] .ms-stufe-name { color: var(--blue-ink); }
.ms-stufe[data-fertig] .ms-stufe-name { color: var(--muted); }

/* Die Marke einer Zeile: wartet — läuft — fertig. */
.ms-marke {
  font-family: var(--font-ui);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .06em;
  padding: 3px 7px;
  border-radius: 5px;
  background: var(--surface-sunken);
  color: var(--muted);
  white-space: nowrap;
  flex: none;
  transition: background-color var(--ms-zustand) var(--ms-kurve),
              color var(--ms-zustand) var(--ms-kurve);
}

.ms-marke[data-stand="laeuft"] {
  background: var(--blue-tint);
  color: var(--blue-ink);
  animation: ms-puls 1.4s ease-in-out infinite;
}

/* Am Ende steht sie still. Eine pulsende Marke auf einer fertigen
   Zeile liest sich als hängengeblieben — der eine Zustand, den die
   Bewegung hier falsch erzählen kann. */
.ms-marke[data-stand="fertig"] {
  background: var(--wb-gruen-tint, var(--wb-gruen-tint));
  color: var(--wb-gruen-ink, var(--wb-gruen-ink));
  animation: none;
}

/* Die Zeile selbst, gestaffelt eingeblendet — 80 ms plus 110 je Zeile
   setzt shared/motion.js.

   SICHTBAR IST DER GRUNDZUSTAND. Die Klasse allein versteckt nichts;
   versteckt wird erst, wenn data-ms-wartet dasteht, und das setzt nur
   JavaScript — unmittelbar bevor es das Aufdecken einplant, und
   BAMotion.einlauf().halt() nimmt es allen Zeilen wieder ab, die noch
   warten.

   Andersherum wäre es ein stiller Totalausfall: Stünde opacity: 0 an
   der Klasse und der Rückruf bliebe aus — Uhr abgeräumt, Ansicht
   gewechselt, Skript gar nicht geladen —, wäre die Liste leer, ohne
   Fehler und ohne Hinweis. Dieselbe Überlegung steht bei .hp-auf auf
   der Startseite, wo sie einmal teuer gelernt wurde. */
.ms-zeile {
  transition: opacity var(--ms-auftritt) var(--ms-kurve),
              transform var(--ms-auftritt) var(--ms-kurve),
              border-color var(--ms-auftritt) var(--ms-kurve);
}

.ms-zeile[data-ms-wartet] { opacity: 0; transform: translateY(8px); }
.ms-zeile[data-stand="fertig"] { border-color: var(--violet-border); }

/* ================================================================
   7b — Die Reiterzeile mit wanderndem Strich
   ================================================================

   Der Strich selbst ist ein <i>, das BAMotion.reiterlinie() einhängt
   und misst — Positionen kann CSS nicht rechnen.

   Was hier steht, ist nur die Übergabe: Sobald wirklich ein Strich da
   ist, setzt reiterlinie() data-ms-linie auf die Zeile, und erst dann
   verschwindet der Rand am ausgewählten Reiter.

   Genau in dieser Reihenfolge, und nicht andersherum. Nähme man den
   Rand fest weg und verließe sich darauf, dass das Skript ihn ersetzt,
   hätte eine Seite ohne motion.js eine Reiterzeile ohne jede Anzeige,
   welcher Reiter offen ist — der Zustand wäre unsichtbar, nicht nur
   unbewegt. So ist der Rand die Grundlage und der Strich die Kür.

   Doppelter Selektor, weil die Seiten ihren Rand mit zwei Klassen
   setzen (.sm-tab[aria-selected="true"]) und ihr <style> nach dieser
   Datei kommt. Die Begründung steht ausführlich bei den Knöpfen. */

[data-ms-linie][data-ms-linie] [aria-selected="true"],
[data-ms-linie][data-ms-linie] [aria-current="page"] {
  border-bottom-color: transparent;
}

/* ================================================================
   8 — Seitenwechsel innerhalb der Werkbank
   ================================================================

   Der alte Inhalt sinkt 6 px und verblasst in 120 ms, der neue kommt
   6 px von unten in 180 ms. Der RAHMEN steht still — Reiterzeile,
   Kopf, Seitenleiste. Genau diese Stille ist der Beweis, dass man die
   Werkbank nicht verlassen hat. Wer den Rahmen mitblenden lässt, hat
   einen Seitenwechsel gebaut, keinen Reiterwechsel. */

.ms-blatt {
  transition: opacity var(--ms-zustand) var(--ms-kurve),
              transform var(--ms-zustand) var(--ms-kurve);
}

.ms-blatt[data-kommt] { opacity: 0; transform: translateY(6px); }

.ms-blatt[data-geht] {
  opacity: 0;
  transform: translateY(-6px);
  transition-duration: var(--ms-schnell);
}

/* ================================================================
   9 — Einblenden beim Scrollen: steht woanders
   ================================================================

   --ms-erscheinen gehört zur Startseite, und die hat ihre Mechanik
   schon: .hp-auf in index.html, mit dem Schalter .hp-bereit auf <html>.
   Der Grundzustand dort ist SICHTBAR; versteckt wird erst, wenn das
   Skript bestätigt hat, dass es beobachten kann, und nach zwei
   Sekunden ohne Meldung gibt es die Bewegung wieder auf. Die
   Begründung steht ausführlich an der Regel selbst.

   Hier steht deshalb nichts. Eine zweite Einblendmechanik neben einer
   funktionierenden ist genau der Wildwuchs, gegen den diese Datei
   angelegt wurde — und in der ANWENDUNG hat sie ohnehin nichts zu
   suchen: Wer ein Modul öffnet, will seinen Stand sehen, nicht ihn
   sich verdienen. */

/* ================================================================
   10 — Was die globale Sperre nicht erreicht
   ================================================================

   animation-duration: .001ms hält einen Dauerläufer nicht an, es lässt
   ihn tausendmal je Millisekunde durchlaufen. Also von Hand.

   PULS UND SCHIMMER STEHEN STILL. Beide sind Verzierung einer
   Aussage, die daneben in Worten steht: Die Marke trägt den Namen
   ihrer Stufe, das Skelett trägt die Form dessen, was kommt. Ohne
   Bewegung geht dabei nichts verloren.

   DER RING WIRD LANGSAM STATT STILL, und das ist der Unterschied.
   Er ist an manchen Stellen das EINZIGE Zeichen, dass gerade
   gearbeitet wird — in account.html wechselt die Beschriftung
   bewusst nicht, weil ein springender Knopf unter dem Finger sich
   nach Fehler anfühlt. Ein stehender Ring hieße dort "hängt".
   1.8 Sekunden sind langsam genug, um niemanden zu stechen, und
   schnell genug, um "läuft" zu heißen. Die Zahl kommt aus
   account.html, wo sie seit jeher steht; sie ist hier nicht neu
   erfunden, sondern nach oben gezogen. */

@media (prefers-reduced-motion: reduce) {
  /* animation: none setzt den NAMEN auf none. Das genügt: Die
     Sperre oben überschreibt nur die Dauer, nicht den Namen, und eine
     Animation ohne Namen läuft nicht. */
  .ms-marke[data-stand="laeuft"] { animation: none; }

  .ms-skelett::after { animation: none; opacity: 0; }

  /* Hier dagegen ist !important nötig, und es ist der einzige Ort in
     diesem Haus, an dem es das ist: Die Regel im Unterbau setzt
     animation-duration mit !important, und dagegen kommt keine
     Spezifität an. Ohne diese Zeile liefe der Ring nicht langsam,
     sondern tausendmal je Millisekunde — ein flimmernder Punkt für
     genau die Menschen, die um weniger Bewegung gebeten haben.

     -dreher deckt die Kreisel mit ab, die es vor .ms-ring schon gab
     (.ko-dreher, .sm-dreher). In account.html stand diese Zeile
     bereits, ohne !important und damit wirkungslos; sie ist von dort
     hierher gezogen und tut jetzt, was sie wollte. */
  .ms-ring,
  [class$="-dreher"],
  [class*="-dreher "] { animation-duration: 1.8s !important; }
}
