/*
 * Busy and loading feedback, shared by EVERY surface.
 *
 * WHY THIS FILE EXISTS RATHER THAN A STYLE BLOCK: `.is-loading` is added by
 * public/assets/js/btn-busy.js, which can run on any page, so by the house rule
 * its styles have to live in a stylesheet every surface can load. It did not.
 * It was defined TWICE, once in AdminShell and once in MastersShell, character
 * for character, and was absent from TeamShell, KioskShell and GnaShell. So a
 * button on My Shifts or the kiosk that called AufgussBtn.busy() went
 * transparent with no spinner: the label vanished and nothing replaced it,
 * which looks exactly like a broken button.
 *
 * check-shared-css.mjs did not catch it because it only policed a fixed list of
 * component prefixes (hb-, mde-, k- and so on) and a state class named
 * `is-loading` matched none of them. The gate has been widened to police any
 * class a shared script actually emits, which is the rule that was meant to be
 * enforced all along.
 */

/* The label goes invisible and a spinner ring in the control's own foreground
   colour sits centred. No size change, so nothing reflows around it.

   THE TERMINAL FALLBACK IS `CanvasText`, NOT `currentColor`. currentColor
   cannot work here and never could: it resolves against the element's own
   `color`, which the line below has just set to transparent, so the ring came
   out rgba(0,0,0,0). Label hidden AND spinner invisible: the button simply
   goes blank, which reads as broken rather than busy.

   It only bites where neither --btn-fg nor --ink is defined, which the five
   shells all avoid (the kiosk gets --ink from kiosk.css, on body). The pages
   at risk are the self-contained ones that pull this stylesheet in without a
   theme underneath it, /kiosk/enrol being exactly that. Measured, not assumed:
   with both tokens unset the computed ::after border-top-color is
   rgba(0, 0, 0, 0). CanvasText follows color-scheme, so it stays readable on a
   dark kiosk and a light page alike, and never resolves to nothing. Surfaces
   that want an exact match still set --btn-fg, which is what a coloured button
   with a white label needs: --ink there is the PAGE's foreground, not the
   button's, so on a light theme it would draw a near-black ring on ember. */
.is-loading { color: transparent !important; pointer-events: none; cursor: default; position: relative; }
.is-loading > * { visibility: hidden; }
.is-loading::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 1.1em; height: 1.1em; box-sizing: border-box;
  border: 2px solid color-mix(in srgb, var(--btn-fg, var(--ink, CanvasText)) 28%, transparent);
  border-top-color: var(--btn-fg, var(--ink, CanvasText)); border-radius: 50%;
  transform: translate(-50%, -50%);
  animation: bms-btn-spin .6s linear infinite;
}
@keyframes bms-btn-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }

/*
 * NAVIGATION PROGRESS.
 *
 * These are server-rendered pages, so a slow one leaves the browser sitting on
 * the OLD page with nothing happening until the new HTML arrives. On the admin
 * console that is most visible on the accounting card: every D1 query from the
 * Worker is a real network round trip, several run in sequence in page
 * frontmatter, and a cold start sits on top. Locally the same page renders in
 * 80ms, which is exactly why it never got noticed while building.
 *
 * A bar at the top of the viewport, plus a busy state on the thing that was
 * clicked. Fixed position and transform-only animation, so it cannot affect
 * layout or scroll height (see the mobile shell scroll trap).
 */
.nav-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 3px;
  z-index: 2147483000;               /* above every dialog and sticky bar */
  background: transparent;
  pointer-events: none;
  opacity: 0;
  transition: opacity .18s ease-out;
}
.nav-progress.is-on { opacity: 1; }
.nav-progress::after {
  content: ""; display: block; height: 100%; width: 100%;
  transform-origin: 0 50%;
  transform: scaleX(0);
  background: var(--ember, var(--accent, currentColor));
  /* Creeps towards 90% and waits: the page arrives when it arrives, and a bar
     that reaches the end and stops is a worse lie than one that is still
     moving. */
  animation: bms-nav-creep 12s cubic-bezier(.15, .85, .3, 1) forwards;
}
@keyframes bms-nav-creep {
  0%   { transform: scaleX(0); }
  40%  { transform: scaleX(.55); }
  75%  { transform: scaleX(.8); }
  100% { transform: scaleX(.93); }
}

/* A clicked link that is navigating: dim it so it reads as accepted. Not the
   spinner treatment, because a nav link is often an icon or a whole card and
   hiding its contents mid-navigation is jarring. */
.is-navigating { opacity: .55; transition: opacity .12s ease-out; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .is-loading::after { animation-duration: 1.5s; }
  /* Still shows progress, just without the crawl. */
  .nav-progress::after { animation: none; transform: scaleX(.35); }
}
