/* 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; }

/* Fills the parent's own line box instead of declaring a height of its own.
   Used where the placeholder sits inside the real text element (chart titles,
   totals, axis labels): the parent's font-size decides the height, so the
   skeleton and the text that replaces it occupy an identical box. A hand-set
   height can't match a line box — 1.35rem of text is ~1.62rem tall. */
.skel--inline {
  display: inline-block;
  height: 1em;
  vertical-align: text-bottom;
}

@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; }
}

/* --- Pending values --------------------------------------------------------
 *
 * The replacement for most of the grey-bar skeletons above. A region now
 * renders its REAL markup — real labels, real icons, real chart axes — with an
 * em-dash standing in for each number that hasn't arrived. Because the
 * placeholder and the loaded state are the same template, the box is identical
 * by construction rather than by a hand-tuned width that could only ever
 * approximate it. The dash sits in the same line box as the numeral, so the
 * only thing that changes on swap is the glyph.
 */

.stat-value-pending,
.cell-pending {
  color: color-mix(in srgb, currentColor 35%, transparent);
  animation: value-pending-pulse 1.6s ease-in-out infinite;
}

@keyframes value-pending-pulse {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 0.9; }
}

/* An empty chart frame: axis, gridlines and tick labels are fully drawn, the
   series lines just sit flat on the baseline. Fading the plotted paths (not
   the frame) keeps the chart legible as "no data yet" rather than "zero". */
.dashboard-charts__grid--pending .trend-chart__line,
.dashboard-charts__grid--pending .trend-chart__area {
  opacity: 0.25;
  animation: value-pending-pulse 1.6s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .stat-value-pending,
  .cell-pending,
  .dashboard-charts__grid--pending .trend-chart__line,
  .dashboard-charts__grid--pending .trend-chart__area {
    animation: none;
  }
}
