/* =========================================================
   Component — Busy

   What a slow request looks like.

   Until now it looked like nothing. A merchant uploading a 40 MB
   clip, or a shopper on a Syrian mobile connection pressing «أكمل
   الطلب», pressed a button that did not change, waited on a page
   that did not change, and pressed it again — which is how a
   checkout becomes two orders and an upload becomes two files.

   The whole system hangs off one attribute, `aria-busy="true"`,
   which is what the platform is already obliged to set for screen
   readers. js/busy.js sets it on the form and on the button that
   submitted it, and everything below follows from that: no state
   class to keep in sync, no second vocabulary, and a control that
   is announced as busy is also drawn as busy.

   Everything animated here is a transform or an opacity on a
   pseudo-element, so nothing below reflows the page or repaints a
   layer the browser cannot promote. The reduced-motion rule in
   app.css freezes every animation on the platform, so both
   indicators have a still form that still reads as «working» —
   otherwise reduced motion would mean no feedback at all, which is
   the bug this file exists to fix.
   ========================================================= */

/* ---------------------------------------------------------
   The button

   The label is not removed and the button is not resized: the
   label goes transparent in place and a ring is drawn over it.
   Swapping the text for a spinner re-measures the button, which
   moves the row it sits in at the exact moment the person is
   still looking at where they clicked.

   Not [disabled]: a disabled submit button is dropped from the
   payload it was about to send, so a form that reads which button
   was pressed loses that. It is taken out of reach with
   pointer-events instead, and js/busy.js refuses the second
   submit outright.
   --------------------------------------------------------- */
.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  cursor: progress;
}

/* The icon inside a button paints in currentColor, which is now
   transparent — but an <svg> with its own fill would still show
   through the ring, so the whole label is hidden as one. */
.btn[aria-busy="true"] > * {
  opacity: 0;
}

.btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: 1.15em;
  height: 1.15em;
  margin-block-start: -0.575em;
  margin-inline-start: -0.575em;
  border-radius: var(--radius-pill);

  /* Half a ring drawn, half left open — the gap is what makes the
     rotation legible; a closed ring spinning looks still.

     Two borders coloured and two left transparent, rather than one
     translucent ring under one opaque arc: no colour function is
     involved, so there is nothing here a browser can fail to parse
     and leave the merchant with an invisible spinner.

     --btn-fg rather than currentColor, because currentColor is the
     transparent the label was just set to. */
  border: 2px solid transparent;
  border-block-start-color: var(--btn-fg);
  border-inline-end-color: var(--btn-fg);

  animation: athar-spin var(--dur-spin) var(--ease-linear) infinite;
  will-change: transform;
}

@keyframes athar-spin {
  to { transform: rotate(1turn); }
}

/* ---------------------------------------------------------
   The form

   A thread of light along the top edge, crawling. It is drawn on
   the form itself rather than on the page, so the person's eye
   does not have to leave the thing they are waiting on — and it
   costs one composited pseudo-element rather than a fixed overlay
   that would have to sit above the grain layer.

   The fields go quiet but stay readable: a form that greys out
   entirely while it uploads hides the very data the person is
   waiting to see accepted.
   --------------------------------------------------------- */
form[aria-busy="true"] {
  position: relative;
  cursor: progress;
}

form[aria-busy="true"] .field__control,
form[aria-busy="true"] .field__check,
form[aria-busy="true"] .field__file {
  opacity: 0.62;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

form[aria-busy="true"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-soft);
  pointer-events: none;
}

form[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 34%;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  pointer-events: none;

  animation: athar-crawl var(--dur-crawl) var(--ease-in-out) infinite;
  will-change: transform;
}

/* Travels the width of the form and back, in transform only. The
   percentages are of the bar's own 34%, so the two ends land flush
   against the form's edges at any width. */
@keyframes athar-crawl {
  0% { transform: translateX(0); }
  50% { transform: translateX(194%); }
  100% { transform: translateX(0); }
}

/* RTL runs the other way: translateX is physical, and a progress
   bar that crawls away from the reading edge reads as rewinding. */
[dir="rtl"] form[aria-busy="true"]::after {
  animation-name: athar-crawl-rtl;
}

@keyframes athar-crawl-rtl {
  0% { transform: translateX(0); }
  50% { transform: translateX(-194%); }
  100% { transform: translateX(0); }
}

/* ---------------------------------------------------------
   A busy region that is neither

   A panel refreshing in place, a table reloading a page of rows.
   Same attribute, same vocabulary, no spinner: the content dims
   and stops taking clicks, which is all a region needs to say.
   --------------------------------------------------------- */
[data-busy-region][aria-busy="true"] {
  opacity: 0.55;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

/* ---------------------------------------------------------
   Reduced motion

   app.css cuts every animation on the platform to 0.01ms, so both
   indicators above stop dead — the spinner freezes at whatever
   angle it started on and the bar parks at the inline start. Left
   alone, that is a loading state that does not load.

   So under reduced motion they become still marks instead: a
   complete ring rather than a stalled three-quarter one, and a
   full-width bar rather than a stalled third of one. Neither
   moves, both say the same thing, and aria-busy is carrying the
   message to anybody not looking at either.
   --------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy="true"]::after {
    border-color: var(--btn-fg);
  }

  form[aria-busy="true"]::after {
    width: 100%;
    opacity: 0.7;
  }
}

/* A link that is navigating.

   Same ring the buttons get, and the same reason it is not a
   width change: the row must not move while the next page is
   still loading. */
a.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  cursor: progress;
}
