/**
 * client/assets/css/components.css
 *
 * Shared component styles referenced by every later phase's markup.
 * Structural/skinning only — no page-specific rules live here.
 * Every value below references a token from theme.css; nothing is
 * hard-coded (UI/UX §1 discipline), which is what lets Phase 32 (Dark
 * Mode) work without touching this file.
 */

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
  background: var(--color-bg-app);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
}

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  margin: 0;
  letter-spacing: -0.01em;
}
h1 { font-size: var(--font-size-h1); line-height: var(--line-height-h1); font-weight: var(--font-weight-heading-strong); }
h2 { font-size: var(--font-size-h2); line-height: var(--line-height-h2); font-weight: var(--font-weight-heading); }
h3 { font-size: var(--font-size-h3); line-height: var(--line-height-h3); font-weight: var(--font-weight-heading); }
h4 { font-size: var(--font-size-h4); line-height: var(--line-height-h4); font-weight: var(--font-weight-heading); }

.ant-display {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading-strong);
  font-size: var(--font-size-display);
  line-height: var(--line-height-display);
}

.ant-caption {
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  color: var(--color-text-secondary);
}

.ant-label {
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  font-weight: var(--font-weight-label);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
  color: var(--color-text-secondary);
}

@media (max-width: 639px) {
  .ant-display { font-size: var(--font-size-display-mobile); }
  h1 { font-size: var(--font-size-h1-mobile); }
  h2 { font-size: var(--font-size-h2-mobile); }
  h3 { font-size: var(--font-size-h3-mobile); }
  h4 { font-size: var(--font-size-h4-mobile); }
  body { font-size: var(--font-size-body-mobile); }
  .ant-caption { font-size: var(--font-size-caption-mobile); }
  .ant-label { font-size: var(--font-size-label-mobile); }
}

/* ----------------------------------------------------------------
 * Buttons
 * ---------------------------------------------------------------- */
.ant-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--font-size-body);
  line-height: 1.2;
  border: none;
  border-radius: var(--radius-md);
  padding: 10px 20px;
  cursor: pointer;
  transition: filter 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
  white-space: nowrap;
  user-select: none;
}
.ant-btn i { font-size: var(--icon-size-button); }
.ant-btn:active { transform: translateY(1px) scale(0.99); }

.ant-btn--primary {
  background: linear-gradient(135deg, var(--color-primary-600), var(--color-primary-500));
  color: var(--color-neutral-white);
  box-shadow: 0 4px 12px rgba(30, 58, 138, 0.25), 0 1px 2px rgba(14, 24, 48, 0.12);
}
.ant-btn--primary:hover { filter: brightness(1.08); box-shadow: 0 6px 16px rgba(30, 58, 138, 0.3); }
.ant-btn--primary:active { filter: brightness(0.95); box-shadow: 0 2px 6px rgba(30, 58, 138, 0.2); }
.ant-btn--primary:disabled,
.ant-btn--primary.is-disabled {
  background: var(--color-neutral-200);
  color: var(--color-neutral-500);
  box-shadow: none;
  opacity: 1;
  cursor: not-allowed;
}

.ant-btn--secondary {
  background: var(--color-bg-surface);
  color: var(--color-primary-600);
  border: 1px solid var(--color-primary-300);
  box-shadow: 0 1px 2px rgba(14, 24, 48, 0.04);
}
.ant-btn--secondary:hover { background: var(--color-primary-50); border-color: var(--color-primary-400); }

.ant-btn--danger {
  background: linear-gradient(135deg, #D64545, var(--color-error-500));
  color: var(--color-neutral-white);
  box-shadow: 0 4px 12px rgba(225, 75, 75, 0.25);
}
.ant-btn--danger:hover { filter: brightness(1.06); }

.ant-btn--ghost {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px solid transparent;
}
.ant-btn--ghost:hover { background: var(--color-neutral-100); color: var(--color-text-primary); }

.ant-btn--icon {
  padding: var(--space-2);
  border-radius: var(--radius-pill);
  box-shadow: none;
}

.ant-btn--sm { padding: var(--space-1) var(--space-3); font-size: var(--font-size-caption); }

/* ----------------------------------------------------------------
 * Cards
 * ---------------------------------------------------------------- */
.ant-card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: var(--space-5);
  transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}
.ant-card:hover { box-shadow: var(--shadow-2); border-color: var(--color-border); }

.ant-card--accent {
  background: var(--gradient-card-accent);
  border: none;
  color: var(--color-neutral-white);
}
.ant-card--accent:hover { box-shadow: var(--shadow-3); }

/* ----------------------------------------------------------------
 * Badges / chips
 * ---------------------------------------------------------------- */
.ant-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  letter-spacing: 0.01em;
}
.ant-badge--success { background: var(--color-success-100); color: var(--color-success-500); }
.ant-badge--warning  { background: var(--color-warning-100); color: #B27E10; }
.ant-badge--error    { background: var(--color-error-100);   color: var(--color-error-500); }
.ant-badge--info     { background: var(--color-info-100);    color: var(--color-info-500); }

/* Phase 33 — gap closure. vehicle-types.css (Phase 14) shipped the
   neutral/grey variant for its "Global source" tag, but other pages
   (e.g. the Phase 30 billing-audit page's "Locked" rows) reference the
   variant without loading that page's stylesheet. Moved to the shared
   components.css file so the variant is site-wide; vehicle-types.css
   keeps its own copy harmlessly. */
.ant-badge--neutral  { background: var(--color-neutral-100); color: var(--color-neutral-700); }

.ant-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-size-caption);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.ant-chip:hover { border-color: var(--color-primary-300); color: var(--color-primary-600); }
.ant-chip.is-active {
  background: linear-gradient(135deg, var(--color-primary-600), var(--color-primary-500));
  border-color: transparent;
  color: var(--color-neutral-white);
  box-shadow: 0 2px 8px rgba(30, 58, 138, 0.25);
}
.ant-chip-row {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  padding-bottom: var(--space-1);
  scrollbar-width: none;
}
.ant-chip-row::-webkit-scrollbar { display: none; }

/* ----------------------------------------------------------------
 * Forms — every control type is styled here: text inputs, textarea,
 * select, checkbox, radio, switch, and their focus/error/valid states.
 * ---------------------------------------------------------------- */
.ant-field { margin-bottom: var(--space-4); }
.ant-field label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--font-size-caption);
  font-weight: 600;
  color: var(--color-text-secondary);
  letter-spacing: 0.01em;
}
.ant-field label .ant-field__required {
  color: var(--color-error-500);
  margin-left: 2px;
}

.ant-field input[type='text'],
.ant-field input[type='email'],
.ant-field input[type='tel'],
.ant-field input[type='password'],
.ant-field input[type='number'],
.ant-field input[type='search'],
.ant-field input[type='url'],
.ant-field input[type='date'],
.ant-field input[type='datetime-local'],
.ant-field input[type='month'],
.ant-field select,
.ant-field textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 10px 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.ant-field input::placeholder,
.ant-field textarea::placeholder {
  color: var(--color-text-placeholder);
  opacity: 1;
}

.ant-field input:focus,
.ant-field select:focus,
.ant-field textarea:focus {
  outline: none;
  border-color: var(--color-primary-400);
  box-shadow: var(--shadow-focus);
  background: var(--color-bg-surface);
}

.ant-field textarea {
  min-height: 108px;
  resize: vertical;
  line-height: var(--line-height-body);
}

.ant-field input[disabled],
.ant-field select[disabled],
.ant-field textarea[disabled],
.ant-field input[readonly],
.ant-field select[readonly],
.ant-field textarea[readonly] {
  background: var(--color-bg-subtle);
  color: var(--color-text-placeholder);
  cursor: not-allowed;
}

.ant-field.is-invalid input,
.ant-field.is-invalid select,
.ant-field.is-invalid textarea {
  border-color: var(--color-error-500);
}
.ant-field.is-invalid input:focus,
.ant-field.is-invalid select:focus,
.ant-field.is-invalid textarea:focus {
  border-color: var(--color-error-500);
  box-shadow: 0 0 0 var(--focus-ring-width) rgba(225, 75, 75, 0.14);
}
/* Select2 hides the native <select> and renders its own box, so the
   rule above never shows on a Select2-upgraded field — this covers it. */
.ant-field.is-invalid .select2-container--default .select2-selection--single,
.ant-field.is-invalid .select2-container--default .select2-selection--multiple {
  border-color: var(--color-error-500);
}
.ant-field__error {
  display: none;
  color: var(--color-error-500);
  font-size: var(--font-size-caption);
  font-weight: 500;
  margin-top: 6px;
  align-items: center;
  gap: 6px;
}
.ant-field.is-invalid .ant-field__error { display: flex; }
.ant-field.is-invalid .ant-field__error::before {
  font-family: 'Font Awesome 6 Free';
  font-weight: 900;
  content: '\f06a';
  font-size: 12px;
}
.ant-field.is-valid input {
  border-color: var(--color-success-500);
}
.ant-field.is-valid input:focus {
  border-color: var(--color-success-500);
  box-shadow: 0 0 0 var(--focus-ring-width) rgba(31, 169, 122, 0.14);
}
.ant-field__success-icon {
  display: none;
  color: var(--color-success-500);
}
.ant-field.is-valid .ant-field__success-icon { display: inline-flex; }

.ant-field__hint {
  margin-top: 6px;
  font-size: var(--font-size-caption);
  color: var(--color-text-placeholder);
}

/* ----------------------------------------------------------------
 * Checkboxes & radio buttons — native fallback and custom controls.
 * Custom controls are appended to the markup as sibling label + input
 * pairs using the same .ant-field pattern.
 * ---------------------------------------------------------------- */
.ant-field input[type='checkbox'],
.ant-field input[type='radio'] {
  width: 18px;
  height: 18px;
  accent-color: var(--color-primary-500);
  cursor: pointer;
  margin: 0;
}

.ant-choice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  background: var(--color-bg-surface);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
  margin-bottom: var(--space-2);
}
.ant-choice:hover { border-color: var(--color-primary-300); }
.ant-choice:has(input:checked) {
  border-color: var(--color-primary-400);
  background: var(--color-primary-50);
  box-shadow: var(--shadow-focus);
}
.ant-choice input[type='checkbox'],
.ant-choice input[type='radio'] { flex: 0 0 auto; }
.ant-choice__body { min-width: 0; }
.ant-choice__title { font-size: var(--font-size-body); font-weight: 600; }
.ant-choice__caption { font-size: var(--font-size-caption); color: var(--color-text-secondary); }

/* ----------------------------------------------------------------
 * Switch (used by status toggles on vehicles / locations / etc.)
 * ---------------------------------------------------------------- */
.ant-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  cursor: pointer;
}
.ant-switch input {
  position: absolute;
  inset: 0;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.ant-switch__track {
  position: relative;
  width: 46px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--color-neutral-300);
  transition: background 0.2s ease, box-shadow 0.2s ease;
  pointer-events: none;
}
.ant-switch__track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--color-neutral-white);
  box-shadow: 0 1px 3px rgba(14, 24, 48, 0.25);
  transition: transform 0.2s ease;
}
.ant-switch input:checked + .ant-switch__track {
  background: var(--color-success-500);
  box-shadow: 0 0 0 4px rgba(31, 169, 122, 0.12);
}
.ant-switch input:checked + .ant-switch__track::after { transform: translateX(20px); }
.ant-switch input:focus-visible + .ant-switch__track {
  box-shadow: var(--shadow-focus);
}

/* ----------------------------------------------------------------
 * Skeleton loaders (UI/UX §2.1)
 * ---------------------------------------------------------------- */
.ant-skeleton {
  border-radius: var(--radius-md);
  background: linear-gradient(90deg, var(--color-primary-100) 0%, var(--color-neutral-50) 50%, var(--color-primary-100) 100%);
  background-size: 200% 100%;
  animation: ant-skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes ant-skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.ant-skeleton--row { height: 56px; margin-bottom: var(--space-3); }
.ant-skeleton--card { height: 140px; margin-bottom: var(--space-4); }
.ant-skeleton--text { height: 14px; width: 60%; margin-bottom: var(--space-2); }

/* ----------------------------------------------------------------
 * Tables — shared base chrome. Previously duplicated inside
 * per-module sheets (vehicles.css etc.); pages like vehicle-types.php
 * that only loaded their own sheet rendered an unstyled <table>.
 * Centralizing the base here fixes every list consistently while
 * module sheets keep their own row/breakpoint refinements.
 * ---------------------------------------------------------------- */
.ant-table-responsive {
  overflow-x: auto;
  margin-bottom: var(--space-4);
  -webkit-overflow-scrolling: touch;
}
.ant-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-1);
}
.ant-table th,
.ant-table td {
  padding: 12px 16px;
  text-align: left;
  font-size: 14px;
  vertical-align: middle;
}
.ant-table thead th {
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--color-border-soft);
  white-space: nowrap;
}
.ant-table tbody tr {
  border-bottom: 1px solid var(--color-border-soft);
  transition: background 0.12s ease;
}
.ant-table tbody tr:last-child { border-bottom: 0; }
.ant-table tbody tr:last-child td:first-child { border-bottom-left-radius: var(--radius-lg); }
.ant-table tbody tr:last-child td:last-child { border-bottom-right-radius: var(--radius-lg); }
.ant-table tbody tr:hover,
.ant-table tbody tr:focus-visible {
  background: var(--color-primary-50);
  outline: none;
}
.ant-table th.is-sortable {
  cursor: pointer;
  user-select: none;
}
.ant-table th.is-sortable:hover { color: var(--color-primary-500); }
.ant-table th.is-sorted { color: var(--color-primary-500); }
.ant-table th.is-sorted::after {
  content: ' \25B2';
  font-size: 9px;
}
.ant-table th.is-sorted.is-sorted-desc::after { content: ' \25BC'; }

.ant-table__actions-col {
  width: 72px;
  text-align: right;
  white-space: nowrap;
}
.ant-table__actions-col .ant-btn--icon {
  opacity: 0.55;
}

/* ----------------------------------------------------------------
 * Standard states (UI/UX §2.1): empty / error / no-internet /
 * slow-network / no-search-results / permission-denied
 * ---------------------------------------------------------------- */
.ant-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-7) var(--space-5);
  gap: var(--space-2);
  background: var(--color-bg-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-lg);
  min-height: 260px;
}
.ant-state__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-pill);
  font-size: 24px;
  color: var(--color-primary-500);
  background: var(--color-primary-50);
  margin-bottom: var(--space-2);
  transition: transform 0.2s ease;
}
.ant-state:hover .ant-state__icon { transform: scale(1.05); }
.ant-state--error .ant-state__icon { color: var(--color-error-500); background: var(--color-error-100); }
.ant-state--permission .ant-state__icon { color: var(--color-neutral-500); background: var(--color-neutral-100); }
.ant-state__title { font-family: var(--font-heading); font-size: var(--font-size-h4); font-weight: 700; }
.ant-state__caption { font-size: var(--font-size-caption); color: var(--color-text-secondary); max-width: 360px; line-height: var(--line-height-caption); }
.ant-state .ant-btn { margin-top: var(--space-2); }

.ant-banner-no-internet {
  display: none;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-warning-100);
  color: var(--color-warning-500);
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-size-caption);
  font-weight: var(--font-weight-body-strong);
}
.ant-banner-no-internet.is-visible { display: flex; }
.ant-content--dimmed { opacity: 0.5; pointer-events: none; }

.ant-slow-network-bar {
  display: none;
  height: 3px;
  width: 100%;
  overflow: hidden;
  background: var(--color-primary-100);
  position: relative;
}
.ant-slow-network-bar.is-visible { display: block; }
.ant-slow-network-bar::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 40%;
  background: var(--color-primary-500);
  animation: ant-indeterminate 1.2s ease-in-out infinite;
}
@keyframes ant-indeterminate {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
.ant-slow-network-caption {
  display: none;
  font-size: var(--font-size-caption);
  color: var(--color-text-secondary);
  padding: var(--space-1) var(--space-4);
}
.ant-slow-network-caption.is-visible { display: block; }

/* ----------------------------------------------------------------
 * Modal (used for confirmation dialogs, session-expired reauth, etc.)
 * ---------------------------------------------------------------- */
.ant-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(9, 18, 40, 0.55);
  backdrop-filter: blur(3px);
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: var(--space-4);
  animation: ant-fade-in 0.15s ease;
}
@keyframes ant-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.ant-modal-overlay.is-open { display: flex; }
.ant-modal {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-4);
  max-width: 480px;
  width: 100%;
  padding: var(--space-6);
  animation: ant-modal-pop 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.2);

  /* Popup height caps to fit content (was a flat, forced height for
     every modal — confirmations, session-expired re-login, Add Trip,
     any future one — which made short modals like "Close this trip?"
     open with a large empty gap below their one line of text and two
     buttons). `max-height` instead of `height`: a short modal now
     shrinks to fit its own content; a long one (Add Trip's full form)
     still caps at this height and scrolls internally rather than
     pushing past the viewport — same consistency goal, without
     forcing short content to stretch.
     `min(560px, 85vh)` keeps the cap from overflowing a short
     phone-in-landscape viewport; on any normal desktop or tablet
     screen it is a flat 560px ceiling every time. */
  max-height: min(560px, 85vh);
  overflow-y: auto;
}
@keyframes ant-modal-pop {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.ant-modal__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-4);
  /* Stays put while the content between it and the footer scrolls
     inside the now fixed-height .ant-modal (see that rule's own
     comment). Sticky rather than a separate wrapper element, so no
     existing modal's markup — confirmations, reauth, Add Trip, any
     future one — needs to change to get this. */
  position: sticky;
  top: calc(-1 * var(--space-6));
  padding-top: var(--space-6);
  margin-top: calc(-1 * var(--space-6));
  background: var(--color-bg-surface);
  /* Above in-form absolute-positioned controls (e.g. trips.css's
     .ant-addtrip-location-pin, z-index: 2) so the "Detect my
     location" crosshairs button scrolls in BEHIND the sticky header
     instead of painting on top of it — equal z-index used to leave
     the outcome to DOM order, and the pin (later in the markup) was
     winning. */
  z-index: 3;
}
.ant-modal__header .ant-label,
.ant-modal__header h2 { font-family: var(--font-heading); font-weight: 700; }
.ant-modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-5);
  /* Same sticky treatment as the header, pinned to the bottom of the
     scroll area instead of the top. */
  position: sticky;
  bottom: calc(-1 * var(--space-6));
  padding-bottom: var(--space-6);
  margin-bottom: calc(-1 * var(--space-6));
  background: var(--color-bg-surface);
  z-index: 2;
}

.ant-inline-confirm {
  display: none;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-2);
  padding: var(--space-2) var(--space-3);
}
.ant-inline-confirm.is-open { display: flex; }

/* ----------------------------------------------------------------
 * Toasts
 * ---------------------------------------------------------------- */
.ant-toast-container {
  position: fixed;
  bottom: var(--space-5);
  right: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  z-index: 1100;
}
@media (max-width: 639px) {
  .ant-toast-container { left: var(--space-3); right: var(--space-3); bottom: calc(64px + var(--space-3)); }
}
.ant-toast {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-neutral-900);
  color: var(--color-neutral-white);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-3);
  font-size: var(--font-size-caption);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  backdrop-filter: blur(4px);
}
.ant-toast.is-visible { opacity: 1; transform: translateY(0); }
[data-theme='dark'] .ant-toast {
  background: var(--color-dark-surface);
  border: 1px solid var(--color-dark-border);
}
.ant-toast--success i { color: var(--color-success-500); }
.ant-toast--error i { color: var(--color-error-500); }
.ant-toast--info i { color: var(--color-info-500); }

/* ----------------------------------------------------------------
 * Upload zones (UI/UX §2.2) — structural shell; per-file/batch
 * progress markup is data-driven by the module that uses it.
 * ---------------------------------------------------------------- */
.ant-upload-zone {
  border: 1.5px dashed var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  text-align: center;
  color: var(--color-text-secondary);
  background: var(--color-bg-subtle);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.ant-upload-zone:hover { border-color: var(--color-primary-400); }
.ant-upload-zone.is-dragover {
  border-color: var(--color-primary-400);
  background: var(--color-primary-50);
  box-shadow: var(--shadow-focus);
}
.ant-upload-thumb {
  position: relative;
  width: 88px;
  height: 88px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-neutral-100);
}
.ant-upload-thumb.is-failed { outline: 2px solid var(--color-error-500); }
.ant-upload-thumb__progress-ring {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 11, 31, 0.35);
  color: var(--color-neutral-white);
  font-size: var(--font-size-caption);
  font-weight: var(--font-weight-body-strong);
}
.ant-upload-batch-bar {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-neutral-100);
  overflow: hidden;
  margin-bottom: var(--space-3);
}
.ant-upload-batch-bar__fill {
  height: 100%;
  background: var(--color-primary-500);
  transition: width 0.2s ease;
}

/* ----------------------------------------------------------------
 * Listing pages (UI/UX §2.3) — search bar / sort control shell
 * ---------------------------------------------------------------- */
.ant-listing-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.ant-search-input {
  position: relative;
  flex: 1 1 260px;
  max-width: 420px;
}
.ant-search-input input {
  padding: 10px 16px 10px 42px;
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  width: 100%;
}
.ant-search-input input::placeholder { color: var(--color-text-placeholder); opacity: 1; }
.ant-search-input input:focus {
  outline: none;
  border-color: var(--color-primary-400);
  box-shadow: var(--shadow-focus);
}
.ant-search-input i {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-text-placeholder);
  font-size: 14px;
  pointer-events: none;
}
.ant-pagination-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}
.ant-pagination-footer__info {
  font-size: var(--font-size-caption);
  color: var(--color-text-secondary);
}
.ant-pagination-footer__controls {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.ant-load-more { display: none; width: 100%; }
@media (max-width: 639px) {
  .ant-pagination-footer--desktop { display: none; }
  .ant-load-more { display: block; }
}
@media (min-width: 640px) {
  .ant-load-more { display: none; }
}

/* ----------------------------------------------------------------
 * Phase 31 — Horizontally-scrolling chip rows (UI/UX §2.4: filter
 * chip row "horizontally scrollable on mobile with a fade + chevron
 * scroll indicator when chips overflow"). chip-scroll.js (Phase 31)
 * wraps every .ant-chip-row in a .ant-chip-scroll enhancer at runtime,
 * so no page markup changes; .ant-chip-row already carries the
 * flex/overflow rules above. All values are theme tokens so Phase 32's
 * dark mode picks these up automatically.
 * ---------------------------------------------------------------- */
.ant-chip-scroll {
  position: relative;
}
.ant-chip-scroll::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: var(--space-1);
  width: var(--space-6);
  background: linear-gradient(90deg, transparent, var(--color-bg-surface));
  pointer-events: none;
  opacity: 0;
  transition: opacity 150ms ease;
}
.ant-chip-scroll.is-overflowing::after {
  opacity: 1;
}
.ant-chip-scroll__chevron {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-58%);
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  box-shadow: var(--shadow-1);
  display: none;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: var(--font-size-label);
  line-height: 1;
}
.ant-chip-scroll.is-overflowing .ant-chip-scroll__chevron {
  display: flex;
}
.ant-chip-scroll.is-end .ant-chip-scroll::after {
  opacity: 0;
}
.ant-chip-scroll.is-end .ant-chip-scroll__chevron {
  opacity: 0.2;
  pointer-events: none;
}
