/* Small standalone shared components: Empty state, Spinner, Action-needed
   notice, Impersonation banner, and the topbar autosuggest-running pill.
   Extracted from components.css (CSS split, CLAUDE.md §12).
   Consumed by frontend/js/components/empty-state.js, action-needed.js,
   impersonation-banner.js, autosuggest-indicator.js, and broadly by
   loading-state markup across many views. */
@layer components {

/* ── Empty state (components/empty-state.js) ─────────────────────────────── */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8);
  text-align: center;
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--muted);
}
.empty-state .icon { color: var(--muted); opacity: 0.6; }
.empty-state-title { font-size: var(--text-base); color: var(--text); font-weight: 500; }
.empty-state-sub { font-size: var(--text-sm); color: var(--muted); }

/* ── Spinner ───────────────────────────────────────────────────────────────── */
.spinner-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8, 3rem);
}

.spinner {
  display: inline-block;
  width: 2rem;
  height: 2rem;
  border: 3px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

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

/* "Find it" flash — one keyframe for every jump-to target: Current/My
   Schedule's "Jump to today" cell or agenda row (.flash), the roster grid
   row a violation chip scrolls to (.rgv2-flash-row) and the people-matrix
   day (.pm-flash). An inset shadow instead of a background swap, so the
   target keeps its own tint (weekend, today) underneath. --flash-tint
   (base.css) carries the light/dark colour. */
@keyframes flash-highlight {
  0%, 100% { box-shadow: none; }
  30%      { box-shadow: inset 0 0 0 999px var(--flash-tint); }
}

/* ── Action-needed (shared component, components/action-needed.js) ───────────
   One visual language for "here's a fact, here's what to do" — used both in
   the persistent banner slot (banner.js) and inline on My Schedule's "Next
   schedule" card, so the same underlying fact never looks like two unrelated
   features that happen to say similar things. Urgency reads primarily through
   hue (amber/orange = worth knowing soon, red = acting now), reinforced at
   three levels — a background wash, a solid icon badge, and a solid CTA
   button — the same escalation every other status-coded surface in this app
   uses (badges, status pills, violation chips), just applied with more
   weight since this is the one surface meant to interrupt, not just label. */
.action-needed {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-sm);
  z-index: 1;
}
.action-needed.tone-amber {
  background: var(--warning-bg);
  color: var(--warning);
}
.action-needed.tone-blue {
  background: var(--info-bg);
  color: var(--info);
}
.action-needed.tone-red {
  background: var(--danger-bg);
  color: var(--danger);
}

/* A solid colour swatch pinned to the top edge reads as a stronger urgency
   signal than a left stripe on a full-bleed banner, and stays legible at a
   glance even before the message text is read. */
.action-needed::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
}
.action-needed.tone-amber::before { background: var(--warning); }
.action-needed.tone-blue::before  { background: var(--info); }
.action-needed.tone-red::before   { background: var(--danger); }

.action-needed-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex-shrink: 0;
  color: #fff;
}
.action-needed.tone-amber .action-needed-icon { color: var(--on-warning); background: var(--warning); }
.action-needed.tone-blue .action-needed-icon  { background: var(--info); }
.action-needed.tone-red .action-needed-icon   { background: var(--danger); }

/* min-width:0 overrides the flex-item default of min-width:auto, which
   otherwise refuses to shrink below the message's unwrapped width and forces
   the whole single-column mobile grid (#app) to overflow the viewport. */
.action-needed-msg { flex: 1; min-width: 0; font-weight: 500; }
.action-needed-msg strong { font-weight: 700; }
/* Optional detail under the message (a list kept in view): body text, not the tone colour. */
.action-needed-detail { margin-top: var(--space-1); color: var(--text); font-weight: 400; }

/* The action button is the shared .banner-action (base-ui.css) in the tone's colour. */
.action-needed.tone-amber .banner-action { color: var(--on-warning); background: var(--warning); }
.action-needed.tone-blue .banner-action  { background: var(--info); }
.action-needed.tone-red .banner-action   { background: var(--danger); }
/* A second choice beside the solid one (actionNeededHtml `actions`): outlined in the tone's text colour. */
.action-needed .banner-action.banner-action--secondary { background: transparent; color: inherit; box-shadow: inset 0 0 0 1px currentColor; }

.action-needed-close {
  background: none;
  border: none;
  color: inherit;
  opacity: 0.55;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

/* A longer message (e.g. a shift's full "at Location · HH:MM–HH:MM" detail)
   otherwise squeezes .action-needed-msg's flex:1 column down to a
   one-word-per-line sliver next to the action button on a phone-width
   screen. Wrapping the row lets the message use the full width and drops
   the button to its own line below, instead of fighting it for space. */
@media (max-width: 600px) {
  .action-needed { flex-wrap: wrap; }
  .action-needed-msg { flex-basis: 100%; }
}
.action-needed-close:hover { opacity: 1; background: rgba(0,0,0,0.08); }

/* ── Impersonation banner (components/impersonation-banner.js) ───────────────
   Deliberately its own always-red, never-dismissible bar rather than another
   .action-needed tone — this must stay unmissable for the entire duration of
   an admin "God Mode" session (CLAUDE.md §3.1g), not fade into the same
   dismissible-notice pattern as an ordinary attention-needed rule. */
.impersonation-banner-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-sm);
  line-height: 1.4;
  background: var(--danger-bg);
  color: var(--danger);
  border-bottom: 1px solid var(--danger);
  position: relative;
  z-index: 1;
}
.impersonation-banner-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--danger);
  color: #fff;
}
.impersonation-banner-msg { flex: 1; min-width: 0; font-weight: 600; }
@media (max-width: 600px) {
  .impersonation-banner-bar { flex-wrap: wrap; }
  .impersonation-banner-msg { flex-basis: 100%; }
}


/* .action-needed's tone text colours need no dark override here — all
   three now read through --warning/--info/--danger, which already flip
   under [data-theme="dark"] in base.css. */
[data-theme="dark"] .action-needed-close:hover { background: rgba(255,255,255,0.1); }

/* Topbar pill for an in-flight background autosuggest run, visible to every
   connected rostermaker/admin on every page — see
   features/roster/autosuggest-indicator.js. Sits between the menu-search and
   notification-bell icon-buttons; hidden (via the shared .hidden utility)
   when there is no run, queued run or finished outcome to show. */
#autosuggest-indicator {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  color: var(--text);
  font-size: .82rem;
  white-space: nowrap;
  max-width: 420px;
  overflow: hidden;
}
#autosuggest-indicator-text {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
}
.autosuggest-pill-label, .autosuggest-pill-short { overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
.autosuggest-pill-short { display: none; }
.autosuggest-pill-icon { flex: 0 0 auto; font-weight: 700; }
/* +n: more queued runs (or more finished runs while an outcome shows). */
.autosuggest-pill-count {
  flex: 0 0 auto;
  padding: 0 var(--space-1);
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: .72rem;
  font-weight: 600;
}
.autosuggest-pill-open { flex: 0 0 auto; font-weight: 600; color: inherit; text-decoration: underline; }
#autosuggest-indicator-cancel {
  width: 22px;
  height: 22px;
  font-size: .75rem;
  padding: 0;
  flex: 0 0 auto;
}
/* The budget bar (plan D2) along the pill's bottom edge: its fill is
   --autosuggest-fill, set by the indicator; indeterminate while loading and
   building; shown only while a run is going. */
.autosuggest-pill-bar, .autosuggest-card-bar {
  display: none;
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: linear-gradient(var(--primary), var(--primary)) no-repeat left / var(--autosuggest-fill, 0%) 100%;
}
.autosuggest-pill--running .autosuggest-pill-bar { display: block; }
.autosuggest-bar-indeterminate .autosuggest-pill-bar,
.autosuggest-card-bar.autosuggest-bar-indeterminate {
  background-size: 30% 100%;
  animation: autosuggest-bar-slide 1.4s ease-in-out infinite;
}
@keyframes autosuggest-bar-slide {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .autosuggest-bar-indeterminate .autosuggest-pill-bar,
  .autosuggest-card-bar.autosuggest-bar-indeterminate { animation: none; background-size: 100% 100%; opacity: .35; }
}
/* Outcomes (plan D4) in the existing status tokens, never lifecycle hues. */
.autosuggest-pill--success { background: var(--success-bg); color: var(--success); }
.autosuggest-pill--warning { background: var(--warning-bg); color: var(--warning); }
.autosuggest-pill--danger { background: var(--danger-bg); color: var(--danger); }
.autosuggest-pill--success #autosuggest-indicator-text,
.autosuggest-pill--warning #autosuggest-indicator-text,
.autosuggest-pill--danger #autosuggest-indicator-text { color: inherit; }

/* The popover at the pill (action-menu.js openActionPopover): the running
   card with its steps, clock and bar, then the queue and the finished runs,
   each row its label above its controls or inline confirm. */
.autosuggest-queue { min-width: min(360px, calc(100vw - 32px)); }
.autosuggest-card { display: grid; gap: var(--space-1); padding: var(--space-2) 0; }
.autosuggest-steps { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin: 0; padding: 0; list-style: none; font-size: var(--text-sm); }
.autosuggest-step--todo { color: var(--muted); }
.autosuggest-step--current { font-weight: 600; }
.autosuggest-step--done { color: var(--success); }
.autosuggest-card-clock { font-variant-numeric: tabular-nums; font-size: var(--text-sm); }
.autosuggest-card-bar {
  display: block;
  position: relative;
  height: 4px;
  border-radius: var(--r-pill);
  background-color: var(--surface-sunk);
}
.autosuggest-card-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.autosuggest-queue-heading { margin: var(--space-2) 0 0; font-size: var(--text-sm); font-weight: 600; }
.autosuggest-queue-row { display: grid; justify-items: start; gap: var(--space-1); padding: var(--space-2) 0; }
.autosuggest-queue-row + .autosuggest-queue-row { border-top: 1px solid var(--border); }
.autosuggest-outcome--success > p > span { color: var(--success); }
.autosuggest-outcome--warning > p > span { color: var(--warning); }
.autosuggest-outcome--danger > p > span { color: var(--danger); }
.autosuggest-queue-footer { margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--border); }

@media (max-width: 768px) {
  #autosuggest-indicator { max-width: 45vw; }
  #autosuggest-indicator-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The short label leads with step and clock, so truncation keeps them. */
  .autosuggest-pill--running .autosuggest-pill-label { display: none; }
  .autosuggest-pill--running .autosuggest-pill-short { display: inline; }
}
/* Phones: the topbar has no room for the pill's own controls or the +n
   count; Stop, Remove, Open, Dismiss and the queue stay in the popover, and
   the pill keeps a readable width. */
@media (max-width: 600px) {
  #autosuggest-indicator { min-width: 5.5rem; padding-inline: var(--space-2); font-size: .75rem; }
  #autosuggest-indicator-text { padding-inline: 0; }
  #autosuggest-indicator-cancel, .autosuggest-pill-open, .autosuggest-pill-count { display: none; }
}


.load-unavailable { padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: var(--space-3); }
.load-unavailable h2 { font-size: var(--text-base); margin: 0 0 var(--space-2); }
.load-unavailable p { font-size: var(--text-sm); }
@media (max-width: 600px) {
  .attention-banner-slot .action-needed { padding: var(--space-2) var(--space-3); gap: var(--space-2); }
  .attention-banner-slot .action-needed:not(.tone-red) { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 44px; }
  .attention-banner-slot .action-needed:not(.tone-red) .action-needed-icon { display: none; }
  .attention-banner-slot .banner-action { padding-inline: var(--space-2); white-space: normal; }
  .attention-banner-slot .action-needed-msg { flex: 1 1 160px; font-size: var(--text-sm); }
  .attention-banner-slot .banner-action, .attention-banner-slot .action-needed-close { min-height: 44px; min-width: 44px; }
}

} /* @layer components */

@layer components {
.period-workflow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  border-top: 1px solid var(--border);
}
.period-workflow a, .period-workflow > span {
  padding: var(--space-1) var(--space-2);
  border-radius: var(--r-control);
  font-size: var(--text-sm);
  text-decoration: none;
}
.period-workflow a { color: var(--text-2); }
.period-workflow > span { color: var(--muted); }
.period-workflow a:hover { background: var(--surface-sunk); }
.period-workflow a[aria-current="page"] { background: var(--p-50); color: var(--p-800); font-weight: 600; }
.roster-statusbar .period-workflow { margin-bottom: 0; }
@media (max-width: 600px) {
  .period-workflow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .period-workflow a, .period-workflow > span { min-height: 40px; display: flex; align-items: center; }
}

/* ── Affected-shifts panel (features/roster/affected-shifts-panel.js) ────────
   Shared between the roster grid's "Record illness" follow-up and the Leave
   Requests review modal's sick-leave section. */
.affected-shifts-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.affected-shift-row { padding: var(--space-2) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.affected-shift-coverage:not(.hidden) { border-top: 1px solid var(--border); margin-top: var(--space-2); padding-top: var(--space-2); }
}
