/* ============================================================
   ORNLA — Anmeldung
   Eigenes Stylesheet: die Login-Seite lädt kein App-CSS und kein
   App-JS. So kann eine noch nicht angemeldete Sitzung nichts aus
   der Anwendung nachladen.

   Die Startsequenz steht vollständig hier. Grundregel dabei:
   das Basis-CSS jedes Elements beschreibt immer den RUHEZUSTAND,
   die Keyframes beschreiben nur den Weg dorthin. Alles läuft mit
   animation-fill-mode: both. Dadurch kann nichts unsichtbar
   hängenbleiben — nicht bei reduzierter Bewegung, nicht in einer
   alten Engine, und nicht wenn login.js gar nicht erst lädt.
   ============================================================ */
:root {
  --bg: #070b18;
  --card: rgba(20, 27, 52, .85);
  --card2: #1b2444;
  --border: rgba(126, 148, 255, .13);
  --border-strong: rgba(126, 148, 255, .28);
  --text: #eef1fb;
  --muted: #939dc3;
  --accent: #6e8bff;
  --violet: #a86bff;
  --ok: #34e3b0;
  --warn: #ffc069;
  --err: #ff7b7b;
  --radius: 16px;

  /* Tempo-Regler der Startsequenz. Jede Dauer und jede Verzögerung
     unten steht als calc(Xms * var(--s)) — damit lässt sich die ganze
     Choreografie mit einer Zahl abstimmen: Kurzfassung ab dem zweiten
     Aufruf, Endzustand für die Sichtprüfung, reduzierte Bewegung.
     Funktionale Animationen (.ornla-spin, .toast) benutzen --s bewusst
     NICHT, sie sind kein Schmuck. */
  --s: 1;
}
/* Zweiter Aufruf in derselben Sitzung → Kurzfassung. Gesetzt von login.js.
   Gleiche Spezifität wie :root, entscheidet also die Position. */
.intro-short { --s: .35; }

* { box-sizing: border-box; margin: 0; }
html, body { height: 100%; }
body {
  font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  background: var(--bg); color: var(--text);
  font-size: 14.5px; line-height: 1.5; -webkit-font-smoothing: antialiased;
}
svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* Die Marke ist Fläche, kein Strich. Die Regel darüber gilt für die Icons,
   nicht für die Bildmarke — ohne diese Zeile erbt jedes <use> die Kontur und
   die Marke steht als Outline da (Styleguide 08). Bei 148 px wären das
   1.7/48 * 148 = 5,2 px heller Rand. Erfasst auch die Spinner-Instanzen,
   die login.js zur Laufzeit erzeugt. */
use { stroke: none; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.muted { color: var(--muted); }

/* ============================================================
   Bühne — drei rein dekorative, viewport-fixierte Ebenen
   ============================================================
   Nach der Startsequenz bewegt sich hier nichts mehr. Das ist Absicht:
   solange nichts animiert, bleibt der backdrop-filter-Schnappschuss der
   Karte gültig und muss nicht pro Frame neu gerechnet werden. Die
   Wertigkeit kommt aus der statischen Komposition — Bühnenlicht,
   maskiertes Raster, Vignette. */
.stage { position: fixed; inset: 0; z-index: -1; pointer-events: none; contain: layout paint style; }
.stage > * { position: absolute; inset: 0; }

/* Bühnenlicht, auf das Logo ausgerichtet statt auf die Seitenränder. */
.stage-veil {
  background:
    radial-gradient(760px 520px at 50% 24%, rgba(123, 149, 255, .20), transparent 62%),
    radial-gradient(620px 420px at 50% 22%, rgba(168, 107, 255, .13), transparent 58%),
    radial-gradient(900px 520px at 50% 118%, rgba(52, 227, 176, .055), transparent 60%);
}

/* Tiefe ohne Lärm — vollständig statisch. Radial weggeblendet, damit das
   Raster nur um das Logo herum existiert und die Ränder nicht zerschneidet. */
.stage-grid {
  background-image:
    repeating-linear-gradient(90deg, rgba(126, 148, 255, .045) 0 1px, transparent 1px 44px),
    repeating-linear-gradient(0deg,  rgba(126, 148, 255, .045) 0 1px, transparent 1px 44px);
  -webkit-mask-image: radial-gradient(circle at 50% 24%, #000 0%, rgba(0, 0, 0, .55) 34%, transparent 64%);
          mask-image: radial-gradient(circle at 50% 24%, #000 0%, rgba(0, 0, 0, .55) 34%, transparent 64%);
}

/* Drückt die Ränder weg, damit Marke und Karte in der Mitte im Licht stehen. */
.stage-vig { background: radial-gradient(circle at 50% 42%, transparent 45%, rgba(4, 6, 14, .55) 100%); }

/* ============================================================
   Layout — eine zentrierte Spalte, Logo mittig auf jedem Breakpoint
   ============================================================ */
.auth-wrap {
  min-height: 100dvh;
  display: flex; flex-direction: column; align-items: center; gap: 18px;
  padding: clamp(22px, 4vh, 52px) 20px calc(clamp(22px, 4vh, 52px) + env(safe-area-inset-bottom, 0px));
}
/* Zentriert vertikal, ohne den oberen Rand abzuschneiden: die Platzhalter
   wachsen nur in freien Raum (flex: 1 0 0) und können nicht negativ werden.
   justify-content:center würde den Inhalt bei mehr als 100dvh Höhe über die
   Startkante schieben — dort ist er unscrollbar und damit unerreichbar. */
.auth-wrap::before, .auth-wrap::after { content: ""; flex: 1 0 0; }

.brand-stack { --mk: clamp(88px, 12vmin + 34px, 148px); display: grid; justify-items: center; gap: 18px; }

/* Träger der Hauptanimation. Kein Hintergrund, keine Kachel, kein Rahmen
   (Styleguide 08). aspect-ratio ist hier Pflicht, nicht Kosmetik:
   transform-origin 50%/50% ist nur so lange der Markenmittelpunkt, wie die
   Box exakt quadratisch ist — sonst kippt die Umlaufbahn des Knotens. */
.hero-mark { position: relative; width: var(--mk); aspect-ratio: 1; }
.hero-mark svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.hm-arc, .hm-node { transform-origin: 50% 50%; }

/* Ruhezustand des Impulsrings: unsichtbar. translate und transform sind
   getrennte Eigenschaften — so streiten sich Zentrierung und Skalierung nicht. */
.ring {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  width: calc(var(--mk) * 1.1875); aspect-ratio: 1;
  border: 1px solid rgba(126, 148, 255, .30); border-radius: 50%;
  opacity: 0;
}
.ring.r2 { border-color: rgba(168, 107, 255, .24); }
.ring.r3 { border-color: rgba(52, 227, 176, .16); }

/* margin-right hebt die Laufweite hinter dem A auf — sonst steht die
   Wortmarke um eine halbe Laufweite zu weit links. Volltonfarbe statt
   Verlauf: background-clip:text verträgt sich nicht mit transformierten
   Inline-Blocks, und der Verlauf gehört der Bildmarke (Styleguide 08). */
.wordmark {
  font-weight: 600; font-size: calc(var(--mk) * .215); line-height: 1;
  letter-spacing: .19em; margin-right: -.19em; color: var(--text);
}

.auth-panel { width: 100%; max-width: 400px; display: grid; justify-items: center; gap: 14px; }
.auth-card {
  width: 100%; background: var(--card); border: 1px solid var(--border);
  border-radius: 20px; padding: 30px 30px 26px; backdrop-filter: blur(16px);
  box-shadow: 0 20px 60px rgba(2, 5, 18, .5);
}
.auth-legal { font-size: 11.5px; color: var(--muted); text-align: center; }
.lockline {
  display: flex; gap: 8px; align-items: center; justify-content: center;
  text-align: center; font-size: 11.4px; color: var(--muted);
}
/* Ohne feste Größe rendert ein Inline-SVG mit der Ersatzgröße 300x150. */
.lockline svg { width: 14px; height: 14px; flex-shrink: 0; }
.loading { color: var(--muted); padding: 20px 0; display: flex; align-items: center; gap: 12px; }

/* ============================================================
   Startsequenz
   ============================================================
   Die Marke setzt sich nach ihrer eigenen Konstruktionsregel zusammen:
   der Knoten läuft dem dicken Ende voraus, das Netz zieht nach
   (Styleguide 02). Erst danach geht das Bühnenlicht an.

   Geometrie, nachgerechnet am Markup: Mittelpunkt 24/24, der Knoten
   (34.61, 13.39) liegt 15.005 Einheiten entfernt auf 315°, der Bogen
   läuft im Uhrzeigersinn von 353° (dünnes Ende) bis 277° (dickes Ende).
   Umfang der Bahn 2*PI*15 = 94.2478. */

/* 1 — Der Knoten kommt an (0–620 ms).
   rotate(-300deg) setzt ihn auf 15°, also direkt neben das dünne Ende des
   Bogens; von dort fährt er 300° im Uhrzeigersinn auf seine Ruheposition.
   Kein Overshoot im Winkel — der Knoten rastet ein, er hüpft nicht.
   Die Kurve war ursprünglich cubic-bezier(.17,.86,.24,1): damit lagen nach
   einem Drittel der Zeit schon 87 % des Weges hinter ihm, also 260° in rund
   10 Bildern. Das liest sich als Blitz, nicht als Umlaufbahn. Jetzt flacher
   und etwas länger — die Bahn bleibt lesbar, das Abbremsen bleibt hart. */
@keyframes hm-node-orbit {
  0%   { opacity: 0; transform: rotate(-300deg) scale(.62); }
  14%  { opacity: 1; }
  100% { opacity: 1; transform: rotate(0deg) scale(1); }
}
.hm-node { animation: hm-node-orbit calc(620ms * var(--s)) cubic-bezier(.32, .55, .3, 1) both; }

/* 2 — Der Bogen zieht nach (190–1190 ms).
   Die Marke ist eine Fläche, an ihr greift stroke-dashoffset nicht
   (Styleguide 07). Also wird sie nicht gezeichnet, sondern freigelegt: die
   Blende ist derselbe gleichmäßige Ring, den der Styleguide für bestimmten
   Fortschritt vorsieht. Startwert 79.85 = der belegte Sektor, siehe die
   Rechnung am Maskenkreis in login.html.
   Die Kurve ist bewusst hinten lastig: wenn der Knoten bei 620 ms einrastet,
   ist der Bogen erst rund ein Drittel weit; die letzten 10 % laufen über
   290 ms aus.
   Genau das ist die Aussage — das Ereignis ist durch, das Netz zieht nach.
   Eine frontlastige Kurve lässt den Bogen den Knoten einholen und zerstört
   die Lesart. */
@keyframes hm-arc-draw { from { stroke-dashoffset: 79.85; } to { stroke-dashoffset: 0; } }
.hm-wipe { animation: hm-arc-draw calc(1000ms * var(--s)) cubic-bezier(.68, .05, .36, 1) calc(190ms * var(--s)) both; }

/* 3 — Setzen (0–900 ms). Ein knapper Maßstabs-Settle, damit die Marke
   ankommt statt nur zu erscheinen. Overshoot bewusst bei 2,8 % — spürbar,
   nicht albern. Kein rotate: Rotation ist als Gestaltungsmittel
   ausgeschlossen, sie kippt die Marke (Styleguide 02/08). */
@keyframes hm-settle {
  0%   { transform: scale(.9); }
  58%  { transform: scale(1.028); }
  100% { transform: scale(1); }
}
.hero-mark { animation: hm-settle calc(900ms * var(--s)) cubic-bezier(.22, .76, .24, 1) both; }

/* 4 — Erst das Objekt, dann das Licht (0–1400 ms). Der Raum geht an,
   während sich die Marke zusammensetzt. Läuft genau einmal. */
@keyframes stage-up { from { opacity: .28; } to { opacity: 1; } }
.stage-veil { animation: stage-up calc(1400ms * var(--s)) ease-out both; }

/* 5 — Die Karte. Verzögerung 120 ms und Startopazität .4 sind Pflicht,
   nicht Geschmack: login.js fokussiert das E-Mail-Feld direkt nach dem
   ersten card.innerHTML, und das passiert erst nach dem Roundtrip auf
   /api/auth/me — also nicht verlässlich nach 100 ms. Ein fokussiertes Feld
   mit opacity 0 schluckt Tastendrücke blind und lässt auf iOS die Tastatur
   gegen ein leeres Bild fahren.
   HARTE REGEL: animation-delay hier nie über 150 ms, Startopazität nie
   unter .35, und niemals visibility:hidden, display:none oder
   pointer-events:none auf #card. */
@keyframes card-in {
  from { opacity: .4; transform: translateY(10px); }
  to   { opacity: 1;  transform: translateY(0); }
}
.auth-card { animation: card-in calc(420ms * var(--s)) cubic-bezier(.2, .8, .24, 1) calc(120ms * var(--s)) both; }

/* 6 — Schrittwechsel. Karteninhalt wird bei jedem Schritt und jedem Fehler
   neu gesetzt; die Kinder zu animieren würde bei jedem Tippfehler erneut
   abspielen und flackern. Stattdessen ein kurzer Überblend-Impuls auf der
   Karte selbst, ausgelöst vom MutationObserver in login.js.
   Als Longhand geschrieben, weil hier bewusst der komplette animation-Satz
   ersetzt wird. Gefahrlos, weil card-in dann längst durch ist und sein
   Endzustand mit dem Basis-CSS der Karte identisch ist. */
@keyframes card-swap { from { opacity: .45; } to { opacity: 1; } }
.auth-card.swap {
  animation-name: card-swap;
  animation-duration: 170ms;
  animation-timing-function: ease-out;
  animation-delay: 0s;
  animation-fill-mode: none;
}

/* 7 — Impuls, genau einmal. Der Basisdurchmesser var(--mk) * 1.1875 ist
   keine Geschmacksfrage: sichtbarer Radius der Marke = 19.5/48 der
   Kantenlänge, Schutzraum = 9/48 (ein Knotendurchmesser, Styleguide 05)
   → (19.5 + 9) * 2 / 48 = 1.1875. Der Ring existiert bei scale(1) also
   exakt an der Schutzraumgrenze — und ist dort noch mit opacity 0
   unsichtbar. Er kann konstruktionsbedingt nie als Rahmen um die Marke
   erscheinen (Styleguide 08). */
@keyframes ring-out {
  0%   { opacity: 0;  transform: scale(1); }
  9%   { opacity: .5; }
  100% { opacity: 0;  transform: scale(3.2); }
}
/* Der Impuls setzt exakt dann ein, wenn der Knoten einrastet (620 ms). */
.ring    { animation: ring-out calc(1500ms * var(--s)) cubic-bezier(.16, .72, .3, 1) calc(620ms * var(--s)) both; }
.ring.r2 { animation-duration: calc(1650ms * var(--s)); animation-delay: calc(790ms * var(--s)); }
.ring.r3 { animation-duration: calc(1800ms * var(--s)); animation-delay: calc(960ms * var(--s)); }

/* 8 — Wortmarke, Buchstabe für Buchstabe (720–1320 ms). Kein filter:blur —
   fünf gleichzeitige Offscreen-Buffer während der teuersten Phase des
   Seitenstarts, für einen Effekt, den bei 45 ms Versatz niemand einzeln
   wahrnimmt. letter-spacing wird nicht animiert, das wäre Reflow pro Frame. */
@keyframes wm-in {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: translateY(0); }
}
.wordmark span { display: inline-block; animation: wm-in calc(420ms * var(--s)) cubic-bezier(.2, .72, .22, 1) both; }
.wordmark span:nth-child(1) { animation-delay: calc(720ms * var(--s)); }
.wordmark span:nth-child(2) { animation-delay: calc(765ms * var(--s)); }
.wordmark span:nth-child(3) { animation-delay: calc(810ms * var(--s)); }
.wordmark span:nth-child(4) { animation-delay: calc(855ms * var(--s)); }
.wordmark span:nth-child(5) { animation-delay: calc(900ms * var(--s)); }

/* 9 — Fußzeilen. Letzte Einblendung, danach steht die Seite still. */
@keyframes text-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.auth-legal { animation: text-in calc(360ms * var(--s)) ease-out calc(1020ms * var(--s)) both; }
.lockline   { animation: text-in calc(360ms * var(--s)) ease-out calc(1100ms * var(--s)) both; }

/* ---------------- Formular ---------------- */
.auth-card h2 { font-size: 21px; font-weight: 750; letter-spacing: -.2px; }
.auth-card .sub { color: var(--muted); font-size: 13px; margin-top: 5px; margin-bottom: 22px; }

.field { display: block; margin-bottom: 14px; }
.field > span { display: block; font-size: 11.5px; color: var(--muted); margin-bottom: 6px; letter-spacing: .3px; font-weight: 500; }
.field input {
  width: 100%; padding: 11px 13px; border-radius: 11px; font: inherit; color: var(--text);
  background: rgba(8, 12, 26, .7); border: 1px solid var(--border);
  transition: border-color .15s, box-shadow .15s;
}
.field input:focus {
  outline: none; border-color: var(--border-strong);
  box-shadow: 0 0 0 3px rgba(110, 139, 255, .14);
}
.field input::placeholder { color: rgba(147, 157, 195, .55); }
.field input[aria-invalid="true"] { border-color: rgba(255, 123, 123, .5); }

.pw-wrap { position: relative; }
.pw-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; color: var(--muted); cursor: pointer; padding: 7px; border-radius: 8px;
}
.pw-toggle:hover { color: var(--text); background: rgba(126, 148, 255, .1); }
.pw-toggle svg { width: 17px; height: 17px; }

.code-input {
  letter-spacing: .5em; text-align: center; font-size: 22px; font-weight: 700;
  font-variant-numeric: tabular-nums; padding: 13px 13px 13px 20px !important;
}

.btn {
  width: 100%; padding: 12px 16px; border: 0; border-radius: 11px; cursor: pointer;
  font: inherit; font-weight: 650; color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--violet));
  box-shadow: 0 8px 22px rgba(110, 139, 255, .28);
  transition: transform .12s, box-shadow .12s, opacity .12s;
}
.btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 12px 26px rgba(110, 139, 255, .36); }
.btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
.btn-ghost {
  background: rgba(126, 148, 255, .09); border: 1px solid var(--border); box-shadow: none; color: var(--text);
}
.btn-ghost:hover:not(:disabled) { background: rgba(126, 148, 255, .16); box-shadow: none; }
.btn-row { display: flex; gap: 9px; margin-top: 16px; }
.btn-row .btn { margin-top: 0; }
.mt { margin-top: 16px; }

/* Der Ersatz-Fokusring aus .field input:focus blendet auf etwa #212b50 gegen
   die Kartenfläche #141b34 — rund 1,25:1 bei geforderten 3:1 (WCAG 2.4.11).
   Die hellere Bühne verschärft das noch. Diese Regel steht bewusst NACH der
   outline:none-Regel: gleiche Spezifität, es entscheidet die Position. */
.field input:focus-visible,
.btn:focus-visible,
.pw-toggle:focus-visible,
.copy-btn:focus-visible,
.demo-row:focus-visible,
a:focus-visible {
  outline: 2px solid #cfd8ff; outline-offset: 2px;
}

.alert {
  display: flex; gap: 9px; align-items: flex-start; padding: 10px 12px; border-radius: 11px;
  font-size: 13px; margin-bottom: 16px; border: 1px solid;
}
.alert svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
.alert-err { background: rgba(255, 123, 123, .1); border-color: rgba(255, 123, 123, .32); color: #ffb3b3; }
.alert-ok { background: rgba(52, 227, 176, .1); border-color: rgba(52, 227, 176, .3); color: #8ff0d3; }
.alert-info { background: rgba(110, 139, 255, .1); border-color: rgba(110, 139, 255, .3); color: #c3cfff; }
.alert-warn { background: rgba(255, 192, 105, .1); border-color: rgba(255, 192, 105, .32); color: #ffd9a3; }

.linkline { text-align: center; font-size: 12.5px; color: var(--muted); margin-top: 15px; }

/* ---------------- Passwortstärke ---------------- */
.meter { height: 4px; border-radius: 99px; background: rgba(126, 148, 255, .14); overflow: hidden; margin-top: 8px; }
.meter > i { display: block; height: 100%; width: 0; border-radius: 99px; transition: width .25s, background .25s; }
.meter-hint { font-size: 11.5px; color: var(--muted); margin-top: 6px; }

/* ---------------- 2FA-Einrichtung ---------------- */
.mfa-steps { counter-reset: s; display: grid; gap: 14px; margin-bottom: 18px; }
.mfa-step { display: flex; gap: 11px; font-size: 13.2px; color: var(--muted); }
.mfa-step b { color: var(--text); font-weight: 600; }
.mfa-step .n {
  width: 21px; height: 21px; border-radius: 99px; flex-shrink: 0; display: grid; place-items: center;
  background: rgba(110, 139, 255, .18); color: var(--accent); font-size: 11px; font-weight: 700;
}
.secret-box {
  background: rgba(8, 12, 26, .75); border: 1px dashed var(--border-strong); border-radius: 11px;
  padding: 12px; text-align: center; margin: 10px 0 4px;
}
.secret-box code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 14.5px; letter-spacing: 1.5px; color: var(--text); word-break: break-all;
}
.copy-btn {
  background: none; border: 0; color: var(--accent); cursor: pointer; font: inherit; font-size: 11.5px;
  margin-top: 7px; padding: 3px 8px; border-radius: 7px;
}
.copy-btn:hover { background: rgba(110, 139, 255, .12); }

.rec-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 7px; margin: 14px 0;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 13px;
}
.rec-grid span {
  background: rgba(8, 12, 26, .7); border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 9px; text-align: center; letter-spacing: .5px;
}

/* ---------------- Demo-Zugänge ---------------- */
.demo-box { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border); }
.demo-head { font-size: 11px; letter-spacing: .5px; color: var(--muted); text-transform: uppercase; margin-bottom: 10px; font-weight: 600; }
.demo-row {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; margin-bottom: 6px;
  background: rgba(126, 148, 255, .055); border: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; font: inherit; color: var(--text); text-align: left;
  transition: background .13s, border-color .13s;
}
.demo-row:hover { background: rgba(126, 148, 255, .13); border-color: var(--border-strong); }
.demo-badge {
  width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; flex-shrink: 0;
  font-size: 10px; font-weight: 700; letter-spacing: .3px; color: #fff;
}
.demo-badge.ow { background: linear-gradient(135deg, #6e8bff, #a86bff); }
.demo-badge.la { background: linear-gradient(135deg, #34e3b0, #1f9c86); }
.demo-badge.bu { background: linear-gradient(135deg, #ffc069, #e08a2e); }
/* Das Kontakt/Owner-Konto rendert sonst als durchsichtiges Kästchen. Bewusst
   violett-dominant und dunkler auslaufend — ein bloß gespiegelter .ow-Verlauf
   wäre auf 26x26 px nicht unterscheidbar. */
.demo-badge.co { background: linear-gradient(135deg, #a86bff, #6e60ff); }
.demo-role { font-size: 12.8px; font-weight: 600; }
.demo-mail { font-size: 11px; color: var(--muted); }
.demo-note { font-size: 11px; color: var(--muted); margin-top: 9px; line-height: 1.45; }

/* ---------------- Toasts ---------------- */
#toasts { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); display: grid; gap: 8px; z-index: 99; }
.toast {
  padding: 10px 15px; border-radius: 11px; font-size: 13px; border: 1px solid var(--border-strong);
  background: var(--card2); box-shadow: 0 10px 30px rgba(2, 5, 18, .5);
  animation: up .2s ease-out;
}
.toast.err { border-color: rgba(255, 123, 123, .4); }
@keyframes up { from { opacity: 0; transform: translateY(8px); } }

/* ---------------- Breakpoints ----------------
   Höhe zuerst: die Anmeldeansicht mit Demo-Block ist real rund 615 px hoch.
   Mit Marke, Wortmarke und Fußzeilen kommt die Seite auf 850–950 px — ohne
   diese Staffelung steht die Heldengeste auf einem 1366x768-Laptop halb
   außerhalb des Bilds. */
@media (max-height: 780px) {
  .brand-stack { --mk: clamp(80px, 9vmin + 26px, 112px); gap: 13px; }
  .lockline { display: none; }
}
@media (max-height: 640px) {
  .brand-stack { --mk: 82px; gap: 10px; }
}
@media (max-width: 900px) {
  /* Ein backdrop-filter muss neu gerechnet werden, sobald sich darunter etwas
     bewegt — auf Mittelklasse-Android der teuerste Posten der Seite. Während
     der Startsequenz fährt .stage-veil hoch, also wird er mobil ersatzlos
     gegen eine deckende Fläche getauscht. */
  .auth-card { backdrop-filter: none; background: rgba(17, 23, 45, .94); padding: 24px 20px 22px; }
  .stage-grid {
    background-image:
      repeating-linear-gradient(90deg, rgba(126, 148, 255, .045) 0 1px, transparent 1px 34px),
      repeating-linear-gradient(0deg,  rgba(126, 148, 255, .045) 0 1px, transparent 1px 34px);
  }
}
@media (max-width: 560px) {
  .brand-stack { --mk: clamp(84px, 22vw, 104px); gap: 14px; }
}

/* ---- ORNLA Ladeindikator -------------------------------------------------
   Das Markenzeichen, rotiert. Die verlaufende Strichstärke erzeugt beim
   Drehen die Bewegungsunschärfe — deshalb braucht es keine zweite Grafik.
   Regeln: brand/logo-styleguide.html Abschnitt 07. Nie im Header mitdrehen. */
.ornla-spin { animation: ornla-spin .9s linear infinite; }
@keyframes ornla-spin { to { transform: rotate(360deg); } }

/* ---- Reduzierte Bewegung -------------------------------------------------
   MUSS die letzte Regel der Datei bleiben: der Block setzt
   :root, .intro-short { --s: .001 }, und .intro-short hat dieselbe
   Spezifität wie :root — es entscheidet die Position.

   Vorher stand hier ein pauschales * { animation: none !important }. Das
   expandiert zu animation-name: none und hat damit auch den Ladeindikator
   erwischt: die nachfolgende Regel setzte nur noch die Dauer und konnte den
   Namen nicht zurückholen. Bei reduzierter Bewegung stand das Zeichen still
   und niemand konnte sehen, ob die Seite hängt. Styleguide 07 verlangt
   ausdrücklich das Gegenteil. */
@media (prefers-reduced-motion: reduce) {
  /* Statt Animationen zu killen, wird die Startsequenz auf Dauer ~0 gestellt:
     sie läuft ab, ist aber sofort fertig. Weil das Basis-CSS jedes Elements
     bereits den Ruhezustand beschreibt, kann dabei nichts unsichtbar
     hängenbleiben — die Blende landet bei stroke-dashoffset 0, die Marke ist
     vollständig sichtbar, Karte und Wortmarke stehen bei opacity 1. */
  :root, .intro-short { --s: .001; }

  /* Reine Deko, ersatzlos weg. */
  .ring { display: none; }

  /* Funktional, kein Schmuck: läuft weiter, nur langsamer (Styleguide 07). */
  .ornla-spin { animation: ornla-spin 2.4s linear infinite !important; }

  /* Zustandswechsel ohne Bewegung, aber nicht auf 0 — transitionend soll
     weiterhin feuern, damit künftige Logik daran nicht stirbt. */
  * { transition-duration: .01ms !important; }

  /* Ausnahme: die Passwortstärke transportiert Information, keinen Effekt. */
  .meter > i { transition-duration: .25s !important; }
}
