/* ==========================================================================
   roster_filter.css — the SHARED roster filter, as a COLLAPSIBLE LEFT RAIL
   (roster_filter.js).

   The facets inside the rail reuse the Treasure chrome (treasure__panel /
   __group / __check / __range / __clear / __search / __count — defined in
   treasure.css) verbatim, so they look 1:1 with Treasure. This file carries only
   the NEW chrome: the rf-layout two-column grid (rail + results), the rail
   collapse affordance (chevron toggle + active-filter dot + body wrapper), the
   reflow-on-collapse, and the narrow-viewport top drawer. No new design tokens —
   values come from base.css.
   ========================================================================== */

/* ---- The rail sort control (optional, opt-in via cfg.sorter) -------------- */

/* The sort <select> leads the rail body when a consumer opts in. It reuses the
   .field/.field__select chrome (components.css) so it reads 1:1 with the
   bestiary/treasure sort menus; rf-sort only sets the rail-local rhythm so the
   label sits above the select like the other rail fields. */
.rf-sort {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.rf-sort__select {
  width: 100%;
}

/* ---- The two-column layout (rail + results) ------------------------------- */

.rf-layout {
  display: grid;
  grid-template-columns: minmax(15rem, 17rem) minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: start;
}

/* Collapsed: the rail shrinks to a thin spine (its head only) and the results
   REFLOW into the freed width — the grid flips to `min-content 1fr`. */
.rf-layout--collapsed {
  grid-template-columns: min-content minmax(0, 1fr);
}

.rf-results {
  min-width: 0;
}

/* The live count lives in the rail body; nothing extra needed for the results
   column besides keeping it from overflowing its track. */

/* ---- The rail panel ------------------------------------------------------- */

/* The .treasure__panel base (sticky, bordered surface, scroll) comes from
   treasure.css; rf-panel only adds the collapse-aware bits. */
.rf-panel {
  min-width: 0;
}

/* The head is the collapse trigger row: the chevron toggle (title + active dot)
   on the left, the Clear control on the right. */
.rf-panel-head {
  align-items: center;
}

/* The chevron toggle button wraps the chevron + the title + the active dot, so
   the whole head row is the affordance. Looks like a heading, behaves like a
   button. */
.rf-collapse {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  color: inherit;
  text-align: left;
}

.rf-collapse__chevron {
  width: 0.85rem;
  height: 0.85rem;
  flex: none;
  color: var(--color-text-muted);
  transition: transform var(--transition-fast, 0.12s ease);
}

/* Expanded (aria-expanded=true): chevron points down (rotate the left-pointing
   glyph). Collapsed: chevron points right (its authored direction). */
.rf-collapse[aria-expanded="true"] .rf-collapse__chevron {
  transform: rotate(-90deg);
}

.rf-collapse:hover .rf-collapse__chevron,
.rf-collapse:focus-visible .rf-collapse__chevron {
  color: var(--color-accent);
}

.rf-panel-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The active-filter dot — a small accent pip that stays lit (even on the
   collapsed spine) whenever any filter is applied, so a narrowed roster still
   reads as filtered. */
.rf-active-dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: none;
  border-radius: 999px;
  background-color: var(--color-accent);
}

.rf-active-dot[hidden] {
  display: none;
}

/* The body wraps search + facets + ranges + count so one collapse hides them all
   with no layout jump (re-establishes the column flow/gap the panel gave those
   children directly, so expanded looks identical to before). */
.rf-panel-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

/* The count sits at the foot of the rail body, separated by a hairline. */
.rf-count {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-sm);
}

/* ---- Collapsed: hide the body, shrink the spine --------------------------- */

.rf-layout--collapsed .rf-panel {
  /* The spine is just the head row; let it size to content (the chevron + dot)
     while staying sticky/bordered like the expanded panel. */
  max-height: none;
  overflow: visible;
}

.rf-layout--collapsed .rf-panel-body {
  display: none;
}

.rf-layout--collapsed .rf-panel-title,
.rf-layout--collapsed .rf-clear {
  /* On the thin spine the chevron + active dot carry the meaning; the title and
     Clear belong to the expanded rail. Keep the title for screen readers but
     visually hide it so the spine stays narrow. */
  display: none;
}

/* ---- Narrow viewport: the rail becomes a top drawer ----------------------- */

@media (max-width: 820px) {
  .rf-layout,
  .rf-layout--collapsed {
    grid-template-columns: minmax(0, 1fr);
  }

  .rf-panel {
    position: static;
    max-height: none;
    overflow: visible;
  }

  /* On a stacked layout the collapse toggle still works as a show/hide for the
     facet drawer; collapsed hides the body exactly as on desktop. */
  .rf-layout--filters-open .rf-panel-body {
    display: flex;
  }
}

/* ==========================================================================
   Shared list-loading indicator (#8, batch-9 reconciliation) — the ONE
   on-brand "loading" idiom used by every list in the app (maps, the shared
   rosters, bestiary, treasure).

   Per the OSR design system's no-spinner rule (Alex Do-Not #11), the busy read
   comes from an ANIMATED MUTED-TEXT label, NOT a rotating disc: the line pulses
   and a three-dot ellipsis cycles, so a slow fetch reads as active work rather
   than a blank wait. This file is globally loaded, so the rule is shared.

   Two adoption paths, both styled here:
   - window.ShadowDark.loadingIndicatorHTML() (and the rosterLoadingHtml alias)
     emit `.roster-loading` with a `.roster-loading__label` + `.roster-loading__dots`.
   - The existing `.saved-roster__loading` "Loading…" hosts (e.g. buildings.js,
     saved.js) upgrade AUTOMATICALLY: this file gives that class the same pulse,
     so a plain-text host animates with no markup change. (Those hosts print
     their own trailing ellipsis, so they get the pulse but not the cycling dots.)

   No new tokens; values come from base.css. Respects reduced-motion.
   ========================================================================== */

.roster-loading {
  display: flex;
  align-items: baseline;
  gap: 0.15rem;
  color: var(--color-text-muted);
  margin: 0;
  animation: rf-loading-pulse 1.4s ease-in-out infinite;
}

.roster-loading__dots {
  display: inline-flex;
  gap: 0.12rem;
}

.roster-loading__dots span {
  animation: rf-loading-dot 1.2s ease-in-out infinite;
}

.roster-loading__dots span:nth-child(2) { animation-delay: 0.2s; }
.roster-loading__dots span:nth-child(3) { animation-delay: 0.4s; }

/* Auto-upgrade: plain-text "Loading…" hosts gain the pulse (no markup change).
   They carry their own ellipsis, so no injected dots — just the wash. */
.saved-roster__loading {
  animation: rf-loading-pulse 1.4s ease-in-out infinite;
}

@keyframes rf-loading-pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

@keyframes rf-loading-dot {
  0%, 100% { opacity: 0.2; }
  40%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .roster-loading,
  .saved-roster__loading,
  .roster-loading__dots span {
    animation: none;
  }
  .roster-loading__dots span { opacity: 1; }
}
