/* Shared component base layer.
 *
 * The small structural pieces every tool needs: a spinner, a loading state, an
 * empty state, an error banner, a card, a modal. Each of these had been written
 * independently in 8 to 14 tools, and each had drifted - .spinner had TEN
 * distinct definitions across fourteen tools, varying only in size, border width
 * and animation speed, none of which meant anything.
 *
 * LOAD ORDER: this file loads BEFORE the tool's own styles.css, unlike
 * app-header.css and buttons.css which load after.
 *
 * That difference is deliberate and is the whole design of this file. The header
 * and the button roles are a standard: a tool must not vary them, so they win on
 * source order. These components are a BASE: a tool legitimately varies a card's
 * padding for its density, or a modal's max-width between a confirm dialog and a
 * content viewer. Loading first means a tool overrides them with a plain
 * single-class rule and no specificity games.
 *
 * So: put the shape here, leave the sizing a tool has a real reason to change.
 *
 *   <link rel="stylesheet" href="/shared/theme/variables.css">
 *   <link rel="stylesheet" href="/shared/theme/components.css">   <- here
 *   <link rel="stylesheet" href="styles.css">
 *   <link rel="stylesheet" href="/shared/theme/app-header.css">
 *   <link rel="stylesheet" href="/shared/theme/buttons.css">
 */

/* ---- spinner ----------------------------------------------------------- */

/* No margin here on purpose. Six tools had a margin on the spinner itself to
   space it from a caption; that is the parent's job, and .loading does it with
   a gap. */
.spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

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

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

/* ---- loading and empty states ------------------------------------------ */

.loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 3rem;
  color: var(--color-text-muted);
}

.loading-inline {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 2rem;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.empty-state {
  text-align: center;
  padding: 2rem;
  color: var(--color-text-secondary);
}

/* ---- error banner ------------------------------------------------------ */

/* The text is the -hover variant, not the base. --color-danger on
   --color-danger-light measures 3.96:1 in light mode and fails; seven tools had
   that bug. The dismiss glyph inside uses `color: inherit`, so it inherits a
   passing colour from here. */
.error-banner {
  background: var(--color-danger-light);
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-sm);
  color: var(--color-danger-hover);
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
}

.error-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
}

/* ---- card -------------------------------------------------------------- */

/* radius-lg and 2rem because six tools already agreed on exactly that, so this
   default leaves the fewest overrides behind. Padding is the property tools most
   often need to change, and it is a plain declaration they can override without
   specificity tricks.

   The border is a deliberate addition: those six had a shadow and no border, and
   a shadow is close to invisible on a dark card, so their cards had no visible
   edge in dark mode. */
.card {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 2rem;
}

/* ---- modal ------------------------------------------------------------- */

/* One z-index. Tools had 100, 200 and 1000, which only matters when two of them
   are wrong relative to something else on the page. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgba(0, 0, 0, 0.5);
}

/* max-width is deliberately modest and expected to be overridden: a confirm
   dialog and a content viewer are not the same width. */
.modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 520px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--color-bg-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
