/* ================================================================
   PostPro Nexus - SHARED SKELETON + PROGRESS SYSTEM
   Board 12a of design_handoff_nexus_console/Nexus Directions.
   ----------------------------------------------------------------
   ONE loading recipe for every module. Nothing in here is module
   specific and nothing in here blocks the page:

     - chrome and toolbar stay interactive, only the CONTENT area
       is skeletonned
     - a 2px hairline runs directly under the 48px top bar
     - blocks mirror the target layout so the swap costs zero shift
     - a corner chip appears past 400ms, gains Retry past 5s
     - a failure lands as a quiet error card, never endless shimmer

   TOKENS: every colour reads --nx-* first and falls back to the
   identical Console value. Pages that load css/tokens.css get the
   token; pages still on the legacy nexus.css stack get the literal.
   A bare var(--missing) invalidates the whole declaration, so the
   fallback is load bearing - do not drop it.

   Load AFTER nexus.css / console.css on every page.
   ================================================================ */

:root {
  --nxsk-line:      var(--nx-line, #E1E8E8);
  --nxsk-block:     var(--nx-row-divider, #EDF2F2);
  --nxsk-surface:   var(--nx-surface, #FFFFFF);
  --nxsk-sunken:    var(--nx-sunken, #FAFCFC);
  --nxsk-accent:    var(--nx-accent, #0F9D8F);
  --nxsk-accent-tx: var(--nx-accent-text, #0B7568);
  --nxsk-muted:     var(--nx-muted, #5C7070);
  --nxsk-faint:     var(--nx-faint, #93A6A6);
  --nxsk-ink:       var(--nx-ink, #0C1517);

  /* The chip is ink-on-dark in BOTH themes, same as the top bar. */
  --nxsk-chip-bg:   #0C1517;
  --nxsk-chip-fg:   #B8C7C7;
  --nxsk-chip-dot:  #2DD4BF;

  --nxsk-topbar-h:  var(--nx-topbar-h, 48px);
  --nxsk-mono:      var(--nx-font-mono, 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --nxsk-line:      var(--nx-line, #1E2A2B);
    --nxsk-block:     var(--nx-row-divider, #182022);
    --nxsk-surface:   var(--nx-surface, #101617);
    --nxsk-sunken:    var(--nx-sunken, #0C1214);
    --nxsk-accent:    var(--nx-accent, #2DD4BF);
    --nxsk-accent-tx: var(--nx-accent-text, #5EEAD4);
    --nxsk-muted:     var(--nx-muted, #7E9494);
    --nxsk-faint:     var(--nx-faint, #5C7070);
    --nxsk-ink:       var(--nx-ink, #E6EDED);
  }
}

:root[data-theme="dark"] {
  --nxsk-line:      var(--nx-line, #1E2A2B);
  --nxsk-block:     var(--nx-row-divider, #182022);
  --nxsk-surface:   var(--nx-surface, #101617);
  --nxsk-sunken:    var(--nx-sunken, #0C1214);
  --nxsk-accent:    var(--nx-accent, #2DD4BF);
  --nxsk-accent-tx: var(--nx-accent-text, #5EEAD4);
  --nxsk-muted:     var(--nx-muted, #7E9494);
  --nxsk-faint:     var(--nx-faint, #5C7070);
  --nxsk-ink:       var(--nx-ink, #E6EDED);
}

/* ================================================================
   1. PROGRESS HAIRLINE
   2px under the top bar. Track = line token, fill = accent.
   Indeterminate: ease-out to ~85% over 1.6s, then jump to 100%
   and fade when the data lands. ONE per page, never per card.
   ================================================================ */

.nxsk-hairline {
  position: fixed;
  top: var(--nxsk-topbar-h);
  left: 0;
  right: 0;
  height: 2px;
  background: var(--nxsk-line);
  z-index: 950;
  overflow: hidden;
  pointer-events: none;
  opacity: 1;
  transition: opacity 180ms linear;
}
.nxsk-hairline[hidden] { display: none; }
.nxsk-hairline.is-fading { opacity: 0; }

.nxsk-hairline__fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--nxsk-accent);
  transition: width 1600ms cubic-bezier(.16, 1, .3, 1);
}
.nxsk-hairline.is-running  .nxsk-hairline__fill { width: 85%; }
.nxsk-hairline.is-complete .nxsk-hairline__fill {
  width: 100%;
  transition: width 140ms linear;
}

/* A page whose top bar is not the 48px Console chrome (login, print
   views, embedded panels) pins the hairline to the very top. */
.nxsk-hairline--top { top: 0; }

/* ================================================================
   2. SKELETON BLOCKS
   Background = row-divider token, radius 3-4px. Shimmer is an
   opacity cycle so the block never changes size or colour family.
   ================================================================ */

.nxsk {
  display: block;
  background: var(--nxsk-block);
  border-radius: 3px;
  animation: nxsk-shimmer 1200ms ease infinite;
  animation-delay: var(--nxsk-d, 0ms);
}

@keyframes nxsk-shimmer {
  0%, 100% { opacity: .5; }
  50%      { opacity: 1; }
}

/* Heights from the board: 20px value block, 9px row bar, 7px sub bar. */
.nxsk--value  { height: 20px; border-radius: 4px; }
.nxsk--bar    { height: 9px; }
.nxsk--sub    { height: 7px; }
.nxsk--chip   { height: 16px; width: 56px; border-radius: 4px; }
.nxsk--avatar { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 22px; }
.nxsk--thumb  { width: 32px; height: 32px; border-radius: 4px; flex: 0 0 32px; }

/* Generic body-copy bar for detail cards, plus the two shapes a
   skeleton must copy or the page reads as a different page: a mono
   meta run, and a round avatar where a round avatar lands. */
.nxsk--text   { height: 12px; width: 100%; }
.nxsk--mono   { height: 9px; width: 64px; }
.nxsk--circle { width: 36px; height: 36px; border-radius: 50%; flex: 0 0 auto; }

/* Hand written skeleton markup gets its stagger from here; the JS
   builder sets --nxsk-d directly. ~100ms between siblings. */
.nxsk-stagger > *:nth-child(1)  { --nxsk-d: 0ms; }
.nxsk-stagger > *:nth-child(2)  { --nxsk-d: 100ms; }
.nxsk-stagger > *:nth-child(3)  { --nxsk-d: 200ms; }
.nxsk-stagger > *:nth-child(4)  { --nxsk-d: 300ms; }
.nxsk-stagger > *:nth-child(5)  { --nxsk-d: 400ms; }
.nxsk-stagger > *:nth-child(6)  { --nxsk-d: 500ms; }
.nxsk-stagger > *:nth-child(7)  { --nxsk-d: 600ms; }
.nxsk-stagger > *:nth-child(8)  { --nxsk-d: 700ms; }
.nxsk-stagger > *:nth-child(9)  { --nxsk-d: 800ms; }
.nxsk-stagger > *:nth-child(n+10) { --nxsk-d: 900ms; }

/* A cell keeps the line box of the real content it stands in for,
   which is what makes the swap shift free. */
.nxsk-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 18px;
}
.nxsk-cell--two {
  display: block;
  min-height: 34px;
}
.nxsk-cell--two > .nxsk + .nxsk { margin-top: 6px; }

/* ================================================================
   3. KPI RIBBON
   One bordered surface bar split by the row-divider; each cell is a
   20px value block over a 7px label bar, 7px apart.
   ================================================================ */

.nxsk-kpi {
  display: flex;
  background: var(--nxsk-surface);
  border: 1px solid var(--nxsk-line);
  border-radius: 8px;
  overflow: hidden;
}
.nxsk-kpi__cell {
  flex: 1;
  padding: 12px 16px;
  border-right: 1px solid var(--nxsk-block);
  min-width: 0;
}
.nxsk-kpi__cell:last-child { border-right: 0; }
.nxsk-kpi__cell > .nxsk--label { margin-top: 7px; height: 7px; border-radius: 3px; }

/* ================================================================
   4. CARD / DETAIL SKELETONS
   Header band = one 20px bar at 30% plus two 60px chip bars.
   Body + rail cards = an overline bar and 2-3 content bars.
   ================================================================ */

.nxsk-card {
  background: var(--nxsk-surface);
  border: 1px solid var(--nxsk-line);
  border-radius: 8px;
  padding: 14px 16px;
}
.nxsk-card + .nxsk-card { margin-top: 12px; }
.nxsk-card > .nxsk + .nxsk { margin-top: 10px; }
.nxsk-card__overline { height: 7px; width: 84px; border-radius: 3px; }

.nxsk-headerband {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.nxsk-headerband > .nxsk--value { width: 30%; min-width: 160px; }
.nxsk-headerband > .nxsk--chip  { width: 60px; }

/* A plain block grid for card registers (File Center, Components). */
.nxsk-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}

/* ================================================================
   5. CORNER CHIP - slow loads
   Past 400ms, bottom right 20/16. Ink surface in both themes with a
   live timer and a pulsing teal dot. Retry joins past 5s.
   ================================================================ */

.nxsk-chip {
  position: fixed;
  right: 20px;
  bottom: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--nxsk-chip-bg);
  color: var(--nxsk-chip-fg);
  font-family: var(--nxsk-mono);
  font-size: 10px;
  letter-spacing: .08em;
  line-height: 1;
  padding: 7px 12px;
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(12, 21, 23, .3);
  /* Above every other bottom-right fixture (command-menu hint, batch
     tray, return-to-top pill) - a status readout nothing may cover. */
  z-index: 1200;
  pointer-events: none;
  transition: bottom 120ms var(--nx-ease, ease);
}
.nxsk-chip__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--nxsk-chip-dot);
  flex: 0 0 6px;
  animation: nxsk-pulse 1000ms ease infinite;
}
.nxsk-chip__retry {
  pointer-events: auto;
  appearance: none;
  background: none;
  border: 0;
  padding: 0 0 0 4px;
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--nxsk-chip-dot);
  text-decoration: underline;
  cursor: pointer;
}
.nxsk-chip__retry:hover { color: #FFFFFF; }

@keyframes nxsk-pulse {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

/* The chip must never sit on top of a caption. Any element carrying
   data-nxsk-caption keeps clearance for as long as the chip is up. */
html.nxsk-chip-open [data-nxsk-caption] { padding-right: 240px; }

/* ================================================================
   6. ERROR CARD
   Replaces the skeleton on failure. Mono code, one plain sentence,
   Retry. Never leave shimmer running on a dead request.
   ================================================================ */

.nxsk-error {
  background: var(--nxsk-surface);
  border: 1px solid var(--nxsk-line);
  border-radius: 8px;
  padding: 20px;
}
.nxsk-error__code {
  font-family: var(--nxsk-mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--nxsk-faint);
}
.nxsk-error__msg {
  margin-top: 6px;
  font-size: 13px;
  color: var(--nxsk-ink);
}
.nxsk-error__retry {
  margin-top: 12px;
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--nxsk-accent-tx);
  cursor: pointer;
}
.nxsk-error__retry:hover { text-decoration: underline; }

/* Dropped into a table body the card keeps the table's own gutters. */
td > .nxsk-error, th > .nxsk-error { border: 0; padding: 24px 0; }

/* ================================================================
   7. INFINITE SCROLL SENTINEL
   Same bars, plus a ring and the mono progress readout.
   ================================================================ */

.nxsk-more {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
}
.nxsk-more__ring {
  width: 14px;
  height: 14px;
  flex: 0 0 14px;
  border: 2px solid var(--nxsk-block);
  border-top-color: var(--nxsk-accent);
  border-radius: 50%;
  animation: nxsk-spin 800ms linear infinite;
}
.nxsk-more__bars {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 6px;
}
.nxsk-more__label {
  font-family: var(--nxsk-mono);
  font-size: 10px;
  letter-spacing: .08em;
  color: var(--nxsk-faint);
  white-space: nowrap;
}

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

/* Two frames for the same sentinel: a table row, and a standalone
   block for card grids (HR people) where there is no table to sit in. */
tr.nxsk-more-row > td {
  padding: 10px var(--nx-s4, 16px);
  background: var(--nxsk-sunken);
}
tr.nxsk-more-row > td > .nxsk-more { padding: 0; }
.nxsk-more--block {
  margin-top: 8px;
  padding: 10px 16px;
  background: var(--nxsk-sunken);
  border: 1px solid var(--nxsk-line);
  border-radius: 8px;
}
.nxsk-more--block.hidden,
.nxsk-more[hidden] { display: none; }

/* A load that ended in failure freezes every block still on screen.
   Static grey reads as "unknown"; a pulse reads as "still coming". */
html.nxsk-frozen .nxsk,
html.nxsk-frozen .skel,
html.nxsk-frozen .nxsk-chip__dot,
html.nxsk-frozen .nxsk-more__ring {
  animation: none;
}
html.nxsk-frozen .nxsk,
html.nxsk-frozen .skel { opacity: .5; }

/* ================================================================
   8. REDUCED MOTION
   No shimmer, no pulse, no spin: static blocks. The hairline still
   moves, because its width is information rather than decoration -
   but nexus.css and console.css both carry a global
   `transition-duration: .01ms !important` under this media query, so
   in practice it snaps from 0 to 85% to 100% instead of easing. That
   is the right behaviour here; do not try to win that specificity
   fight with an !important of our own.
   ================================================================ */

@media (prefers-reduced-motion: reduce) {
  .nxsk,
  .skel,
  .nxsk-chip__dot,
  .nxsk-more__ring {
    animation: none;
  }
  .nxsk, .skel { opacity: .75; }
  .nxsk-chip__dot { opacity: 1; }
  .nxsk-hairline { transition: none; }
}

/* ================================================================
   9. DEPRECATED SHIM
   `.loading-spinner` was the app's universal loading marker: a ring in
   nexus.css, a centred 220px strip in console.css. Board 12a retires
   both. Every known site has been converted; this exists so that any
   site missed anywhere degrades to a quiet left-aligned bar rather
   than reintroducing a spinner or a centred strip. Delete once a
   `grep -r loading-spinner` comes back empty.
   ================================================================ */

.loading-spinner {
  display: block;
  width: 140px;
  height: 9px;
  max-width: 100%;
  margin: 10px 0;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: var(--nxsk-block);
  animation: nxsk-shimmer 1200ms ease infinite;
}
@media (prefers-reduced-motion: reduce) {
  .loading-spinner { animation: none; opacity: .75; }
}
