/* 00 — the loading state and the reveal.
 *
 * Replaces css/gate.css. The loading state is exe.cute's own monogram — the "ex" — standing in
 * for the clone's 140x16 pill. There is deliberately no text, no percentage and no wordmark: the
 * whole loading state is one mark on a flat field, and its only job is to say "something is
 * arriving" without asking to be read. The mark fills left-to-right like the bar it replaced —
 * see the clipPath in index.html, whose rect loader.js widens with the real manifest progress.
 *
 * The stacking order the entrance depends on:
 *   .page          static, no z-index      the whole document, hidden while loading
 *   .gl-overlay    z 30                    the halftone mask (js/gl/mask.js)
 *   .loader        z 40                    the mark
 *
 * STACKING TRAP, inherited from the clone and worth naming because it is silent: .page creates no
 * stacking context, so any descendant with a z-index above 30 paints ON TOP of the mask. In the
 * clone that is the header at z 50, and it only looks right because every label inside it is
 * scramble-gated to opacity 0. v3's chrome is at z 50 too — but .page.is-loading sets
 * visibility:hidden, which DOES inherit into descendants regardless of stacking, so the whole
 * document is genuinely hidden and not merely painted under something. Do not swap that
 * visibility rule for an opacity or a background without re-checking the chrome bars. */

.page.is-loading {
  visibility: hidden;
  pointer-events: none;
  user-select: none;
}

/* The no-JS / dead-CDN failsafe. It un-hides the page and retires the pill on its own, so a reader
 * whose module graph never arrives is never left staring at a black rectangle. js/ui/loader.js
 * cancels it by adding .is-managed — but ONLY if it gets there before the delay below, so this can
 * never be un-done after it has already fired. See loader.js's FAILSAFE_MS: the two must agree. */
@keyframes loaderFailsafeReveal { to { visibility: visible; pointer-events: auto; user-select: auto; } }
@keyframes loaderFailsafeHide { to { opacity: 0; visibility: hidden; } }
.page.is-loading { animation: loaderFailsafeReveal 0.01s linear 2.4s forwards; }
.page.is-loading.is-managed { animation: none; }
.loader { animation: loaderFailsafeHide 0.3s linear 2.4s forwards; }
.loader.is-managed { animation: none; }

/* ---------------------------------------------------------------- the mask */

.gl-overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* --------------------------------------------------------------- the mark */

.loader {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 40;
  width: 140px;
  height: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 1;
  color: var(--bone, #ecebe6);
  /* the fade at hand-off. loader.js's FADE_MS is this number. */
  transition: opacity 250ms cubic-bezier(0.25, 1, 0.5, 1);
}

.loader svg {
  display: block;
  width: 140px;
  height: auto;
  overflow: visible;
}

/* The dim body of the mark — the "track" of this bar. Same shape, 12% of the bone ink, so the
 * ghost reads as the silhouette of what is arriving rather than as a hole in the page. */
.loader__ghost {
  fill: currentColor;
  opacity: 0.12;
}

/* The bright fill — the part the clip rect uncovers. Full bone. */
.loader__fill {
  fill: currentColor;
  /* THE FILL LAGS THE NUMBER, on purpose. The manifest is six tasks and they usually settle in a
   * burst, so without this the clip would jump 0 -> 940 in two frames and read as a glitch. With
   * it, the reveal edge is still travelling when the figure behind it already says 100. */
  transition: none;
}

/* The clip rect itself is transitioned from loader.js's inline style, in SVG user units. No CSS
 * here: a width on a clipPath rect is an attribute, and CSS transitions on presentation
 * attributes of clipping geometry are not portable. The 520ms easing of the old bar is carried
 * by the JS damping in js/core/assets.js — the value loader.js receives is already smoothed. */

/* Reduced motion keeps the mark and keeps the hold — only the 800ms mask retraction is dropped, in
 * js/ui/loader.js, which is the one place the clone changes under this preference too. */

@media (prefers-reduced-motion: reduce) {
  .loader { transition-duration: 1ms; }
}
