/* ============================================================================
   vth-loading.css — the three loading affordances, and nothing else.

   THE RULE THIS FILE EXISTS TO ENFORCE (ruled 2026-09-06):

     NEVER block the whole page while data loads.

     1. Data arriving into a region  -> SKELETON MASK in the shape of that region
                                        (.vth-skeleton / .vth-skel-*), or
                                        .vth-masked over content already on screen.
     2. A button that starts work    -> INLINE SPINNER inside the button, button
                                        DISABLED for the duration (.is-loading).
     3. Infinite scroll / paging     -> A LOADER AT THE BOTTOM of the list
                                        (.vth-loadmore, already in vth-reports.css).

   Everything here is scoped to `.vth` and uses tokens. Motion is disabled under
   prefers-reduced-motion — a shimmer is decoration, not information.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1. SKELETON — the grey masking loader
   Use for a region that has NO content yet. Match the shape of what is coming:
   a skeleton that is the wrong size is worse than none, because the layout
   jumps when the real data lands.
   --------------------------------------------------------------------------- */
.vth .vth-skeleton{
  position:relative;
  overflow:hidden;
  display:block;
  background:var(--surface-muted);
  border-radius:var(--radius-sm);
  /* Nothing inside a skeleton is readable. Hide it from assistive tech and let
     the live region announce the real content when it arrives. */
  color:transparent;
  user-select:none;
  pointer-events:none;
}
.vth .vth-skeleton::after{
  content:"";
  position:absolute;
  inset:0;
  transform:translateX(-100%);
  background:linear-gradient(90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,.65) 50%,
    rgba(255,255,255,0) 100%);
  animation:vth-skeleton-sweep 1.4s ease-in-out infinite;
}
@keyframes vth-skeleton-sweep{ to{ transform:translateX(100%); } }

/* Shape helpers. Heights are set from the type scale so a text skeleton
   occupies exactly the line box the real text will. */
.vth .vth-skel-text     { height:var(--type-leading-sm); border-radius:var(--radius-xs); }
.vth .vth-skel-text--sm { height:var(--type-leading-2xs); }
.vth .vth-skel-text--lg { height:var(--type-leading-lg); }
.vth .vth-skel-circle   { border-radius:50%; flex:none; }
.vth .vth-skel-pill     { border-radius:var(--radius-pill); }
.vth .vth-skel-chip     { height:26px; width:96px; border-radius:var(--radius-pill); }
.vth .vth-skel-switch   { height:24px; width:44px; border-radius:var(--radius-pill); }
.vth .vth-skel-btn      { height:44px; width:120px; border-radius:var(--radius-md); }
.vth .vth-skel-avatar   { height:40px; width:40px; border-radius:50%; flex:none; }

/* Width helpers, so a column of skeleton lines does not look like a solid block. */
.vth .vth-skel-w-25{ width:25%; } .vth .vth-skel-w-40{ width:40%; }
.vth .vth-skel-w-50{ width:50%; } .vth .vth-skel-w-60{ width:60%; }
.vth .vth-skel-w-75{ width:75%; } .vth .vth-skel-w-100{ width:100%; }

/* A stacked pair — the name-over-email cell that repeats down every list here. */
.vth .vth-skel-stack{ display:flex; flex-direction:column; gap:6px; }
.vth .vth-skel-row{ display:flex; align-items:center; gap:var(--space-3); }

/* Skeleton rows inside a real <table>, so column widths stay put while loading
   and the header remains readable. */
.vth .vth-table tr.vth-skel-tr td{ padding-top:var(--space-4); padding-bottom:var(--space-4); }

/* ---------------------------------------------------------------------------
   2. MASK — content already on screen, being refreshed
   Filtering or paging an existing table must NOT blank it. Dim what is there,
   lock interaction, and float one small spinner. The user keeps their context.
   --------------------------------------------------------------------------- */
.vth .vth-masked{ position:relative; }
.vth .vth-masked > *{
  opacity:.45;
  filter:grayscale(.35);
  transition:opacity .15s ease;
  pointer-events:none;
}
.vth .vth-masked::after{
  content:"";
  position:absolute;
  top:0; left:50%;
  transform:translateX(-50%);
  margin-top:var(--space-8);
  width:28px; height:28px;
  border-radius:50%;
  border:3px solid var(--border-light);
  border-top-color:var(--vth-blue);
  animation:vth-spin .7s linear infinite;
  z-index:2;
}
/* Sticky variant: keeps the spinner in view on a long table being refreshed. */
.vth .vth-masked--sticky::after{ position:sticky; top:40vh; }

/* ---------------------------------------------------------------------------
   3. BUTTON — inline spinner, button disabled
   The button keeps its exact width (the label stays in flow at opacity 0), so
   nothing reflows when work starts. Never swap the label for "Loading…" — that
   resizes the button and moves everything beside it.
   --------------------------------------------------------------------------- */
.vth .vth-btn.is-loading{
  position:relative;
  pointer-events:none;   /* not `disabled`, so the element stays focusable and
                            a screen reader keeps its aria-busy announcement */
  cursor:progress;
}
.vth .vth-btn.is-loading > *{ opacity:0; }
.vth .vth-btn.is-loading::after{
  content:"";
  position:absolute;
  top:50%; left:50%;
  width:16px; height:16px;
  margin:-8px 0 0 -8px;
  border-radius:50%;
  border:2px solid currentColor;
  border-top-color:transparent;
  opacity:.85;
  animation:vth-spin .6s linear infinite;
}
/* Icon-only buttons get a spinner sized to the button. */
.vth .vth-btn--icon.is-loading::after{ width:18px; height:18px; margin:-9px 0 0 -9px; }

/* A spinner that sits NEXT TO a label rather than replacing it — for actions
   where the label must stay legible (e.g. "Download all (ZIP)"). */
.vth .vth-inline-spin{
  width:15px; height:15px; flex:none;
  border-radius:50%;
  border:2px solid currentColor;
  border-top-color:transparent;
  opacity:.7;
  animation:vth-spin .6s linear infinite;
}

/* ---------------------------------------------------------------------------
   4. IN-FLIGHT CONTROLS — optimistic toggles and checkboxes
   §11.4 requires toggles to be optimistic with rollback and disabled while the
   request is in flight. This is what "in flight" looks like: the control shows
   its new state immediately, dimmed, and refuses further clicks until settled.
   --------------------------------------------------------------------------- */
.vth .is-busy{ opacity:.55; pointer-events:none; cursor:progress; }

/* ---------------------------------------------------------------------------
   5. BOTTOM-OF-LIST loader (infinite scroll / load more)
   `.vth-loadmore` and `.vth-loadmore__spin` already exist in vth-reports.css and
   are reused verbatim — see decisions D-1. Only the states that file does not
   cover are added here.
   --------------------------------------------------------------------------- */
.vth .vth-loadmore--end{ color:var(--text-muted); font-size:var(--type-size-2xs); }
.vth .vth-loadmore--error{ color:var(--color-error); }
.vth .vth-loadmore--error button{
  font-family:inherit; font-size:inherit; font-weight:var(--weight-semibold);
  color:var(--vth-blue); background:none; border:none; padding:0 0 0 var(--space-2);
  text-decoration:underline; cursor:pointer;
}

/* ---------------------------------------------------------------------------
   Reduced motion — keep the affordance, drop the movement.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .vth .vth-skeleton::after{ animation:none; opacity:.25; }
  .vth .vth-masked::after,
  .vth .vth-btn.is-loading::after,
  .vth .vth-inline-spin,
  .vth .vth-loadmore__spin{ animation-duration:2.4s; }
}

/* ---------------------------------------------------------------------------
   6. TOAST tones
   `.vth-toast` (vth-reports.css) is the dark neutral bar. A failed action must
   not look like a completed one, so the error tone is stated here rather than
   relying on the message text alone.
   --------------------------------------------------------------------------- */
/* Translucent, not flat black (ruled 2026-09-06). The toast floats over content, and
   a solid slab reads as a modal; letting a little of the page through says "this is
   temporary". `opacity` is deliberately NOT used — that would fade the text with the
   surface and cost legibility. The alpha lives in the background colour, and a blur
   keeps the text crisp over whatever is behind it.

   .vth-toast is defined in vth-reports.css, which SEVEN live pages share, so nothing
   there is edited; these override from this file, which loads after it. */
.vth .vth-toast{
  background:rgba(23, 26, 33, .92);
  -webkit-backdrop-filter:blur(8px);
  backdrop-filter:blur(8px);
  border:1px solid rgba(255,255,255,.10);
}
/* Browsers without backdrop-filter get the flatter, safer alpha rather than text over
   an unblurred page. */
@supports not ((backdrop-filter:blur(8px)) or (-webkit-backdrop-filter:blur(8px))){
  .vth .vth-toast{ background:rgba(23, 26, 33, .97); }
}

/* ---- CTAs on a dark toast ----
   `.vth-btn--secondary` is a white button with BLUE text and `.vth-btn--ghost` is blue
   text on nothing: both are near-invisible here, because the whole button palette is
   designed against light surfaces. Inside the toast they are restated in terms of the
   surface they are actually on. */
.vth .vth-toast .vth-btn--secondary{
  background:#fff;
  border-color:#fff;
  color:var(--text-primary);      /* dark text on the white chip — not blue on black */
}
.vth .vth-toast .vth-btn--secondary:hover{ background:rgba(255,255,255,.88); border-color:rgba(255,255,255,.88); }

.vth .vth-toast .vth-btn--ghost{
  color:rgba(255,255,255,.82);
  border:1px solid rgba(255,255,255,.28);
  background:transparent;
}
.vth .vth-toast .vth-btn--ghost:hover{ color:#fff; border-color:rgba(255,255,255,.55); background:rgba(255,255,255,.10); }

/* Focus rings are tuned for light backgrounds too. */
.vth .vth-toast .vth-btn:focus-visible{
  outline:none;
  box-shadow:0 0 0 3px rgba(255,255,255,.45);
}

.vth .vth-toast--error{ background:rgba(185, 28, 28, .94); border-color:rgba(255,255,255,.16); }
@supports not ((backdrop-filter:blur(8px)) or (-webkit-backdrop-filter:blur(8px))){
  .vth .vth-toast--error{ background:rgba(185, 28, 28, .98); }
}
