/* Shared error / empty / loading states for every gateway page.
 *
 * Use:
 *   .error-state  — full-width error panel (failed load, broken flow)
 *   .error-card   — inline banner above content (partial failure, warning)
 *   .empty-state  — "nothing here yet" for tabs/tables/lists
 *
 * Existing single-underscore selectors (.empty-state-title, .empty-state-body)
 * stay supported so predictions.html / saved.html / intelligence.html keep
 * rendering unchanged. New code should prefer the double-underscore BEM
 * form (.empty-state__title) so searches, linting, and design-system docs
 * line up with the rest of the gateway.
 */


/* ── Full-page error ────────────────────────────────────────────────────── */

.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-8);
  text-align: center;
  color: var(--text-secondary);
  border: 1px solid var(--border-ghost);
  border-radius: var(--radius-md, var(--radius));
  background: var(--surface-raised, var(--bg-raised));
}
.error-state__icon {
  width: 24px;
  height: 24px;
  opacity: 0.5;
  margin-bottom: var(--space-3);
}
.error-state__title {
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: var(--space-2);
}
.error-state__body {
  font-size: var(--text-base);
  max-width: 340px;
  margin-bottom: var(--space-4);
  line-height: 1.55;
}
.error-state__actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  justify-content: center;
}
.error-state__actions button,
.error-state__actions .btn,
.error-state__actions a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
  color: var(--text-primary);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.error-state__actions button:hover,
.error-state__actions .btn:hover,
.error-state__actions a:hover {
  background: var(--bg-raised);
  border-color: var(--border-strong);
}


/* ── Inline error banner ─────────────────────────────────────────────────── */

.error-card {
  padding: var(--space-4);
  background: var(--surface-raised, var(--bg-raised));
  border-left: 2px solid var(--text-tertiary);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  color: var(--text-secondary);
  line-height: 1.55;
}
.error-card strong {
  color: var(--text-primary);
  font-weight: 500;
}
.error-card + .error-card {
  margin-top: var(--space-2);
}


/* ── Empty state ─────────────────────────────────────────────────────────── */

.empty-state {
  text-align: center;
  padding: var(--space-10) var(--space-4);
  color: var(--text-secondary);
}

/* BEM canonical form (new code) */
.empty-state__title {
  font-size: var(--text-xl);
  color: var(--text-primary);
  margin-bottom: var(--space-3);
  letter-spacing: -0.01em;
}
.empty-state__body {
  max-width: 400px;
  margin: 0 auto var(--space-5);
  font-size: var(--text-base);
  line-height: 1.6;
}
.empty-state__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  background: var(--interactive-bg);
  color: var(--interactive-text);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  border: 1px solid var(--interactive-bg);
  transition: background 0.12s ease;
}
.empty-state__cta:hover {
  background: var(--interactive-hover);
  border-color: var(--interactive-hover);
}

/* Back-compat: legacy single-dash class names used by predictions.html,
   saved.html, intelligence.html. Map 1:1 onto the BEM rules. */
.empty-state-title {
  font-size: var(--text-xl);
  color: var(--text-primary);
  margin-bottom: var(--space-3);
  letter-spacing: -0.01em;
}
.empty-state-body {
  max-width: 400px;
  margin: 0 auto var(--space-5);
  font-size: var(--text-base);
  line-height: 1.6;
}


/* ── Inline empty state — compact variant for tables, lists, grids ─────── */

.empty-state--inline {
  padding: var(--space-6) var(--space-4);
  border: 1px dashed var(--border-ghost);
  border-radius: var(--radius-sm);
  background: transparent;
}
.empty-state--inline .empty-state__title,
.empty-state--inline .empty-state-title {
  font-size: 15px;
  margin-bottom: var(--space-2);
}
.empty-state--inline .empty-state__body,
.empty-state--inline .empty-state-body {
  font-size: var(--text-sm);
  margin-bottom: 0;
}

/* Automatic fallback: any element tagged `[data-empty-hint="..."]` shows
   that hint via `::after` when the element renders with no children. Lets
   us keep empty-state copy in the HTML without backend logic — the server
   just renders the container; CSS picks up the empty case. */
[data-empty-hint]:empty {
  display: block;
  padding: var(--space-6) var(--space-4);
  border: 1px dashed var(--border-ghost);
  border-radius: var(--radius-sm);
  text-align: center;
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}
[data-empty-hint]:empty::after {
  content: attr(data-empty-hint);
}


/* ── Simple loading text fallback (pages without a skeleton container) ──── */

.loading-text {
  color: var(--text-tertiary);
  font-size: var(--text-base);
  padding: var(--space-5);
  text-align: center;
}
.loading-text::after {
  content: "";
  display: inline-block;
  width: 4px;
  margin-left: 2px;
  vertical-align: baseline;
  animation: loading-dots 1.2s steps(4) infinite;
}
@keyframes loading-dots {
  0%, 20%  { content: "."; }
  40%      { content: ".."; }
  60%      { content: "..."; }
  80%, 100%{ content: ""; }
}


/* ══════════════════════════════════════════════════════════════════════
   In-flight, disabled, validation, progress — shared across the product.
   Every interactive control on every page should pick these up by
   default so the user never sees a jarring "did my click register?"
   moment. Also covers the toast host positioning so toast.js works
   without any per-page CSS.
   ══════════════════════════════════════════════════════════════════════ */

/* ── In-flight buttons (spec Part 4) ────────────────────────────────── */
/* Add .is-busy to any button that's currently sending a request. It
   hides the label and exposes a small spinner via the pseudo-element
   so no layout shift happens on toggle. */

.btn.is-busy,
button.is-busy,
.nv-busy {
  position: relative;
  color: transparent !important;
  pointer-events: none;
  cursor: wait;
}
.btn.is-busy::after,
button.is-busy::after,
.nv-busy::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--text-primary);
  opacity: 0.7;
  animation: nv-spin 0.7s linear infinite;
}
@keyframes nv-spin { to { transform: rotate(360deg); } }

/* Small inline spinner you can drop next to text in a button label. */
.nv-spinner-sm {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  opacity: 0.7;
  vertical-align: -2px;
  animation: nv-spin 0.7s linear infinite;
}


/* ── Disabled state (spec Part 6) ───────────────────────────────────── */
/* Both native `[disabled]` and ARIA-only `[aria-disabled]` get the
   same visual so server-rendered anchors that can't use the HTML
   disabled attribute still look right. */

button[disabled],
input[disabled],
select[disabled],
textarea[disabled],
[aria-disabled="true"]:not(.nv-busy) {
  opacity: 0.5;
  cursor: not-allowed;
}
[aria-disabled="true"]:not(.nv-busy) * { pointer-events: none; }


/* ── Form-field validation (spec Part 8) ────────────────────────────── */
/* Inline, per-field, blur-triggered. Apply via `.field` wrapper +
   `.field--error` / `.field--valid` state classes. */

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--space-3);
}
.field__label {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
}
.field__input,
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field input[type="search"],
.field textarea,
.field select {
  padding: 10px 12px;
  background: var(--bg-base);
  color: var(--text-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--text-base);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field__input:focus-visible,
.field input:focus-visible,
.field textarea:focus-visible,
.field select:focus-visible {
  outline: none;
  border-color: var(--text-primary);
  box-shadow: 0 0 0 3px var(--interactive-ghost);
}
.field__hint {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.5;
}
.field__error {
  font-size: 12px;
  color: var(--text-primary);
  font-weight: 500;
  padding: 4px 8px;
  border-left: 2px solid var(--text-primary);
  background: var(--bg-surface);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  display: none;
}
.field--error .field__input,
.field--error .field input,
.field--error .field textarea,
.field--error .field select { border-color: var(--text-primary); }
.field--error .field__error { display: block; }


/* ── Progress (spec Part 7) ─────────────────────────────────────────── */

.progress {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--bg-surface);
  border: 1px solid var(--border-ghost);
  border-radius: var(--radius-sm);
}
.progress__title {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
}
.progress__bar {
  width: 100%;
  height: 6px;
  background: var(--bg-raised);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.progress__fill {
  height: 100%;
  background: var(--text-primary);
  transition: width 0.3s ease;
}
.progress__meta {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
/* Indeterminate (unknown total) — animated sweep bar. */
.progress--indeterminate .progress__fill {
  width: 35%;
  animation: nv-progress-sweep 1.4s ease-in-out infinite;
}
@keyframes nv-progress-sweep {
  0%   { margin-left: -35%; }
  100% { margin-left: 100%;  }
}


/* ══════════════════════════════════════════════════════════════════════
   Toast host + cards (consumed by static/toast.js).
   Lives in states.css so every page that loads states.css inherits the
   toast styles automatically — no extra <link> per page.
   ══════════════════════════════════════════════════════════════════════ */

.nv-toast-host {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column-reverse;  /* newest closest to bottom edge */
  gap: 8px;
  z-index: 200;
  pointer-events: none;            /* host is a container; toasts opt-in */
  max-width: calc(100vw - 32px);
}
.nv-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--bg-float);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  line-height: 1.4;
  min-width: 220px;
  max-width: 420px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.nv-toast--enter { opacity: 1; transform: translateY(0); }
.nv-toast--exit  { opacity: 0; transform: translateY(8px); }
.nv-toast-body {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nv-toast-icon-wrap {
  display: flex;
  align-items: center;
  color: var(--text-secondary);
  flex-shrink: 0;
}
.nv-toast-icon { width: 14px; height: 14px; }
.nv-toast-close {
  background: transparent;
  border: none;
  color: var(--text-tertiary);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-md);
  line-height: 1;
  padding: 0 4px;
  flex-shrink: 0;
}
.nv-toast-close:hover { color: var(--text-primary); }

/* Error toast: border-emphasised, never coloured red (monochrome). */
.nv-toast--error {
  border-color: var(--text-primary);
  box-shadow: 0 0 0 1px var(--text-primary), var(--shadow-lg);
}

.nv-toast-spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  opacity: 0.7;
  animation: nv-spin 0.7s linear infinite;
}

/* Mobile: stick to the top so the OS status bar / virtual keyboard
   don't fight the toast for space at the bottom. */
@media (max-width: 640px) {
  .nv-toast-host {
    top: 12px;
    bottom: auto;
    flex-direction: column;
  }
}


/* ══════════════════════════════════════════════════════════════════════
   Empty-state variants (spec Part 1)
   Additive to the existing .empty-state / .empty-state-title family.
   ══════════════════════════════════════════════════════════════════════ */

.empty-state--compact {
  padding: var(--space-4) var(--space-3);
}
.empty-state--compact .empty-state-title,
.empty-state--compact .empty-state__title {
  font-size: var(--text-base);
  margin-bottom: 4px;
}
.empty-state__cta-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: var(--space-3);
}
