/*
 * QERIONN animation registry -- shared presentation.
 *
 * The static fallback (label + glyph) is the default, always-rendered
 * state. It only visually steps back once q-animation-player.js adds
 * the "q-anim--live" class after a real asset has actually rendered --
 * never before, and never as a guess. A blocked fetch, a JS error, a
 * reduced-motion preference or a disabled feature flag all leave this
 * file's defaults untouched, which is what makes the "readable by
 * default, motion is the enhancement" contract hold.
 */

.q-anim {
  --q-anim-accent: #D6B25E;
  --q-anim-accent-light: #F0D58A;
  --q-anim-ink: #060E1C;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  min-width: 64px;
}

/* The primary visual element of a status card, not an inline icon.
   Mobile: 160-240px. Desktop (>=768px, this project's existing
   tablet/desktop breakpoint): 240-340px. Fallback is sized identically
   to the live SVG so mounting the real asset never shifts layout. */
.q-anim svg,
.q-anim-fallback {
  display: block;
  width: clamp(160px, 45vw, 240px);
  height: clamp(160px, 45vw, 240px);
}
@media (min-width: 768px) {
  .q-anim svg,
  .q-anim-fallback {
    width: clamp(240px, 22vw, 340px);
    height: clamp(240px, 22vw, 340px);
  }
}

/* Once live, the SVG takes over and the static glyph steps aside --
   kept in the DOM (not removed) so a later JS failure or a dynamic
   reduced-motion toggle still has something correct to fall back to. */
.q-anim--live .q-anim-fallback {
  display: none;
}
.q-anim:not(.q-anim--live) svg {
  display: none;
}

.q-anim-fallback {
  border-radius: 50%;
  border: 2px solid var(--q-anim-accent);
  position: relative;
  flex: none;
  box-sizing: border-box;
}
.q-anim-fallback::after {
  content: "";
  position: absolute;
  inset: 24%;
  border-radius: 50%;
  background: var(--q-anim-accent-light);
  opacity: 0.55;
}

/* A handful of states get a distinguishing glyph shape beyond the
   shared ring -- enough that "processing" doesn't look identical to
   "success" even before any script runs. Deliberately simple: these
   are text-free, colour-only differentiators, not a full icon set.
   Percentage insets so the same rule scales with the fallback's own
   responsive size instead of pinning a fixed pixel offset. */
.q-anim-glyph--check::after { border-radius: 4px; transform: rotate(45deg) scale(0.5); }
.q-anim-glyph--alert::after { border-radius: 2px; }
.q-anim-glyph--clock::after { inset: 34% 46% 14% 46%; border-radius: 2px; }
.q-anim-glyph--refresh { border-style: dashed; }
.q-anim-glyph--empty-box { border-style: dotted; }
.q-anim-glyph--empty-box::after { opacity: 0.25; }

.q-anim-label {
  font-size: 0.8125rem;
  font-weight: 600;
  color: inherit;
}

.q-anim-description {
  font-size: 0.75rem;
  opacity: 0.75;
  max-width: 220px;
}

.q-anim-progress {
  width: 100%;
  max-width: 160px;
  accent-color: var(--q-anim-accent);
}

/* Belt-and-braces client-side reduced-motion handling: the server
   already renders DISABLED (no data-anim-src) when it knows the
   user's stored preference, and the player itself checks
   matchMedia before ever fetching an asset. This rule covers the
   remaining case -- OS-level preference changed after page load,
   with no server round-trip to notice. */
@media (prefers-reduced-motion: reduce) {
  .q-anim svg {
    display: none !important;
  }
  .q-anim .q-anim-fallback {
    display: inline-block !important;
  }
}

/* In-flight host.
   Server-rendered and [hidden] until real work starts, so the page is
   unchanged when JS is unavailable. `hidden` alone is not enough here
   because the flex rule below would otherwise win over it -- a
   long-standing HTML/CSS trap where display:flex silently re-shows a
   hidden element. */
.q-anim-inflight[hidden] {
  display: none !important;
}

.q-anim-inflight {
  display: flex;
  justify-content: center;
  margin: 14px 0 0;
  padding: 14px;
  border: 1px solid rgba(214, 178, 94, 0.28);
  border-radius: 10px;
  background: rgba(214, 178, 94, 0.06);
  /* Reserves its own row rather than overlaying the trigger: an
     overlay on submit shifts nothing but hides the control the user
     just pressed, and this block only ever appears in place of nothing.
     Height is not fixed -- the partial's label and description wrap on
     narrow screens, and clipping the words would defeat the point of
     having them. */

  /* "Reserves its own row" was only true where the host was a block. Owner
     reported 7 Aug 2026 that pressing "Re-scan active CV" made every button in
     the row grow to the height of the animation: the trigger's <form> is a
     flex item in .su-card-actions, revealing this block made that item ~340px
     tall, and the row's default align-items:stretch then stretched every
     sibling button to match.
     Taking the full line makes the block behave as its comment already
     claimed, in a wrapping flex row as well as in a block. */
  flex-basis: 100%;
  align-self: flex-start;
}

/* An in-flight indicator sits beside a button; it is not a hero illustration.
   The shared .q-anim canvas is clamp(160px,45vw,240px) rising to
   clamp(240px,22vw,340px) on desktop, which is the right size for a full-page
   processing state and roughly eight times a 44px control. Capping it here
   keeps one asset usable in both places without a second set of files, and is
   what stops a progress indicator from dominating the card it belongs to. */
.q-anim-inflight .q-anim {
  width: 88px;
  height: 88px;
}

/* Terminal and empty states sit inline with page content rather than
   in a bordered panel -- they are the content, not an interruption. */
.q-anim-terminal {
  display: flex;
  justify-content: center;
  margin: 10px 0;
}

@media (max-width: 560px) {
  .q-anim-inflight {
    padding: 12px 10px;
  }
}
