/*
  TALVAXO Beta 10.0 Platform Finish II (Part B/C/D, "Global Page-Change Loading Experience"):
  reusable branded transition/loader overlay, shared by (1) normal page-to-page navigation, (2) the
  Login -> Dashboard signature transition (see login.css for the login-specific size override), and
  (3) genuinely long operations (Garmin sync, initial Plan generation) via .talvaxo-loader--modal.

  Mandat-Vorgaben, wortwoertlich umgesetzt:
  - "no generic spinner", "do not rotate the logo" -> KEIN Spinner-Element, KEINE rotate()-Animation
    irgendwo in dieser Datei. Bewegung ist ausschliesslich opacity/transform:scale (Lichtpuls, siehe
    talvaxoLoaderPulse), nie eine Drehung.
  - "clearly noticeable, not a tiny 16px spinner, not an oversized logo covering the screen" -> Marke
    bei 88px Container/56px Bildhoehe (Login-Variante groesser, siehe login.css), zentriert auf einem
    gedimmten Scrim, nicht vollflaechig.
  - "use opacity/transform, no new Canvas" -> reines CSS, keine zusaetzliche <canvas>.
  - "immediate appearance, no artificial minimum duration" -> keine transition-delay vor dem
    Erscheinen; verschwindet sobald der echte Seiteninhalt das Skeleton ersetzt hat, kein
    kuenstliches Zeitfenster (siehe page-loader.js).

  Nutzerauftrag 2026-08-30 ("Menue-Balken soll immer sichtbar sein, auch wenn der Loader
  erscheint"): der Loader deckt auf Desktop-Breite (>900px, derselbe Breakpoint wie die
  Off-Canvas-Sidebar in components.css) bewusst NICHT die volle Breite ab, sondern beginnt erst
  nach var(--nav-width) — die Sidebar selbst bleibt dadurch unverdeckt UND ungedimmt (der Scrim
  rendert dort gar nicht erst). Auf Mobile (<=900px) ist die Sidebar ohnehin ein per Hamburger
  geoeffnetes Off-Canvas-Drawer, standardmaessig nicht sichtbar — dort deckt der Loader wieder die
  volle Breite ab, aber der Hamburger-Button selbst bekommt einen hoeheren z-index als der Loader
  (siehe components.css .nav-hamburger), bleibt also als der "immer sichtbare Menue-Zugang" auf
  jeder Bildschirmgroesse klickbar.
*/

.talvaxo-loader {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  left: var(--nav-width);
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  /* Nutzerauftrag 2026-08-30: --loader-page-scrim statt --loader-scrim (deutlich transparenter,
     schwaecherer Blur) — die Welle soll waehrend des Ladens sichtbar/hervortreten, nicht hinter
     einem dunklen Scrim verschwinden (siehe --loader-page-scrim-Begruendung in tokens.css).
     Nutzerauftrag 2026-08-30 (Folgekorrektur): backdrop-filter komplett entfernt — der Loader
     selbst liegt ueber der Welle (z-index:500 vs. 100, siehe unten), ein backdrop-blur auf ihm
     haette die davor liegende Welle trotz hohem z-index weiterhin milchig/unscharf gemacht. Die
     reduzierte Scrim-Deckkraft allein reicht fuer Kontrast zum Text/Logo. */
  background: var(--loader-page-scrim);
  opacity: 1;
  transition: opacity 220ms ease;
}
@media (max-width: 900px) {
  .talvaxo-loader { left: 0; }
}
.talvaxo-loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}
/* Nach Ablauf der Opacity-Transition ganz aus dem Layout/A11y-Baum entfernen (page-loader.js setzt
   display:none per Timeout passend zur 220ms-Dauer) — verhindert, dass ein unsichtbares, aber
   weiterhin fokussierbares/klickbares Overlay liegen bleibt. */
.talvaxo-loader.is-removed {
  display: none;
}

/* Nutzerauftrag 2026-08-30 ("Hintergrundwelle mit Partikeln in den Loading-Screen integrieren —
   die Welle tritt beim Erscheinen des Loaders staerker hervor und geht danach wieder zurueck in
   den Hintergrund"): #wave-canvas/#wave-particles sind spaetere Geschwister von
   #talvaxo-page-loader im selben <body> (siehe pages/*.html — Loader-Markup steht vor den
   Canvas-Elementen), deshalb per allgemeinem Geschwister-Selektor ohne jede JS-Aenderung
   erreichbar. Reiner CSS-Filter auf dem <canvas>-ELEMENT selbst (nicht die Rendering-Engine, siehe
   wave-canvas-engine.js — die bleibt unangetastet) — solange der Loader sichtbar ist (kein
   .is-hidden), wirkt die Welle heller/satter; sobald er verschwindet, faedet sie weich zurueck auf
   ihren normalen Ambient-Zustand. */
/* Nutzerauftrag 2026-08-30 (mehrere Korrekturrunden — zusammengefasst):
   1. scale()/clip-path/erhoehter z-index wurden alle wieder verworfen: clip-path blendet den
      geclippten Bereich VOLLSTAENDIG aus (keine <canvas> kann gleichzeitig "normal" UND
      "geboostet" an verschiedenen Stellen aussehen, es gibt nur die eine Ebene) — das liess die
      Sidebar komplett ohne Welle erscheinen und riss durch die Transform/Clip-Interaktion
      zusaetzlich eine Luecke auf.
   2. Danach wurde der Boost selbst zu stark reduziert (1.45->1.18, scale komplett entfernt) — das
      nahm dem gesamten Effekt ("Welle kommt spuerbar naeher, wird groesser, geht wieder zurueck")
      die Wirkung, obwohl NUR die Sidebar das Problem war, nicht der Effekt an sich. Explizites
      Nutzer-Feedback: der urspruengliche starke Effekt soll erhalten bleiben.
   FINALE LOESUNG: der volle, starke Boost (Helligkeit/Saettigung/Kontrast + scale) ist wieder da —
   die Sidebar-Bleed-Through-Problematik wird NICHT an der Welle geloest, sondern separat an der
   Sidebar selbst (siehe Regel weiter unten: ihre eigene Deckkraft steigt waehrend des Ladens
   moderat, gerade genug, um den staerkeren Hintergrund wieder auszugleichen, bleibt dabei aber klar
   durchscheinend/milchig statt deckend). */
/* Nutzerauftrag 2026-08-30 ("Welle soll wirklich in den Vordergrund kommen, alle Seitenelemente
   sollen dahinter sein, ausser die Sidebar"): z-index 0 -> 100 waehrend des Ladens — hoeher als
   .app-shell (z-index:1, traegt Karten/Hauptinhalt), damit die Welle sichtbar VOR den Panels
   liegt. Anders als beim vorherigen, verworfenen Versuch wird die Sidebar diesmal NICHT per
   clip-path aus der Welle herausgeschnitten (das hatte die Welle dort komplett ausgeblendet und
   eine Luecke erzeugt) — stattdessen bekommt .app-nav einfach selbst einen hoeheren z-index
   (siehe components.css) UND gleicht die staerkere Welle durch ihre eigene, leicht angehobene
   Deckkraft aus (0.78 -> 0.90, siehe Regel weiter unten) — beides zusammen haelt sie zuverlaessig
   "immer im Vordergrund", ohne den Milchglas-Charakter zu verlieren. */
/* Nutzerauftrag 2026-08-30 ("stottert immer noch trotz defer-Fix") — die Long-Tasks-API zeigte
   NULL blockierende Haupt-Thread-Tasks waehrend einer echten, reproduzierten Navigation trotz
   sichtbarem Freeze im Bildschirm-Mitschnitt. Das deutet auf einen GPU/Compositor-seitigen Stall
   hin, nicht auf blockierendes JS — "will-change" + "transform:scale()" auf einem vollflaechigen,
   staendig per JS neu gezeichneten <canvas> zwingt den Browser, dafuer fruehzeitig eine eigene,
   potenziell teure Compositor-Ebene anzulegen/zu skalieren, was selbst bei leerem Haupt-Thread zu
   sichtbaren Rendering-Ausssetzern fuehren kann. Beides entfernt — nur noch der reine
   Helligkeits-/Saettigungs-Filter bleibt (deutlich guenstiger, keine Ebenen-Neuberechnung, keine
   Skalierung eines grossflaechigen Elements). Weniger dramatisches "Naeherkommen" als vorher, aber
   real testbar/reversibel, falls es das GPU-Problem tatsaechlich behebt. */
/* Mandat "FINAL CLOSURE PASS" Teil D (Nutzeranweisung: "wir bewegen uns in die Welle hinein",
   Zoom/Tiefe muss echt sichtbar sein): transform:scale() war hier bereits einmal drin und wurde
   entfernt, WEIL zur damaligen Zeit vermutet wurde, es sei die Ursache eines im Bildschirm-Mitschnitt
   sichtbaren Freezes (siehe Kommentar oben, "GPU/Compositor-seitiger Stall"). Diese Vermutung wurde
   NIE tatsaechlich bestaetigt — die spaeter gefundene, echte Ursache lag vollstaendig ausserhalb des
   Renderings (Windows/Chrome-'localhost'-Verbindungsverzoegerung + ein einzelnstraengiger Dev-Server,
   siehe docs/reports/TALVAXO_TRANSITION_PERFORMANCE_BASELINE.md) — beides seither behoben. scale()
   wird hier deshalb erneut eingefuehrt, diesmal aber GEMESSEN statt vermutet (rAF-Kadenz + Long
   Animation Frames API, siehe qa/performance-final/). will-change ist bewusst NUR ueber denselben
   :not(.is-hidden)-Selektor aktiv (nicht dauerhaft) — die Compositor-Ebene wird nur waehrend des
   tatsaechlichen Uebergangs offengehalten, nicht permanent (das waere selbst ein bekanntes
   Performance-Antipattern). Kein Konflikt mit der Sidebar-immer-sichtbar-Anforderung: das war
   nachweislich (siehe Kommentar-Historie oben) ein clip-path-Problem, nicht ein scale-Problem — hier
   wird kein clip-path verwendet. */
.talvaxo-loader:not(.is-hidden) ~ #wave-canvas,
.talvaxo-loader:not(.is-hidden) ~ #wave-particles {
  filter: brightness(1.45) saturate(1.3);
  transform: scale(1.15);
  z-index: 100;
  will-change: filter, transform;
}
#wave-canvas, #wave-particles {
  transition: filter 1300ms ease, transform 1300ms ease;
}
@media (prefers-reduced-motion: reduce) {
  #wave-canvas, #wave-particles { transition: filter 120ms linear; transform: none !important; }
}

/* Nutzerauftrag 2026-08-30 ("waehrend die Seite im Hintergrund laedt, soll sie ausgeblurt werden"):
   echtes filter:blur() auf .app-main SELBST — bewusst NICHT backdrop-filter auf dem Loader/Scrim
   (das war der 2026-08-30 fruehen-Runde-Fund weiter unten bei der Depth-Vignette: backdrop-filter
   muss pro Frame alles NEU weichzeichnen, was optisch darunterliegt, inklusive der animierten Welle
   — teuer UND haette die eigentlich bewusst scharfe/geboostete Welle mitverschwommen). filter:blur()
   direkt auf .app-main betrifft NUR den (waehrend des Ladens praktisch eingefrorenen, nicht mehr
   aktiv neu gezeichneten) Seiteninhalt selbst — der Browser blurred diese Ebene EINMAL beim
   Uebergang, kein Neuberechnen pro Frame wie bei backdrop-filter. Sidebar (jetzt ausserhalb
   .app-shell, siehe Sidebar-Stacking-Context-Fix) und Welle bleiben davon unberuehrt/scharf — nur
   der eigentliche, gerade ladende Seiteninhalt verschwimmt. */
.talvaxo-loader:not(.is-hidden) ~ .app-shell .app-main {
  filter: blur(7px);
}
.app-main {
  transition: filter 320ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .app-main { transition: filter 80ms linear; }
}

/* Depth-Vignette (Mandat Teil D §24, "blur/depth impression... true CSS blur NOT mandatory"):
   echtes backdrop-filter:blur() ist bekanntlich teuer (muss den kompletten dahinterliegenden Inhalt
   pro Frame neu weichzeichnen) — stattdessen ein rein opazitaetsbasierter radialer Verlauf, der zum
   Rand hin dunkler wird. Kompositor-guenstig (nur opacity, keine Filter-/Blur-Neuberechnung), erzeugt
   aber denselben "Fokus auf die Mitte, Raender treten zurueck"-Tiefeneindruck. Liegt UNTER der Marke
   (z-index unten), UEBER der Welle. */
.talvaxo-loader-depth-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 0%, transparent 35%, rgba(2, 6, 14, 0.35) 100%);
  opacity: 0;
  transition: opacity 480ms ease;
}
.talvaxo-loader:not(.is-hidden) .talvaxo-loader-depth-vignette {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .talvaxo-loader-depth-vignette { transition: opacity 80ms linear; }
}

/* Nutzerauftrag 2026-08-30 ("Sidebar soll IMMER im Vordergrund sein und IMMER den milchigen Effekt
   haben — bloss nicht mehr durchsichtig wirken"): die Sidebar bleibt bewusst Milchglas (kein voller
   Deckungsgrad, siehe vorherige, bewusst zurueckgenommene "fast deckend"-Version) — nur ihre eigene
   Alpha steigt waehrend des Ladens leicht an (0.78 -> 0.90), gerade genug, um den jetzt wieder
   staerkeren Wellen-Boost dahinter auszugleichen, ohne den Frostglas-Charakter zu verlieren. */
/* Beta 10.0 FINAL CLOSURE PASS: Selektor von ".app-shell .app-nav" auf "#app-nav-slot .app-nav"
   korrigiert — .app-nav liegt seit dem Sidebar-Stacking-Context-Fix (siehe pages/*.html) nicht mehr
   in .app-shell, sondern in #app-nav-slot, einem direkten Geschwister von .talvaxo-loader (~
   erreicht NUR direkte Geschwister, nicht deren Kinder — .app-nav selbst ist ein KIND von
   #app-nav-slot, siehe assets/js/nav.js slot.innerHTML, deshalb weiterhin ein Nachfahre-Selektor
   NACH dem Geschwister-Sprung noetig, kein reiner Geschwister-Selektor wie bei der Welle oben).
   Ohne diese Korrektur haette der Selektor nach dem DOM-Umbau nie wieder gegriffen (0 Treffer,
   stillschweigend wirkungslos). */
.talvaxo-loader:not(.is-hidden) ~ #app-nav-slot .app-nav {
  background: linear-gradient(180deg, var(--nav-bg-top-loading, var(--nav-bg-top)) 0%, var(--nav-bg-bottom-loading, var(--nav-bg-bottom)) 100%);
}
.app-nav {
  transition: background 1300ms ease;
}

/* Nutzerauftrag 2026-08-30 ("Loader soll so gross sein wie die Login->Dashboard-Transition"):
   108px Bildhoehe beibehalten (siehe Historie unten) — der Container ist seit dem v10.1(12)
   Logo-Wechsel (naechster Kommentar) nicht mehr fix 168x168px quadratisch, sondern sizes-to-content
   (die Marke ist jetzt deutlich breiter als hoch, kein Quadrat mehr). */
/* Nutzerauftrag v10.1(12) ("Beim Ladeeffekt soll auch das neue Logo verwendet werden... nimm das
   neue Talvaxologo und lass die vier Kreise nur um das grosse X kreisen"): vorher zeigte die Marke
   ausschliesslich das freigestellte X-Icon (talvaxo-x.png, 566x379) — inzwischen zeigt Nav/Footer/
   Sidebar ueberall das neue, echte Wordmark-Lockup (assets/brand/talvaxo-logo.png, "TALVAXO" mit
   dem X als 6. Buchstabe), nur der Loader hinkte hier noch hinterher. Einfach das ganze Lockup an
   Stelle des X-Icons einzusetzen haette die vier Ringe mittig um die GESAMTE (viel breitere) Marke
   gelegt, nicht mehr um das X selbst -- Nutzerwunsch war explizit "die Ringe nur um das grosse X
   kreisen lassen". Deshalb: .talvaxo-loader-mark zeigt jetzt das volle Lockup, aber
   .talvaxo-loader-rings4 ist NICHT mehr zentrisch (inset:-21px auf allen Seiten), sondern gezielt
   auf die X-Position INNERHALB des Lockups versetzt. Position praezise vermessen (Alpha-Kanal-
   Spaltendichte von assets/hero/talvaxo-wordmark-large.png, selbe Wortmarke/selbes Seitenverhaeltnis
   wie talvaxo-logo.png): X-Bounding-Box bei x=425-699 von 800px, y=12-270 von 286px -- bei 108px
   Bildhoehe (Skalierung 0.3776) entspricht das x=160.5-263.9px, y=4.5-101.9px innerhalb der Marke.
   Die untenstehenden 138/16/-17/-16px sind dieser Vermessung + einem Halo-Rand entnommen (kein
   Raten). */
.talvaxo-loader-mark {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.talvaxo-loader-mark img {
  position: relative;
  z-index: 2;
  display: block;
  width: auto;
  height: 108px;
  object-fit: contain;
}
/* Nutzerauftrag 2026-08-30 ("genau derselbe Effekt wie beim Seitenwechsel von Login zu
   Dashboard"): 1:1 aus .login-signature-logo-ring/-contour in login.css uebernommen (identische
   Werte, identische inset/Farbstops — kein eigener CSS-Blur-Filter mehr, die Weichheit kommt wie
   dort ausschliesslich aus dem radialen Farbverlauf selbst). Login nutzt eine EINMALIGE
   Forwards-Animation (der Moment kommt genau einmal vor); der Seiten-Loader kann beliebig lange
   sichtbar bleiben, deshalb pulsiert dieselbe visuelle Form hier in einer Endlosschleife statt
   einmalig einzublenden — das ist der einzige Unterschied, kein optischer.
   Zwei Ebenen wie beim Login: .talvaxo-loader-ring = der weiche Glow-Blob (radial-gradient,
   inset:-30px), .talvaxo-loader-contour = der duenne, scharfe Konturring (conic-gradient +
   Masken-Ring, inset:-14px). */
/* Nutzerauftrag 2026-08-30 ("Kreis etwas groesser, etwas dicker, genau die Farben unserer
   Plattform, Glow-Effekt mit schoenerem weiteren Verlauf"): Konturring vergroessert (-14px ->
   -22px) und verdickt (Maskenschwelle 2.5px -> 5px), deckender (0.7 -> 0.88) fuer klar erkennbare
   Marken-Cyan/Blau/Violett statt eines duennen, blassen Strichs. Glow-Blob mit mehr Farbstops fuer
   einen sanfteren, WEITEREN Verlauf (bis 92% statt 78%, groesserer inset -30px -> -42px) statt
   eines vergleichsweise abrupten Randes. */
/* Nutzerauftrag 2026-08-30 (Runde 2):
   - "Ring zeigt Pixel/nicht schoen kreisfoermig" — die Maske hatte zwei Farbstops auf EXAKT
     derselben Position (0px Uebergangsbreite), das erzeugt zwangslaeufig eine harte, ungeglaettete
     Kante. Jetzt ein echter, mehrere Pixel breiter Verlaufsbereich zwischen den Stops -> weiche,
     rund wirkende Kontur statt sichtbarer Treppenstufen.
   - "Blur zu hart, groesser aber transparenter mit Verlauf" — Glow-Blob mit niedrigerer
     Spitzenopazitaet (0.46 -> 0.30) und einem noch weiter hinausgezogenen, feineren Verlauf statt
     einer vergleichsweise kompakten/intensiven Flaeche. */
/* Nutzerauftrag 2026-08-30 (Runde 3, "Blur-Effekt soll so gross wie der Ring sein, nicht
   ausserhalb sichtbar"): inset von -58px auf -20px verkleinert, knapp innerhalb des Konturrings
   (inset:-22px, siehe .talvaxo-loader-contour unten) — selbst am Pulse-Hoehepunkt (scale 1.04)
   bleibt der Glow dadurch innerhalb der vom Konturring definierten Flaeche statt sichtbar
   darueber hinauszuragen. */
/* Nutzerauftrag 2026-08-30 ("dieser Blueheffekt soll wieder verschwinden — wirklich nur das Logo
   und die vier Ringe, die es umkreisen"): der weiche, pulsierende Glow-Blob hinter der Marke
   (radial-gradient, siehe Kommentar-Historie oben) ist jetzt bewusst ausgeblendet — die Marke
   traegt seither auch keinen eigenen drop-shadow-Bloom mehr (siehe .talvaxo-loader-mark img oben).
   display:none statt Regel-Loeschung, damit die Historie/Begruendung oben nicht verwaist. */
.talvaxo-loader-ring {
  display: none;
}

/* Beta 10.0 FINAL CLOSURE PASS ("Loader-Animationskonzept", Nutzerauftrag) — ersetzt den bisherigen
   EINEN Konturring durch vier duenne, eng gestaffelte, gegenlaeufig rotierende Ringe mit hellem
   "Kometenkopf" statt gleichmaessiger Helligkeit ("Variante 2 — Dicht"). Gewaehlt nach einem echten
   Vergleich mehrerer Richtungen in einem separaten Vorschau-Artifact (Orbit-Punkte, Wellenform-Ringe,
   ein echter 3D-Tilt via CSS perspective, mehrere 2D-Abwandlungen) — diese hier war die vom Nutzer
   bevorzugte. Logo bleibt unrotiert (Mandat "do not spin the logo" weiterhin in Kraft — <img> liegt
   als eigenes, unrotiertes Element bei z-index:2 darueber); rotiert wird ueber die eigenstaendige
   CSS-Property "rotate" (nicht "transform: rotate()"), damit es nicht mit der scale()-Atmen-Animation
   des gemeinsamen Wrappers kollidiert (dieselbe Technik wie beim vorherigen Konturring).
   talvaxo-loader-rings4 = gemeinsamer Wrapper (atmet als Ganzes, sehr langsam) — talvaxo-loader-ring4-N
   = je ein Ring darin (eigene Rotation, eigenes Tempo/Richtung/Farbe). Insets sind relativ zum
   210x210px-Wrapper (168px Marke + 2x21px durch dessen eigenen inset:-21px) berechnet, nicht zur
   168px-Marke direkt. */
.talvaxo-loader-rings4 {
  position: absolute;
  /* siehe Vermessungs-Kommentar bei .talvaxo-loader-mark oben -- kein inset:-21px auf allen Seiten
     mehr (das war fuer die zentrische X-Icon-Marke korrekt, nicht fuer das jetzt viel breitere
     Lockup). Ergibt einen ca. 148x141px Ring-Wrapper, mittig um die vermessene X-Box plus Halo-Rand. */
  left: 138px; right: 16px; top: -17px; bottom: -16px;
  animation: talvaxoLoaderRingsBreathe 4400ms ease-in-out infinite;
}
@keyframes talvaxoLoaderRingsBreathe {
  0%, 100% { transform: scale(0.97); }
  50% { transform: scale(1.03); }
}
.talvaxo-loader-ring4 {
  position: absolute; inset: 0; border-radius: 50%;
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 3px), #000 100%);
          mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 3px), #000 100%);
}
/* Insets proportional zum neuen, kleineren ~148x141px Ring-Wrapper skaliert (Faktor ~0.688 gegenueber
   dem vorherigen 210px-Wrapper: 36/24/12/0 -> 25/17/8/0), gleiche relative Ring-Abstaende/-Staffelung
   wie zuvor. */
.talvaxo-loader-ring4-1 {
  inset: 25px;
  background: conic-gradient(from 0deg, transparent 0deg, transparent 10deg, rgba(0,229,255,0.6) 40deg, rgba(0,229,255,0.6) 170deg, #D6FEFF 190deg, rgba(0,229,255,0.6) 200deg, transparent 220deg, transparent 360deg);
  filter: drop-shadow(0 0 2px rgba(0,229,255,0.55));
  animation: talvaxoLoaderRingSpin 4200ms linear infinite;
}
.talvaxo-loader-ring4-2 {
  inset: 17px;
  background: conic-gradient(from 90deg, transparent 0deg, transparent 10deg, rgba(0,122,255,0.55) 35deg, rgba(0,122,255,0.55) 140deg, #D6E9FF 158deg, rgba(0,122,255,0.55) 168deg, transparent 185deg, transparent 360deg);
  filter: drop-shadow(0 0 2px rgba(0,122,255,0.5));
  animation: talvaxoLoaderRingSpin 3200ms linear infinite reverse;
}
.talvaxo-loader-ring4-3 {
  inset: 8px;
  background: conic-gradient(from 180deg, transparent 0deg, transparent 8deg, rgba(122,92,255,0.5) 28deg, rgba(122,92,255,0.5) 110deg, #EFEAFF 125deg, rgba(122,92,255,0.5) 133deg, transparent 148deg, transparent 360deg);
  filter: drop-shadow(0 0 2px rgba(122,92,255,0.45));
  animation: talvaxoLoaderRingSpin 5600ms linear infinite;
}
.talvaxo-loader-ring4-4 {
  inset: 0px;
  background: conic-gradient(from 270deg, transparent 0deg, transparent 8deg, rgba(0,229,255,0.35) 25deg, rgba(0,229,255,0.35) 85deg, #E8FEFF 96deg, rgba(0,229,255,0.35) 102deg, transparent 112deg, transparent 360deg);
  filter: drop-shadow(0 0 2px rgba(0,229,255,0.35));
  animation: talvaxoLoaderRingSpin 6800ms linear infinite reverse;
}
@keyframes talvaxoLoaderRingSpin {
  from { rotate: 0deg; }
  to { rotate: 360deg; }
}

.talvaxo-loader-text {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: center;
  max-width: 280px;
}

.talvaxo-loader--error .talvaxo-loader-mark img { filter: none; }
.talvaxo-loader--error .talvaxo-loader-ring { animation: none; opacity: 0.4; background: var(--color-warning); }
.talvaxo-loader--error .talvaxo-loader-rings4 { animation: none; }
.talvaxo-loader--error .talvaxo-loader-ring4 { animation: none; opacity: 0.4; background: var(--color-warning); filter: none; }
.talvaxo-loader-retry {
  font: inherit;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--talvaxo-blue);
  background: none;
  border: 1px solid var(--talvaxo-blue);
  border-radius: 999px;
  padding: 8px 18px;
  cursor: pointer;
}
.talvaxo-loader-retry:hover { background: var(--surface-interactive); }
.talvaxo-loader-retry:focus-visible { outline: 2px solid var(--talvaxo-blue); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .talvaxo-loader { transition: opacity 80ms linear; }
  .talvaxo-loader-ring { animation: none; opacity: 0.55; }
  .talvaxo-loader-rings4 { animation: none; }
  .talvaxo-loader-ring4 { animation: none; opacity: 0.5; }
}
