/* Shared loading-state primitives.
 *
 * Every page renders its shell immediately and pulls its data in over htmx, so
 * these placeholders are what the user actually sees first on every route. The
 * governing rule is that a skeleton must occupy the SAME box as the content
 * that replaces it — same row height, same column count, same tile size — so
 * the swap changes pixels but never geometry. A skeleton that reserves the
 * wrong height is worse than none: it makes the page jump exactly when the user
 * has started reading.
 */

.skel {
  border-radius: 6px;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--text) 7%, transparent) 25%,
    color-mix(in srgb, var(--text) 12%, transparent) 37%,
    color-mix(in srgb, var(--text) 7%, transparent) 63%
  );
  background-size: 400% 100%;
  animation: skel-sweep 1.4s ease infinite;
}

.skel--value { width: 3ch; height: 1.6rem; }
.skel--label { width: 6.5ch; height: 0.71rem; margin-top: 0.32rem; }
.skel--line { width: 100%; height: 0.8rem; }
.skel--code { width: 12ch; height: 0.82rem; }
.skel--sub { width: 18ch; height: 0.72rem; margin-top: 0.3rem; }
.skel--topbar-stat { width: 1.5ch; height: 0.9rem; }

@keyframes skel-sweep {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* --- Table skeletons -------------------------------------------------------
 *
 * Rendered as real <tr>/<td> inside the same <table> as the loaded rows, so the
 * browser's column sizing is identical before and after the swap. Widths vary
 * per cell to read as text rather than a block of identical bars.
 */

.skel-row td {
  /* Matches .data-table td padding so a skeleton row is exactly a data row. */
  height: 100%;
}

.skel-cell {
  display: block;
  height: 0.82rem;
  border-radius: 6px;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--text) 7%, transparent) 25%,
    color-mix(in srgb, var(--text) 12%, transparent) 37%,
    color-mix(in srgb, var(--text) 7%, transparent) 63%
  );
  background-size: 400% 100%;
  animation: skel-sweep 1.4s ease infinite;
}

/* Staggered widths keyed off the cell's position, so successive rows don't line
   up into a solid rectangle. */
.skel-cell--code { width: 11ch; }
.skel-cell--pill { width: 7ch; height: 1.35rem; border-radius: 999px; }
.skel-cell--date { width: 15ch; }
.skel-cell--short { width: 5ch; }
.skel-cell--mid { width: 9ch; }
.skel-cell--wide { width: 18ch; }

/* --- Stat tile / chip skeletons -------------------------------------------- */

.skel-tile__value {
  display: block;
  width: 3.5ch;
  height: 1.75rem;
  border-radius: 6px;
  background: color-mix(in srgb, var(--text) 9%, transparent);
  animation: skel-sweep 1.4s ease infinite;
  background-image: linear-gradient(
    90deg,
    color-mix(in srgb, var(--text) 7%, transparent) 25%,
    color-mix(in srgb, var(--text) 12%, transparent) 37%,
    color-mix(in srgb, var(--text) 7%, transparent) 63%
  );
  background-size: 400% 100%;
}

/* Region-level fade-in. Applied to a fragment's root on swap so data arrives as
   a settle rather than a flash — 120ms is deliberately below the threshold
   where it reads as an animation the user has to wait through. */
.fragment-in {
  animation: fragment-fade 0.12s ease-out;
}

@keyframes fragment-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* An htmx region that failed. Keeps the reserved height so a failed panel does
   not collapse the page around it. */
.fragment-error {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 1rem 1.1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.fragment-error__retry {
  border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  padding: 0.32rem 0.7rem;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}

.fragment-error__retry:hover { border-color: var(--accent); color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  /* animation:none alone leaves the sweep gradient parked off-screen, rendering
     a near-invisible block — so the skeleton needs a flat fill here. */
  .skel,
  .skel-cell,
  .skel-tile__value {
    animation: none;
    background: color-mix(in srgb, var(--text) 9%, transparent);
  }

  .fragment-in { animation: none; }
}

/* --- Refetch spinner ---------------------------------------------------
 *
 * Shown in place of a region's content while a filter/pagination change is
 * in flight (as opposed to the skeleton rows above, which only cover first
 * paint). The region wraps its content plus this spinner and toggles
 * visibility via htmx's own htmx-request class, so every trigger path
 * (chip click, quick-pick, date field, search, page link) gets it for free
 * with no per-trigger JS. */

.refetch-region {
  position: relative;
  min-height: 0;
}

.refetch-spinner {
  display: none;
}

.refetch-region.htmx-request > :not(.refetch-spinner) {
  visibility: hidden;
}

.refetch-region.htmx-request .refetch-spinner {
  display: flex;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
}

.refetch-spinner__icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--text) 12%, transparent);
  border-top-color: var(--accent);
  animation: refetch-spin 0.7s linear infinite;
}

@keyframes refetch-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .refetch-spinner__icon { animation: none; }
}
