/* BioStudio AI — the one loading indicator (js/lib/bs-loader.js).
   <bs-loader size="56" layout="column" label="Loading" detail="3 of 5"></bs-loader>

   Every style animates only transform and opacity, so the browser runs it on the
   compositor thread and it keeps moving while the page's main thread is busy.
   Per-instance attributes (set by bs-loader.js, flipped live when an admin changes
   the style): data-s = effective style id, data-text = label style.
   Per-dot variables: --p order along the spiral 0..1, --a angle 0..1, --rn distance
   from centre 0..1, --ux/--uy unit vector outward, --ox/--oy centre relative to the
   dot (in mark sizes), --q/--q2 stable random 0..1. */

bs-loader {
  --sz: 20px;
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: calc(var(--sz) * 0.35 + 3px);
  max-width: 100%;
  font: inherit;
  line-height: 1.2;
  vertical-align: middle;
  box-sizing: border-box;
}
bs-loader[hidden] { display: none; }
bs-loader[data-layout="column"] {
  flex-direction: column;
  gap: 16px;
  text-align: center;
  font-size: clamp(13px, calc(var(--sz) * 0.2), 16px);
}
/* Fill the parent and centre in it (window placeholders, covers). */
bs-loader[fill] {
  display: flex;
  width: 100%;
  height: 100%;
  min-height: 120px;
  padding: 24px;
}
bs-loader *, bs-loader *::before, bs-loader *::after { box-sizing: border-box; }

bs-loader .bs-ld-mark {
  position: relative;
  flex: none;
  display: block;
  width: var(--sz);
  height: var(--sz);
  overflow: visible;
}
bs-loader .bs-ld-rot,
bs-loader .bs-ld-g,
bs-loader .bs-ld-ring,
bs-loader .bs-ld-halo,
bs-loader .bs-ld-whole {
  position: absolute;
  inset: 0;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}
bs-loader .bs-ld-g i {
  position: absolute;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  transform-origin: 50% 50%;
  /* The dots are the logo's own colours; no theme should repaint them. */
  forced-color-adjust: none;
}
bs-loader .bs-ld-ring,
bs-loader .bs-ld-halo { display: none; }
bs-loader .bs-ld-whole {
  background: url("/icons/logo-mark.svg") center / contain no-repeat;
}

bs-loader .bs-ld-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  min-width: 0;
}
bs-loader[data-layout="column"] .bs-ld-text { align-items: center; }
bs-loader .bs-ld-label { white-space: nowrap; opacity: 0.72; font-weight: 500; }
bs-loader .bs-ld-detail { font-size: 0.86em; opacity: 0.55; max-width: 38ch; overflow-wrap: anywhere; }
bs-loader .bs-ld-detail:empty { display: none; }
bs-loader[data-text="none"] .bs-ld-label { display: none; }

bs-loader .bs-ld-ell { display: none; }
bs-loader[data-text="dots"] .bs-ld-ell { display: inline; }
bs-loader .bs-ld-ell i { font-style: normal; opacity: 0.2; animation: bsLdEll 1.2s infinite; }
bs-loader .bs-ld-ell i::before { content: "."; }
bs-loader .bs-ld-ell i:nth-child(2) { animation-delay: 0.2s; }
bs-loader .bs-ld-ell i:nth-child(3) { animation-delay: 0.4s; }
@keyframes bsLdEll { 0%, 100% { opacity: 0.2; } 30% { opacity: 1; } }

bs-loader[data-text="shimmer"] .bs-ld-label {
  opacity: 1;
  background-image: linear-gradient(90deg, color-mix(in srgb, currentColor 45%, transparent) 0%, color-mix(in srgb, currentColor 45%, transparent) 35%, currentColor 50%, color-mix(in srgb, currentColor 45%, transparent) 65%, color-mix(in srgb, currentColor 45%, transparent) 100%);
  background-size: 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: bsLdShim 1.8s linear infinite;
}
@keyframes bsLdShim { from { background-position: 100% 0; } to { background-position: 0% 0; } }

@keyframes bsLdRot { to { transform: rotate(360deg); } }
@keyframes bsLdRotRev { to { transform: rotate(-360deg); } }

/* A · Steady spin ---------------------------------------------------------- */
bs-loader[data-s="spin"] .bs-ld-rot { animation: bsLdRot 1.8s linear infinite; }

/* B · Spin & breathe ------------------------------------------------------- */
bs-loader[data-s="breathe"] .bs-ld-rot { animation: bsLdRot 3.4s linear infinite; }
bs-loader[data-s="breathe"] .bs-ld-g { animation: bsLdBreathe 1.7s ease-in-out infinite; }
@keyframes bsLdBreathe { 0%, 100% { transform: scale(0.82); opacity: 0.68; } 50% { transform: scale(1); opacity: 1; } }

/* C · Navy spinner --------------------------------------------------------- */
bs-loader[data-s="navy"] .bs-ld-rot { animation: bsLdRot 1.15s linear infinite; }
bs-loader[data-s="navy"] .bs-ld-g i {
  background: var(--bs-ld-navy, var(--accent, #00376e)) !important;
  opacity: calc(0.1 + 0.9 * var(--a));
}
bs-loader[data-s="navy"] .bs-ld-whole {
  background: conic-gradient(from 0deg, color-mix(in srgb, var(--bs-ld-navy, var(--accent, #00376e)) 10%, transparent), var(--bs-ld-navy, var(--accent, #00376e)));
  -webkit-mask: url("/icons/logo-mark.svg") center / contain no-repeat;
  mask: url("/icons/logo-mark.svg") center / contain no-repeat;
}

/* D · Ring around the logo ------------------------------------------------- */
bs-loader[data-s="ring"] .bs-ld-rot { inset: 21%; animation: bsLdRot 8s linear infinite; }
bs-loader[data-s="ring"] .bs-ld-ring { display: block; animation: bsLdRot 1.3s linear infinite; }
bs-loader[data-s="ring"] .bs-ld-ring::before,
bs-loader[data-s="ring"] .bs-ld-ring::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - max(2px, calc(var(--sz) * 0.07))), #000 calc(100% - max(2px, calc(var(--sz) * 0.07)) + 0.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - max(2px, calc(var(--sz) * 0.07))), #000 calc(100% - max(2px, calc(var(--sz) * 0.07)) + 0.5px));
}
bs-loader[data-s="ring"] .bs-ld-ring::before { background: color-mix(in srgb, currentColor 16%, transparent); }
bs-loader[data-s="ring"] .bs-ld-ring::after {
  background: conic-gradient(from 0deg, transparent 0deg, var(--bs-ld-navy, var(--accent, #00376e)) 280deg, transparent 281deg);
}

/* E · Step turn ------------------------------------------------------------ */
bs-loader[data-s="step"] .bs-ld-rot { animation: bsLdStep 2.7s infinite; }
@keyframes bsLdStep {
  0%     { transform: rotate(0deg);   animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  22%    { transform: rotate(120deg); }
  33.33% { transform: rotate(120deg); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  55.33% { transform: rotate(240deg); }
  66.67% { transform: rotate(240deg); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  88.67% { transform: rotate(360deg); }
  100%   { transform: rotate(360deg); }
}

/* F · Sweep ---------------------------------------------------------------- */
bs-loader[data-s="sweep"] .bs-ld-mark {
  -webkit-mask-image: conic-gradient(from 0deg, rgba(0, 0, 0, 0.12), #000 86%, rgba(0, 0, 0, 0.12));
  mask-image: conic-gradient(from 0deg, rgba(0, 0, 0, 0.12), #000 86%, rgba(0, 0, 0, 0.12));
  animation: bsLdRot 1.2s linear infinite;
}
bs-loader[data-s="sweep"] .bs-ld-rot { animation: bsLdRotRev 1.2s linear infinite; }
bs-loader[data-s="sweep"] .bs-ld-g { animation: bsLdRot 14s linear infinite; }

/* G · Ripple --------------------------------------------------------------- */
bs-loader[data-s="ripple"] .bs-ld-rot { animation: bsLdRot 7s linear infinite; }
bs-loader[data-s="ripple"] .bs-ld-g i {
  animation: bsLdRipple 1.6s ease-in-out infinite;
  animation-delay: calc((var(--rn) - 1) * 1.6s);
}
@keyframes bsLdRipple { 0%, 70%, 100% { transform: scale(0.5); opacity: 0.4; } 35% { transform: scale(1.15); opacity: 1; } }

/* H · Twist ---------------------------------------------------------------- */
bs-loader[data-s="twist"] .bs-ld-rot { animation: bsLdRot 4s linear infinite; }
bs-loader[data-s="twist"] .bs-ld-g i {
  transform-origin: calc(var(--ox) * var(--sz)) calc(var(--oy) * var(--sz));
  animation: bsLdTwist 2.4s cubic-bezier(.45, 0, .55, 1) infinite;
}
@keyframes bsLdTwist { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(calc((1 - var(--rn)) * 170deg)); } }

/* I · Spiral draw ---------------------------------------------------------- */
bs-loader[data-s="draw"] .bs-ld-rot { animation: bsLdRot 9s linear infinite; }
bs-loader[data-s="draw"] .bs-ld-g i {
  animation: bsLdDraw 2.8s ease-out infinite both;
  animation-delay: calc(var(--p) * 1.12s);
}
@keyframes bsLdDraw {
  0% { opacity: 0; transform: scale(0.2); }
  8% { opacity: 1; transform: scale(1); }
  70% { opacity: 1; transform: scale(1); }
  80%, 100% { opacity: 0; transform: scale(0.2); }
}

/* J · Twinkle -------------------------------------------------------------- */
bs-loader[data-s="twinkle"] .bs-ld-rot { animation: bsLdRot 8s linear infinite; }
bs-loader[data-s="twinkle"] .bs-ld-g i {
  animation: bsLdTwinkle calc(0.9s + var(--q) * 1.3s) ease-in-out infinite;
  animation-delay: calc(var(--q2) * -2.2s);
}
@keyframes bsLdTwinkle { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.15; transform: scale(0.6); } }

/* K · Assemble ------------------------------------------------------------- */
bs-loader[data-s="assemble"] .bs-ld-rot { animation: bsLdRot 10s linear infinite; }
bs-loader[data-s="assemble"] .bs-ld-g i {
  animation: bsLdAssemble 3.4s cubic-bezier(.25, .7, .25, 1) infinite both;
  animation-delay: calc(var(--p) * 0.75s);
}
@keyframes bsLdAssemble {
  0% { opacity: 0; transform: translate(calc(var(--ux) * var(--sz) * 0.38 + (var(--q) - 0.5) * var(--sz) * 0.2), calc(var(--uy) * var(--sz) * 0.38 + (var(--q2) - 0.5) * var(--sz) * 0.2)) scale(0.3); }
  30%, 72% { opacity: 1; transform: translate(0px, 0px) scale(1); }
  92%, 100% { opacity: 0; transform: translate(calc(var(--ux) * var(--sz) * 0.38 + (var(--q) - 0.5) * var(--sz) * 0.2), calc(var(--uy) * var(--sz) * 0.38 + (var(--q2) - 0.5) * var(--sz) * 0.2)) scale(0.3); }
}

/* L · Glow ----------------------------------------------------------------- */
bs-loader[data-s="glow"] .bs-ld-rot { animation: bsLdRot 2.2s linear infinite; }
bs-loader[data-s="glow"] .bs-ld-halo {
  display: block;
  inset: -12%;
  border-radius: 50%;
  background: conic-gradient(#f97316, #db2777, #753ded, #226be9, #06b6d4, #1bbe82, #c38828, #f97316);
  filter: blur(calc(var(--sz) * 0.2));
  opacity: 0.3;
  animation: bsLdRot 3s linear infinite, bsLdHalo 1.8s ease-in-out infinite;
}
@keyframes bsLdHalo { 0%, 100% { opacity: 0.12; } 50% { opacity: 0.4; } }

/* Off-screen loaders stop animating (bs-loader.js sets data-paused). */
bs-loader[data-paused] *,
bs-loader[data-paused] { animation-play-state: paused !important; }

/* Reduced motion: a still logo that only breathes in opacity. */
@media (prefers-reduced-motion: reduce) {
  bs-loader *, bs-loader *::before, bs-loader *::after { animation: none !important; }
  bs-loader .bs-ld-mark { animation: bsLdPulse 2.4s ease-in-out infinite !important; }
  bs-loader[data-s="navy"] .bs-ld-g i { opacity: 1; }
}
@keyframes bsLdPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }

/* ------------------------------------------------------------------------
   Mini loader for places that cannot hold an element (a ::before on a status
   line, a legacy ring-spinner class). It follows the admin's small style through
   the custom properties below, which bs-loader.js keeps in step with
   html[data-bs-loader-small]. A site uses it like this:

     .x-spinner { width: 14px; height: 14px; border: 0; border-radius: 0;
       background: var(--bs-mini-bg); -webkit-mask: var(--bs-mini-mask);
       mask: var(--bs-mini-mask); animation: var(--bs-mini-anim); }

   Ring, Sweep and Glow need extra elements, so a mini loader shows Steady spin
   for them.
   ------------------------------------------------------------------------ */
:root {
  --bs-mini-bg: url("/icons/logo-mark.svg") center / contain no-repeat;
  --bs-mini-mask: none;
  --bs-mini-anim: bsLdRot 1.8s linear infinite;
}
html[data-bs-loader-small="breathe"] { --bs-mini-anim: bsLdRot 3.4s linear infinite, bsLdMiniFade 1.7s ease-in-out infinite; }
html[data-bs-loader-small="step"] { --bs-mini-anim: bsLdStep 2.7s infinite; }
html[data-bs-loader-small="navy"] {
  --bs-mini-bg: conic-gradient(from 0deg, color-mix(in srgb, var(--bs-ld-navy, var(--accent, #00376e)) 10%, transparent), var(--bs-ld-navy, var(--accent, #00376e)));
  --bs-mini-mask: url("/icons/logo-mark.svg") center / contain no-repeat;
  --bs-mini-anim: bsLdRot 1.15s linear infinite;
}
@keyframes bsLdMiniFade { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  :root, html[data-bs-loader-small] { --bs-mini-anim: bsLdPulse 2.4s ease-in-out infinite; }
}

/* ------------------------------------------------------------------------
   Busy badge: a small floating loader shown while the page thread is lagging
   (js/lib/bs-loader.js startBusyBadge). Pointer-transparent, never traps focus.
   ------------------------------------------------------------------------ */
.bs-busy-badge {
  position: fixed;
  right: 16px;
  bottom: 56px;
  z-index: 2147483000;
  display: flex;
  align-items: center;
  padding: 8px 14px 8px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface, #ffffff) 94%, transparent);
  color: var(--ink, #1c2735);
  border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
  box-shadow: 0 8px 24px rgba(8, 18, 32, 0.18);
  font: 500 13px/1.2 Manrope, system-ui, sans-serif;
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.bs-busy-badge.is-on { opacity: 1; transform: none; }
.bs-busy-badge[hidden] { display: none; }
