/* ============================================================
 * MapOutcomes — Feedback States (P2-30)
 * Canonical loading / empty / error state components.
 *
 * Spec: agentdb doc #12409 (P2-30 Full Implementation Spec v1.0)
 * Loaded on all pages via header.php (before component CSS).
 * Component CSS loaded later may still override these rules —
 * no component file should redefine the base classes below.
 *
 * Tokens: all values map to Design System v1.0 tokens defined in
 * header.css :root, with hex fallbacks for safety.
 *
 * ── Usage Decision Tree ──────────────────────────────────────
 *   < 500ms    → instant swap; no skeleton (content appears directly)
 *   500ms–3s   → L1 skeleton (.loading-skeleton)
 *   3s–30s     → L2 progress (.mo-progress) or L3 inline section loading
 *   > 30s      → L4 spinner + % + ETA (.mo-processing)
 *
 *   Empty      → E1 zero-state / E2 filtered / E3 permission-blocked
 *   Error      → ER1 field validation / ER2 toast / ER3 network / ER4 fatal
 *
 * ── Accessibility rules ──────────────────────────────────────
 *   - One announcement channel per status: never mirror status text
 *     in both aria-live and visible text (4.1.3)
 *   - Status is never conveyed by colour alone (1.4.1)
 *   - prefers-reduced-motion: all state animations stop (2.3.3)
 *   - Focus is never moved into toasts or status regions
 *   - Error text ≥ 12px, contrast ≥ 4.5:1 on its background
 * ============================================================ */

/* ============================================================
 * L1 — Skeleton loading (canonical)
 * Shimmer gradient in the parchment palette. Existing HTML uses
 * inline styles for sizing; modifier classes below are available
 * for new markup.
 * ============================================================ */

.loading-skeleton {
  background: linear-gradient(
    90deg,
    var(--parchment-cream, #F4F1E8) 25%,
    var(--mist, #E8EBED) 50%,
    var(--parchment-cream, #F4F1E8) 75%
  );
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
  border-radius: var(--radius-sm, 4px);
}

@keyframes skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* L1 modifiers (for new markup; existing pages size via inline style) */
.loading-skeleton--title {
  height: 20px;
  width: 40%;
  margin-bottom: 12px;
}
.loading-skeleton--line {
  height: 12px;
  width: 100%;
  margin-bottom: 8px;
}
.loading-skeleton--line-short {
  height: 12px;
  width: 60%;
  margin-bottom: 8px;
}
.loading-skeleton--block {
  height: 80px;
  width: 100%;
  margin-bottom: 12px;
}
.loading-skeleton--card {
  display: block;
  padding: 16px;
}

/* Skeleton grid layouts (L1 for list/card views) */
.loading-skeleton-grid {
  display: grid;
  gap: 16px;
}
.loading-skeleton-grid--2 {
  grid-template-columns: repeat(2, 1fr);
}
.loading-skeleton-grid--3 {
  grid-template-columns: repeat(3, 1fr);
}

/* ============================================================
 * L2 — Progress bar
 * Dense 6px bar for most views; .mo-progress--tall (8px) for
 * modals. Terminal states: .is-complete (green), .is-error (red).
 * The element carries role="progressbar" + aria-valuenow/min/max;
 * the fill width is visual only.
 * ============================================================ */

.mo-progress {
  position: relative;
  width: 100%;
  height: 6px;
  background: var(--mist, #E8EBED);
  border-radius: var(--radius-pill, 999px);
  overflow: hidden;
}

.mo-progress--tall {
  height: 8px;
  border-radius: var(--radius-sm, 4px);
}

.mo-progress-fill {
  height: 100%;
  width: 0%;
  min-width: 2%; /* prevent zero-width bar at start */
  background: var(--topographic-teal, #0A7A8C);
  border-radius: inherit;
  transition: width 0.3s ease;
}

/* Terminal states — colour + shape change, never colour alone */
.mo-progress.is-complete .mo-progress-fill {
  background: var(--success-green, #2D5F3F);
}
.mo-progress.is-error .mo-progress-fill {
  background: var(--danger-red, #B4534E);
}

/* Optional status line next to the bar */
.mo-progress-status {
  font-size: var(--text-xs, 12px);
  color: var(--slate-gray, #5A6C75);
  margin-top: 4px;
}

/* Contextual: upload cards keep a compact fixed-width bar */
.upload-card .mo-progress {
  width: 120px;
  height: 8px;
  background: var(--parchment-cream, #F4F1E8);
}

/* ============================================================
 * L3 — Inline section loading (no new CSS)
 * Reuse L1 skeleton modifiers inside the section being refreshed.
 * Set aria-busy="true" on the section container while loading.
 * Under 500ms: skip the skeleton entirely — swap content in place.
 * ============================================================ */

/* (L3 intentionally defines no new selectors — it reuses L1.) */

/* ============================================================
 * L4 — Long processing (spinner + percentage + ETA)
 * For jobs over 30 seconds (large imports, full re-analyses).
 * CSS-only spinner — no icon-font dependency.
 * ============================================================ */

.mo-processing {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 32px 24px;
  text-align: center;
}

.mo-processing-spinner {
  width: 24px;
  height: 24px;
  border: 3px solid var(--mist, #E8EBED);
  border-top-color: var(--topographic-teal, #0A7A8C);
  border-radius: 50%;
  animation: mo-spin 1s linear infinite;
}

@keyframes mo-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.mo-processing-label {
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--charcoal, #2C3539);
}

/* Details row: step info · percentage · ETA (bullet separators) */
.mo-processing-details {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-xs, 12px);
  color: var(--slate-gray, #5A6C75);
}
.mo-processing-details > span + span::before {
  content: '\2022';
  margin-right: 8px;
}

/* ============================================================
 * Empty states (E1 zero / E2 filtered / E3 permission-blocked)
 * Base + canonical sub-elements. Per-page sub-element overrides
 * in component CSS still apply (loaded later) where they exist.
 *
 * HTML pattern:
 *   <div class="empty-state" role="status">
 *     <div class="empty-state-icon" aria-hidden="true">…</div>
 *     <div class="empty-state-title">…</div>
 *     <div class="empty-state-description">…</div>
 *     <a class="btn-empty-state" href="…">…</a>
 *   </div>
 *
 * Icon rule: decorative only (aria-hidden), 64px box.
 * Calm test: no alarm colour in empty states — these are
 * expected conditions, not failures.
 * ============================================================ */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 24px;
  text-align: center;
}

.empty-state-icon {
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 16px;
  color: var(--slate-gray, #5A6C75);
  font-size: 48px;
  line-height: 1;
}

.empty-state-title {
  font-size: var(--text-lg, 18px);
  font-weight: 600;
  color: var(--charcoal, #2C3539);
  margin-bottom: 8px;
}

.empty-state-description {
  font-size: var(--text-sm, 13px);
  color: var(--slate-gray, #5A6C75);
  line-height: 1.5;
  max-width: 480px;
  margin: 0 auto;
}

/* E2 — filtered empty: smaller padding, clear-filter CTA expected */
.empty-state--filtered {
  padding: 40px 24px;
}

/* E3 — permission-blocked: frost panel signals a boundary,
 * not an error; copy explains who can grant access. */
.empty-state--blocked {
  background: var(--frost, #F3F5F6);
  border: 1px solid var(--mist, #E8EBED);
  border-radius: var(--radius-md, 8px);
}

/* Primary action for empty states (canonical — was duplicated
 * in evidence-upload / ai-results / gap-detail) */
.btn-empty-state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  font-size: var(--text-sm, 14px);
  font-weight: 600;
  color: var(--white, #FFFFFF);
  background: var(--topographic-teal, #0A7A8C);
  border: none;
  border-radius: var(--radius-md, 8px);
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.btn-empty-state:hover {
  background: var(--mapoutcomes-blue-dark, #086474);
}

.btn-empty-state:focus-visible {
  outline: 3px solid var(--topographic-teal, #0A7A8C);
  outline-offset: 2px;
}

/* ============================================================
 * ER1 — Field validation error
 * Inline under the field. Shown on blur (first failure) and on
 * submit. role="alert" + aria-describedby from the field.
 * No red-tinted background — fails 4.5:1 for the red text.
 * ============================================================ */

.mo-field-error {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 4px;
  font-size: var(--text-xs, 12px);
  line-height: 1.4;
  color: var(--danger-red, #B4534E);
}

/* Flagged control: red border + tinted focus ring (not a red bg) */
input.is-error,
select.is-error,
textarea.is-error {
  border-color: var(--danger-red, #B4534E);
}
input.is-error:focus,
select.is-error:focus,
textarea.is-error:focus {
  outline: 3px solid color-mix(in srgb, var(--danger-red, #B4534E) 30%, transparent);
  outline-offset: 1px;
}

/* Banner variant (form-level summary): frost background keeps
 * red text at 4.5:1+ contrast */
.mo-error-banner {
  background: var(--frost, #F3F5F6);
  border: 1px solid var(--danger-red, #B4534E);
  border-left: 4px solid var(--danger-red, #B4534E);
  border-radius: var(--radius-md, 8px);
  padding: 12px 16px;
  margin-bottom: 16px;
}
.mo-error-banner-title {
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--charcoal, #2C3539);
  margin-bottom: 4px;
}
.mo-error-banner-detail {
  font-size: var(--text-xs, 12px);
  color: var(--slate-gray, #5A6C75);
}

/* ============================================================
 * ER2 — Toast (canonical; replaces .ai-correction-toast)
 * The toast element is the live region (role="status",
 * aria-live="polite") — do not add a second announcement.
 * The container is a structural stacker (no aria-live).
 * Position: bottom-right desktop, bottom-center ≤767px
 * (spec #12409 + #12549 ruling C). z-index: var(--z-overlay,
 * 1040) — below modals (1060), above sticky chrome (1020).
 * Auto-dismiss: success/info 5s, warning 8s, error persists.
 * Max 3 stacked (4th dismisses the oldest). Focus never moves
 * into the toast.
 * ============================================================ */

.mo-toast-container {
  position: fixed;
  bottom: var(--space-4, 16px);
  right: var(--space-4, 16px);
  z-index: var(--z-overlay, 1040);
  display: flex;
  flex-direction: column-reverse;   /* newest on bottom, stable reading order */
  gap: var(--space-2, 8px);
  pointer-events: none;             /* clicks pass through the gaps */
  max-width: min(420px, calc(100vw - var(--space-8, 32px)));
}

@keyframes mo-toast-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes mo-toast-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(16px); }
}

.mo-toast {
  display: none;
  min-width: 280px;
  max-width: 100%;
  background: var(--white, #FFFFFF);
  border: 1px solid var(--mist, #E8EBED);
  border-radius: var(--radius-md, 8px);
  box-shadow: var(--shadow-lg, 0 8px 32px rgba(44, 53, 57, 0.20));
  padding: 14px 20px;
  pointer-events: auto;
  animation: mo-toast-in 0.25s ease;
}

.mo-toast.is-visible {
  display: flex;
  align-items: center;
  gap: 10px;
}

.mo-toast.is-dismissing {
  animation: mo-toast-out 0.2s ease forwards;
}

/* Severity: left accent border + icon colour (never colour alone —
 * icon shape and text carry the meaning) */
.mo-toast.is-success {
  border-left: 4px solid var(--success-green, #2D5F3F);
}
.mo-toast.is-warning {
  border-left: 4px solid var(--compass-gold, #C9A661);
}
.mo-toast.is-error {
  border-left: 4px solid var(--danger-red, #B4534E);
}

.mo-toast-icon {
  flex-shrink: 0;
}
.mo-toast.is-success .mo-toast-icon {
  color: var(--success-green, #2D5F3F);
}
.mo-toast.is-warning .mo-toast-icon {
  color: var(--compass-gold, #C9A661);
}
.mo-toast.is-error .mo-toast-icon {
  color: var(--danger-red, #B4534E);
}

.mo-toast-text {
  font-size: var(--text-sm, 13px);
  color: var(--charcoal, #2C3539);
  line-height: 1.4;
}

/* Action button inside the toast (e.g. Undo, 10s window) */
.mo-toast-action {
  margin-left: auto;
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--text-xs, 12px);
  font-weight: 600;
  color: var(--topographic-teal, #0A7A8C);
  padding: 4px 8px;
  flex-shrink: 0;
  text-decoration: underline;
}
.mo-toast-action:focus-visible {
  outline: 3px solid var(--topographic-teal, #0A7A8C);
  outline-offset: 2px;
}

/* Dismiss control */
.mo-toast-dismiss {
  margin-left: auto;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--slate-gray, #5A6C75);
  padding: 4px;
  flex-shrink: 0;
}
.mo-toast-dismiss:focus-visible {
  outline: 3px solid var(--topographic-teal, #0A7A8C);
  outline-offset: 2px;
}

/* ============================================================
 * ER3 — Network error (retry-able)
 * Shown when a request fails mid-flow. Retry button is secondary
 * weight; while retrying it shows the spinner and is inert.
 * ============================================================ */

.mo-network-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 32px 24px;
  text-align: center;
  background: var(--frost, #F3F5F6);
  border: 1px solid var(--mist, #E8EBED);
  border-radius: var(--radius-md, 8px);
}

.mo-network-error-title {
  font-size: var(--text-base, 15px);
  font-weight: 600;
  color: var(--charcoal, #2C3539);
}

.mo-network-error-detail {
  font-size: var(--text-xs, 12px);
  color: var(--slate-gray, #5A6C75);
  max-width: 480px;
}

.btn-retry {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--topographic-teal, #0A7A8C);
  background: var(--white, #FFFFFF);
  border: 1px solid var(--topographic-teal, #0A7A8C);
  border-radius: var(--radius-md, 8px);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.btn-retry:hover {
  background: var(--frost, #F3F5F6);
}
.btn-retry:focus-visible {
  outline: 3px solid var(--topographic-teal, #0A7A8C);
  outline-offset: 2px;
}

/* While a retry request is in flight */
.btn-retry.is-retrying {
  opacity: 0.6;
  pointer-events: none;
}
.btn-retry.is-retrying::before {
  content: '';
  width: 12px;
  height: 12px;
  border: 2px solid var(--mist, #E8EBED);
  border-top-color: var(--topographic-teal, #0A7A8C);
  border-radius: 50%;
  animation: mo-spin 1s linear infinite;
}

/* ============================================================
 * ER4 — Fatal / full-page error (404, 500, session-lost)
 * Full-viewport centred panel. Copy tells the user what happened,
 * why, and the single next action.
 * ============================================================ */

.mo-fatal {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  text-align: center;
  padding: 48px 24px;
}

.mo-fatal-code {
  font-size: 64px;
  font-weight: 700;
  line-height: 1;
  color: var(--topographic-teal, #0A7A8C);
  margin-bottom: 12px;
}

.mo-fatal-title {
  font-size: var(--text-lg, 18px);
  font-weight: 600;
  color: var(--charcoal, #2C3539);
  margin-bottom: 8px;
}

.mo-fatal-detail {
  font-size: var(--text-sm, 13px);
  color: var(--slate-gray, #5A6C75);
  max-width: 480px;
  margin: 0 auto 20px;
}

/* ============================================================
 * Responsive
 * ============================================================ */

@media (max-width: 767px) {
  .mo-toast-container {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    bottom: var(--space-2, 8px);
    max-width: calc(100vw - var(--space-4, 16px));
  }
  .upload-card .mo-progress {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .loading-skeleton-grid--2,
  .loading-skeleton-grid--3 {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
 * Reduced motion (2.3.3)
 * All state animations stop; static fallbacks preserve meaning.
 * ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .loading-skeleton {
    animation: none;
    background: var(--parchment-cream, #F4F1E8);
  }
  .mo-toast,
  .mo-toast.is-dismissing {
    animation: none;
  }
  .mo-processing-spinner {
    animation: none;
    border-top-color: var(--topographic-teal, #0A7A8C);
  }
  .btn-retry.is-retrying::before {
    animation: none;
  }
  .mo-progress-fill {
    transition: none;
  }
  .btn-empty-state,
  .btn-retry {
    transition: none;
  }
}
