/*
 * RideXpress Design System — Patterns v1
 *
 * Compositions of two or more components.css primitives into a
 * recurring, reusable unit -- per docs/engineering/
 * FRONTEND_ARCHITECTURE.md section 3. A page template should rarely
 * need a new one-off CSS rule; if a rule is needed more than twice,
 * promote it here instead of repeating it (same duplication-avoidance
 * discipline as docs/engineering/FOUNDATION_COMPLETION_REPORT.md's
 * backend consolidation program).
 *
 * Load order: tokens.css, components.css, then this file.
 *
 * v1 scope: Portal nav shell (sidebar + topbar + mobile collapse).
 * Open/close state for the mobile nav is wired with Alpine.js, not CSS
 * classes toggled server-side -- see the shell template's x-data usage.
 */

/* ============================== Portal shell ============================== */
.rx-shell {
  display: flex;
  min-height: 100vh;
  background: var(--rx-color-surface-alt);
}

.rx-shell__sidebar {
  width: 260px;
  flex-shrink: 0;
  background: var(--rx-color-dark-surface);
  color: var(--rx-color-text-on-dark);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  z-index: var(--rx-z-sticky);
  transition: transform var(--rx-motion-base) var(--rx-ease-decelerate);
}
.rx-shell__sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--rx-space-3);
  padding: var(--rx-space-5) var(--rx-space-5);
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-lg);
  color: var(--rx-color-white);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.rx-shell__sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: var(--rx-space-1);
  padding: var(--rx-space-4);
  flex: 1;
}
/* Sidebar sits on a dark surface, so its rx-nav-item needs the on-dark
   text tokens rather than the default (light-surface) muted tone. */
.rx-shell__sidebar-nav .rx-nav-item {
  color: var(--rx-color-text-on-dark-muted);
  min-height: 44px;
}
.rx-shell__sidebar-nav .rx-nav-item:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--rx-color-text-on-dark);
}
.rx-shell__sidebar-nav .rx-nav-item.is-active {
  background: var(--rx-color-brand);
  color: var(--rx-color-ink);
}
.rx-shell__sidebar-group-label {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-xs);
  font-weight: var(--rx-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rx-color-text-on-dark-muted);
  padding: var(--rx-space-4) var(--rx-space-4) var(--rx-space-2);
}

.rx-shell__main {
  flex: 1;
  min-width: 0; /* prevents flex overflow from wide tables/content */
  display: flex;
  flex-direction: column;
}

.rx-shell__topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-4);
  padding: var(--rx-space-4) var(--rx-space-6);
  background: var(--rx-color-surface);
  border-bottom: 1px solid var(--rx-color-border);
  position: sticky;
  top: 0;
  z-index: var(--rx-z-sticky);
}
.rx-shell__topbar-toggle {
  display: none; /* shown only below --rx-bp-md, see media query */
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--rx-space-2);
  color: var(--rx-color-text);
  /* 44px is the WCAG 2.5.5 / Apple HIG minimum touch target -- this
     is the sole trigger for the entire mobile sidebar, so it must be
     reliably tappable (docs/engineering/MOBILE_RESPONSIVENESS_REVIEW.md
     finding 1: the glyph+padding alone computed to ~32-35px). */
  min-width: 44px;
  min-height: 44px;
  font-size: var(--rx-text-lg);
}

.rx-shell__content {
  padding: var(--rx-space-6);
  flex: 1;
}

/* ---------- Topbar search ---------- */
.rx-shell__search {
  flex: 1;
  max-width: 360px;
  margin: 0 var(--rx-space-4);
}
.rx-shell__search .rx-input {
  padding-top: 8px;
  padding-bottom: 8px;
}
@media (max-width: 1023px) { /* == --rx-bp-lg - 1px: staff portals are desktop-first, see FRONTEND_ARCHITECTURE.md section 4 */
  .rx-shell__search { display: none; }
}

/* ---------- Topbar icon buttons (theme toggle, notifications) ---------- */
.rx-shell__icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--rx-radius-md);
  border: 1px solid transparent;
  background: none;
  color: var(--rx-color-text-muted);
  font-size: var(--rx-text-base);
  cursor: pointer;
  transition: background-color var(--rx-motion-instant) var(--rx-ease-standard), color var(--rx-motion-instant) var(--rx-ease-standard);
}
.rx-shell__icon-btn:hover, .rx-shell__icon-btn.is-open {
  background: var(--rx-color-surface-sunken);
  color: var(--rx-color-text);
}
.rx-shell__icon-btn-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  font-size: 10px;
  line-height: 16px;
}

/* ---------- Notification panel ---------- */
.rx-shell__notif-panel {
  width: 320px;
  max-height: 400px;
  overflow-y: auto;
  padding: var(--rx-space-2);
}
.rx-shell__notif-item {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  white-space: normal;
}
.rx-shell__notif-body {
  display: block;
  font-weight: var(--rx-weight-regular);
  color: var(--rx-color-text-muted);
  font-size: var(--rx-text-xs);
}
.rx-shell__notif-when {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
}

/* ---------- Global loading progress bar ---------- */
.rx-shell__progress {
  height: 3px;
  background: transparent;
}
.rx-shell__progress.is-active {
  background: linear-gradient(90deg, var(--rx-color-brand) 0%, var(--rx-color-brand-strong) 50%, var(--rx-color-brand) 100%);
  background-size: 200% 100%;
  animation: rx-progress-sweep 1.2s ease-in-out infinite;
}
@keyframes rx-progress-sweep {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ---------- Mobile collapse (< 768px == --rx-bp-md) ---------- */
@media (max-width: 767px) {
  .rx-shell__sidebar {
    position: fixed;
    left: 0;
    top: 0;
    transform: translateX(-100%);
    box-shadow: var(--rx-shadow-lg);
  }
  .rx-shell__sidebar.is-open {
    transform: translateX(0);
  }
  .rx-shell__topbar-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* Pages with page_actions buttons (CRUD list/detail) arithmetically
     overflow a 375px viewport without this -- the topbar has no wrap
     behavior otherwise (docs/engineering/MOBILE_RESPONSIVENESS_REVIEW.md
     finding 2). Actions wrap to their own row rather than clipping. */
  .rx-shell__topbar {
    flex-wrap: wrap;
    row-gap: var(--rx-space-2);
  }
  .rx-shell__content {
    padding: var(--rx-space-4);
  }
  /* Backdrop shown behind the open mobile sidebar; markup-provided,
     visibility toggled the same way as .rx-modal-backdrop. */
  .rx-shell__backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 20, 25, 0.55);
    z-index: calc(var(--rx-z-sticky) - 1);
  }
}

/* ============================== Page header ============================== */
.rx-page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--rx-space-4);
  flex-wrap: wrap;
  margin-bottom: var(--rx-space-6);
}
.rx-page-header__title {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-2xl);
  color: var(--rx-color-text);
  margin: var(--rx-space-2) 0 0;
}
.rx-page-header__actions {
  display: flex;
  gap: var(--rx-space-3);
  flex-wrap: wrap;
}

/* ============================== Stat card ============================== */
/* Track B UX audit (finalgate-track-b): this is the shared KPI-tile
   primitive used by 16 different dashboard templates (dispatch, fleet,
   finance, analytics, reports, hr, marketing, seo, telecaller,
   adminuser, vendor, driver -- `grep -rl rx-stat-card templates/`).
   Verified live on analytics_dashboard.html/reports_dashboard.html: the
   value was set in --rx-text-3xl (tokens.css documents that token itself
   as "36px -- hero subheads", i.e. built for a marketing page's large
   subhead copy, not a small operational number like "2" or "12.5%" in a
   dense tile grid) plus 20px padding on all four sides -- the combined
   effect reads as oversized for what's usually a 1-5 character value,
   and was the single largest contributor to the "oversized tiles /
   excessive whitespace" pattern flagged across the V2 dashboards.
   --rx-text-numeric-lg (24px) is already the design system's own
   purpose-built token for exactly this ("a booking card/summary's price
   total" per its own tokens.css comment) -- .rx-pricing-card__price
   already uses it for the same kind of compact numeric display
   (patterns.css). Padding tightened from --rx-space-5 (20px) to
   --rx-space-4 (16px) for the same density reason. Label/trend styling,
   card structure, and every page's context data are unchanged -- this
   is a pure type-scale/spacing correction, not a redesign. */
.rx-stat-card {
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  box-shadow: var(--rx-shadow-sm);
  padding: var(--rx-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--rx-space-2);
}
.rx-stat-card__label {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
}
.rx-stat-card__value {
  font-family: var(--rx-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-numeric-lg);
  font-weight: var(--rx-weight-semibold);
  color: var(--rx-color-text);
}
.rx-stat-card__trend {
  display: inline-flex;
  align-items: center;
  gap: var(--rx-space-1);
  font-size: var(--rx-text-xs);
  font-weight: var(--rx-weight-medium);
}
.rx-stat-card__trend--up { color: var(--rx-color-success); }
.rx-stat-card__trend--down { color: var(--rx-color-danger); }

/* ============================== Data table toolbar ============================== */
.rx-table-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-4);
  flex-wrap: wrap;
  margin-bottom: var(--rx-space-4);
}
.rx-table-toolbar__filters {
  display: flex;
  align-items: center;
  gap: var(--rx-space-3);
  flex-wrap: wrap;
}
.rx-table-toolbar__search {
  min-width: 240px;
  min-height: 44px;
}
.rx-table-scroll {
  position: relative;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
}
.rx-table-scroll > .rx-table { min-width: max-content; }

/* ============================== CRUD list loading state ============================== */
/* Stripe-style skeleton overlay for the generic CRUD list's search-driven
   HTMX refetch (taxipro/crud_views.py CrudListView + crud/list.html,
   templates/design_system/crud/_list_results.html) -- closes the gap
   named in MOTION_LANGUAGE.md 2.5 ("skeleton primitive exists, not yet
   wired into a real HTMX loading state"). The skeleton mirrors the
   .rx-card/.rx-table shell it covers (same column count as the real
   table) rather than a generic spinner, per Stripe's "shape of what's
   coming" pattern cited in that doc's research section.

   Positioned as an inset overlay over the results region (not swapped
   in as a layout replacement), so the previous results stay in the DOM
   and the overlay just fades over them via the existing .htmx-indicator
   opacity convention (components.css) -- avoids a blank flash between
   the old and new result sets while the request is in flight. */
.rx-crud-list__body { position: relative; }
.rx-crud-list__skeleton {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none; /* never intercepts clicks, active or not */
  background: var(--rx-color-surface-alt);
  /* min-height, not a height override: `inset:0` still stretches this box
     to match .rx-crud-list__body's real-content height for the common
     case, so a long previous result set stays fully covered by the
     overlay during the refetch. min-height only takes over when that
     stretched height would be shorter than the skeleton's own content --
     e.g. a search that had just narrowed to 1-2 real rows -- which
     previously let the skeleton's 5-row table silently render past the
     bottom of its own forced (too-short) box
     (MOBILE_RESPONSIVENESS_REVIEW_2.md finding 6). ~260px = count line
     (space-3 skeleton height + space-2 margin) + flush card wrapping a
     5-row table (~44px header row + 5 * ~37px body rows, from this
     file's own td/th padding and .rx-skeleton--text height) -- an
     approximation like the fixed 5-row count itself, not a
     pixel-measured value. */
  min-height: 260px;
}
.rx-crud-list__count {
  color: var(--rx-color-text-muted);
  font-size: var(--rx-text-sm);
  margin-bottom: var(--rx-space-2);
}

/* ============================== Staggered list-item reveal ============================== */
/* Material's list/grid entrance choreography: items in a freshly-
   rendered list fade/slide in with a small incremental per-item delay
   instead of popping in as one flat block -- reads as a *sequence*
   rendering, not a single blob of content appearing at once. Applied to
   the CRUD list's table rows (.rx-table--stagger, on the table in
   crud/_list_results.html) and the recent-activity feed pattern
   (.rx-activity-feed__item -- used in preview.html today; ready for the
   first real dashboard feed that adopts the pattern).

   Capped at the first 10 items, and the per-item delay step is derived
   from --rx-motion-fast (150ms / 5 = 30ms) rather than an invented
   number: 10 x 30ms = 270ms max stagger delay, so the whole cascade
   (last item's delay + its own fade-in) finishes in ~420ms -- subtle,
   not a light show. Rows past the 10th share that same 270ms delay via
   the base rule below instead of restarting at 0ms or getting no delay
   at all, so a 200-row table never produces a multi-second cascade AND
   the un-staggered tail doesn't read as a separate, disconnected pop-in
   from the staggered head -- everything settles together. */
.rx-stagger-item,
.rx-table--stagger tbody tr,
.rx-activity-feed__item {
  animation: rx-stagger-in var(--rx-motion-fast) var(--rx-ease-decelerate) both;
  animation-delay: calc(var(--rx-motion-fast) / 5 * 9);
}
.rx-stagger-item:nth-child(1), .rx-table--stagger tbody tr:nth-child(1), .rx-activity-feed__item:nth-child(1) { animation-delay: 0ms; }
.rx-stagger-item:nth-child(2), .rx-table--stagger tbody tr:nth-child(2), .rx-activity-feed__item:nth-child(2) { animation-delay: calc(var(--rx-motion-fast) / 5 * 1); }
.rx-stagger-item:nth-child(3), .rx-table--stagger tbody tr:nth-child(3), .rx-activity-feed__item:nth-child(3) { animation-delay: calc(var(--rx-motion-fast) / 5 * 2); }
.rx-stagger-item:nth-child(4), .rx-table--stagger tbody tr:nth-child(4), .rx-activity-feed__item:nth-child(4) { animation-delay: calc(var(--rx-motion-fast) / 5 * 3); }
.rx-stagger-item:nth-child(5), .rx-table--stagger tbody tr:nth-child(5), .rx-activity-feed__item:nth-child(5) { animation-delay: calc(var(--rx-motion-fast) / 5 * 4); }
.rx-stagger-item:nth-child(6), .rx-table--stagger tbody tr:nth-child(6), .rx-activity-feed__item:nth-child(6) { animation-delay: calc(var(--rx-motion-fast) / 5 * 5); }
.rx-stagger-item:nth-child(7), .rx-table--stagger tbody tr:nth-child(7), .rx-activity-feed__item:nth-child(7) { animation-delay: calc(var(--rx-motion-fast) / 5 * 6); }
.rx-stagger-item:nth-child(8), .rx-table--stagger tbody tr:nth-child(8), .rx-activity-feed__item:nth-child(8) { animation-delay: calc(var(--rx-motion-fast) / 5 * 7); }
.rx-stagger-item:nth-child(9), .rx-table--stagger tbody tr:nth-child(9), .rx-activity-feed__item:nth-child(9) { animation-delay: calc(var(--rx-motion-fast) / 5 * 8); }
.rx-stagger-item:nth-child(10), .rx-table--stagger tbody tr:nth-child(10), .rx-activity-feed__item:nth-child(10) { animation-delay: calc(var(--rx-motion-fast) / 5 * 9); }
@keyframes rx-stagger-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* ============================== Form section ============================== */
.rx-form-section {
  padding-bottom: var(--rx-space-6);
  margin-bottom: var(--rx-space-6);
  border-bottom: 1px solid var(--rx-color-border);
}
.rx-form-section:last-of-type {
  border-bottom: none;
  margin-bottom: 0;
}
.rx-form-section__title {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-lg);
  color: var(--rx-color-text);
  margin: 0 0 var(--rx-space-1);
}
.rx-form-section__description {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
  margin: 0 0 var(--rx-space-4);
}
.rx-form-section__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--rx-space-4);
}

/* ============================== Info cards (booking/vehicle/driver/customer) ==============================
 * Shared base for the 4 static "info card" patterns below -- all four
 * repeated components.css's .rx-card primitive (background/border/
 * border-radius/box-shadow) plus an identical --rx-space-5 padding
 * verbatim, violating this file's own "promote after 2 uses" rule
 * (DEAD_CODE_AUDIT.md finding 3). .rx-fare-card (further down) is
 * legitimately excluded -- it's an interactive selectable button (2px
 * border, no shadow, hover/selected states), not a static info card.
 */
.rx-booking-card,
.rx-vehicle-card,
.rx-driver-card,
.rx-customer-card {
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  box-shadow: var(--rx-shadow-sm);
  padding: var(--rx-space-5);
}
/* Shared by driver/customer cards -- byte-identical name style, meta
   style differing only in bottom margin (kept as a modifier below). */
.rx-driver-card__name,
.rx-customer-card__name {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-lg);
  color: var(--rx-color-text);
  margin: 0;
}
.rx-driver-card__meta,
.rx-customer-card__meta {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
}
.rx-driver-card__meta { margin: 2px 0 var(--rx-space-2); }
.rx-customer-card__meta { margin: 2px 0 0; }

/* ============================== Booking summary card ============================== */
.rx-booking-card__route {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
  margin: 2px 0 var(--rx-space-3);
}
.rx-booking-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rx-space-4);
  margin-top: var(--rx-space-4);
  padding-top: var(--rx-space-4);
  border-top: 1px solid var(--rx-color-border);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
}
.rx-booking-card__meta strong {
  color: var(--rx-color-text);
  font-weight: var(--rx-weight-medium);
}

/* ============================== Vehicle card ============================== */
.rx-vehicle-card {
  display: flex;
  flex-direction: column;
  gap: var(--rx-space-3);
}
.rx-vehicle-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-3);
}
.rx-vehicle-card__number {
  font-family: var(--rx-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-lg);
  font-weight: var(--rx-weight-semibold);
  color: var(--rx-color-text);
}
.rx-vehicle-card__meta {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
}
.rx-vehicle-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--rx-space-3);
  border-top: 1px solid var(--rx-color-border);
  font-size: var(--rx-text-sm);
}

/* ============================== Driver card ============================== */
.rx-driver-card {
  display: flex;
  align-items: flex-start;
  gap: var(--rx-space-4);
}
.rx-driver-card__body { flex: 1; min-width: 0; }
.rx-driver-card__badges {
  display: flex;
  gap: var(--rx-space-2);
  flex-wrap: wrap;
}

/* ============================== Customer card ============================== */
.rx-customer-card {
  display: flex;
  align-items: center;
  gap: var(--rx-space-4);
}
.rx-customer-card__body { flex: 1; min-width: 0; }
.rx-customer-card__stat {
  text-align: right;
  flex-shrink: 0;
}
.rx-customer-card__stat-value {
  display: block;
  font-family: var(--rx-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-xl);
  font-weight: var(--rx-weight-semibold);
  color: var(--rx-color-text);
}
.rx-customer-card__stat-label {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
}

/* ============================== Timeline ============================== */
/* Vertical event history -- ride status changes, audit trail entries.
   Semantic status color drives the dot; the connecting line is purely
   structural (border-color), same "semantic color separate from
   accent" rule as everywhere else in this system. */
.rx-timeline {
  display: flex;
  flex-direction: column;
}
.rx-timeline__item {
  position: relative;
  padding-left: var(--rx-space-8);
  padding-bottom: var(--rx-space-6);
}
.rx-timeline__item:last-child { padding-bottom: 0; }
.rx-timeline__item::before {
  /* connecting line */
  content: '';
  position: absolute;
  left: 5px;
  top: 18px;
  bottom: 0;
  width: 2px;
  background: var(--rx-color-border);
}
.rx-timeline__item:last-child::before { display: none; }
.rx-timeline__dot {
  position: absolute;
  left: 0;
  top: 4px;
  width: 12px;
  height: 12px;
  border-radius: var(--rx-radius-full);
  background: var(--rx-color-border);
  border: 2px solid var(--rx-color-surface);
  box-shadow: 0 0 0 2px var(--rx-color-border);
}
.rx-timeline__dot--success { background: var(--rx-color-success); box-shadow: 0 0 0 2px var(--rx-color-success); }
.rx-timeline__dot--info { background: var(--rx-color-info); box-shadow: 0 0 0 2px var(--rx-color-info); }
.rx-timeline__dot--danger { background: var(--rx-color-danger); box-shadow: 0 0 0 2px var(--rx-color-danger); }
.rx-timeline__title {
  font-size: var(--rx-text-sm);
  font-weight: var(--rx-weight-medium);
  color: var(--rx-color-text);
  margin: 0;
}
.rx-timeline__meta {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
  margin-top: 2px;
}

/* ============================== Recent activity feed ============================== */
/* Page-embedded activity list (dashboard "recent activity" panel) --
   distinct from the topbar notification panel (portal_base.html),
   which is a dropdown, not page content. */
.rx-activity-feed {
  display: flex;
  flex-direction: column;
}
.rx-activity-feed__item {
  display: flex;
  gap: var(--rx-space-3);
  padding: var(--rx-space-3) 0;
  border-bottom: 1px solid var(--rx-color-border);
}
.rx-activity-feed__item:last-child { border-bottom: none; }
.rx-activity-feed__body { flex: 1; min-width: 0; }
.rx-activity-feed__text {
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text);
  margin: 0;
}
.rx-activity-feed__text strong { font-weight: var(--rx-weight-semibold); }
.rx-activity-feed__when {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
  margin-top: 2px;
}

/* ============================== Date range ============================== */
/* Two native <input type="date"> fields rather than a custom calendar
   widget -- fully functional/accessible/keyboard-operable with zero
   JS and zero new dependency, consistent with this project's "no
   build step" constraint (FRONTEND_ARCHITECTURE.md section 1). A
   richer JS calendar can replace this later if the product genuinely
   needs range-preview/shortcut-buttons UX that native inputs can't do
   -- not built preemptively here. */
.rx-date-range {
  display: flex;
  align-items: center;
  gap: var(--rx-space-2);
}
.rx-date-range .rx-input { width: auto; }
.rx-date-range__sep {
  color: var(--rx-color-text-muted);
  font-size: var(--rx-text-sm);
}

/* ============================== Auth shell (pre-login pages) ============================== */
.rx-auth-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--rx-space-8);
  padding: var(--rx-space-6);
  background: var(--rx-color-dark-surface);
}
.rx-auth-shell__brand {
  text-align: center;
  color: var(--rx-color-text-on-dark);
}
.rx-auth-shell__brand-mark {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-3xl);
  color: var(--rx-color-white);
}
.rx-auth-shell__brand-tagline {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-on-dark-muted);
  margin: var(--rx-space-2) 0 0;
}
.rx-auth-card {
  background: var(--rx-color-surface);
  border-radius: var(--rx-radius-lg);
  box-shadow: var(--rx-shadow-lg);
  padding: var(--rx-space-8);
  width: 100%;
  max-width: 400px;
}
.rx-auth-card [hidden] {
  display: none !important;
}
.rx-auth-card__title {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-2xl);
  color: var(--rx-color-text);
  margin: 0 0 var(--rx-space-1);
}
.rx-auth-card__subtitle {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
  margin: 0 0 var(--rx-space-6);
}
.rx-auth-card__footer {
  margin-top: var(--rx-space-6);
  padding-top: var(--rx-space-4);
  border-top: 1px solid var(--rx-color-border);
  text-align: center;
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
}
.rx-auth-card__footer a { color: var(--rx-color-brand-strong); text-decoration: none; }
.rx-auth-card__footer a:hover { text-decoration: underline; }

/* ============================== Booking wizard ==============================
 * docs/engineering/BOOKING_UX_RESEARCH.md's prioritized build list, §7.
 * Three-step flow (Route+Schedule / Vehicle / Confirm) over the existing
 * customerPricingDetails / cusvalidate-coupon / customer_addbooking
 * endpoints -- presentation-only, no new backend contract.
 */

/* ---------- Stepper ---------- */
.rx-stepper {
  display: flex;
  align-items: center;
  gap: var(--rx-space-2);
  list-style: none;
  margin: 0 0 var(--rx-space-6);
  padding: 0;
}
.rx-stepper__step {
  display: flex;
  align-items: center;
  gap: var(--rx-space-2);
  color: var(--rx-color-text-muted);
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
}
.rx-stepper__dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--rx-radius-full);
  border: 2px solid var(--rx-color-border);
  color: var(--rx-color-text-muted);
  font-weight: var(--rx-weight-semibold);
  font-size: var(--rx-text-xs);
  flex-shrink: 0;
  background: var(--rx-color-surface);
  transition: background-color var(--rx-motion-fast) var(--rx-ease-standard),
              border-color var(--rx-motion-fast) var(--rx-ease-standard),
              color var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-stepper__step.is-active .rx-stepper__dot {
  border-color: var(--rx-color-brand);
  background: var(--rx-color-brand);
  color: var(--rx-color-ink);
}
.rx-stepper__step.is-complete .rx-stepper__dot {
  border-color: var(--rx-color-success);
  background: var(--rx-color-success);
  color: var(--rx-color-white);
}
.rx-stepper__step.is-active .rx-stepper__label { color: var(--rx-color-text); font-weight: var(--rx-weight-medium); }
.rx-stepper__step.is-complete .rx-stepper__label { color: var(--rx-color-text); }
.rx-stepper__connector {
  width: 32px;
  height: 2px;
  background: var(--rx-color-border);
  flex-shrink: 0;
}
.rx-stepper__step.is-complete + .rx-stepper__connector { background: var(--rx-color-success); }
@media (max-width: 479px) { /* == --rx-bp-sm - 1px */
  .rx-stepper__label { display: none; }
  .rx-stepper__connector { width: 20px; }
}

/* ---------- Persistent route summary (the shared-element between steps) ---------- */
.rx-route-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-3);
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  padding: var(--rx-space-4);
  margin-bottom: var(--rx-space-5);
}
.rx-route-summary__route {
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text);
  min-width: 0;
}
.rx-route-summary__route strong { font-weight: var(--rx-weight-medium); }
.rx-route-summary__meta {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
  margin-top: 2px;
}
.rx-route-summary__edit {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--rx-color-brand-strong);
  font-size: var(--rx-text-sm);
  font-weight: var(--rx-weight-medium);
  cursor: pointer;
  padding: var(--rx-space-2) var(--rx-space-3);
  min-height: 44px;
}
/* Booking Journey V2 polish pass: a pickup->drop-off route connector,
   the standard travel-booking convention (flight/train itineraries) for
   visually linking two points instead of leaving them as two unrelated
   rows. Fixed-height (not absolutely positioned against the icon rows
   above/below it) so it can never drift out of alignment at a different
   font-size/zoom -- flows in normal document order like everything else
   on this card. Purely decorative (aria-hidden), replaces what were
   previously two inline-styled divs with no shared wrapper; the visible
   text/strong content and its reading order are unchanged. */
.rx-route-summary__point {
  display: flex;
  align-items: center;
  gap: var(--rx-space-2);
}
.rx-route-summary__pin {
  display: inline-flex;
  width: 14px;
  flex-shrink: 0;
  justify-content: center;
  color: var(--rx-color-brand-strong);
}
.rx-route-summary__connector {
  width: 1px;
  height: 10px;
  margin: 2px 0 2px 6.5px;
  background-image: linear-gradient(var(--rx-color-border-strong) 60%, transparent 40%);
  background-size: 1px 4px;
  background-repeat: repeat-y;
}

/* ---------- Fare card (selectable vehicle/category option, step 2) ----------
 * A <div>, not a <button>: it contains two independent actions (select
 * vehicle, toggle fare breakdown) plus a <dl>, and interactive content
 * must not nest inside a <button> -- browsers silently split a nested
 * button out of its parent at parse time, which broke the card's visual
 * grouping (MOBILE_RESPONSIVENESS_REVIEW_2.md finding 1, confirmed live
 * in Chrome). .rx-fare-card__select below is the actual clickable
 * "pick this vehicle" control; .rx-fare-card__toggle is its own sibling
 * button, not nested inside it.
 */
.rx-fare-card {
  background: var(--rx-color-surface);
  border: 2px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  transition: border-color var(--rx-motion-fast) var(--rx-ease-standard),
              box-shadow var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-fare-card:hover { border-color: var(--rx-color-brand); }
.rx-fare-card.is-selected {
  border-color: var(--rx-color-brand);
  box-shadow: 0 0 0 3px var(--rx-color-brand-soft);
}
.rx-fare-card__select {
  display: block;
  width: 100%;
  padding: var(--rx-space-4);
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}
.rx-fare-card__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-3);
}
.rx-fare-card__identity {
  display: flex;
  align-items: center;
  gap: var(--rx-space-3);
  min-width: 0;
}
.rx-fare-card__image {
  width: 48px;
  height: 48px;
  border-radius: var(--rx-radius-md);
  object-fit: cover;
  background: var(--rx-color-surface-sunken);
  flex-shrink: 0;
}
.rx-fare-card__name {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-base);
  color: var(--rx-color-text);
  margin: 0;
}
.rx-fare-card__seats {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
  margin-top: 2px;
}
.rx-fare-card__price {
  font-family: var(--rx-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-lg);
  font-weight: var(--rx-weight-semibold);
  color: var(--rx-color-text);
  flex-shrink: 0;
  text-align: right;
}
.rx-fare-card__toggle {
  /* No top margin -- .rx-fare-card__select above already carries its own
     bottom padding, so the border-top divider sits directly under it. */
  padding: var(--rx-space-3) var(--rx-space-4) 0;
  border-top: 1px solid var(--rx-color-border);
  font-size: var(--rx-text-xs);
  color: var(--rx-color-brand-strong);
  background: none;
  border-left: none;
  border-right: none;
  border-bottom: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  /* Every vehicle option has one -- the 2nd-most-tapped control on step 2
     after the card itself, so it gets the same 44px baseline as the
     route-summary edit button and pill-radio labels on this same page
     (MOBILE_RESPONSIVENESS_REVIEW_2.md finding 3), not the smaller
     32px this previously had. */
  min-height: 44px;
}
.rx-fare-card__breakdown {
  margin-top: var(--rx-space-3);
  padding: var(--rx-space-3) var(--rx-space-4) var(--rx-space-4);
  border-top: 1px dashed var(--rx-color-border);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--rx-space-1) var(--rx-space-3);
  font-size: var(--rx-text-sm);
}
.rx-fare-card__breakdown dt { color: var(--rx-color-text-muted); }
.rx-fare-card__breakdown dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; color: var(--rx-color-text); }
.rx-fare-card__breakdown dd.rx-fare-card__total { font-weight: var(--rx-weight-semibold); }

/* Phase 3B: price is the single most decision-relevant number on this
   card -- bumped from --rx-text-lg to --rx-text-2xl so it visually
   dominates the Non A/C / A/C price boxes, matching "large, visually
   dominant total price" from the fare-results redesign brief. Additive
   modifier, doesn't touch .rx-fare-card__price's own base rule (still
   used as-is elsewhere this class might appear). */
.rx-fare-card__price--lg {
  font-size: var(--rx-text-2xl);
}

/* ---------- Transactional section wrapper (Booking Journey V2) ----------
 * Root-cause fix, not a patch: the 6 booking-journey pages (5 results +
 * checkout) wrapped their content in .rx-marketing__section, but that
 * class's only rule lives in marketing-patterns.css, which none of these
 * 6 pages' extra_css blocks ever linked -- the class matched no rule at
 * all, so these pages got none of the homepage's centered-column rhythm.
 * Rather than load the entire marketing stylesheet (pulls in rules for
 * ~9,000 unrelated SEO/landing pages) just to fix one class, this gives
 * the transactional pages their own equivalent, loaded from patterns.css
 * which they already include. Deliberately supplies only max-width/
 * centering/horizontal padding -- every one of these pages already sets
 * its own vertical padding inline (padding-top/padding-bottom), so this
 * doesn't duplicate that.
 */
.rx-transactional__section {
  max-width: 1000px;
  margin: 0 auto;
  padding-left: var(--rx-space-6);
  padding-right: var(--rx-space-6);
}

/* ---------- Compact fare card (Booking Journey V2, results page) ----------
 * Scoped entirely under .rx-fare-card--compact -- an additive modifier
 * applied only by templates/website/_quote_vehicle_card.html's own root
 * element. .rx-fare-card / .rx-fare-card__price / .rx-fare-card__breakdown
 * etc. are also used unmodified by localpackage_list.html (its own,
 * differently-shaped card) and templates/design_system/pages/booking_new.html
 * (component showcase) -- neither carries this modifier class, so neither
 * is affected by anything below. See _quote_vehicle_card.html's own header
 * comment for the full contract this rebuild preserves.
 */
.rx-fare-card--compact {
  border-width: 1px;
  box-shadow: var(--rx-shadow-sm);
  padding: var(--rx-space-3);
  display: flex;
  flex-direction: column;
  gap: var(--rx-space-2);
}
.rx-fare-card--compact .rx-fare-card__top {
  display: flex;
  align-items: center;
  gap: var(--rx-space-3);
}
.rx-fare-card--compact .rx-fare-card__image {
  width: 40px;
  height: 40px;
  border-radius: var(--rx-radius-sm);
}
.rx-fare-card--compact .rx-fare-card__meta {
  min-width: 0;
  flex: 1;
}
.rx-fare-card--compact .rx-fare-card__name {
  font-size: var(--rx-text-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rx-fare-card--compact .rx-fare-card__price-slot {
  flex-shrink: 0;
  text-align: right;
}
.rx-fare-card--compact .rx-fare-card__price-tile {
  background: none;
  padding: 0;
  border-radius: 0;
  text-align: right;
}
.rx-fare-card--compact .rx-fare-card__price-tile .rx-fare-card__price--lg {
  font-size: var(--rx-text-xl);
  margin: 0;
}
.rx-fare-card--compact .rx-fare-card__price-tile.is-unavailable .rx-fare-card__price--lg {
  font-size: var(--rx-text-sm);
}
/* Only the tile matching the checked car-type radio is visible -- see
   _quote_vehicle_card.html's header comment for why :has() is used here
   instead of a sibling combinator (keeps the radio+label pairing intact
   for .rx-pill-option's own :checked styling). */
.rx-fare-card--compact:has(.rx-fare-card__type-radio[value="ac"]:checked) .rx-fare-card__price-tile[data-type="non_ac"],
.rx-fare-card--compact:has(.rx-fare-card__type-radio[value="non_ac"]:checked) .rx-fare-card__price-tile[data-type="ac"] {
  display: none;
}
.rx-fare-card--compact:has(.rx-fare-card__type-radio[value="ac"]:checked) .rx-fare-card__forms form[data-type="non_ac"],
.rx-fare-card--compact:has(.rx-fare-card__type-radio[value="non_ac"]:checked) .rx-fare-card__forms form[data-type="ac"] {
  display: none;
}
.rx-fare-card--compact .rx-fare-card__type-pills {
  gap: var(--rx-space-2);
}
/* Booking Journey V2 polish pass: 36px undershoots Material Design 3's
   48x48dp minimum touch target (WCAG 2.5.8 AA) -- these two pill labels
   and the Fare-details toggle right below are the most-tapped controls
   on this card after the vehicle identity itself. Bumped to 44px (still
   under 48 to keep the compact card's density, but a real, measurable
   improvement over 36 -- matches .rx-route-summary__edit and
   .rx-pill-option's own non-compact 44px baseline elsewhere on this same
   page, so the card is no longer the one undersized control). Purely a
   sizing change: no id/class/JS this card's own contract declares
   load-bearing is touched. */
.rx-fare-card--compact .rx-fare-card__type-pills .rx-pill-option label {
  min-height: 44px;
  padding: 0 var(--rx-space-3);
  font-size: var(--rx-text-xs);
}
.rx-fare-card--compact .rx-fare-card__fixed-note {
  margin: 0;
  font-size: var(--rx-text-xs);
  color: var(--rx-color-danger);
}
.rx-fare-card--compact .rx-fare-card__toggle--v2 {
  padding: var(--rx-space-2) 0 0;
  border-top: 1px solid var(--rx-color-border);
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: var(--rx-space-1);
}
/* The toggle's own disclosure marker rotates open/closed via the
   aria-expanded state Bootstrap's collapse.js already sets on this exact
   button (unchanged) -- no new JS, purely a CSS read of an attribute
   that was already there. */
.rx-fare-card__toggle-icon {
  display: inline-flex;
  transition: transform var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-fare-card__toggle--v2[aria-expanded="true"] .rx-fare-card__toggle-icon {
  transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
  .rx-fare-card__toggle-icon { transition: none; }
}
.rx-fare-card--compact .rx-fare-card__forms {
  display: flex;
  gap: var(--rx-space-3);
}
.rx-fare-card--compact .rx-fare-card__forms form {
  flex: 1;
}

/* Booking Journey V2 polish pass: a restrained selectable-card affordance
   (Uber/Lyft-style vehicle selection convention -- the option you're
   about to tap should visibly react) plus a fully-unavailable card
   (neither A/C nor Non-A/C priced for this route) recedes slightly so
   working options scan first, without ever hiding a real category or
   touching the disabled-button/"Not Available" text contract JS already
   owns. Both are pure CSS, scoped to :hover/:focus-within/:has() --
   zero markup change. */
.rx-fare-card--compact {
  transition: border-color var(--rx-motion-fast) var(--rx-ease-standard),
              box-shadow var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-fare-card--compact:hover,
.rx-fare-card--compact:focus-within {
  box-shadow: var(--rx-shadow-md);
}
.rx-fare-card--compact:has(.rx-fare-card__price-tile[data-type="ac"].is-unavailable):has(.rx-fare-card__price-tile[data-type="non_ac"].is-unavailable) {
  opacity: 0.7;
}
.rx-fare-card--compact:has(.rx-fare-card__price-tile[data-type="ac"].is-unavailable):has(.rx-fare-card__price-tile[data-type="non_ac"].is-unavailable):hover {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .rx-fare-card--compact { transition: none; }
}

/* Fare-results status region (loading/empty/error), shared by all 5
   AJAX quote pages. Hidden by default via the `hidden` attribute; JS
   toggles it and swaps the modifier class per state. role="status"
   aria-live="polite" is set in the template itself. */
.rx-fare-status {
  display: flex;
  align-items: center;
  gap: var(--rx-space-3);
  padding: var(--rx-space-4);
  border-radius: var(--rx-radius-lg);
  border: 1px solid transparent;
  font-size: var(--rx-text-sm);
  margin-bottom: var(--rx-space-4);
}
.rx-fare-status--loading {
  background: var(--rx-color-surface-sunken);
  border-color: var(--rx-color-border);
  color: var(--rx-color-text-muted);
}
.rx-fare-status--empty {
  background: var(--rx-color-surface-sunken);
  border-color: var(--rx-color-border);
  color: var(--rx-color-text);
}
.rx-fare-status--error {
  background: var(--rx-color-danger-bg);
  border-color: var(--rx-color-danger);
  color: var(--rx-color-danger);
}
.rx-fare-status__spinner {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: rx-fare-status-spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .rx-fare-status__spinner { animation: none; }
}
@keyframes rx-fare-status-spin {
  to { transform: rotate(360deg); }
}

/* ---------- Pill radio group (trip type / toll option) ---------- */
.rx-pill-group {
  display: flex;
  gap: var(--rx-space-2);
  flex-wrap: wrap;
}
.rx-pill-option {
  position: relative;
}
.rx-pill-option input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.rx-pill-option label {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--rx-space-4);
  border-radius: var(--rx-radius-full);
  border: 1.5px solid var(--rx-color-border);
  color: var(--rx-color-text);
  font-size: var(--rx-text-sm);
  cursor: pointer;
  transition: border-color var(--rx-motion-fast) var(--rx-ease-standard),
              background-color var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-pill-option input:checked + label {
  border-color: var(--rx-color-brand);
  background: var(--rx-color-brand-soft);
  font-weight: var(--rx-weight-medium);
}
.rx-pill-option input:focus-visible + label {
  outline: 2px solid var(--rx-color-brand-strong);
  outline-offset: 2px;
}

/* ---------- Sticky fare/action bar (steps 2-3, mobile bottom / desktop inline) ---------- */
.rx-fare-bar {
  position: sticky;
  bottom: 0;
  z-index: var(--rx-z-sticky);
  background: var(--rx-color-surface);
  border-top: 1px solid var(--rx-color-border);
  box-shadow: var(--rx-shadow-lg);
  padding: var(--rx-space-4);
  margin: var(--rx-space-6) calc(var(--rx-space-4) * -1) calc(var(--rx-space-4) * -1);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-4);
}
.rx-fare-bar__price {
  font-family: var(--rx-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-xl);
  font-weight: var(--rx-weight-semibold);
  color: var(--rx-color-text);
}
.rx-fare-bar__price-original {
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-muted);
  text-decoration: line-through;
  margin-right: var(--rx-space-2);
}
.rx-fare-bar__label {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
}

/* ---------- Step panels: shared-axis slide, direction-aware ----------
 * Transform/opacity only (composite-safe, per MOTION_LANGUAGE.md /
 * BOOKING_UX_RESEARCH.md §5.1) -- never height/left. Forward = incoming
 * slides from the right, outgoing exits left; backward reverses.
 * prefers-reduced-motion collapses both to a plain opacity cross-fade.
 */
.rx-step-panel { will-change: transform, opacity; }
.rx-step-forward-enter-active,
.rx-step-forward-leave-active,
.rx-step-back-enter-active,
.rx-step-back-leave-active {
  transition: transform var(--rx-motion-base) var(--rx-ease-decelerate),
              opacity var(--rx-motion-base) var(--rx-ease-decelerate);
}
.rx-step-forward-enter-from { transform: translateX(24px); opacity: 0; }
.rx-step-forward-leave-to   { transform: translateX(-24px); opacity: 0; transition-timing-function: var(--rx-ease-accelerate); }
.rx-step-back-enter-from    { transform: translateX(-24px); opacity: 0; }
.rx-step-back-leave-to      { transform: translateX(24px); opacity: 0; transition-timing-function: var(--rx-ease-accelerate); }

@media (prefers-reduced-motion: reduce) {
  .rx-shell__sidebar { transition: none; }
  .rx-step-forward-enter-active,
  .rx-step-forward-leave-active,
  .rx-step-back-enter-active,
  .rx-step-back-leave-active { transition: opacity var(--rx-motion-fast) linear; }
  .rx-step-forward-enter-from, .rx-step-forward-leave-to,
  .rx-step-back-enter-from, .rx-step-back-leave-to { transform: none; }
  .rx-stagger-item,
  .rx-table--stagger tbody tr,
  .rx-activity-feed__item { animation: none; }
}

/* ==================================================================== */
/* == Accessibility corrections (ACCESSIBILITY_AUDIT.md)               == */
/* ==================================================================== */
/*
 * Appended rather than edited in place above, to ease merging with
 * parallel work on this file.
 *
 * Off-canvas sidebar: below 768px the closed sidebar is moved off
 * screen with transform: translateX(-100%) alone (patterns.css:205-215).
 * A transformed element is still visible to the accessibility tree and
 * still focusable, so every nav link in the closed drawer stayed in the
 * tab order -- and because the <aside> precedes .rx-shell__main in the
 * DOM, the FIRST thing a keyboard user hit on any mobile portal page
 * was a run of links they could not see (WCAG 2.4.3 / 2.4.7).
 *
 * visibility: hidden removes them from both the tab order and the a11y
 * tree. It is delayed by the drawer's own duration on close so the
 * slide-out still plays, and applied immediately on open. visibility is
 * discretely animatable, which is what lets this work without touching
 * the existing transform rules.
 */
@media (max-width: 767px) { /* == --rx-bp-md - 1px */
  .rx-shell__sidebar {
    visibility: hidden;
    transition: transform var(--rx-motion-base) var(--rx-ease-decelerate),
                visibility 0s linear var(--rx-motion-base);
  }
  .rx-shell__sidebar.is-open {
    visibility: visible;
    transition: transform var(--rx-motion-base) var(--rx-ease-decelerate),
                visibility 0s linear 0s;
  }
}
@media (prefers-reduced-motion: reduce) {
  /* No slide to wait for, so drop the visibility delay entirely. The
     rule above already sets transition: none for the sidebar; this
     restores only the (instant) visibility handling. */
  .rx-shell__sidebar,
  .rx-shell__sidebar.is-open { transition: visibility 0s linear 0s; }
}

/* Breadcrumb current-page marker. .is-current styled the last crumb but
   conveyed nothing programmatically; aria-current="page" is added in
   patterns/_breadcrumbs.html and carries the same visual weight. */
.rx-breadcrumb__item[aria-current="page"] { color: var(--rx-color-text); }

/* The booking wizard's step panels take tabindex="-1" so goStep() can
   move focus into the newly shown step (see booking_new.html). They are
   containers, not controls -- suppress the UA outline that programmatic
   focus would otherwise draw around the full-width panel on every
   Continue press. The announcement, not a ring, is the feedback here. */
.rx-step-panel:focus { outline: none; }

/* ============================== Map-unavailable honest placeholder (Wave 21 C) ==============================
 * No driver-location field exists anywhere in superadmin/models.py (confirmed
 * in Wave 20's design-to-engineering-map.md #2) -- there is no live position to
 * plot. Rather than a fake pin or a silently-empty map area, this pattern
 * states the real state plainly and falls back to the same driver list a map
 * would otherwise represent, as a table. Used by the dispatch board preview
 * (Workstream H) instead of the Wave 20 mockup's illustrative live map pane.
 */
.rx-map-unavailable {
  background: var(--rx-color-surface-sunken);
  border: 1px dashed var(--rx-color-border-strong);
  border-radius: var(--rx-radius-lg);
  padding: var(--rx-space-8) var(--rx-space-6);
  text-align: center;
  color: var(--rx-color-text-muted);
}
.rx-map-unavailable__icon {
  font-size: var(--rx-text-3xl);
  margin-bottom: var(--rx-space-3);
  opacity: 0.6;
}
.rx-map-unavailable__title {
  font-family: var(--rx-font-body);
  font-weight: var(--rx-weight-semibold);
  color: var(--rx-color-text);
  margin: 0 0 var(--rx-space-2);
}
.rx-map-unavailable__body {
  font-size: var(--rx-text-sm);
  max-width: 52ch;
  margin: 0 auto;
}

/* ============================== Wave 22 Phase 4: net-new components ==============================
 * Genuine gaps found by auditing the existing library against Wave 22's
 * required component list (docs/design/ridexpress-design-2.0/
 * implementation-architecture.md already found most of it existed --
 * this is the remainder). Every rule below reads only design tokens,
 * per this file's own convention.
 */

/* ---------- Trust card ("Why RideXpress" sections) ---------- */
.rx-trust-card {
  display: flex;
  flex-direction: column;
  gap: var(--rx-space-3);
  padding: var(--rx-space-6);
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  transition: border-color var(--rx-motion-fast) var(--rx-ease-standard),
              box-shadow var(--rx-motion-fast) var(--rx-ease-standard),
              transform var(--rx-motion-fast) var(--rx-ease-standard);
}
/* Wave 37 Track A: same gap/fix as .rx-marketing__service-card
   (marketing-patterns.css) -- no hover feedback at all previously.
   Same lift+shadow treatment as .rx-route-card below, this file's own
   established precedent, not a new pattern. */
.rx-trust-card:hover {
  border-color: var(--rx-color-border-strong);
  box-shadow: var(--rx-shadow-md);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .rx-trust-card { transition: none; }
  .rx-trust-card:hover { transform: none; }
}
.rx-trust-card__icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--rx-radius-md);
  background: var(--rx-color-brand-soft);
  color: var(--rx-color-accent-strong);
  flex-shrink: 0;
}
.rx-trust-card__title {
  font-family: var(--rx-font-body);
  font-weight: var(--rx-weight-semibold);
  font-size: var(--rx-text-lg);
  color: var(--rx-color-text-primary);
  margin: 0;
}
.rx-trust-card__body {
  color: var(--rx-color-text-secondary);
  font-size: var(--rx-text-sm);
  margin: 0;
}

/* ---------- Review card ----------
 * Per Wave 22 research (research-wave22.md, Airbnb row): a numeric
 * score + a short specific quote, never a bare star row alone. Built
 * for structure only -- not populated anywhere yet, since no real
 * review content exists in this codebase (design-principles.md #5 --
 * never simulate confidence with invented content). */
.rx-review-card {
  padding: var(--rx-space-5);
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  display: flex;
  flex-direction: column;
  gap: var(--rx-space-2);
}
.rx-review-card__score {
  font-family: var(--rx-font-body);
  font-weight: var(--rx-weight-bold);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-lg);
  color: var(--rx-color-text-primary);
}
.rx-review-card__quote {
  font-size: var(--rx-text-base);
  color: var(--rx-color-text-primary);
  font-style: normal;
  margin: 0;
}
.rx-review-card__attribution {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
}

/* ---------- Route card (popular-routes section) ---------- */
/* Wave 33 Track A: had a :hover state but no :focus-visible at all --
   every other card/link/button primitive in this system (.rx-nav-item,
   .rx-btn, .rx-route-summary__edit, .rx-marketing__cta, etc.) has an
   explicit keyboard-focus ring; this one was a real, silent gap on the
   homepage's "Popular outstation routes" links. The actual ring rule is
   centralized in components.css's shared :focus-visible selector list
   (this pattern's own established convention -- see e.g.
   .rx-fare-card__select/.rx-route-summary__edit there, both patterns.css
   classes with their ring defined centrally in components.css). */
.rx-route-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rx-space-4);
  padding: var(--rx-space-4) var(--rx-space-5);
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-md);
  text-decoration: none;
  transition: border-color var(--rx-motion-fast) var(--rx-ease-standard),
              box-shadow var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-route-card:hover {
  border-color: var(--rx-color-border-strong);
  box-shadow: var(--rx-shadow-sm);
}
.rx-route-card__route {
  font-family: var(--rx-font-body);
  font-weight: var(--rx-weight-medium);
  color: var(--rx-color-text-primary);
  font-size: var(--rx-text-sm);
}
.rx-route-card__price {
  font-weight: var(--rx-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--rx-color-accent-strong);
  font-size: var(--rx-text-base);
  flex-shrink: 0;
}

/* ---------- Pricing card (static "our pricing" marketing display -- distinct
   from .rx-fare-card, which is the live, selectable booking-flow option) ---------- */
.rx-pricing-card {
  padding: var(--rx-space-6);
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  text-align: center;
  transition: border-color var(--rx-motion-fast) var(--rx-ease-standard),
              box-shadow var(--rx-motion-fast) var(--rx-ease-standard),
              transform var(--rx-motion-fast) var(--rx-ease-standard);
}
.rx-pricing-card:hover {
  border-color: var(--rx-color-border-strong);
  box-shadow: var(--rx-shadow-md);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .rx-pricing-card { transition: none; }
  .rx-pricing-card:hover { transform: none; }
}
/* Wave 37 Track A: real per-Category vehicle photo slot -- the data was
   always computed (_active_vehicle_price_cards() in website/views.py
   already returns category.image, the real admin-uploaded photo tied to
   this exact Category/Pricing row) but no marketing template ever
   rendered it. Fixed aspect-ratio + object-fit:cover so mixed real
   upload dimensions don't distort or stretch (Wave 37's own image-audit
   requirement) -- object-position:center rather than a hardcoded crop
   focus, since these are real uploads of unknown composition. */
.rx-pricing-card__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: center;
  border-radius: var(--rx-radius-md);
  margin-bottom: var(--rx-space-3);
  background: var(--rx-color-surface-alt);
}
.rx-pricing-card__name {
  font-size: var(--rx-text-sm);
  color: var(--rx-color-text-secondary);
  margin: 0 0 var(--rx-space-2);
}
.rx-pricing-card__price {
  font-family: var(--rx-font-body);
  font-weight: var(--rx-weight-bold);
  font-variant-numeric: tabular-nums;
  font-size: var(--rx-text-numeric-lg);
  color: var(--rx-color-text-primary);
}
.rx-pricing-card__unit {
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
}

/* ---------- Location field (pickup/drop inputs) ---------- */
.rx-location-field {
  position: relative;
}
.rx-location-field__icon {
  position: absolute;
  left: var(--rx-space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--rx-color-text-muted);
  pointer-events: none;
  display: flex;
}
.rx-location-field input {
  padding-left: var(--rx-space-10);
}

/* ---------- Confirmation panel (post-booking summary) ---------- */
.rx-confirmation-panel {
  background: var(--rx-color-surface);
  border: 1px solid var(--rx-color-border);
  border-radius: var(--rx-radius-lg);
  padding: var(--rx-space-8);
  text-align: center;
}
.rx-confirmation-panel__icon {
  width: 56px;
  height: 56px;
  margin: 0 auto var(--rx-space-4);
  border-radius: var(--rx-radius-full);
  background: var(--rx-color-success-bg);
  color: var(--rx-color-on-success-bg);
  display: flex;
  align-items: center;
  justify-content: center;
}
.rx-confirmation-panel__title {
  font-family: var(--rx-font-display);
  font-size: var(--rx-text-2xl);
  color: var(--rx-color-text-primary);
  margin: 0 0 var(--rx-space-2);
}
.rx-confirmation-panel__id {
  font-family: var(--rx-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--rx-color-text-secondary);
  margin: 0 0 var(--rx-space-6);
}
.rx-confirmation-panel__summary {
  text-align: left;
  border-top: 1px solid var(--rx-color-border);
  padding-top: var(--rx-space-4);
}

/* ---------- Page-level error state (distinct from .rx-field__error,
   which is inline form validation, and .rx-empty-state, which is "no
   data yet" -- this is "something went wrong loading this page") ---------- */
.rx-page-error {
  text-align: center;
  padding: var(--rx-space-16) var(--rx-space-6);
}
.rx-page-error__icon {
  font-size: var(--rx-text-4xl);
  margin-bottom: var(--rx-space-4);
  color: var(--rx-color-on-danger-bg, var(--rx-color-danger));
}
.rx-page-error__title {
  font-family: var(--rx-font-body);
  font-weight: var(--rx-weight-semibold);
  font-size: var(--rx-text-xl);
  color: var(--rx-color-text-primary);
  margin: 0 0 var(--rx-space-2);
}
.rx-page-error__body {
  color: var(--rx-color-text-secondary);
  max-width: 48ch;
  margin: 0 auto var(--rx-space-6);
}

/* ---------- Ride lifecycle stepper (Wave 23 Section 6) -- a compact,
   honest view of a real ride's progress through its 4 actually-captured
   stages (booked/assigned/in_progress/completed). Rendered via the
   ride_lifecycle_stepper inclusion tag (superadmin/templatetags/
   custom_filters.py) -- never hand-authored per-row, so the stage
   mapping stays centralized. State is never color-only: each step
   carries its own text label (Section 14 accessibility). ---------- */
.rx-lifecycle-stepper {
  display: flex;
  align-items: flex-start;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}
.rx-lifecycle-stepper__step {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
  min-width: 64px;
  position: relative;
  font-family: var(--rx-font-body);
  font-size: var(--rx-text-xs);
  color: var(--rx-color-text-muted);
  text-align: center;
}
.rx-lifecycle-stepper__step:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 5px;
  left: 50%;
  width: 100%;
  height: 2px;
  background: var(--rx-color-border);
  z-index: 0;
}
.rx-lifecycle-stepper__step--done:not(:last-child)::after {
  background: var(--rx-color-success);
}
.rx-lifecycle-stepper__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--rx-color-surface);
  border: 2px solid var(--rx-color-border);
  z-index: 1;
  margin-bottom: var(--rx-space-1);
}
.rx-lifecycle-stepper__step--done .rx-lifecycle-stepper__dot {
  background: var(--rx-color-success);
  border-color: var(--rx-color-success);
}
.rx-lifecycle-stepper__step--current .rx-lifecycle-stepper__dot {
  background: var(--rx-color-brand);
  border-color: var(--rx-color-brand);
}
.rx-lifecycle-stepper__step--current .rx-lifecycle-stepper__label {
  color: var(--rx-color-text-primary);
  font-weight: var(--rx-weight-semibold);
}
.rx-lifecycle-stepper--cancelled {
  padding: var(--rx-space-1) 0;
}

/* Phase 3D: booking_list.html's customer-detail fields carry the
   legacy .contact-name/.contact-msg classes (static/css/style.css)
   alongside .rx-input, for a hardcoded border-color with no :focus
   variant. .rx-input:focus's own border-color change (components.css)
   should out-specificity that alone, but in practice the focus state
   wasn't visibly distinguishable on this exact field combination
   (flagged as a known limitation in
   docs/program/phase3c-checkout-confirmation-implementation.md).
   Rather than touch style.css (sitewide, used well beyond this page)
   or the shared components.css :focus-visible ring list (deliberately
   scoped to non-input primitives -- see that file's own comment),
   this is a narrowly-scoped, unambiguous override: three classes of
   specificity, loaded after both style.css and components.css, so it
   wins regardless of cascade order. */
.rx-input.contact-name:focus-visible,
.rx-input.contact-msg:focus-visible {
  border-color: var(--rx-color-focus-ring);
  box-shadow: 0 0 0 3px var(--rx-color-focus-ring);
  outline: none;
}

/* Growth cycle: article + sidebar layout, introduced for the blog CMS
   article template (blog_detail.html). Generic/reusable rather than
   one-off inline styles -- the same 2-col/1-col split any future
   article-style page (main content + a narrower rail of supporting
   widgets) would need. Stacks to a single column below the same
   768px breakpoint every other Design 2.0 pattern in this file uses. */
.rx-article-layout {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--rx-space-6);
  align-items: start;
}
@media (max-width: 767px) {
  .rx-article-layout {
    grid-template-columns: 1fr;
  }
}
