/* ============================================================
   Komponente: Button
   ------------------------------------------------------------
   Quelle: Figma „Buttons & Links", ausgelesen am 25.08.2026.
   Jede Farbe, jedes Padding stammt aus der Komponente selbst,
   nicht aus den Design Rules — die waren an mehreren Stellen
   veraltet.

   Setzt tokens.css und base.css voraus.

   VERWENDUNG
     <button class="btn btn--primary btn--md">Lesen</button>
     <button class="btn btn--ghost btn--md" disabled>Lesen</button>

   Für Screenshots und Reviews lassen sich Zustände erzwingen:
     .is-hover · .is-pressed · .is-focus
   Im echten Prototyp nie setzen — dafür sind die Pseudoklassen da.
   ============================================================ */

/* ------------------------------------------------------------
   Basis
   ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);

  border-radius: var(--radius-full);
  font-family: var(--font-family-inter);
  font-weight: var(--font-weight-bold);
  text-align: center;
  text-decoration: none;

  /* DER STRICH IST EIN INNERER SCHATTEN, KEIN border.
     ------------------------------------------------------------
     In Figma liegt der Strich INNEN (strokeAlign INSIDE) und
     zählt nicht zur Höhe. Ein CSS-border tut das doch: bei
     border-box zählt er zwar nicht doppelt zur gesetzten Breite,
     aber diese Buttons haben KEINE gesetzte Höhe — sie ergibt
     sich aus Padding plus Zeilenhöhe, und da kommen die zwei
     Pixel oben drauf.

     Gemessen am 04.09.2026: small 47 statt 45, medium 55,6 statt
     54, warning 39 statt 37. Genau der Fall, den AGENTS.md §8a
     ausdrücklich verbietet („NIE ein CSS-border an beiden").

     Der Rand liegt deshalb in --btn-outline und wird als inset
     box-shadow gezeichnet. Er ist immer da, nur oft durchsichtig
     — sonst springt der Button, sobald ein Zustand einen Rand
     ergänzt. Der Glow liegt daneben in --btn-glow, damit ein
     Zustand mit Glow den Rand nicht überschreibt. */
  --btn-outline: transparent;
  --btn-glow: 0 0 0 0 transparent;
  box-shadow:
    inset 0 0 0 var(--outline-default) var(--btn-outline),
    var(--btn-glow);
}

/* Ein einzelner Button steht nie rechtsbündig, sondern über die
   volle Breite — Design Rules §13. */
.btn--full {
  display: flex;
  width: 100%;
}

/* ------------------------------------------------------------
   Größen
   Gemessen in Figma: Padding vertikal/horizontal, daraus Höhe.
   ------------------------------------------------------------ */

.btn--sm {
  padding: var(--space-sm) var(--space-md);      /* 12 / 16 → 45 */
  font-size: var(--font-size-small-text);
  line-height: var(--line-height-small-text);
  letter-spacing: var(--letter-spacing-small-text-bold);
}

.btn--md {
  padding: var(--space-md) var(--space-lg);      /* 16 / 24 → 54 */
  font-size: var(--font-size-medium-text);
  line-height: var(--line-height-medium-text);
}

.btn--lg {
  padding: var(--space-lg) var(--space-xl);      /* 24 / 36 → 74,4 */
  font-size: var(--font-size-large-text);
  line-height: var(--line-height-large-text);
}

/* ------------------------------------------------------------
   GLOW — nur Akzentfamilie
   ------------------------------------------------------------
   Design Rules §4: Hover und Press der Akzentfamilie tragen
   zusätzlich einen Glow (Schlagschatten X0/Y0, Blur 10 bzw. 25).
   Die neutrale Familie bekommt NIE einen Glow — dort trägt die
   Fill-Änderung, und der Warning-Button trägt die Rot-Semantik.

   Der Glow ist nie alleiniges Zustands-Signal (WCAG 1.4.1):
   Fill und Rand ändern sich immer mit.
   ------------------------------------------------------------ */

/* ------------------------------------------------------------
   Primary — nutzergewollte Hauptaktion. Einer pro Fläche.
   Fill und Fill-Hover tragen denselben Wert; im Hover kommt
   allein der Rand dazu. So ist es in Figma gebaut.
   ------------------------------------------------------------ */

.btn--primary {
  background-color: var(--button-primary-fill);
  color: var(--button-primary-text);
}

.btn--primary:hover:not(:disabled),
.btn--primary.is-hover {
  background-color: var(--button-primary-fill-hover);
  --btn-outline: var(--button-primary-outline);
  --btn-glow: var(--glow-hover);
}

.btn--primary:active:not(:disabled),
.btn--primary.is-pressed {
  background-color: var(--button-primary-fill-press);
  --btn-outline: var(--button-primary-outline);
  --btn-glow: var(--glow-press);
}

/* ------------------------------------------------------------
   Secondary — Aktion mit gewollter Bremse
   ------------------------------------------------------------ */

.btn--secondary {
  background-color: var(--button-secondary-fill);
  --btn-outline: var(--button-secondary-outline);
  color: var(--button-secondary-text);
}

.btn--secondary:hover:not(:disabled),
.btn--secondary.is-hover {
  background-color: var(--button-secondary-fill-hover);
  --btn-outline: var(--button-secondary-outline-hover-press);
  --btn-glow: var(--glow-hover);
}

.btn--secondary:active:not(:disabled),
.btn--secondary.is-pressed {
  background-color: var(--button-secondary-fill-press);
  --btn-outline: var(--button-secondary-outline-hover-press);
  --btn-glow: var(--glow-press);
}

/* ------------------------------------------------------------
   Ghost — neutral auf unruhigem Grund. Trägt immer einen Rand,
   das ist der Unterschied zu Tertiär.
   ------------------------------------------------------------ */

.btn--ghost {
  background-color: transparent;
  --btn-outline: var(--button-ghost-outline);
  color: var(--button-ghost-text);
}

.btn--ghost:hover:not(:disabled),
.btn--ghost.is-hover {
  background-color: var(--button-ghost-fill-hover);
}

.btn--ghost:active:not(:disabled),
.btn--ghost.is-pressed {
  background-color: var(--button-ghost-fill-hover);
  --btn-outline: var(--button-ghost-outline-press);
}

/* ------------------------------------------------------------
   Tertiär — neutral, wenn die Umgebung die Affordance trägt.
   Ohne Rand, außer im Press-Zustand.
   ------------------------------------------------------------ */

.btn--tertiary {
  background-color: transparent;
  color: var(--button-ghost-text);
}

.btn--tertiary:hover:not(:disabled),
.btn--tertiary.is-hover {
  background-color: var(--button-ghost-fill-hover);
}

.btn--tertiary:active:not(:disabled),
.btn--tertiary.is-pressed {
  background-color: var(--button-ghost-fill-hover);
  --btn-outline: var(--button-ghost-outline-press);
}

/* ------------------------------------------------------------
   Warning — destruktiv, irreversibel
   ------------------------------------------------------------
   Sonderfall im System: eigene Größe (Padding 8/12, sichtbar
   37px) und kein disabled-Zustand. Hover und Pressed sind
   identisch — in Figma eine gemeinsame Variante.

   ABWEICHUNG VON FIGMA: Dort sitzt die sichtbare Pille (37px)
   in einer 45px-Fläche, und der Fokus-Ring umfasst die 45px.
   Das widerspricht der Regel „Ring folgt der sichtbaren Fläche,
   nicht der Hit-Area". Hier ist es nach der Regel gebaut: die
   Hit-Area entsteht über das ::after-Overlay von .touch-target,
   die Border-Box bleibt die sichtbare Pille, der Ring sitzt
   4px davor. In Figma nachzuziehen.

     <button class="btn btn--warning touch-target">Beenden</button>
   ------------------------------------------------------------ */

.btn--warning {
  padding: var(--space-xs) var(--space-sm);      /* 8 / 12 → 37 */
  font-size: var(--font-size-small-text);
  line-height: var(--line-height-small-text);
  letter-spacing: var(--letter-spacing-small-text-bold);
  background-color: transparent;
  color: var(--red-label-text);
}

.btn--warning:hover:not(:disabled),
.btn--warning:active:not(:disabled),
.btn--warning.is-hover,
.btn--warning.is-pressed {
  background-color: var(--warning-red-fill);
}

/* ------------------------------------------------------------
   Disabled
   ------------------------------------------------------------
   Gilt für alle Familien, aber nicht überall gleich: Tertiär
   bleibt ohne Rand, Ghost behält einen. Warning kennt den
   Zustand nicht.

   cursor bleibt default statt not-allowed — ein durchgestrichener
   Kreis liest sich als Fehler, nicht als „hier ist gerade nichts
   zu tun".
   ------------------------------------------------------------ */

.btn:disabled,
.btn.is-disabled {
  background-color: var(--button-disabled-fill);
  --btn-outline: var(--button-disabled-outline);
  --btn-glow: 0 0 0 0 transparent;         /* kein Glow im Ruhezustand ohne Aktion */
  color: var(--button-disabled-text);
  cursor: default;
}

.btn--ghost:disabled,
.btn--ghost.is-disabled {
  background-color: transparent;
  --btn-outline: var(--button-disabled-outline);
}

.btn--tertiary:disabled,
.btn--tertiary.is-disabled {
  background-color: transparent;
  --btn-outline: transparent;
}

/* ------------------------------------------------------------
   Laden — zustand=laden (§4)
   ------------------------------------------------------------
   Ein ZUSTAND, keine eigene Komponente. Der Button behält Größe,
   Fläche, Rand und Padding aus dem Ruhezustand.

   KEINE BREITENÄNDERUNG: Das Label bleibt im Layout und hält die
   Breite, geht aber auf opacity 0. Der Spinner liegt absolut
   zentriert darüber. Ein Spinner NEBEN dem Label würde den Button
   verbreitern; ein Icon-Slot würde die Ruheoptik aller anderen
   Varianten ändern.

   Das Label darf deshalb nicht display:none bekommen — dann
   verliert es seine Breite und der Button schrumpft.

28 px in ALLEN DREI GRÖSSEN — die native Größe der
   Figma-Komponente, unskaliert. Sie ist größer als die Inhaltsbox
   (medium 25 px, large 24 px), und das ist kein Problem: der
   Spinner ist ABSOLUT positioniert und zentriert, nicht Teil des
   Auto-Layouts. Er wächst den Button also nicht. Er ragt 1,5 bzw.
   2 px in das Padding, von 16 bzw. 24 px — optisch belanglos.

   SEIT 31.08.2026 AUCH IN SMALL. Der Satz „Kein laden in small"
   ist aufgehoben (§4). .btn--laden ist größenunabhängig und
   braucht dafür keine eigene Regel. In small füllt der Ring 62 %
   der Buttonhöhe (74 × 45, je 8,5 px Luft), in medium 49 %, in
   large 39 %. Das ist bekannt und gewollt.

   DEN SPINNER NICHT KLEINER MACHEN. Skalieren ändert auch die
   Strichstärke und bricht die Reihe — es gibt ihn vorerst nur in
   28.

   Die frühere Rechnung „24 px, sonst wächst der Button" war
   falsch: sie hat den Spinner als Layout-Kind behandelt. In Figma
   war die Instanz auf 24 gesetzt, ohne den Strich mitzuskalieren
   — gezeichnet wurden trotzdem 28, und zwar 2 px nach unten und
   rechts aus der Box heraus. Der Ring saß sichtbar neben der
   Mitte. Korrigiert am 28.08.2026.

   LADEN IST NICHT DISABLED (§16). Keine Disabled-Tokens, und im
   Markup aria-disabled statt disabled — echtes disabled nimmt den
   Button aus der Tab-Reihenfolge und der Fokus springt mitten in
   der Aktion weg. Deshalb hier auch :not(:disabled) NICHT nötig:
   ein ladender Button ist nicht disabled.

   Erscheint erst nach 300 ms, und über ~2 s gehört die Auskunft
   nicht in den Button (§4). Das ist Logik, nicht CSS.

     <button class="btn btn--primary btn--md btn--laden"
             aria-disabled="true">
       <span class="btn__label">QR-Code scannen</span>
       <svg class="btn__spinner spinner" viewBox="0 0 28 28" aria-hidden="true">
         <circle class="spinner__bahn"  cx="14" cy="14" r="12"></circle>
         <circle class="spinner__bogen" cx="14" cy="14" r="12"></circle>
       </svg>
     </button>

   DER SPINNER NIMMT DIE LABELFARBE. Er ersetzt das Label, also
   erbt er dessen Farbe — über currentColor. Feste Spinnerfarben
   funktionieren nicht: in Figma war der Bogen an primary-fill
   gebunden und damit auf dem Primary-Button unsichtbar (1,00:1).

   Der freistehende Spinner in spinner.css behält seine Tokens
   (label-outline / text-accent) — er liegt auf surface-primary,
   nicht auf einer Buttonfläche. Zwei Verwendungen, zwei Regeln.
   ------------------------------------------------------------ */

.btn--laden {
  position: relative;
  cursor: default;
}

/* DER SPINNER IST NUR IM LADEZUSTAND ZU SEHEN.
   ------------------------------------------------------------
   Figma legt ihn nur in der Variante `zustand=laden` an, und
   das Beispiel oben zeigt ihn deshalb nur dort im Markup. Wer
   ihn dauerhaft stehen lässt und den Zustand über eine Klasse
   schaltet — was bei einem Knopf, der zwischen den Zuständen
   hin und her geht, das Naheliegende ist — bekam ihn ohne diese
   Regel IMMER zu sehen: `.btn__spinner` ist absolut
   positioniert, und ohne `.btn--laden` hat der Knopf kein
   `position: relative`, an dem er hängen könnte. Der Ring
   rutschte dann unter den Knopf.

   Am 05.09.2026 im WLAN-Sheet des Tablet-Prototypen aufgefallen.
   ------------------------------------------------------------ */
.btn__spinner {
  display: none;
}

.btn--laden .btn__spinner {
  display: block;
}

/* Hält die Breite, ist aber unsichtbar. Nie display:none.

   DAS LABEL MUSS IN EINEM ELEMENT STEHEN — <span class="btn__label">.
   Ein nackter Textknoten ist mit keinem Selektor erreichbar: er ist
   kein Element, also greift der Kindselektor nicht, und das Label
   bliebe bei voller Deckkraft unter dem Spinner stehen. Genau das
   Bild, das in Figma als „Text und Icon überlagern sich" auffiel.
   Am 28.08.2026 im Beispiel oben korrigiert; der Selektor war immer
   richtig, das Markup nicht. */
.btn--laden > :not(.btn__spinner) {
  opacity: 0;
}

/* ZENTRIERT ÜBER inset + margin, NICHT über transform.

   motion.css dreht .spinner über `animation: motion-drehen`, und eine
   Animation auf transform ÜBERSCHREIBT ein statisches transform
   vollständig. Mit translate(-50%,-50%) verlor der Spinner beim ersten
   Frame seine Zentrierung und rutschte um seine halbe Größe nach unten
   rechts — genau der Fehler, der in Figma dadurch entstand, dass die
   Instanz verkleinert wurde, ohne den Strich mitzuskalieren.

   inset: 0 mit margin: auto zentriert ohne transform und ohne die Größe
   zu kennen. transform bleibt frei für die Bewegung.

   DIE MASSE BRAUCHEN ZWEI KLASSEN. spinner.css setzt .spinner auf 58 px
   und wird nach button.css geladen — bei gleicher Spezifität gewinnt die
   spätere Datei. Der Button-Spinner war deshalb 58 statt 28 px.
   Nachgemessen im Browser am 28.08.2026. */
.btn__spinner.spinner {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 28px;
  height: 28px;
}

/* Bogen = volle Labelfarbe, er trägt die Auskunft.
   Bahn  = dieselbe Farbe auf 30 %, sie gibt ihm einen Weg.

   BEIDE über currentColor, kein festes Grau. Die Buttonfläche
   wechselt je Familie und je Mode; ein fester Grauwert wäre auf
   der einen unsichtbar und auf der anderen zu laut. So verschiebt
   sich das Paar mit der Familie mit, ohne vier Sonderregeln — auf
   primary ein dunkleres Blaugrau auf hellem Blau, auf secondary und
   ghost ein helles Grau auf dunklem Grund.

   Die Deckkraft gehört an das ELEMENT, nicht an die Farbe. In Figma
   ist genau das am 28.08.2026 schiefgegangen: die 30 % lagen auf dem
   Paint, das setBoundVariableForPaint zurückgibt — das Objekt ist
   eingefroren, die Zuweisung wurde still verworfen, die Bahn stand
   auf 100 %. Hier ist es opacity am <circle>. */
.btn__spinner .spinner__bogen {
  stroke: currentColor;
}

.btn__spinner .spinner__bahn {
  stroke: currentColor;
  opacity: 0.3;
}

/* Ein ladender Button gibt nicht nach — das Nachgeben ist die
   Bestätigung, dass ein Tippen angekommen ist, und das ist hier
   schon passiert (motion.css). */
.btn--laden:active {
  transform: none;
}

/* ------------------------------------------------------------
   Fokus
   ------------------------------------------------------------
   Der Ring kommt aus base.css und gilt für alle Familien gleich.
   Hier nur die erzwingbare Variante fürs Review.
   ------------------------------------------------------------ */

.btn.is-focus {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
