/* ============================================================
   RentManager (RM) — Design System
   The single source of truth for the app's visual language.

   HOW TO USE (for any new feature):
   - Never hardcode a hex colour. Use --rm-* tokens or Radzen's
     --rz-* (which this file remaps to the RM palette).
   - Compose pages from the shared components in
     VehicleManagement.UI/Components (RmPageHeader, RmStatusPill,
     RmLifecycleRail, RmKpiTile) instead of re-styling by hand.
   - See DESIGN_SYSTEM.md at the repo root for the full guide.

   Load AFTER Radzen's material-base.css so the --rz-* remap wins.

   Contents
   1. Radzen theme remap  (bottle-green primary + status vocab)
   2. RM semantic tokens (--rm-*), light now, dark dormant
   3. Shared component classes (status pill, lifecycle rail,
      KPI tile, page header, dispatch board, report tabs)

   Dark mode is defined under :root[data-theme="dark"] only — NOT
   under prefers-color-scheme — so it stays dormant until a theme
   switcher + a Radzen dark theme are wired up (Phase F). This
   avoids a half-dark UI where our components flip but Radzen
   stays light.
   ============================================================ */

/* ---- 1. Radzen theme remap -------------------------------- */
:root {
  /* primary — deep teal-pine (2026 rebrand). Teal reads as calm, technical and
     modern where the old bottle-green read as flat; it also leaves green free to
     mean one specific thing (Completed) instead of being both brand AND status. */
  --rz-primary: #0F766E;
  --rz-primary-light: #2A9187;
  --rz-primary-lighter: rgba(15, 118, 110, 0.12);
  --rz-primary-dark: #0B5A54;
  --rz-primary-darker: #084842;
  --rz-on-primary: #ffffff;
  --rz-on-primary-light: #ffffff;
  --rz-on-primary-lighter: #0F766E;
  --rz-on-primary-dark: #ffffff;
  --rz-on-primary-darker: #ffffff;

  /* secondary — warm stone so secondary buttons stay calm beside teal + coral */
  --rz-secondary: #57544E;
  --rz-secondary-light: #6E6A63;
  --rz-secondary-lighter: rgba(87, 84, 78, 0.12);
  --rz-secondary-dark: #45423D;
  --rz-secondary-darker: #35332E;
  --rz-on-secondary: #ffffff;
  --rz-on-secondary-lighter: #57544E;

  /* semantic = the RM status vocabulary */
  --rz-info: #2C68B4;          /* Confirmed */
  --rz-info-light: #4D84C6;
  --rz-info-lighter: rgba(44, 104, 180, 0.16);
  --rz-info-dark: #234F8A;
  --rz-info-darker: #1D4372;
  --rz-on-info-lighter: #2C68B4;

  --rz-success: #1F8A5B;       /* Completed / positive */
  --rz-success-light: #45A179;
  --rz-success-lighter: rgba(31, 138, 91, 0.16);
  --rz-success-dark: #187047;
  --rz-success-darker: #145C3A;
  --rz-on-success-lighter: #1F8A5B;

  --rz-warning: #C67A0E;       /* Active / attention */
  --rz-warning-light: #D9942B;
  --rz-warning-lighter: rgba(198, 122, 14, 0.18);
  --rz-warning-dark: #9E6109;
  --rz-warning-darker: #855109;
  --rz-on-warning-lighter: #C67A0E;

  --rz-danger: #BB362D;        /* Cancelled / critical */
  --rz-danger-light: #CC5A52;
  --rz-danger-lighter: rgba(187, 54, 45, 0.18);
  --rz-danger-dark: #952B24;
  --rz-danger-darker: #7A231D;
  --rz-on-danger-lighter: #BB362D;

  /* chart series — teal + coral lead, then the status hues */
  --rz-series-1: #0F766E;
  --rz-series-2: #E1553B;
  --rz-series-3: #2C68B4;
  --rz-series-4: #6C4FBE;
  --rz-series-5: #1B7950;
  --rz-series-6: #C67A0E;
  --rz-series-7: #4FA98A;
  --rz-series-8: #B23A24;

  /* page ground — warm stone */
  --rz-body-background-color: #F5F5F3;
}

/* ---- 2. RM semantic tokens -------------------------------- */
:root {
  /* Warm-stone neutrals (2026 rebrand). A green-tinted grey read as "faded bottle
     green"; a warm stone lets teal and coral both sit forward without the ground
     competing. All three inks are re-checked for AA on the warm grounds below. */
  --rm-ground: #F5F5F3;
  --rm-surface: #FFFFFF;
  --rm-surface-2: #ECECE8;
  --rm-ink: #1C1A17;
  --rm-ink-2: #57544E;
  /* Muted label ink. #6E6A63 is the lightest warm-stone value that still clears
     4.5:1 on all three grounds (surface-2 is the worst case at 4.54:1) — it carries
     every field label in every reflowed mobile grid card, so it must pass on a phone. */
  --rm-ink-3: #6E6A63;
  --rm-line: #E4E3DE;
  --rm-line-strong: #D2D0C9;

  --rm-primary: #0F766E;        /* teal-pine — see the Radzen remap note above */
  --rm-primary-deep: #0B5A54;
  --rm-primary-soft: #D8EEEB;
  --rm-primary-ink: #FFFFFF;

  /* Coral — the rebrand's second voice, spent sparingly on the ONE action that
     matters on a screen (the primary CTA, the booking you're placing/selecting) and
     nowhere else, so it never competes with the status vocabulary.
       --rm-accent       CTA fill; white text passes AA on it (4.74:1)
       --rm-accent-bright decorative only (bars, dots, rings) — never carries text
       --rm-accent-deep   hover / pressed / borders
       --rm-accent-soft   tinted chip background; pair with --rm-accent-on-soft
       --rm-accent-ink    text on the CTA fill */
  --rm-accent: #C64A34;         --rm-accent-ink: #FFFFFF;
  --rm-accent-bright: #E1553B;
  --rm-accent-deep: #A83D28;
  --rm-accent-soft: #FCE7E1;    --rm-accent-on-soft: #B23A24;

  /* Status pill foregrounds are measured against their OWN -bg, since that is the only
     pairing they ever render in (.rm-status sets both). Active and Completed failed AA:
     #C67A0E on #FBEED4 was 2.95:1 — failing even the large-text 3:1 floor — and #1F8A5B on
     #E1EFE8 was 3.65:1. Active is the status a dispatcher reads most (a trip happening now),
     outdoors, on a cheap LCD. Both are now the lightest hue-preserving value clearing 4.5:1. */
  --rm-confirmed: #2C68B4;   --rm-confirmed-bg: #E4EDF8;   /* 4.74:1 */
  --rm-arrived:   #6C4FBE;   --rm-arrived-bg:   #ECE6F8;   /* 4.93:1 */
  --rm-active:    #9A5F0A;   --rm-active-bg:    #FBEED4;   /* was #C67A0E @ 2.95:1 -> 4.55:1 */
  --rm-completed: #1B7950;   --rm-completed-bg: #E1EFE8;   /* was #1F8A5B @ 3.65:1 -> 4.54:1 */
  --rm-cancelled: #BB362D;   --rm-cancelled-bg: #F8E3E1;   /* 4.64:1 */

  /* WhatsApp share (RmWhatsAppButton). A third-party brand mark, so the hue is theirs and not
     ours — but it is declared here as a token rather than inlined in a page, because that is the
     only sanctioned way a non-palette colour enters this system.
     --rm-wa is the brand green and paints the GLYPH ONLY: white text on #25D366 is 1.75:1 and
     fails AA outright, which is why this renders as a tinted chip rather than a filled brand
     button. Text uses --rm-wa-ink on --rm-wa-soft (5.86:1). */
  --rm-wa: #25D366;   --rm-wa-soft: #E4F7EC;   --rm-wa-ink: #0A6B48;   --rm-wa-hover: #D2F1DF;

  /* Signed-out slab — the dark board panel beside every auth form.
     Deliberately NOT re-declared in the dark block: the board is always the board. A panel that
     inverted with the theme would make the front door look like two different products, and it is
     already dark enough that dark-mode users see no seam. Inks are measured against --rm-slab:
     ink 12.9:1, ink-2 7.2:1, accent 8.4:1. */
  --rm-slab: #0B2E2A;
  --rm-slab-deep: #071F1D;
  --rm-slab-2: #124440;
  --rm-slab-line: #1C544D;
  --rm-slab-track: #0F3B36;
  --rm-slab-ink: #EAF3F1;
  --rm-slab-ink-2: #9DBDB7;
  --rm-slab-accent: #57C9BC;

  --rm-font-num: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", "Menlo", monospace;
  /* MUST end in a real generic family, never `inherit`: a CSS-wide keyword is not a valid
     <custom-ident> inside a font-family list, which made the whole declaration invalid at
     computed-value time and silently voided every .rm-bn on the page. Note the named faces
     here are Windows/desktop fonts — on the Android phones drivers carry, none of them exist,
     so Bangla still falls back to whatever the device ships. Self-hosting a Bengali subset via
     @font-face is the real fix; this at least makes the rule valid. */
  --rm-font-bn: "Nirmala UI", "Noto Sans Bengali", "Hind Siliguri", sans-serif;
  /* Display — a DIN/road-signage condensed grotesk for page-header titles. DIN is the
     language of license plates and traffic signage, which is exactly this product's world.
     Bahnschrift ships on Windows (the dispatcher's desktop); the fallbacks keep a condensed
     feel elsewhere and end in sans-serif so Android never shows tofu. Body/UI stays the
     system sans; numbers stay --rm-font-num. */
  --rm-font-display: "Bahnschrift", "DIN Alternate", "DIN Condensed", "Oswald", "Arial Narrow", sans-serif;

  --rm-r-sm: 6px; --rm-r-md: 10px; --rm-r-lg: 16px;
  --rm-shadow: 0 1px 2px rgba(20,33,27,.05), 0 6px 20px -12px rgba(20,33,27,.25);
  /* Used only by RmFleetStrip's track to sink it into the page like a gauge. Forks in the
     dark block for the same reason --rm-shadow does: a black inset at this alpha is meant
     to read against --rm-surface-2, and that surface is #ECECE8 in light but #232420 in
     dark — near-black on near-black would not register. */
  --rm-fleet-gauge-shadow: inset 0 1px 2px rgba(0, 0, 0, .08);
}

/* Dark theme — activated when the switcher (Phase F) stamps
   data-theme="dark" on <html>. We ARE the Radzen theme (this file
   remaps --rz-*), so there's no separate Radzen dark stylesheet to
   load: we flip Radzen here too by remapping the tokens its whole
   palette derives from (--rz-white + the base-50..900 grayscale +
   the text tokens). That keeps our Rm* components and every Radzen
   card/grid/input/menu in the same (dark) key — no half-dark UI. */
:root[data-theme="dark"] {
  /* Warm-stone dark (2026 rebrand) — the same stone family as light, dropped to a
     deep charcoal so teal and coral both glow against it. */
  --rm-ground: #121311; --rm-surface: #1A1B18; --rm-surface-2: #232420;
  /* #908A7D is the darkest warm value clearing 4.5:1 on all three dark grounds
     (surface-2 is the worst case at 4.55:1) — the label ink on reflowed cards. */
  --rm-ink: #ECECE6; --rm-ink-2: #A8A49B; --rm-ink-3: #908A7D;
  --rm-line: #2C2C27; --rm-line-strong: #3A3A33;
  --rm-primary: #2FA093; --rm-primary-deep: #3CB8A9; --rm-primary-soft: #10302B;
  /* Dark ink on the lighter teal fill is the readable CTA pairing (5.98:1);
     white would be ~3:1. Shadow is deep + wide so cards keep elevation on dark. */
  --rm-primary-ink: #04120F;
  --rm-shadow: 0 1px 2px rgba(0,0,0,.42), 0 8px 24px -12px rgba(0,0,0,.62);
  /* A stronger black alone still under-registers on #232420, so the dark value pairs it with
     a faint light rim on the opposite inner edge — the groove now reads by two cues (a dark
     edge AND a lit edge) instead of relying on one that is weak against a dark ground. */
  --rm-fleet-gauge-shadow: inset 0 1px 2px rgba(0, 0, 0, .6), inset 0 -1px 0 rgba(255, 255, 255, .05);

  /* Coral, dark: brighter fill, dark ink; a near-black-coral soft for chips. */
  --rm-accent: #F0714F;         --rm-accent-ink: #1A0A06;
  --rm-accent-bright: #F2795E;
  --rm-accent-deep: #C85436;
  --rm-accent-soft: #331812;    --rm-accent-on-soft: #F2A88F;

  --rm-confirmed:#6AA0E6;--rm-confirmed-bg:#16283f;--rm-arrived:#A48CE6;--rm-arrived-bg:#241d3a;
  --rm-active:#E8A93A;--rm-active-bg:#35280d;--rm-completed:#45BE8E;--rm-completed-bg:#103026;
  --rm-cancelled:#E8756B;--rm-cancelled-bg:#351c1a;

  /* Brand green stays put (it is the logo); the chip around it inverts. 8.77:1. */
  --rm-wa-soft: #10291F;   --rm-wa-ink: #5FD9A0;   --rm-wa-hover: #16382A;

  /* flip Radzen: warm-stone base surface + grayscale so cards, panels,
     grids, inputs, sidebar and text all invert with us */
  --rz-white: #1A1B18;
  --rz-base-50:  #202119;
  --rz-base-100: #121311;   /* body background */
  --rz-base-200: #2C2C27;   /* hairlines / borders derive from here */
  --rz-base-300: #3A3A33;
  --rz-base-400: #55534A;   /* disabled text */
  --rz-base-500: #6E6B61;
  --rz-base-600: #928E83;
  --rz-base-700: #A8A49B;   /* secondary text */
  --rz-base-800: #E6E5DF;   /* primary text */
  --rz-base-900: #F5F5F0;
  --rz-on-base: #ECECE6;
  --rz-on-base-light: #ECECE6;
  --rz-on-base-lighter: #ECECE6;
  --rz-body-background-color: #121311;
  --rz-text-color: #ECECE6;
  --rz-text-secondary-color: #A8A49B;
  --rz-text-disabled-color: #6E6B61;
  /* brighten teal so buttons/links read against dark */
  --rz-primary: #2A9187;
  --rz-primary-light: #3CB8A9;
}

/* ---- helpers ---------------------------------------------- */
.rm-num { font-family: var(--rm-font-num); font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.rm-bn  { font-family: var(--rm-font-bn); }

/* Screen-reader-only: present in the accessibility tree, invisible on screen. The standard
   clip-rect pattern — NOT display:none / visibility:hidden, both of which remove the node from
   the tree too and would defeat the point. Use for a text channel that stands in for
   information a sighted user gets from colour/shape alone (see RmAvailabilityStrip's
   per-day busy-block description). */
.rm-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Horizontal rule inside a card. Exists because <RadzenDivider /> does NOT: Radzen.Blazor
   5.9.6 ships no such component, so the Razor compiler emits it as a literal unknown HTML
   element (warning RZ10012) which the browser renders as an invisible inline node. It has
   never drawn a line anywhere in this app. Use .rm-divider instead — and if you see
   RZ10012 for a Radzen* element, it is this bug, not a missing @using. */
.rm-divider {
  border: 0;
  border-top: 1px solid var(--rm-line);
  margin: 1rem 0;
}

/* ---- Coral CTA + chip (the rebrand's accent, used sparingly) ----
   .rm-cta is for the ONE primary action on a screen — "Confirm booking",
   "Start trip". Everything else stays a teal Radzen button so coral keeps its
   meaning. It layers over a RadzenButton via class="rm-cta" — LOWERCASE: Radzen exposes no
   public Class parameter, so `Class` lands in RadzenComponent.Attributes under a key its
   GetCssClass() never merges, and the class silently does nothing. It also stands alone. */
.rm-cta {
  background: var(--rm-accent) !important; color: var(--rm-accent-ink) !important;
  border-color: var(--rm-accent) !important;
}
.rm-cta:hover, .rm-cta:focus-visible {
  background: var(--rm-accent-deep) !important; border-color: var(--rm-accent-deep) !important;
}
.rm-cta:focus-visible { outline: 2px solid var(--rm-accent-deep); outline-offset: 2px; }
.rm-chip-accent {
  color: var(--rm-accent-on-soft); background: var(--rm-accent-soft);
  font-size: .64rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}

/* Identifier badges inside data grids — vehicle registrations ("DHAKA METRO-GHA-11-2345"),
   booking / invoice numbers — can be longer than their column. Radzen renders them nowrap
   and the grid cell clips with overflow:hidden, so the tail was silently chopped off.
   Let these badges wrap onto a second line so the full code is always readable; short
   badges (counts, statuses) are unaffected because they never need to wrap. */
.rz-grid-table .rz-badge {
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: left;
}

/* ---- 3a. Page header -------------------------------------- */
.rm-page-header {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 20px;
}
/* The title is frequently an identifier, not a word — a BD registration ("DHAKA
   METRO-CHA-14-2021") is 23 characters of condensed uppercase. At a flat 1.7rem that
   took two lines and ~90px of a 390px phone screen before the page said anything, so
   the size is fluid: 1.7rem on a desktop, ~1.46rem at 390px, where a typical plate
   then fits on one line. `balance` keeps a title that still needs two lines from
   breaking as a long line over an orphan ("DHAKA METRO-CHA-14- / 2021"), and
   break-word is the floor that stops an unusually long identifier overflowing
   its box entirely. */
.rm-page-header .rm-ph-title {
  font-family: var(--rm-font-display);
  font-size: clamp(1.4rem, 6vw, 1.7rem);
  font-weight: 700; letter-spacing: .005em; text-transform: uppercase;
  color: var(--rm-ink); margin: 0;
  text-wrap: balance; overflow-wrap: break-word;
}
.rm-page-header .rm-ph-sub { font-size: .85rem; color: var(--rm-ink-2); margin: 4px 0 0; }

/* The action cluster. Declared here rather than inline on RmPageHeader so the phone
   rule below can restate it as a grid without an !important fight with a style
   attribute — the same reason RmStoredImage publishes --rm-img-size. */
.rm-ph-actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }

/* On a phone, free-flowing wrap makes a ragged staircase of it: this page's four
   buttons measured 208 / 90 / 153 / 118px, so they broke into two rows that lined up
   with nothing and read as a layout failure rather than a set of choices. An equal-track
   grid gives every secondary action the same width, and the primary — the one thing
   this screen is for — spans the full row above them. No page markup changes: the
   primary is found by the .rz-primary Radzen already stamps on it.

   The track count is auto-fit rather than a hardcoded `1fr 1fr`, because a fixed pair
   has no headroom and silently clips. Measured on an SM-A256E at /vehicles/{id}: a
   304px cluster gave two 148px tracks while "Documents" needed 178px, so Radzen's
   overflow:hidden ate the final S and the button read "DOCUMENT". Two things make that
   deficit unbounded — a longer label (বাংলা runs longer than English for most of this
   vocabulary) and Android's system font scale, which is applied inside the WebView and
   measured 18.2px against a 16px base on that handset. minmax() in rem tracks the same
   scale the labels do, so the cluster drops to one full-width column exactly when a pair
   would no longer fit, instead of clipping. */
@media (max-width: 820px) {
  .rm-page-header { align-items: flex-start; }
  .rm-ph-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: .5rem;
    width: 100%;
  }
  .rm-ph-actions > .rz-primary,
  .rm-ph-actions > .rm-cta { grid-column: 1 / -1; }
  /* Radzen buttons are inline-flex and shrink-to-fit; in a grid track they must fill it. */
  .rm-ph-actions > .rz-button { width: 100%; justify-content: center; }
}

/* ---- 3b. Status pill -------------------------------------- */
.rm-status {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .78rem; font-weight: 700; line-height: 1;
  padding: 5px 11px 5px 9px; border-radius: 999px; white-space: nowrap;
  color: var(--rm-ink-2); background: var(--rm-surface-2);
}
.rm-status .rm-status-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
.rm-status.is-confirmed { color: var(--rm-confirmed); background: var(--rm-confirmed-bg); }
.rm-status.is-arrived   { color: var(--rm-arrived);   background: var(--rm-arrived-bg); }
.rm-status.is-active    { color: var(--rm-active);    background: var(--rm-active-bg); }
.rm-status.is-completed { color: var(--rm-completed); background: var(--rm-completed-bg); }
.rm-status.is-cancelled { color: var(--rm-cancelled); background: var(--rm-cancelled-bg); }

/* ---- 3c. Lifecycle rail (signature) ----------------------- */
.rm-rail { display: flex; align-items: stretch; width: 100%; }
.rm-rail .seg {
  flex: 1; position: relative; min-width: 0; padding: 10px 10px 10px 24px;
  background: var(--rm-surface-2);
  border-top: 1px solid var(--rm-line); border-bottom: 1px solid var(--rm-line);
}
.rm-rail .seg:first-child { border-left: 1px solid var(--rm-line); border-radius: var(--rm-r-md) 0 0 var(--rm-r-md); padding-left: 14px; }
.rm-rail .seg:last-child  { border-right: 1px solid var(--rm-line); border-radius: 0 var(--rm-r-md) var(--rm-r-md) 0; }
.rm-rail .seg::after {
  content: ""; position: absolute; right: -9px; top: 50%; width: 16px; height: 16px;
  transform: translateY(-50%) rotate(45deg); background: inherit;
  border-right: 1px solid var(--rm-line); border-top: 1px solid var(--rm-line);
  border-radius: 3px; z-index: 2;
}
.rm-rail .seg:last-child::after { display: none; }
.rm-rail .seg .k { font-size: .62rem; letter-spacing: .1em; text-transform: uppercase; font-weight: 700; color: var(--rm-ink-3); }
.rm-rail .seg .v { font-size: .8rem; font-weight: 700; margin-top: 2px; color: var(--rm-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rm-rail .seg .dot { position: absolute; left: 7px; top: 13px; width: 9px; height: 9px; border-radius: 50%; background: var(--rm-line-strong); }
.rm-rail .seg.done { background: var(--rm-primary-soft); }
.rm-rail .seg.done .dot { background: var(--rm-completed); }
.rm-rail .seg.done .k { color: var(--rm-completed); }
.rm-rail .seg.now { background: var(--rm-active-bg); }
.rm-rail .seg.now .dot { background: var(--rm-active); animation: rm-pulse 2s infinite; }
.rm-rail .seg.now .k { color: var(--rm-active); }
@keyframes rm-pulse { 0%{box-shadow:0 0 0 0 rgba(198,122,14,.45);} 70%{box-shadow:0 0 0 7px rgba(198,122,14,0);} 100%{box-shadow:0 0 0 0 rgba(198,122,14,0);} }
@media (prefers-reduced-motion: reduce) { .rm-rail .seg.now .dot { animation: none; } }

/* ---- 3d. KPI tile ----------------------------------------- */
.rm-kpi {
  background: var(--rm-surface); border: 1px solid var(--rm-line);
  border-radius: var(--rm-r-md); padding: 14px 15px; position: relative; overflow: hidden;
}
.rm-kpi::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--rm-kpi-accent, var(--rm-primary)); }
.rm-kpi .rm-kpi-k { font-size: .66rem; letter-spacing: .1em; text-transform: uppercase; color: var(--rm-ink-3); font-weight: 700; }
.rm-kpi .rm-kpi-v { font-family: var(--rm-font-num); font-size: 1.6rem; font-weight: 700; margin-top: 6px; letter-spacing: -.02em; color: var(--rm-ink); }
.rm-kpi .rm-kpi-v small { font-size: .8rem; color: var(--rm-ink-3); font-weight: 600; }
.rm-kpi .rm-kpi-delta { font-size: .72rem; font-weight: 700; margin-top: 4px; display: inline-flex; align-items: center; gap: 4px; }
.rm-kpi .rm-kpi-delta.up { color: var(--rm-completed); }
.rm-kpi .rm-kpi-delta.down { color: var(--rm-cancelled); }

/* ---- 3d-1. Offline banner ---------------------------------
   Rendered by RmOfflineBanner, which only ever shows in the MAUI
   app (see IConnectivityMonitor). Sits under the app bar and
   pushes content down rather than overlaying it — an overlay
   would cover the row the user was reading. */
.rm-offline {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 16px; font-size: .78rem; font-weight: 600;
  background: var(--rm-active-bg); color: var(--rm-ink);
  border-bottom: 1px solid var(--rm-active);
}
.rm-offline-dot {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 999px;
  background: var(--rm-active);
}

/* ---- 3d-2. Empty / not-found state -------------------------
   Used by the router's <NotFound>. Matters most on mobile, where
   there is no URL bar to escape a bad deep link with. */
.rm-notfound { padding: 32px 20px; text-align: center; }
.rm-notfound h1 { font-size: 1.1rem; font-weight: 800; color: var(--rm-ink); margin: 0 0 8px; letter-spacing: -.01em; }
.rm-notfound p { color: var(--rm-ink-2); margin: 0 0 20px; }
.rm-notfound a {
  display: inline-block; padding: 10px 20px; border-radius: var(--rm-r-md);
  background: var(--rm-primary); color: var(--rm-primary-ink);
  text-decoration: none; font-weight: 700;
}

/* ---- 3d-3. Fleet strip (RmFleetStrip) ----------------------
   An instrument readout, not a progress bar. Segments butt directly against
   each other with no gap — a gap would make four segments read as wider
   than the fleet they sum to, which is exactly the invariant this bar
   exists to show — and only the track's outer ends are rounded (via
   overflow:hidden on .rm-fleet-bar), never each segment. The hairline
   border + inset shadow sink the bar into the page like a physical gauge
   rather than a floating strip of colour. The shadow is a dedicated token,
   --rm-fleet-gauge-shadow (defined beside --rm-shadow in each theme block),
   not an inline value: it has to fork the same way --rm-shadow does, and
   for the same reason — a flat rgba(0,0,0,…) alpha tuned to register on
   light --rm-surface-2 (#ECECE8) is close to invisible on dark
   --rm-surface-2 (#232420), so the dark value is both stronger and paired
   with a faint light rim on the opposite edge (see the dark block). No
   image/CDN texture — see the note on .rm-auth-slab::before for why this
   RCL, shared with an offline MAUI WebView, never ships one. */
.rm-fleet-bar {
  display: flex;
  height: .75rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--rm-surface-2);
  border: 1px solid var(--rm-line);
  box-shadow: var(--rm-fleet-gauge-shadow);
}
/* A zero-count segment is left out of the markup entirely (RmFleetStrip.razor), so there is
   no hairline sliver to hide here. A segment that IS rendered still gets a min-width floor —
   without it, one vehicle in a 35-car fleet is a ~1px proportional share and disappears, and
   the one owner who most needs to see "1 car still out" is the one this bar would fail. */
.rm-fleet-seg { display: block; min-width: 6px; }
.rm-fleet-running  { background: var(--rm-active); }
.rm-fleet-workshop { background: var(--rm-cancelled); }
.rm-fleet-booked   { background: var(--rm-confirmed); }
.rm-fleet-free     { background: var(--rm-ink-3); }

.rm-fleet-keys {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .5rem 1rem;
  margin-top: .75rem;
}
@media (min-width: 720px) {
  .rm-fleet-keys { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.rm-fleet-key { display: flex; align-items: baseline; gap: .4rem; }
.rm-fleet-dot { width: .6rem; height: .6rem; border-radius: 50%; flex: 0 0 auto; }
.rm-fleet-count { font-size: 1.25rem; font-weight: 700; color: var(--rm-ink); }
/* Matches .rm-kpi-k exactly so the legend reads as native beside RmKpiTile, not a one-off. */
.rm-fleet-label { font-size: .66rem; letter-spacing: .1em; text-transform: uppercase; color: var(--rm-ink-3); font-weight: 700; }

/* ---- 3d-4. Bucket bar (BookingList.razor) ------------------
   RadzenSelectBar's root is `.rz-selectbutton.rz-selectbutton-horizontal`, an inline-flex row
   that sizes to its content UNLESS a flex ancestor stretches it — which is exactly what
   RadzenStack (display:flex; flex-direction:column; align-items:stretch by default) does to
   it, forcing the bar to the full page width and each `.rz-button` to flex-shrink below its
   label's natural width. Because `.rz-button` also sets `overflow: hidden`, an overflowing
   label is clipped mid-word rather than wrapped or ellipsised — "NEEDS ATTENTI(69)" at 390px.

   This wrapper is a plain block, not a flex container, so the select bar inside it is no
   longer anyone's flex item and reverts to its natural (wider) shrink-to-fit size; the wrapper
   then scrolls horizontally instead of the buttons being crushed. Scoped to .rm-bucket-bar
   specifically so BookingBoard, DriverTrips and ReportsPage — which all use their own
   RadzenSelectBar, with far shorter labels that never needed this — keep Radzen's default
   compress-to-fit behaviour untouched. */
.rm-bucket-bar {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  padding-bottom: 2px; /* keeps a visible scrollbar off the pills' own bottom border */
}
.rm-bucket-bar::-webkit-scrollbar { height: 4px; }
.rm-bucket-bar::-webkit-scrollbar-thumb { background: var(--rm-line-strong); border-radius: 999px; }
.rm-bucket-bar .rz-selectbutton { width: max-content; }   /* never compressed below its labels */
.rm-bucket-bar .rz-button.rz-button-xs { white-space: nowrap; }  /* a count must not wrap off its label */

/* ---- 3e. Report tabs -------------------------------------- */
.rm-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--rm-line); margin-bottom: 16px; overflow-x: auto; }

/* The strip has always scrolled sideways when it had to; what it never did was SAY so. Reports
   carries six bilingual tabs (Revenue আয় / Fleet বহর / Fuel / Money / Papers / Profit) and at
   390px only two and a half fit, with the third cut clean at the viewport edge — which reads as
   the strip being clipped, not as there being more of it. Two of the five report views were
   effectively undiscoverable on a phone.

   The classic layered-background scroll shadow, not a JS scroll listener: the `local` gradients
   are painted relative to the CONTENT so they travel with it, and the `scroll` gradients are
   painted relative to the scroll port so they stay pinned at the edges. At the far left the
   local cover sits exactly over the left shadow and hides it; scroll right and the cover moves
   away, revealing it. So each edge shows a shadow only while there is something past it, with no
   script and no state. Same treatment for .rm-bucket-bar, which scrolls for the same reason. */
.rm-tabs,
.rm-bucket-bar {
  background-image:
    linear-gradient(to right, var(--rm-ground) 40%, rgba(0, 0, 0, 0)),
    linear-gradient(to left,  var(--rm-ground) 40%, rgba(0, 0, 0, 0)),
    linear-gradient(to right, rgba(0, 0, 0, .16), rgba(0, 0, 0, 0)),
    linear-gradient(to left,  rgba(0, 0, 0, .16), rgba(0, 0, 0, 0));
  background-position: left center, right center, left center, right center;
  background-size: 28px 100%, 28px 100%, 14px 100%, 14px 100%;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
.rm-tabs .rm-tab {
  padding: 11px 14px; font-size: .85rem; font-weight: 600; color: var(--rm-ink-2);
  border-bottom: 2px solid transparent; white-space: nowrap; cursor: pointer;
  display: flex; align-items: center; gap: 8px; background: none; border-top: 0; border-left: 0; border-right: 0;
}
.rm-tabs .rm-tab.on { color: var(--rm-primary); border-bottom-color: var(--rm-primary); font-weight: 700; }

/* ---- 3e-1. Flow steps (the "How it works" guide) ----------
   A numbered vertical sequence joined by a connector line — lets the guide page
   show each persona's flow without a diagram library (mermaid would need a JS
   runtime the app doesn't ship). The final step is the outcome, marked coral. */
.rm-flow { display: grid; gap: 0; }
.rm-flow .step { display: grid; grid-template-columns: 30px 1fr; gap: 12px; position: relative; padding-bottom: 16px; }
.rm-flow .step:last-child { padding-bottom: 0; }
.rm-flow .step::before { content: ""; position: absolute; left: 14px; top: 28px; bottom: -2px; width: 2px; background: var(--rm-line-strong); }
.rm-flow .step:last-child::before { display: none; }
.rm-flow .step .n { z-index: 1; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  font-family: var(--rm-font-num); font-size: .8rem; font-weight: 700; color: var(--rm-primary-deep);
  background: var(--rm-primary-soft); border: 1px solid color-mix(in srgb, var(--rm-primary) 30%, transparent); }
.rm-flow .step .t { padding-top: 4px; color: var(--rm-ink-2); font-size: .92rem; line-height: 1.5; }
.rm-flow .step .t b { color: var(--rm-ink); font-weight: 650; }
.rm-flow .step.end .n { color: var(--rm-accent-on-soft); background: var(--rm-accent-soft);
  border-color: color-mix(in srgb, var(--rm-accent) 35%, transparent); }
.rm-flow .step.end .t b { color: var(--rm-accent-on-soft); }

/* ---- 3g. Find-a-vehicle (availability-first booking) ------ */
.rm-find { display: grid; grid-template-columns: 1fr 330px; gap: 20px; align-items: start; }
@media (max-width: 900px) { .rm-find { grid-template-columns: 1fr; } }
.rm-vgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.rm-vcard {
  text-align: left; background: var(--rm-surface); border: 1px solid var(--rm-line);
  border-radius: var(--rm-r-md); padding: 13px 14px; cursor: pointer; display: flex;
  flex-direction: column; gap: 7px; font: inherit; transition: border-color .12s, box-shadow .12s;
}
.rm-vcard:hover { border-color: var(--rm-primary); box-shadow: var(--rm-shadow); }
.rm-vcard:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; }
/* The picked vehicle wears coral — the rebrand's "this is the one you're committing
   to" accent, carried through to the booking block on the dispatch board below. */
.rm-vcard.sel { border-color: var(--rm-accent); box-shadow: 0 0 0 2px var(--rm-accent-soft); }
.rm-vcard.sel .plate { color: var(--rm-accent-on-soft); }
.rm-vcard.busy { opacity: .5; cursor: not-allowed; }
.rm-vcard .rm-vcard-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.rm-vcard .plate { font-family: var(--rm-font-num); font-weight: 700; font-size: .92rem; color: var(--rm-primary); letter-spacing: -.01em; }
.rm-vcard .mk { font-size: .8rem; color: var(--rm-ink-2); }
.rm-vcard .rate { font-family: var(--rm-font-num); font-weight: 700; color: var(--rm-ink); }
.rm-vcard .rate small { font-weight: 500; color: var(--rm-ink-3); }
.rm-vcard .chip { font-size: .64rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--rm-ink-3); background: var(--rm-surface-2); padding: 2px 8px; border-radius: 999px; white-space: nowrap; }

/* ---- 3f. Dispatch board ----------------------------------- */
.rm-board { display: grid; grid-template-columns: 1fr 288px; gap: 20px; align-items: start; }
.rm-brow { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 12px; border-bottom: 1px solid var(--rm-line); }
.rm-brow:last-child { border-bottom: none; }
.rm-track { position: relative; height: 40px; border-radius: 7px;
  background: repeating-linear-gradient(90deg, transparent 0, transparent calc(100%/6 - 1px), var(--rm-line) calc(100%/6 - 1px), var(--rm-line) calc(100%/6)), var(--rm-ground); }
/* Multi-day views draw their own per-day gridlines (.rm-dayline), so drop the fixed 6-column wash. */
.rm-track.days { background: var(--rm-ground); }
.rm-dayline { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--rm-line); pointer-events: none; }
.rm-blk { position: absolute; top: 5px; bottom: 5px; border-radius: 5px; display: flex; align-items: center; padding: 0 8px; font-size: .62rem; font-weight: 700; overflow: hidden; white-space: nowrap; }
/* Trips are the only information on this board, so they carry the weight: a status-tinted fill
   and a solid left rule in the status hue. They used to be --rm-surface-2 — a grey barely
   separable from the track — which made every trip look identical and made a busy Tuesday look
   like an empty one. Fills are the AA-tuned status -bg tokens with their matching ink. */
.rm-blk.busy { background: var(--rm-surface-2); color: var(--rm-ink-2); border: 1px solid var(--rm-line-strong); border-left: 3px solid var(--rm-ink-3);
  cursor: pointer; font: inherit; font-size: .62rem; font-weight: 700; text-align: left;
  /* A 2-hour trip is ~1% of a 7-day track: ~11px, unreadable and barely clickable. The
     percentage still positions it; this floor keeps it hittable. Adjacent short trips may now
     touch, which is a far smaller problem than trips you cannot see or press. */
  min-width: 26px;
  /* Overrides .rm-blk's overflow:hidden, which would clip the hover card to the width of
     the bar itself. The label keeps its own ellipsis via .rm-blk-lbl. */
  overflow: visible; }
.rm-blk.busy.st-confirmed { background: var(--rm-confirmed-bg); color: var(--rm-confirmed); border-color: var(--rm-confirmed); }
.rm-blk.busy.st-arrived   { background: var(--rm-arrived-bg);   color: var(--rm-arrived);   border-color: var(--rm-arrived); }
.rm-blk.busy.st-active    { background: var(--rm-active-bg);    color: var(--rm-active);    border-color: var(--rm-active); }
.rm-blk.busy.st-completed { background: var(--rm-completed-bg); color: var(--rm-completed); border-color: var(--rm-completed); }
.rm-blk.busy.st-cancelled { background: var(--rm-cancelled-bg); color: var(--rm-cancelled); border-color: var(--rm-cancelled); }
.rm-blk.busy:hover { filter: brightness(.96); z-index: 3; box-shadow: var(--rm-shadow); }
.rm-blk.busy:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; z-index: 3; }
.rm-blk.busy.sel { outline: 2px solid var(--rm-primary-deep); outline-offset: 1px; z-index: 3; }
/* min-width:0 is what actually lets a flex child shrink below its content width —
   without it text-overflow never triggers and a long trip ref overflows the bar. */
.rm-blk .rm-blk-lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto; }
/* The booking you're placing / the slot you've chosen wear coral — same "committing
   to this one" thread as the selected vehicle card. Empty available slots stay teal. */
.rm-blk.newb { background: var(--rm-accent); color: var(--rm-accent-ink); outline: 2px solid var(--rm-accent-deep); z-index: 2; cursor: pointer; }
/* Free time is the ABSENCE of information, so it whispers: no border, no permanent label, just
   a faint wash that only resolves into a target under the pointer. The old dashed-and-labelled
   treatment printed "free" across every empty row and drowned the trips. */
.rm-blk.free { background: transparent; color: var(--rm-primary-deep); border: 1px solid transparent; cursor: pointer; font-weight: 600; justify-content: center; font: inherit; font-size: .6rem; }
.rm-blk.free:hover { background: var(--rm-primary-soft); border-color: var(--rm-primary); }
.rm-blk.free:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; background: var(--rm-primary-soft); }
.rm-blk.free.sel { background: var(--rm-accent); color: var(--rm-accent-ink); border: none; outline: 2px solid var(--rm-accent-deep); }
/* Revealed on hover/focus only. Kept in the DOM (not toggled from C#) so it costs no round trip. */
.rm-blk-add { opacity: 0; transition: opacity .12s ease; }
.rm-blk.free:hover .rm-blk-add,
.rm-blk.free:focus-visible .rm-blk-add,
.rm-blk.free.sel .rm-blk-add { opacity: 1; }

/* "Now". Repeated down every row, so it has to be quiet: at full-strength coral with a dot per
   row it read as 31 stacked error markers rather than one time reference. A 1px 55%-opacity rule
   with no dot reads as a single continuous line down the board. pointer-events:none so it never
   sits between a dispatcher and the slot underneath it. */
.rm-nowline { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--rm-accent-bright); opacity: .55; pointer-events: none; z-index: 1; }

/* Per-row load. Utilisation read off bar widths is a guess; this states it. */
.rm-brow .rm-veh .load { font-family: var(--rm-font-num); font-size: .58rem; letter-spacing: .02em; color: var(--rm-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rm-brow .rm-veh .load.idle { opacity: .55; }

/* Key for the status fills — without it the board is a chart with no legend. */
.rm-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--rm-line); font-size: .66rem; color: var(--rm-ink-2); }
.rm-legend-i { display: inline-flex; align-items: center; gap: 6px; }
.rm-legend-i i { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; border-left: 3px solid currentColor; }
.rm-legend-i i.st-confirmed { background: var(--rm-confirmed-bg); color: var(--rm-confirmed); }
.rm-legend-i i.st-arrived   { background: var(--rm-arrived-bg);   color: var(--rm-arrived); }
.rm-legend-i i.st-active    { background: var(--rm-active-bg);    color: var(--rm-active); }
.rm-legend-i i.st-completed { background: var(--rm-completed-bg); color: var(--rm-completed); }
.rm-legend-free i { background: var(--rm-ground); border: 1px solid var(--rm-line-strong); border-left-width: 1px; }
.rm-legend-now i { width: 3px; border-radius: 2px; border: none; background: var(--rm-accent-bright); }

@media (prefers-reduced-motion: reduce) {
  .rm-blk-add { transition: none; }
}
.rm-brow .rm-veh { display: flex; flex-direction: column; gap: 1px; padding: 7px 0; min-width: 0; }
/* Registration is the row's identity — let it wrap to a second line rather than clip with an
   ellipsis (BD plates like "DHAKA METRO-BA-11-2063" are wider than the label column). */
.rm-brow .rm-veh .plate { font-family: var(--rm-font-num); font-weight: 700; font-size: .68rem; color: var(--rm-primary); white-space: normal; overflow-wrap: anywhere; line-height: 1.2; }
.rm-brow .rm-veh .mk { font-size: .66rem; color: var(--rm-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rm-axis { display: flex; justify-content: space-between; font-family: var(--rm-font-num); font-size: .6rem; color: var(--rm-ink-3); padding-top: 4px; }
/* Multi-day axis: labels are absolutely placed at each day's left edge instead of evenly spread. */
.rm-axis-days { position: relative; display: block; height: 1.1em; }
.rm-axis-days span { position: absolute; white-space: nowrap; }
/* Not italic — <i> is used here only because it is the shortest inline element that carries no
   meaning of its own, and this is a presentational fragment of a date. */
.rm-axis-dow { font-style: normal; margin-right: .25em; }
.rm-board .rm-slot-free { cursor: pointer; }
@media (max-width: 860px) { .rm-board { grid-template-columns: 1fr; } }

/* The board on a phone. It stays a board — a week of bars is still the fastest way to see who is
   free — but two things that work at 1400px do not survive at 390px, where the track is ~196px
   wide and a day is 28px of it. */
@media (max-width: 820px) {
  /* Weekday names printed through each other; the date alone is unambiguous. */
  .rm-axis-dow { display: none; }

  /* A trip label needs ~73px and a day-view block has ~14px to give, so what actually rendered was
     a one-character sliver of "TRIP-DHK-0069" — noise that reads as a glyph bug. Multi-day views
     drop the label and let the bar do its job: position says when, the status fill says what, and
     tapping it opens the trip. The Day view keeps its labels; there the blocks are wide enough. */
  .rm-track.days .rm-blk-lbl { display: none; }
}

/* ---- 3h. 360 identity card on a phone ----------------------
   The "what is this vehicle doing right now" card at the top of VehicleDetail. On a
   desktop it is a 220px photo beside its facts, which is the right shape there. At
   390px the card's content box is 294px, so that photo took three quarters of it and
   the headline — the single sentence the page exists to deliver — rendered in an 86px
   gutter and was clipped by the card's own edge (measured: content ran to x=525 in a
   390px viewport).

   The breakpoint changes what the card is ABOUT rather than just letting it wrap: the
   photo demotes from hero to identification chip, and the status sentence becomes the
   headline it always was. Nothing here applies above 820px — the desktop card is
   untouched.
   ------------------------------------------------------------ */
@media (max-width: 820px) {
  /* 220 -> 96px. Still unambiguously "that car", and it hands ~186px back to the text.
     The override token (not --rm-img-size) is what beats the component's inline request;
     see the .rm-stored-img rule for why there are two. */
  .rm-idcard-photo { --rm-img-size-override: 96px; }

  /* The photo's own row only. Radzen publishes its Gap as an inline --rz-gap, so the
     literal `gap` is set instead of the token — this rule outranks Radzen's own .rz-stack
     and never has to see the inline declaration. :has() scopes it to the row holding the
     photo, so the nested stacks inside the text column keep their own rhythm. */
  .rm-idcard .rz-stack:has(> .rm-idcard-photo) { gap: .75rem; }

  /* H6 is a desktop headline size. Set in the ~160px column left beside the chip it ran to
     five lines; 1.05rem holds it to three. break-word is the floor for an unusually long
     place name rather than a general licence to break mid-word. */
  .rm-idcard .rz-text-h6 {
    font-size: 1.05rem;
    line-height: 1.35;
    overflow-wrap: break-word;
  }
}

/* ============================================================
   4. Mobile app shell — phone-width only
   Fixes the header/status-bar collision and swaps the desktop
   sidebar for a thumb-zone bottom tab bar + slide-in menu drawer.
   Everything here is a no-op on desktop: env() insets resolve to
   0, and the bottom nav / drawer default to display:none.
   ============================================================ */

/* Push the app bar clear of the device status bar. 0px on desktop;
   the status-bar height on mobile (needs viewport-fit=cover, which
   the MAUI index.html already sets). */
.rz-header { padding-top: env(safe-area-inset-top, 0px); }

/* Desktop-hidden by default — only the mobile breakpoint reveals them. */
.rm-bottomnav,
.rm-drawer,
.rm-drawer-scrim { display: none; }

@media (max-width: 820px) {
  /* --- compact app bar so it stops wrapping on narrow screens --- */
  .rm-brand-word { display: none; }        /* keep the monogram, drop the wordmark */
  .rm-welcome { display: none; }           /* the name lives in the menu drawer instead */
  .rm-sidebar-toggle { display: none !important; }

  /* --- desktop sidebar is replaced by the bottom nav + drawer --- */
  .rz-sidebar { display: none !important; }
  .rz-body { margin-left: 0 !important; padding-bottom: calc(58px + env(safe-area-inset-bottom, 0px)); }

  /* --- the desktop footer does not come to the phone --- */
  /* It is one non-wrapping row (copyright | build stamp | tagline). Measured on a 384px
     viewport it came to 423px, so it ran 39px past the edge and .rz-layout's overflow-x:hidden
     clipped it — the copyright and the tagline both ended mid-word. Wrapping it instead would
     spend three stacked lines directly above the tab bar on chrome rather than information.
     It is hidden here; the build stamp moves into .rm-phone-build below (and the drawer foot)
     so the one genuinely useful thing in it stays answerable from a screenshot. */
  .rz-footer { display: none; }

  /* The build stamp on a phone, in normal flow at the end of the page.
     Hiding the whole footer used to take the version with it, and the drawer copy is behind
     ShowAppChrome — so a signed-out handset had no way to answer "which version am I on"
     anywhere, which is exactly the screen someone is looking at when they ring support about
     not being able to get in.

     Its own element rather than un-hiding part of the footer: the footer's items are Radzen
     components carrying .rz-text-caption, which sets display at the same one-class specificity
     from a sheet that loads later — so a rule here quietly loses the tie and the item keeps
     rendering.

     Scoped through .rz-body deliberately: the default `.rm-phone-build { display: none }` lives
     further down this file, and a media query adds no specificity of its own — so a bare rule
     here would lose the tie to a later one of equal weight and the stamp would stay hidden at
     every width. */
  .rz-body .rm-phone-build {
    display: block;
    text-align: center;
    padding: .5rem 0 .75rem;
  }

  /* --- bottom tab bar --- */
  .rm-bottomnav {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1100;
    height: calc(58px + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--rm-surface); border-top: 1px solid var(--rm-line);
  }
  .rm-bottomnav a, .rm-bottomnav button {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; text-decoration: none; color: var(--rm-ink-3);
    font-size: .6rem; font-weight: 700; letter-spacing: .02em;
    background: none; border: none; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent; padding: 0;
  }
  .rm-bottomnav .ib { width: 44px; height: 26px; border-radius: 999px; display: grid; place-items: center; }
  .rm-bottomnav a.on { color: var(--rm-primary); }
  .rm-bottomnav a.on .ib { background: var(--rm-primary-soft); }
  .rm-bottomnav a:focus-visible, .rm-bottomnav button:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: -3px; }
  .rm-bottomnav svg { display: block; }

  /* --- slide-in menu drawer (the "More" tab) --- */
  .rm-drawer-scrim {
    display: block; position: fixed; inset: 0; z-index: 1200;
    background: rgba(20, 33, 27, .45); opacity: 0; pointer-events: none;
    transition: opacity .18s ease;
  }
  .rm-drawer-scrim.open { opacity: 1; pointer-events: auto; }
  .rm-drawer {
    display: flex; flex-direction: column; position: fixed; z-index: 1300;
    top: 0; bottom: 0; left: 0; width: min(84vw, 320px);
    padding-top: env(safe-area-inset-top, 0px);
    background: var(--rm-surface); border-right: 1px solid var(--rm-line);
    box-shadow: var(--rm-shadow);
    transform: translateX(-100%); transition: transform .2s ease;
  }
  .rm-drawer.open { transform: translateX(0); }
  .rm-drawer-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 12px 14px 18px; border-bottom: 1px solid var(--rm-line);
    font-weight: 800; letter-spacing: -.01em; color: var(--rm-ink);
  }
  .rm-drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 8px 6px; }
  .rm-drawer-foot { padding: 12px; border-top: 1px solid var(--rm-line); }

  @media (prefers-reduced-motion: reduce) {
    .rm-drawer, .rm-drawer-scrim { transition: none; }
  }
}

/* ============================================================
   5. Data grids on a phone
   The list-page pattern is RmPageHeader + RadzenDataGrid, which
   has no narrow-viewport story of its own: columns carry fixed
   pixel widths (VehicleList alone totals ~890px) so at 390px the
   table was clipped mid-word and the page scrolled sideways.

   The fix is Radzen's OWN reflow mode — pages pass
   Responsive="true", which stamps .rz-datatable-reflow and turns
   each row into a stacked block with its column title shown as a
   label. We do not re-implement that here. This section only:
     (a) extends it from Radzen's 768px breakpoint to our 820px
         mobile-shell breakpoint, so there is no dead band where
         the bottom nav is up but the table is still tabular; and
     (b) dresses the stacked rows as RM cards, since Radzen's
         reflow alone leaves an undifferentiated wall of text.
   ============================================================ */

/* (a) Breakpoint bridge — mirrors the reflow rules Radzen applies
   at 768px. Below 768px these simply agree with Radzen's own. */
@media (max-width: 820px) {
  .rz-datatable-reflow .rz-data-grid-data > table,
  .rz-datatable-reflow .rz-datatable-tablewrapper > table,
  .rz-datatable-reflow .rz-datatable-scrollable-header-box > table,
  .rz-datatable-reflow .rz-datatable-scrollable-table-wrapper > table { table-layout: auto; display: block; }

  .rz-datatable-reflow .rz-data-grid-data > table > tbody,
  .rz-datatable-reflow .rz-datatable-tablewrapper > table > tbody,
  .rz-datatable-reflow .rz-datatable-scrollable-header-box > table > tbody,
  .rz-datatable-reflow .rz-datatable-scrollable-table-wrapper > table > tbody { display: block; }

  .rz-datatable-reflow thead th { display: none; }
  .rz-datatable-reflow .rz-data-row { display: block; }
  .rz-datatable-reflow .rz-data-row > td { display: block; width: 100% !important; text-align: left !important; border: none; }
  .rz-datatable-reflow .rz-data-row > td .rz-column-title { display: block; }
}

/* (b) RM card dressing for the reflowed rows. */
@media (max-width: 820px) {
  .rz-datatable-reflow .rz-data-row {
    border: 1px solid var(--rm-line); border-radius: var(--rm-r-md);
    background: var(--rm-surface); margin-bottom: 10px; padding: 10px 12px;
    box-shadow: var(--rm-shadow);
  }
  /* Radzen stripes alternating rows on the CELLS, not the row:
       .rz-grid-table-striped tbody > tr:not(.rz-expanded-row-content):nth-child(odd) > td
     As cards that reads as a grey slab inset from the card's own padding, so the stripe has
     to be cleared at the level it is applied. That selector scores (0,3,3) — two classes plus
     :not() and :nth-child() — so the override has to carry at least as much weight or it
     silently loses; the :not()/:nth-child() pair here is what buys it, not decoration. */
  .rz-datatable-reflow .rz-grid-table-striped tbody > tr:not(.rz-expanded-row-content):nth-child(n) > td {
    background-color: transparent;
  }

  .rz-datatable-reflow .rz-data-row > td { padding: 4px 0 !important; display: flex; gap: 10px; align-items: baseline; }

  /* Label left, value right — keeps each line scannable at a glance. */
  .rz-datatable-reflow .rz-data-row > td .rz-column-title {
    flex: 0 0 40%; font-size: .68rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .04em; color: var(--rm-ink-3);
  }

  /* Let the value wrap instead of being cut off with an ellipsis.
     Radzen styles table cells `white-space:nowrap; overflow:hidden; text-overflow:ellipsis`,
     which is right for a real table — fixed columns, one line per row. The reflow card
     inherits it, and there the same rule truncates values that had room to wrap: measured at
     384px, "House 47, Road 17, Dhanmondi" needed 256px in a 137px box (/customers) and
     "DHAKA METRO-CHA-15-2091" lost 105px (/legalcases). A registration or an address that
     ends in "…" is not a smaller version of the data, it is the wrong data.
     `white-space` is the load-bearing property here: min-width/overflow-wrap alone do nothing
     while nowrap forbids the break. */
  .rz-datatable-reflow .rz-data-row > td,
  .rz-datatable-reflow .rz-data-row > td .rz-cell-data {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  .rz-datatable-reflow .rz-data-row > td > * { min-width: 0; }
  .rz-datatable-reflow .rz-data-row > td .rz-cell-data {
    flex: 1 1 auto;
    overflow-wrap: anywhere;
  }

  /* A horizontal RadzenStack inside a reflow cell (the actions row is the big one: up to five
     44px buttons plus gaps ≈ 260px in a ~150px value box) must fold onto extra lines. Radzen
     stacks are nowrap flex, so without this the buttons punch through the card's right border
     and drag a horizontal scrollbar across the whole grid — measured on a 384px handset
     (/bookings, Confirmed rows). Wrap is a no-op for vertical stacks.
     !important because RadzenStack writes flex-wrap as an INLINE style (Wrap=NoWrap default),
     which no selector weight can out-score. */
  .rz-datatable-reflow .rz-data-row > td .rz-cell-data .rz-stack {
    flex-wrap: wrap !important;
    row-gap: 8px;
  }

  /* An empty cell would otherwise render as a stray label with nothing beside it. */
  .rz-datatable-reflow .rz-data-row > td:empty { display: none; }

  /* The actions column is buttons, not a labelled value — let it span the card. */
  .rz-datatable-reflow .rz-data-row > td:last-child { padding-top: 10px !important; }

  /* Pager needs room above the fixed bottom nav. */
  .rz-datatable-reflow .rz-pager { padding-bottom: 4px; flex-wrap: wrap; }
}

/* ============================================================
   5b. Touch targets
   Measured on a 384px viewport: the app-bar controls came out
   33px tall (the theme toggle 28x33) and every list page had
   24-44 controls under 40px — mostly the per-row edit/delete
   buttons, which sit side by side and are the easiest thing in
   the app to mis-tap. 44px is the floor both Apple and Google
   publish, and it is about the width of an adult fingertip.

   min-height/min-width rather than height: Radzen sizes these
   from its own scale, and forcing a fixed height would clip the
   label on the buttons that carry text as well as an icon.
   ============================================================ */
@media (max-width: 820px) {
  .rz-header .rz-button,
  .rz-header button { min-height: 44px; }

  /* Row actions in a reflowed card — edit and delete, a thumb-width apart. */
  .rz-datatable-reflow .rz-data-row .rz-button { min-height: 44px; min-width: 44px; }

  /* The pager arrows are the other sub-40px control people actually aim at. */
  .rz-paginator .rz-paginator-element { min-width: 44px; min-height: 44px; }
}

/* ============================================================================
   Auth / onboarding shell
   ----------------------------------------------------------------------------
   Sign in, register, verify, recover and accept-invite all render the same
   shape: one centred card on the page background, no app chrome around it
   (MainLayout withholds the sidebar until a session exists). Defined once here
   so the six pages compose it instead of each carrying its own inline flexbox.

   Everything below resolves through --rm-* tokens, so the dark block further up
   this file inverts these screens with no extra rules.
   ========================================================================== */
/* Two panels: the board slab (identity) and the console (the form). The slab is the whole
   reason this screen reads as RentManager and not as any other SaaS sign-in — it is the product's
   signature screen, the dispatch board, drawn at rest. */
.rm-auth-shell {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
  gap: 28px;
  align-items: stretch;
  min-height: min(76vh, 680px);
  padding: 8px 0 28px;
}

/* ---- the slab ---- */
.rm-auth-slab {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Not space-between: with the board at the top and the copy at the bottom, a tall viewport
     opened a dead void down the middle of the panel. The board now grows to fill it instead. */
  justify-content: flex-start;
  gap: 28px;
  padding: 38px 36px 32px;
  border-radius: var(--rm-r-lg);
  color: var(--rm-slab-ink);
  background:
    radial-gradient(115% 80% at 6% 0%, var(--rm-slab-2) 0%, transparent 58%),
    linear-gradient(158deg, var(--rm-slab) 0%, var(--rm-slab-deep) 100%);
  animation: rm-slab-enter .55s cubic-bezier(.2, .8, .2, 1) both;
}

/* Engineering-drawing grid — the texture is the concept, so it is drawn rather than imaged:
   two repeating gradients cost nothing, need no asset, and survive an offline MAUI WebView. */
.rm-auth-slab::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(90deg, var(--rm-slab-line) 0 1px, transparent 1px 68px),
    repeating-linear-gradient(0deg, var(--rm-slab-line) 0 1px, transparent 1px 52px);
  opacity: .32;
  pointer-events: none;
}

/* Grows into whatever height the card leaves, so the rows spread down the panel rather than
   huddling at the top over an empty middle. */
.rm-slab-board { position: relative; display: flex; flex-direction: column; justify-content: space-evenly; gap: 9px; flex: 1 1 auto; min-height: 0; }

.rm-slab-row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  align-items: center;
  gap: 14px;
}

.rm-slab-plate {
  font-family: var(--rm-font-num);
  font-size: .625rem;
  letter-spacing: .05em;
  color: var(--rm-slab-ink-2);
  white-space: nowrap;
}

.rm-slab-track {
  position: relative;
  height: 15px;
  border-radius: 4px;
  background: var(--rm-slab-track);
}

/* Trip bars carry the real status vocabulary rather than invented decoration, so someone who
   uses the app recognises their own board here. They hold no text, so the light-ground AA
   tuning on those tokens is not in play. */
.rm-slab-bar {
  position: absolute;
  top: 2px;
  bottom: 2px;
  border-radius: 3px;
  transform-origin: left center;
  animation: rm-slab-bar-in .5s cubic-bezier(.2, .8, .2, 1) backwards;
}

/* Light-theme status inks are tuned for AA on light grounds and go muddy on the slab, so these
   are the same hues lifted for a dark field. Decorative only — they carry no text. */
.rm-slab-bar.is-confirmed { background: #6AA0E6; }
.rm-slab-bar.is-active    { background: #E8A93A; }
.rm-slab-bar.is-completed { background: #45BE8E; }
/* Barely there. These read as "nothing booked", and at --rm-slab-line they were as loud as a
   real trip — six long grey bars made the fleet look busy and uniform at the same time. */
.rm-slab-bar.is-idle      { background: var(--rm-slab-line); opacity: .45; }

/* The "now" marker. Coral, spent on exactly one thing — the same rule the palette applies to
   the primary CTA. It pulses rather than sweeps: opacity is compositor-only, where animating
   `left` across the panel would relayout every frame on a driver's phone. */
.rm-slab-now {
  position: absolute;
  top: -8px;
  bottom: -8px;
  left: 61%;
  width: 2px;
  background: var(--rm-accent-bright);
  animation: rm-slab-now 3.6s ease-in-out infinite;
}

.rm-slab-now::before {
  content: "";
  position: absolute;
  top: -3px;
  left: -3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--rm-accent-bright);
}

.rm-slab-copy { position: relative; display: flex; flex-direction: column; gap: 14px; }

.rm-slab-eyebrow {
  margin: 0;
  font-family: var(--rm-font-num);
  font-size: .625rem;
  text-transform: uppercase;
  letter-spacing: .3em;
  color: var(--rm-slab-accent);
}

/* The display face is a DIN/road-signage condensed grotesk — the language of number plates and
   traffic signs, set uppercase and tight so the wordmark reads as signage rather than a logo. */
.rm-slab-word {
  margin: 0;
  font-family: var(--rm-font-display);
  font-size: clamp(2.5rem, 4.4vw, 3.75rem);
  font-weight: 700;
  line-height: .92;
  letter-spacing: .012em;
  text-transform: uppercase;
}

.rm-slab-word span { color: var(--rm-slab-accent); }

.rm-slab-tag {
  margin: 0;
  max-width: 32ch;
  font-size: .9375rem;
  line-height: 1.55;
  color: var(--rm-slab-ink-2);
}

/* The trip lifecycle, the app's signature sequence, as a footer rule. */
.rm-slab-rail { display: flex; align-items: center; flex-wrap: wrap; margin-top: 4px; }

.rm-slab-step {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--rm-font-num);
  font-size: .5625rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--rm-slab-ink-2);
}

.rm-slab-step i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--rm-slab-line);
}

.rm-slab-step.on { color: var(--rm-slab-ink); }
.rm-slab-step.on i { background: var(--rm-slab-accent); }

.rm-slab-step + .rm-slab-step::before {
  content: "";
  width: 22px;
  height: 1px;
  margin: 0 9px;
  background: var(--rm-slab-line);
}

/* ---- the console ---- */
.rm-auth-console {
  display: flex;
  align-items: center;
  justify-content: center;
  animation: rm-auth-console-enter .5s cubic-bezier(.2, .8, .2, 1) .07s both;
}

.rm-auth-card {
  width: 100%;
  /* Wider than the old 360px sign-in card: registration carries four fields
     plus social buttons, and 360px forced an awkward two-line button label. */
  max-width: 420px;
}

.rm-auth-head {
  margin-bottom: 4px;
}

.rm-auth-eyebrow {
  margin: 0 0 10px;
  font-family: var(--rm-font-num);
  font-size: .625rem;
  text-transform: uppercase;
  letter-spacing: .26em;
  color: var(--rm-primary);
}

/* Same signage face as the slab wordmark, so the two panels read as one object. */
.rm-auth-title {
  font-family: var(--rm-font-display);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--rm-ink);
  margin: 0 0 6px;
}

.rm-auth-sub {
  font-size: .875rem;
  line-height: 1.5;
  color: var(--rm-ink-2);
  margin: 0;
}

/* Instrument-panel field: a tracked-out micro label that lights up with its input. */
.rm-auth-field { display: flex; flex-direction: column; gap: 6px; }

.rm-auth-field > span {
  font-family: var(--rm-font-num);
  font-size: .625rem;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--rm-ink-3);
  transition: color .15s ease;
}

.rm-auth-field:focus-within > span { color: var(--rm-primary); }

/* Coral, because the palette reserves it for the one action that matters on a screen — and on
   this screen there is exactly one.
   The doubled class is not a typo: Radzen ships .rz-button.rz-primary (0,2,0), which outranks a
   single class, so the CTA rendered in Radzen's own washed-out primary instead of coral. Doubling
   ours to (0,3,0) wins without !important, which would have to be undone by the next override. */
.rm-auth-cta.rm-auth-cta.rz-button {
  background: var(--rm-accent);
  border-color: var(--rm-accent);
  color: var(--rm-accent-ink);
}

.rm-auth-cta.rm-auth-cta.rz-button:hover:not(:disabled),
.rm-auth-cta.rm-auth-cta.rz-button:focus-visible:not(:disabled) {
  background: var(--rm-accent-deep);
  border-color: var(--rm-accent-deep);
}

.rm-auth-aside {
  display: flex;
  justify-content: flex-end;
}

.rm-auth-aside a {
  font-size: .8125rem;
  color: var(--rm-primary);
  text-decoration: none;
}

.rm-auth-aside a:hover { text-decoration: underline; }

/* One breakpoint, generous on purpose: the slab is decoration, and a driver on a phone gets the
   form at full width with nothing competing for it.

   This block MUST stay below .rm-auth-slab's own rules. A media query adds no specificity, so
   when it sat above them `display: flex` simply won on source order and the slab stayed visible
   at every width — it only looked right because nobody had opened the page narrow. */
@media (max-width: 1080px) {
  .rm-auth-shell {
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
    padding: 24px 8px;
  }
  .rm-auth-slab { display: none; }
}

@keyframes rm-slab-enter {
  from { opacity: 0; transform: translateX(-14px); }
  to   { opacity: 1; transform: none; }
}

@keyframes rm-auth-console-enter {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes rm-slab-bar-in {
  from { transform: scaleX(0); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}

@keyframes rm-slab-now {
  0%, 100% { opacity: .9; }
  50%      { opacity: .35; }
}

/* "or" rule between the password form and the social buttons. */
.rm-auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--rm-ink-3);
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .06em;
}

.rm-auth-divider::before,
.rm-auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rm-line);
}

/* Provider buttons are deliberately neutral-surface rather than brand-coloured:
   Google's and Facebook's brand guidelines both forbid recolouring their mark,
   and a green-filled "Sign in with Google" reads as a phishing page. */
.rm-auth-provider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 10px 16px;
  font-size: .9375rem;
  font-weight: 500;
  color: var(--rm-ink);
  background: var(--rm-surface);
  border: 1px solid var(--rm-line-strong);
  border-radius: 8px;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}

.rm-auth-provider:hover:not(:disabled) {
  background: var(--rm-ground);
  border-color: var(--rm-ink-3);
}

.rm-auth-provider:disabled {
  opacity: .6;
  cursor: not-allowed;
}

.rm-auth-provider svg {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
}

.rm-auth-foot {
  text-align: center;
  font-size: .875rem;
  color: var(--rm-ink-2);
}

.rm-auth-foot a {
  color: var(--rm-primary);
  text-decoration: none;
  font-weight: 500;
}

.rm-auth-foot a:hover {
  text-decoration: underline;
}

/* Password strength meter on register / reset. */
.rm-auth-meter {
  height: 4px;
  border-radius: 2px;
  background: var(--rm-line);
  overflow: hidden;
}

.rm-auth-meter > span {
  display: block;
  height: 100%;
  border-radius: 2px;
  transition: width .2s ease, background .2s ease;
}

/* Hidden by default; the phone breakpoint turns it on. Declared here beside .rm-build so the
   two are read together and nobody wonders where the desktop rule went. */
.rm-phone-build { display: none; }

/* The build stamp in the app footer. */
.rm-build {
  font-family: var(--rm-font-num);
  font-size: .6875rem;
  letter-spacing: .02em;
  color: var(--rm-ink-3);
  white-space: nowrap;
}

/* Its home on a phone, where the footer is hidden: the drawer foot, under the logout button.
   Centred and quiet — a reference you go looking for, not something competing with the nav. */
.rm-drawer-build {
  display: block;
  margin-top: 10px;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .rm-auth-provider,
  .rm-auth-field > span,
  .rm-auth-meter > span { transition: none; }

  /* `both`/`backwards` fills mean these elements are mid-keyframe at rest — killing the
     animation without also clearing the fill would leave the slab at opacity 0. */
  .rm-auth-slab,
  .rm-auth-console,
  .rm-slab-bar,
  .rm-slab-now { animation: none; opacity: 1; transform: none; }
}

/* ============================================================================
   Platform console
   ----------------------------------------------------------------------------
   The super-admin screens look like the tenant app because they are the same
   app, which is precisely the hazard: an operator acting on Dhaka Rent-A-Car's
   users should never be able to think they are inside their own tenant. The
   accent below marks every platform page with a persistent strip, so "whose
   data am I looking at?" is answered without reading the URL.

   Indigo rather than a shade of the bottle green: it must NOT read as a variant
   of the product's own brand colour, or it stops being a warning.
   ========================================================================== */
:root {
  --rm-platform-accent: #4338CA;
  --rm-platform-soft: #EEF0FB;
  --rm-platform-ink: #FFFFFF;
}

:root[data-theme="dark"] {
  --rm-platform-accent: #818CF8;
  --rm-platform-soft: #1E1B39;
  --rm-platform-ink: #14211B;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --rm-platform-accent: #818CF8;
    --rm-platform-soft: #1E1B39;
    --rm-platform-ink: #14211B;
  }
}

.rm-platform-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  margin-bottom: 16px;
  border-radius: 8px;
  background: var(--rm-platform-accent);
  color: var(--rm-platform-ink);
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .02em;
}

.rm-platform-bar .rm-platform-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 8px;
}

/* Tenant name in a cross-tenant list — the column an operator must not misread. */
.rm-platform-tenant {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--rm-platform-soft);
  color: var(--rm-platform-accent);
  font-size: .75rem;
  font-weight: 600;
}

/* ==========================================================================
   Trip economics — P&L card, driver dues ledger, profit report.
   Everything below is built from existing --rm-* tokens, so it inverts with
   the dark block above for free. No literal colours.
   ========================================================================== */

/* Generic surface used by the P&L card and the dues page's empty/info states.
   Matches the card treatment Radzen gives RadzenCard so the two can sit side by
   side without one looking pasted on. */
.rm-panel {
  background: var(--rm-surface);
  border: 1px solid var(--rm-line);
  border-radius: var(--rm-r-md);
  box-shadow: var(--rm-shadow);
  padding: 1rem 1.25rem;
}

.rm-muted { color: var(--rm-ink-3); font-size: .85rem; }

/* Auto-fitting tile row. Used where the KPI count varies (the P&L card shows
   four, the dues page three), so an explicit grid column count would break one
   of them. */
.rm-kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .75rem;
  margin: .75rem 0 1rem;
}

.rm-table { width: 100%; border-collapse: collapse; }
.rm-table td { padding: .45rem .25rem; border-bottom: 1px solid var(--rm-line); font-size: .88rem; }
.rm-table td:last-child { text-align: right; }
.rm-table tr:last-child td { border-bottom: none; }

/* Money that is negative (a loss, or a payout leaving the ledger) and money the
   owner still owes. Colour is carried by the status tokens rather than a new
   pair, so a loss reads the same red as a cancelled pill throughout the app. */
.rm-neg  { color: var(--rm-cancelled); }
.rm-owed { color: var(--rm-active); }

.rm-error {
  color: var(--rm-cancelled);
  background: var(--rm-cancelled-bg);
  border-radius: var(--rm-r-sm);
  padding: .5rem .75rem;
  font-size: .85rem;
  margin: 0;
}

.rm-pnl-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
}
.rm-pnl-head h3 { margin: 0; font-size: 1rem; font-weight: 700; color: var(--rm-ink); }
.rm-pnl-head .rm-bn { font-weight: 500; color: var(--rm-ink-3); margin-left: .4rem; font-size: .85rem; }

.rm-pnl-note { margin: .25rem 0 0; font-style: italic; }

/* The reimbursement line is part of the costs above it, not a further cost, so
   it is set apart rather than reading as another subtraction. */
.rm-pnl-breakdown .rm-pnl-reimburse td {
  border-top: 1px solid var(--rm-line-strong);
  color: var(--rm-active);
  font-weight: 600;
}

.rm-pnl-actions {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-top: 1rem;
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   Stored images (RmStoredImage) — driver photos and vehicle gallery.
   The component publishes its Size parameter as --rm-img-size and this rule turns
   that into the box, so everything shape-related lives here: a photo and its empty
   placeholder occupy exactly the same square and nothing reflows when the bytes
   arrive. Because the size is a custom property rather than an inline width, a
   media query or a container rule can retune it (see .rm-idcard at phone width)
   without !important.
   --------------------------------------------------------------------------- */
.rm-stored-img {
  /* Two tiers, deliberately. --rm-img-size is what the PAGE asked for and arrives inline from
     the component, so no selector can outrank it. --rm-img-size-override is what a LAYOUT
     imposes (a breakpoint, a container); it comes first in the fallback chain, so an ordinary
     class rule wins over the inline request without !important and without the component
     needing to know a viewport exists. Set the override on the image itself — a custom
     property inherits, so putting it on an ancestor would resize every nested thumbnail too. */
  width: var(--rm-img-size-override, var(--rm-img-size, 40px));
  height: var(--rm-img-size-override, var(--rm-img-size, 40px));
  object-fit: cover;
  border-radius: var(--rm-r-sm);
  border: 1px solid var(--rm-line);
  background: var(--rm-surface-2, var(--rm-surface));
  flex: 0 0 auto;
  display: block;
}

.rm-stored-img-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--rz-text-disabled-color);
}

/* Driver photos read as people, not as thumbnails. */
.rm-avatar { border-radius: 50%; }

.rm-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}

.rm-gallery-item {
  border: 1px solid var(--rm-line);
  border-radius: var(--rm-r-md);
  overflow: hidden;
  background: var(--rm-surface);
  display: flex;
  flex-direction: column;
}

/* The primary photo is what every list screen shows, so it is marked here too —
   otherwise "which one is the cover?" is only answerable by leaving the page. */
.rm-gallery-item.is-primary {
  border-color: var(--rm-accent);
  box-shadow: 0 0 0 1px var(--rm-accent);
}

.rm-gallery-item .rm-stored-img {
  width: 100%;
  height: 140px;
  border: 0;
  border-radius: 0;
}

.rm-gallery-meta {
  padding: .5rem .625rem;
  display: flex;
  flex-direction: column;
  gap: .375rem;
  font-size: .8125rem;
}

.rm-gallery-actions {
  display: flex;
  gap: .25rem;
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   Dispatch-board trip hover card (.rm-tip inside .rm-blk.busy).

   Pure CSS on purpose. An @onmouseover handler in Blazor Server is a server
   round-trip per hover, so scrubbing the mouse across a week of trips would
   queue dozens of them and the card would lag behind the cursor. The content
   is already rendered with the row, so showing it costs nothing.
   --------------------------------------------------------------------------- */
.rm-tip {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 40;
  width: max-content;
  max-width: 300px;
  padding: 10px 12px;
  background: var(--rm-surface);
  color: var(--rm-ink);
  border: 1px solid var(--rm-line-strong);
  border-radius: var(--rm-r-md);
  box-shadow: 0 10px 28px rgb(0 0 0 / 18%);
  font-size: .72rem;
  font-weight: 500;
  white-space: normal;
  line-height: 1.45;
  text-align: left;
  cursor: default;
  /* Never intercept the pointer: the card sits over neighbouring blocks, and a
     hoverable tooltip would block clicks on the trip behind it. */
  pointer-events: none;
}

.rm-blk.busy:hover .rm-tip,
.rm-blk.busy:focus-visible .rm-tip { display: block; }

/* Blocks in the right-hand third anchor their card to the right edge, or a late-week
   trip's card runs off the board. Set by the page (.tip-flip) rather than by a
   positional selector: busy blocks and free-slot buttons are siblings and the free ones
   render last, so nth-last-child would almost never match a busy block. */
.rm-blk.busy.tip-flip .rm-tip { left: auto; right: 0; }

.rm-tip-h {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-bottom: 6px; padding-bottom: 6px; border-bottom: 1px solid var(--rm-line);
}
.rm-tip-n { font-family: var(--rm-font-num); font-weight: 700; font-size: .78rem; color: var(--rm-primary); }
.rm-tip-s {
  font-size: .62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 7px; border-radius: 999px; background: var(--rm-ground); color: var(--rm-ink-2);
}
.rm-tip-s.rm-active    { background: var(--rm-active);    color: #fff; }
.rm-tip-s.rm-completed { background: var(--rm-completed); color: #fff; }
.rm-tip-s.rm-arrived   { background: var(--rm-arrived);   color: #fff; }
.rm-tip-s.rm-cancelled { background: var(--rm-cancelled); color: #fff; }
.rm-tip-s.rm-confirmed { background: var(--rm-confirmed); color: #fff; }

.rm-tip-r { display: grid; grid-template-columns: 62px 1fr; gap: 8px; margin-top: 3px; }
.rm-tip-r .k { color: var(--rm-ink-3); font-size: .66rem; text-transform: uppercase; letter-spacing: .03em; }
.rm-tip-r .v { color: var(--rm-ink); overflow-wrap: anywhere; }

.rm-tip-f {
  display: block; margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--rm-line);
  color: var(--rm-ink-3); font-size: .64rem; font-style: italic;
}

/* Touch devices have no hover: the card would need a tap that also fires the
   click, so it flashes and vanishes. Suppress it and let the tap open the
   detail panel, which is the better surface on a small screen anyway. */
@media (hover: none) {
  .rm-tip { display: none !important; }

  /* Free slots are deliberately silent at rest on a desktop — they only resolve into a target
     under the pointer, so a week of empty rows does not drown the trips (see .rm-blk.free). A
     touch device never produces that pointer, so the slot had NO rest state at all and the page's
     own instruction — "click any open (dashed) slot to book it" — pointed at nothing visible.
     The dashed rule comes back here, and only here; the "+ Book" label stays off, because at a
     28px-per-day track it only ever rendered as a clipped fragment of itself. */
  .rm-blk.free { border: 1px dashed var(--rm-line-strong); }
  .rm-blk-add { display: none; }
}

/* ---------------------------------------------------------------------------
   WhatsApp share (RmWhatsAppButton).

   Sized to sit in a row of RadzenButtons without looking like a foreign object:
   same height, radius and font weight as ButtonSize.Medium. It is an <a>, so
   every button reset has to be stated explicitly (text-decoration, line-height).
   --------------------------------------------------------------------------- */
.rm-wa {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  padding: .5rem .9rem;
  min-height: 2.25rem;
  border-radius: var(--rm-r-sm);
  border: 1px solid transparent;
  background: var(--rm-wa-soft);
  color: var(--rm-wa-ink);
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}

.rm-wa:hover { background: var(--rm-wa-hover); border-color: var(--rm-wa); text-decoration: none; }
.rm-wa:focus-visible { outline: 2px solid var(--rm-wa); outline-offset: 2px; }

/* Full-width variant, for stacking under full-width Radzen buttons in a side panel. */
.rm-wa-block { display: flex; width: 100%; }

/* The glyph carries the brand colour; the label does not (see the token comment). */
.rm-wa-i { width: 1.05rem; height: 1.05rem; flex: 0 0 auto; fill: var(--rm-wa); }

/* ---- RmPhone — a number you can dial or copy ----------------------------------------------
   Tabular figures, because these sit in grid columns where a ragged left edge on a stack of
   numbers is harder to scan than the numbers themselves are to read. The pair wraps as a unit
   so the copy icon is never orphaned onto its own line in a reflowed card. */
.rm-phone {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  /* When the box is too tight for number + copy icon, the ICON moves to the next line.
     The number itself never breaks — see .rm-phone-num. */
  flex-wrap: wrap;
}

.rm-phone-num {
  font-family: var(--rm-font-num);
  color: var(--rm-primary);
  text-decoration: none;
  /* Never truncate a phone number — half a number is not a number. */
  /* nowrap, deliberately: a reflow card squeezed this to ~90px on a 384px handset and
     overflow-wrap:anywhere split "+880-1710000005" across three lines mid-digit — a number
     nobody can read back. Digits stay on one line; if the box is genuinely too tight the
     copy icon wraps instead (flex-wrap on .rm-phone above). */
  white-space: nowrap;
}
.rm-phone-num:hover { text-decoration: underline; }
.rm-phone-num:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 2px; border-radius: 3px; }

/* Not a BD mobile, so not a link: it must not look like one either. */
.rm-phone-flat { color: var(--rm-ink); }

.rm-phone-copy {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: 26px; height: 26px;
  padding: 0; border: none; border-radius: var(--rm-r-sm, 6px);
  background: none; color: var(--rm-ink-3);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rm-phone-copy:hover { background: var(--rm-primary-soft); color: var(--rm-primary); }
.rm-phone-copy:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; }
.rm-phone-copy .rzi { font-size: 1rem; }

@media (max-width: 820px) {
  /* Thumb-sized targets. The icon box grows rather than the icon, so the row height is
     unchanged in a reflowed grid card — the tap area extends past the visible glyph. */
  .rm-phone-copy { width: 44px; height: 44px; margin: -9px 0; }
  .rm-phone-copy .rzi { font-size: 1.15rem; }
}

/* ---- RmSearchBar — finding one row in a list -----------------------------------------------
   Sits above the grid, not inside it, because the mobile reflow hides <thead> and takes
   Radzen's column filters with it (DESIGN_SYSTEM.md §5a). Full width: this is the primary
   action on a list page once the list is longer than a screen. */
.rm-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  border: 1px solid var(--rm-line);
  border-radius: var(--rm-r-md, 8px);
  background: var(--rm-surface);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.rm-search:focus-within {
  border-color: var(--rm-primary);
  box-shadow: 0 0 0 3px var(--rm-primary-soft);
}

.rm-search-icon { color: var(--rm-ink-3); flex: 0 0 auto; font-size: 1.1rem; }

.rm-search-input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  outline: none;
  background: none;
  color: var(--rm-ink);
  font: inherit;
  /* 16px keeps iOS Safari from zooming the viewport on focus; the MAUI WebView does not zoom,
     but the same stylesheet serves the website. */
  font-size: 16px;
  padding: 11px 0;
}
.rm-search-input::placeholder { color: var(--rm-ink-3); }
/* The browser's own clear affordance duplicates rm-search-clear and is too small to hit. */
.rm-search-input::-webkit-search-cancel-button { display: none; }

.rm-search-clear {
  display: grid; place-items: center;
  flex: 0 0 auto;
  width: 32px; height: 32px;
  padding: 0; border: none; border-radius: 999px;
  background: none; color: var(--rm-ink-3);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rm-search-clear:hover { background: var(--rm-surface-2); color: var(--rm-ink); }
.rm-search-clear:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; }

@media (max-width: 820px) {
  .rm-search-clear { width: 44px; height: 44px; }
}

/* ---- Vehicle risk notice (RmVehicleRiskNotice) --------------------------
   Expired papers and unresolved legal cases on the vehicle being booked.
   Uses the cancelled token rather than a new red: an expired paper is the
   same class of "this is wrong" the status vocabulary already names. */
.rm-risk {
  border: 1px solid var(--rm-line);
  border-left: 4px solid var(--rm-cancelled);
  border-radius: var(--rm-r-md);
  background: var(--rm-cancelled-bg);
  padding: .85rem 1rem;
}
.rm-risk h4 { margin: 0 0 .5rem; font-size: .95rem; color: var(--rm-cancelled); }
.rm-risk .rm-risk-grp { margin-bottom: .6rem; }
.rm-risk .rm-risk-grp:last-of-type { margin-bottom: 0; }
.rm-risk .rm-risk-lbl {
  display: block; font-size: .7rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--rm-ink-3); margin-bottom: .15rem;
}
.rm-risk ul { margin: 0; padding-left: 1.1rem; }
.rm-risk li { font-size: .85rem; color: var(--rm-ink-2); margin-bottom: .15rem; }
.rm-risk .rm-risk-ack {
  margin-top: .75rem; padding-top: .6rem; border-top: 1px solid var(--rm-line);
}
.rm-risk.compact { padding: .6rem .7rem; }
.rm-risk.compact h4 { font-size: .85rem; }
.rm-risk.compact li { font-size: .78rem; }

/* ---- Audit diff (RmAuditDiff) -------------------------------------------
   Field-level before/after inside an expanded audit row. Deliberately
   low-chrome: it sits *inside* a grid row, so a second bordered card there
   would read as a nested table nobody can scan. Tokens only, so it inverts
   with the rest of the app. Scrolls in its own box because an audit row can
   name a dozen columns and the page body must never scroll sideways. */
.rm-audit-diff { width: 100%; border-collapse: collapse; font-size: .8rem; }
.rm-audit-diff th {
  text-align: left; padding: .4rem .6rem;
  color: var(--rm-ink-2); font-weight: 600;
  border-bottom: 1px solid var(--rm-line);
}
.rm-audit-diff td {
  padding: .4rem .6rem;
  border-bottom: 1px solid var(--rm-line);
  color: var(--rm-ink); word-break: break-word;
  vertical-align: top;
}

/* ==========================================================================
   Owner dashboard ("/") — one vertical narrative: how much of the fleet is
   out, what came in, who owes, what is about to cost. Replaced a page of
   six lifetime table counts, one of which was outright wrong ("Active trips"
   counted every non-Completed/Cancelled booking, so a 35-vehicle tenant read
   71 trips on the road).
   ========================================================================== */

/* Column width on wide viewports. This page is one narrative column, not a grid of
   panels — full desktop width would spread "5 of 12 cars are out" edge to edge for
   no reason a wider line helps read faster. Phone widths are unaffected: 640px never
   binds below that, so the mobile layout is exactly the brief's single-column stack. */
.rm-dash { max-width: 640px; margin: 0 auto; }

/* Dashboard cards are whole tap targets — an owner taps the block, not a link inside it. */
.rm-dash-card { cursor: pointer; }
.rm-dash-card:hover { border-color: var(--rm-primary); }

/* ---- Debtor row (Dashboard.razor "Owed to you") -----------------------------
   A RadzenStack SpaceBetween row with no width discipline let a long customer name wrap onto
   the amount/age line and, independently, let the amount itself break between the taka figure
   and "· 98 d" — the separator dot ending up alone on its own line. Neither half may wrap into
   the other at 390px: the name gives way (ellipsis) because a debtor's amount and lateness
   matter more than seeing their full name on a landing screen. */
.rm-debtor-row { flex-wrap: nowrap; }  /* pinned defensively — Radzen's own stack default, made explicit */
.rm-debtor-name {
  min-width: 0;              /* lets a flex/stack item shrink below its text's natural width */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rm-debtor-amount { flex: 0 0 auto; white-space: nowrap; }
.rm-debtor-age { color: var(--rm-cancelled); }

/* ---- Tap targets (see Components/RmTapTarget.razor) ------------------------
   .rm-tap is the wrapper an RmTapTarget puts around a whole block. It adds no box of its
   own — the RadzenCard inside still draws the surface — only the cursor and, crucially, a
   visible focus ring, because the element is now in the tab order and a keyboard user has
   to be able to see where they are. Reuses --rm-primary and --rm-r-lg; no new token. */
.rm-tap { cursor: pointer; border-radius: var(--rm-r-lg); }
.rm-tap:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 2px; }

/* One row inside a card, with its own destination — an expiring paper goes to that vehicle's
   documents, a garage job to /maintenance, a case to /legalcases. Padded and hover-lit so it
   reads as its own target rather than as text that happens to move. */
.rm-tap-row {
  display: flex; align-items: flex-start; gap: .625rem;
  padding: .375rem .5rem; margin: 0 -.5rem;
  border-radius: var(--rm-r-sm);
}
.rm-tap-row:hover { background: var(--rm-surface-2); }
.rm-tap-row .rm-tap-icon { font-size: 1.1rem; line-height: 1.35; color: var(--rm-ink-3); }

/* Loading skeletons, sized to the blocks they stand in for so the page does not jump when
   the summary lands. */
.rm-skel {
    /* Two stops only: --rm-surface-2 and a translucent white lift. There is no --rm-surface-3
       token, and a skeleton is not a reason to add one to the palette. */
    background: linear-gradient(90deg,
        var(--rm-surface-2) 25%, rgba(255, 255, 255, .06) 37%, var(--rm-surface-2) 63%);
    background-size: 400% 100%;
    animation: rm-skel-shimmer 1.4s ease-in-out infinite;
    border-radius: .5rem;
}
/* Taller than a single stat bar: the real headline slab carries a date line, the "N of M
   cars are out" line and its Bangla twin, and measures well over 4.5rem on a phone. */
.rm-skel-head { height: 9rem; }
.rm-skel-bar  { height: 3.5rem; }
.rm-skel-tile { height: 5.5rem; }
.rm-skel-list { height: 8rem; }
@keyframes rm-skel-shimmer {
    0%   { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}
@media (prefers-reduced-motion: reduce) {
    .rm-skel { animation: none; }
}

/* ---- Headline slab --------------------------------------------------------
   Deliberately its own class rather than a reuse of .rm-auth-slab, even though the
   background is identical: .rm-auth-slab is `display: none` below 1080px (see the auth
   shell's own breakpoint block above) because that slab is decoration a driver's phone
   never needs. This one IS the content of the one screen a phone opens every morning, so
   it must render at every width instead of only on desktop. Ink stays --rm-slab-ink /
   --rm-slab-ink-2 throughout — light-surface ink would fail contrast on this ground. */
.rm-dash-slab {
  position: relative;
  overflow: hidden;
  padding: 1.5rem 1.375rem;
  border-radius: var(--rm-r-lg);
  color: var(--rm-slab-ink);
  background:
    radial-gradient(115% 80% at 6% 0%, var(--rm-slab-2) 0%, transparent 58%),
    linear-gradient(158deg, var(--rm-slab) 0%, var(--rm-slab-deep) 100%);
  animation: rm-slab-enter .55s cubic-bezier(.2, .8, .2, 1) both;
}

/* Same engineering-drawing grid as .rm-auth-slab::before — the texture is the concept
   and costs no asset, so it is drawn rather than imaged here too. */
.rm-dash-slab::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(90deg, var(--rm-slab-line) 0 1px, transparent 1px 68px),
    repeating-linear-gradient(0deg, var(--rm-slab-line) 0 1px, transparent 1px 52px);
  opacity: .32;
  pointer-events: none;
}

.rm-dash-slab-date {
  position: relative;
  margin: 0;
  font-family: var(--rm-font-num);
  font-size: .75rem;
  letter-spacing: .02em;
  color: var(--rm-slab-ink-2);
}

/* Same condensed signage face RmPageHeader sets uppercase for every page title — this
   headline is that page's title, just a full sentence instead of one word. */
.rm-dash-slab-head {
  position: relative;
  margin: .3rem 0 0;
  font-family: var(--rm-font-display);
  font-size: clamp(1.5rem, 6vw, 2.1rem);
  font-weight: 700;
  line-height: 1.16;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--rm-slab-ink);
}

/* The out count is the number the owner is judging the day by, so it gets the same teal
   accent the slab already uses to mark its one emphasised word (.rm-slab-word span). */
.rm-dash-slab-accent { color: var(--rm-slab-accent); }

.rm-dash-slab-bn {
  position: relative;
  margin: .5rem 0 0;
  font-size: .85rem;
  color: var(--rm-slab-ink-2);
}

/* ---- One orchestrated load -------------------------------------------------
   A single staggered reveal beats scattered micro-interactions. The slab keeps its own
   rm-slab-enter (a translateX, matching the auth screen's board panel); everything below
   it fades/lifts in via rm-auth-console-enter, staggered purely by DOM position — so it
   still staggers correctly however many of the conditional blocks (Money, Receivables)
   are actually present for this caller, with no per-block bookkeeping in the markup. */
.rm-dash > *:not(.rm-dash-slab) {
  animation: rm-auth-console-enter .4s cubic-bezier(.2, .8, .2, 1) both;
}
.rm-dash > *:not(.rm-dash-slab):nth-child(2) { animation-delay: .08s; }
.rm-dash > *:not(.rm-dash-slab):nth-child(3) { animation-delay: .16s; }
.rm-dash > *:not(.rm-dash-slab):nth-child(4) { animation-delay: .24s; }
.rm-dash > *:not(.rm-dash-slab):nth-child(5) { animation-delay: .32s; }
.rm-dash > *:not(.rm-dash-slab):nth-child(6) { animation-delay: .4s; }

@media (prefers-reduced-motion: reduce) {
  /* `both` fills mean these elements are mid-keyframe at rest — killing the animation
     without also clearing the fill would leave them stuck at opacity 0, the same reason
     the auth shell's own reduced-motion block (above) clears .rm-auth-slab/.rm-auth-console. */
  .rm-dash-slab,
  .rm-dash > *:not(.rm-dash-slab) { animation: none; opacity: 1; transform: none; }
}
.rm-audit-diff tr:last-child td { border-bottom: 0; }
/* ---- Route cell (BookingList) ------------------------------
   Where the trip goes. The register showed who, what and when
   but never where, so a Dhaka-Sylhet run and an airport drop
   read identically.

   Both legs truncate rather than wrap: at 240px a two-line
   address would push the row taller than the driver and
   schedule cells beside it, and the grid's mobile reflow
   already stacks cells vertically. The full string stays
   reachable through the title attribute. */
.rm-route { max-width: 100%; }
.rm-route-leg {
  display: flex; align-items: center; gap: 6px;
  min-width: 0;                 /* lets the span shrink so ellipsis works inside flex */
  font-size: .82rem; line-height: 1.5;
}
.rm-route-leg span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* Pin colours carry the direction, but they are decoration: the
   two legs are already ordered top-to-bottom, so a colour-blind
   reader loses nothing. */
.rm-route-pin { font-size: .85rem; flex: 0 0 auto; }
.rm-route-pin.from { color: var(--rm-ink-3); }
.rm-route-pin.to { color: var(--rm-primary); }

/* ---- RmRouteEstimate ---------------------------------------
   An indicative distance and price, shown before a trip runs.

   The basis line is deliberately not muted into invisibility:
   it is the difference between a number an operator can quote
   and one they should hedge. The dashed border says the same
   thing structurally — this box is provisional, unlike the
   solid-bordered cards around it that state settled facts. */
.rm-est {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem .75rem;
  padding: .6rem .75rem;
  border: 1px dashed var(--rm-line-strong);
  border-radius: var(--rm-r-md);
  background: var(--rm-surface-2);
}
.rm-est-km { font-size: 1.15rem; font-weight: 700; color: var(--rm-ink); }
.rm-est-price { font-size: 1rem; font-weight: 700; color: var(--rm-primary); }
.rm-est-basis { font-size: .72rem; color: var(--rm-ink-3); }
.rm-est-none { display: block; font-size: .8rem; color: var(--rm-ink-3); }

/* ---- RmAvailabilityStrip ------------------------------------
   A day is a flex row of segments sized by their minutes, so the bar is a true 24-hour
   scale without any width arithmetic in C#. No new tokens: booked/maintenance reuse the
   same status hues RmStatusMap already resolves "booked"/"maintenance" to (confirmed
   blue, cancelled red), and the clickable free slice borrows the dispatch board's own
   free-slot language (.rm-blk.free) — transparent at rest, teal on hover/focus — because
   "open for booking" is not a trip status and has no hue of its own. */
.rm-avail { display: flex; flex-direction: column; gap: .5rem; }

.rm-avail-row {
  display: grid;
  grid-template-columns: 5.5rem 1fr 12rem;
  align-items: center;
  gap: .75rem;
}

.rm-avail-day { display: flex; flex-direction: column; line-height: 1.2; }
.rm-avail-dow { font-weight: 600; font-size: .8rem; }
.rm-avail-date { font-size: .75rem; color: var(--rz-text-secondary-color); }

.rm-avail-bar {
  display: flex;
  height: 1.5rem;
  border-radius: var(--rm-r-sm);
  overflow: hidden;
  background: var(--rm-surface-2);
}

.rm-avail-seg { border: 0; padding: 0; min-width: 2px; }
.rm-avail-free { background: transparent; }

/* Free, but already gone today or too short to sell — faintly hatched so it reads as
   unavailable without competing with the booked blocks for attention. Pattern, not just
   colour, separates it from bookable free time, so the distinction survives greyscale
   and colour-blindness, not only a hue difference. */
.rm-avail-unbookable {
  background: repeating-linear-gradient(
    45deg,
    transparent 0 3px,
    var(--rm-line-strong) 3px 6px
  );
}

.rm-avail-booked { background: var(--rm-confirmed); }
.rm-avail-maintenance { background: var(--rm-cancelled); opacity: .75; }
.rm-avail-dayoff { background: var(--rz-text-disabled-color); opacity: .5; }

/* Bookable free time is a real <button>, not a styled div — keyboard reachable, with an
   accessible name naming the day and time. outline-offset is negative (inset) because
   .rm-avail-bar clips overflow for its rounded corners; a positive offset ring would be
   cut off on the first/last segment of the row. */
.rm-avail-clickable { cursor: pointer; min-width: 22px; }
.rm-avail-clickable:hover { background: var(--rm-primary-soft); }
.rm-avail-clickable:focus-visible {
  outline: 2px solid var(--rm-primary);
  outline-offset: -2px;
  background: var(--rm-primary-soft);
}

.rm-avail-summary { font-size: .8rem; color: var(--rz-text-secondary-color); }

/* Under ~720px the three columns stop fitting; the summary drops beneath the bar. */
@media (max-width: 720px) {
  .rm-avail-row { grid-template-columns: 4.5rem 1fr; }
  .rm-avail-summary { grid-column: 2; }
}

/* ---- RmRelatedEntityLink --------------------------------------
   The cross-link chip between 360 pages: a 32px photo, name, optional caption and status.
   A plain <a>, styled to sit flush with surrounding text until hovered/focused. */
.rm-rel {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .25rem .5rem;
  margin: 0 -.5rem;
  border-radius: var(--rm-r-sm);
  text-decoration: none;
  color: inherit;
  /* Without this the chip cannot shrink below its content and simply runs out of its column: its
     own min-width defaults to auto, so flex-shrink has nothing to work with no matter how narrow
     the parent is. Measured on the vehicle 360 card at 390px, "TRIP-DHK-0069 / Dhaka → Dhaka"
     wanted 249px inside a 186px row and put its status pill outside the viewport. The inner
     .rm-rel-name / .rm-rel-caption already ellipsize, so shrinking degrades gracefully — this is
     the same rule .rm-blk-lbl relies on for trip labels on the dispatch board. */
  min-width: 0;
  max-width: 100%;
}
.rm-rel:hover { background: var(--rm-surface-2); }
/* The chip bleeds half a rem past its column on both sides so the hover fill reaches the card's
   own padding instead of stopping short of it. On a phone the column already ends at the card
   edge, so the right-hand bleed put the chip's status pill 8px outside the viewport (measured on
   the vehicle 360 card at 390px). The left bleed is kept — it is what keeps the chip's photo
   optically aligned with the text above it — and only the right is given up. */
@media (max-width: 820px) {
  .rm-rel { margin-right: 0; }
}
.rm-rel:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 2px; background: var(--rm-surface-2); }
/* A subject the app cannot yet route to (see the component's Href doc comment) — same box, no
   hover lift, no pointer cursor, so it never invites a click it would refuse. */
.rm-rel-static { cursor: default; }
.rm-rel-static:hover { background: none; }
.rm-rel-text { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.rm-rel-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rm-rel-caption { font-size: .75rem; color: var(--rz-text-secondary-color); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- RmTripRail -------------------------------------------------
   A compact trip list shared by all four 360 pages. Named rm-trip-rail* rather than rm-rail* —
   .rm-rail is already RmLifecycleRail's root class (Confirmed -> Arrived -> Active -> Completed);
   reusing it here would have redefined that selector's display/flex rules out from under it. */
.rm-trip-rail { display: flex; flex-direction: column; }

/* border-radius overrides .rm-tap's --rm-r-lg (the card radius) — this is a full-bleed row
   with a straight border-bottom divider, not a card, and the wider corners read oddly against
   the hover fill and the divider. .rm-tap-row already solves this for a row-inside-a-card, but
   also brings display:flex, its own padding and a horizontal bleed margin that don't fit this
   row's 3-column grid, so the radius alone is pulled in rather than swapping the whole class. */
.rm-trip-rail-row {
  display: grid;
  grid-template-columns: 4.5rem 1fr auto;
  align-items: center;
  gap: .75rem;
  padding: .6rem .25rem;
  border-bottom: 1px solid var(--rm-line);
  border-radius: var(--rm-r-sm);
}
.rm-trip-rail-row:last-child { border-bottom: 0; }
.rm-trip-rail-row:hover { background: var(--rm-surface-2); }
/* .rm-tap:focus-visible already draws the outline; this adds the same background hover already
   gives, so keyboard focus on RmRelatedEntityLink and RmTripRail carry equal visual weight — a
   plain outline on one and outline+background on the other would read as one being "more
   focused" than the other for no reason. */
.rm-trip-rail-row:focus-visible { background: var(--rm-surface-2); }

.rm-trip-rail-when { display: flex; flex-direction: column; line-height: 1.2; }
.rm-trip-rail-date { font-weight: 600; font-size: .8rem; }
.rm-trip-rail-time { font-size: .75rem; color: var(--rz-text-secondary-color); }

.rm-trip-rail-main { min-width: 0; }
.rm-trip-rail-route { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rm-trip-rail-meta { font-size: .75rem; color: var(--rz-text-secondary-color); display: flex; gap: .25rem; flex-wrap: wrap; }

.rm-trip-rail-side { display: flex; align-items: center; gap: .5rem; }
.rm-trip-rail-amount { font-weight: 600; }

.rm-trip-rail-empty {
  padding: 1.25rem;
  text-align: center;
  color: var(--rz-text-secondary-color);
  font-size: .85rem;
}

/* The rail on a phone. Three columns do not fit 294px, and the way they failed was the nasty
   kind: the middle track computed to literally ZERO ("72px 0px 190px"). `1fr` is
   `minmax(auto, 1fr)` and would normally floor at min-content, but .rm-trip-rail-main sets
   `min-width: 0` — correct for letting the route ellipsise, and here it also let the track
   vanish. So the date kept 72px, the amount and pill kept 190px, and the trip itself got none;
   its text painted out of a zero-width box straight across the amount, which is the overlap that
   showed up on the handset.

   Two columns and two rows: the date spans both, the trip takes the full remaining width, and the
   money and status move underneath it instead of bidding for the same line. Nothing is dropped —
   this is the same content, given somewhere to sit. */
@media (max-width: 820px) {
  .rm-trip-rail-row {
    grid-template-columns: 4.5rem minmax(0, 1fr);
    grid-template-areas:
      "when main"
      "when side";
    row-gap: .35rem;
    /* .75rem left the content column at 202px against a fare-plus-pill of 203px, so a
       "Completed" row wrapped its status under its fare while a "Confirmed" row (a shorter
       word) did not — the rail then looked like it had two different row designs. 8px of
       gutter buys the 12px that makes every row behave the same. The date column keeps 4.5rem
       rather than paying for it, because "29 Apr" is not the worst case that column has to
       hold once the UI is in Bangla. */
    column-gap: .5rem;
  }
  .rm-trip-rail-when { grid-area: when; }
  .rm-trip-rail-main { grid-area: main; }
  .rm-trip-rail-side { grid-area: side; flex-wrap: wrap; }

  /* A booking reference is one token, whatever its hyphens say. Left to wrap it broke as
     "TRIP- / DHK- / 0074" down three lines — the same rule the design system already applies to
     phone numbers (§5a), for the same reason: a split identifier is not a narrower identifier. */
  .rm-trip-rail-meta > span:first-child { white-space: nowrap; }

  /* With a real column to live in, a two-word route reads better on a second line than
     ellipsised on one. */
  .rm-trip-rail-route { white-space: normal; }
}

/* ── Monthly revenue bars ────────────────────────────────────────────────────
   Six bars, no chart library. Heights come from the component as inline
   percentages of the best month, so the row reads even on a thin fleet. Used
   by the vehicle 360 page's Earnings card. */
.rm-sparkbars {
    display: flex;
    align-items: flex-end;
    gap: .35rem;
    height: 4.5rem;
}

.rm-sparkbar {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    gap: .2rem;
}

/* The fill's inline height:N% needs to resolve against a track whose own size does not depend
   on the fill's content — sizing .rm-sparkbar-fill directly against .rm-sparkbar (which also
   holds the label) put the fill and the label in the same shrinking flex track, so every bar's
   basis exceeded the row height by a different amount and flex-shrink proportionally compressed
   the *tallest* bars the hardest: 80%, 90% and 100% all rendered within a couple of pixels of
   each other, flattening exactly the comparison a reader makes first. flex:1 here gives the
   track a size fixed by the space left over after the label, independent of the fill's own
   percentage, so the percentage is meaningful again. */
.rm-sparkbar-track {
    flex: 1;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

.rm-sparkbar-fill {
    width: 100%;
    background: var(--rm-completed);
    border-radius: var(--rm-r-sm) var(--rm-r-sm) 0 0;
    min-height: 2px;
}

.rm-sparkbar-label { font-size: .65rem; color: var(--rz-text-secondary-color); }

/* ============================================================
   6. Pickers — the sheet, the date/time field, the entity field

   Radzen's RadzenDatePicker and RadzenDropDown are desktop controls that float
   a panel beside their input. Measured on an SM-A256E at /bookings/create, that
   costs a phone user real work:

     - the minute field is a ±1 spinner, so 4:42 PM → 9:30 PM is up to 48 taps
       on a chevron roughly 20px tall;
     - the year select clips its own value to "2...";
     - day cells are 36×36, against Android's 48dp minimum target;
     - the panel sits over the field being edited, and the entity dropdown opens
       upward across the page header, so you cannot see what you are changing
       while you change it;
     - the dropdown's filter box is a hairline input at the top of a panel that
       the soft keyboard covers the moment it takes focus.

   The replacement is a bottom sheet: anchored under the thumb, full width, with
   its own scroll and a confirm bar that stays put. It is the Android convention
   for this job. On a wide screen the same markup centres itself as a dialog, so
   pages keep one component at every width.

   Everything here is tap-first: no target below 44px, and time is chosen from a
   grid rather than incremented.
   ============================================================ */

.rm-sheet-scrim {
    position: fixed; inset: 0; z-index: 1400;
    background: rgba(20, 33, 27, .45);
    opacity: 0; pointer-events: none;
    /* Stops the page behind scrolling under the sheet. The alternative is
       locking <body> through JS interop, which is a JS module for one bit of
       state. */
    touch-action: none;
    transition: opacity .18s ease;
}
.rm-sheet-scrim.open { opacity: 1; pointer-events: auto; }

.rm-sheet {
    position: fixed; z-index: 1401;
    left: 0; right: 0; bottom: 0;
    display: flex; flex-direction: column;
    max-height: min(88vh, 720px);
    background: var(--rm-surface);
    border-top: 1px solid var(--rm-line);
    border-radius: var(--rm-r-lg) var(--rm-r-lg) 0 0;
    box-shadow: var(--rm-shadow);
    transform: translateY(100%);
    transition: transform .22s cubic-bezier(.2, .8, .2, 1);
}
.rm-sheet.open { transform: translateY(0); }
/* The sheet takes focus on open so Escape works immediately. That is programmatic
   focus on a container, not a user landing on a control, so it must not paint a
   focus ring — the ring belongs to whatever the user tabs to inside. */
.rm-sheet:focus { outline: none; }

/* The grab handle is not decoration: it is the affordance saying "this panel is
   dismissable", which a floating Radzen popup never communicated. */
.rm-sheet-grip {
    width: 36px; height: 4px; border-radius: 999px;
    background: var(--rm-line-strong);
    margin: 10px auto 2px; flex: none;
}

.rm-sheet-head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 6px 18px 12px;
    border-bottom: 1px solid var(--rm-line);
    flex: none;
}
.rm-sheet-title {
    font-weight: 800; letter-spacing: -.01em; color: var(--rm-ink);
    font-size: 1.05rem; margin: 0;
}
/* The live value echoed in the header. The old picker hid the field behind its
   own panel; this keeps the answer visible while you edit it. */
.rm-sheet-value {
    font-family: var(--rm-font-num);
    font-size: .82rem; color: var(--rm-primary); font-weight: 700;
    text-align: right;
}

.rm-sheet-body { flex: 1 1 auto; overflow-y: auto; padding: 14px 18px 4px; }

.rm-sheet-foot {
    flex: none; display: grid; grid-template-columns: auto 1fr; gap: 10px;
    padding: 12px 18px calc(12px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--rm-line);
    background: var(--rm-surface);
}
/* Radzen's medium button is 42px, which is under the 44px floor everything else
   in this section holds to. The sheet's two buttons are the last thing a thumb
   touches before the value is committed, so they do not get to be the smallest
   targets on screen. */
.rm-sheet-foot .rz-button { min-height: 44px; }

/* The bottom tab bar is fixed at z-index 1100 and the sheet sits at 1401, so the
   sheet's confirm bar paints straight over the tabs — measured on an SM-A256E, 42px
   of Cancel/Done sat on top of the nav. The scrim already makes the nav untappable
   while a sheet is open, so the honest thing is to take it off screen rather than
   let two bars fight for the same 58px. :has() rather than a state class: the sheet
   is rendered by whichever picker owns it, and MainLayout has no idea one is open. */
body:has(.rm-sheet) .rm-bottomnav { display: none; }

/* Radzen puts an identity transform on .rz-body (matrix(1,0,0,1,0,0), for the
   sidebar animation). Any transform — identity included — makes an element the
   containing block for its position:fixed descendants, so the scrim and the sheet
   were being laid out against the content area rather than the viewport.
   Measured on the website at 1440px: the scrim covered 250,60 1190x784 and left
   the 250px sidebar undimmed and fully clickable underneath a dialog marked
   aria-modal="true" — modality the page was claiming but not enforcing.

   Dropped only while a sheet is open, so the sidebar animation is untouched the
   rest of the time. Same :has() reasoning as the rule above: the sheet belongs to
   whichever picker opened it, and the layout has no idea it exists. */
body:has(.rm-sheet) .rz-body { transform: none; }

@media (min-width: 821px) {
    /* Same markup, centred. A wide screen has no thumb to reach for. */
    .rm-sheet {
        left: 50%; right: auto; bottom: auto; top: 50%;
        width: min(440px, 92vw);
        transform: translate(-50%, -46%) scale(.98);
        opacity: 0;
        border-radius: var(--rm-r-lg);
        border: 1px solid var(--rm-line);
        transition: opacity .16s ease, transform .16s ease;
    }
    .rm-sheet.open { transform: translate(-50%, -50%) scale(1); opacity: 1; }
    .rm-sheet-grip { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .rm-sheet, .rm-sheet-scrim { transition: none; }
}

/* ---- 6a. The closed field ---------------------------------- */
/* Reads as a form control, behaves as a button. Deliberately not a Radzen input:
   there is nothing to type into, and a focusable text box that opens a sheet on
   click strands a keyboard user inside a field they cannot fill. */
.rm-field {
    display: flex; align-items: center; gap: 10px; width: 100%;
    min-height: 52px; padding: 8px 12px;
    background: var(--rm-surface);
    border: 1px solid var(--rm-line-strong);
    border-radius: var(--rm-r-md);
    font-family: inherit; text-align: left; cursor: pointer;
    color: var(--rm-ink);
}
.rm-field:hover { border-color: var(--rm-primary); }
.rm-field:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; }
.rm-field.is-invalid { border-color: var(--rm-cancelled); }
.rm-field .rzi { color: var(--rm-ink-3); flex: none; }
.rm-field-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rm-field-lbl {
    font-size: .66rem; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--rm-ink-3);
}
/* Wraps rather than ellipsises: a truncated date is a wrong date, and the
   DOCUMENTS button already showed what a fixed width does to a long label once
   Android's font scale is applied. */
.rm-field-val {
    font-size: .95rem; font-weight: 600; color: var(--rm-ink);
    overflow-wrap: anywhere;
}
.rm-field-val.is-empty { color: var(--rm-ink-3); font-weight: 500; }

/* ---- 6b. Chips (quick times, durations, minutes) ------------ */
.rm-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.rm-chip {
    min-height: 44px; padding: 0 14px;
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    border-radius: 999px;
    border: 1px solid var(--rm-line-strong);
    background: var(--rm-surface);
    color: var(--rm-ink-2);
    font-family: inherit; font-size: .85rem; font-weight: 700;
    cursor: pointer;
}
.rm-chip:hover { border-color: var(--rm-primary); color: var(--rm-primary); }
.rm-chip:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 2px; }
.rm-chip.on {
    background: var(--rm-primary); border-color: var(--rm-primary);
    color: var(--rm-primary-ink);
}

.rm-sheet-sec { margin-bottom: 18px; }
.rm-sheet-sec > h4 {
    font-size: .66rem; font-weight: 800; letter-spacing: .08em;
    text-transform: uppercase; color: var(--rm-ink-3);
    margin: 0 0 8px;
}

/* ---- 6c. Date strip ---------------------------------------- */
/* A horizontal run of the next fortnight. Most trips are entered for today,
   tomorrow, or a day this week, and those should cost one tap rather than a
   trip through a month grid. The calendar stays available below it. */
.rm-daystrip {
    display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
}
.rm-daystrip::-webkit-scrollbar { height: 0; }
.rm-day {
    flex: none; width: 58px; min-height: 62px; scroll-snap-align: start;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    border: 1px solid var(--rm-line-strong); border-radius: var(--rm-r-md);
    background: var(--rm-surface); color: var(--rm-ink-2);
    font-family: inherit; cursor: pointer;
}
.rm-day .dow { font-size: .62rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.rm-day .dnum { font-size: 1.15rem; font-weight: 800; font-family: var(--rm-font-num); line-height: 1; }
.rm-day .dmon { font-size: .58rem; color: var(--rm-ink-3); }
.rm-day:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 2px; }
.rm-day.on {
    background: var(--rm-primary); border-color: var(--rm-primary); color: var(--rm-primary-ink);
}
.rm-day.on .dmon { color: var(--rm-primary-soft); }

/* ---- 6d. Month grid ---------------------------------------- */
/* 44px minimum cells, against Radzen's 36. Seven equal fractions so the grid
   fills whatever width the sheet has instead of overflowing it. */
.rm-cal-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    margin-bottom: 8px;
}
.rm-cal-month { font-weight: 800; color: var(--rm-ink); font-size: .95rem; }
.rm-cal-nav {
    width: 44px; height: 44px; display: grid; place-items: center;
    border-radius: 999px; border: 1px solid var(--rm-line-strong);
    background: var(--rm-surface); color: var(--rm-ink-2); cursor: pointer;
}
.rm-cal-nav:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 2px; }
.rm-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.rm-cal .dow {
    text-align: center; font-size: .6rem; font-weight: 800; letter-spacing: .04em;
    text-transform: uppercase; color: var(--rm-ink-3); padding-bottom: 4px;
}
.rm-cal button {
    aspect-ratio: 1; min-height: 44px;
    display: grid; place-items: center;
    border: 1px solid transparent; border-radius: var(--rm-r-sm);
    background: none; color: var(--rm-ink);
    font-family: var(--rm-font-num); font-size: .92rem; font-weight: 600;
    cursor: pointer;
}
.rm-cal button:hover { background: var(--rm-surface-2); }
.rm-cal button:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: -2px; }
.rm-cal button.out { color: var(--rm-ink-3); opacity: .5; }
.rm-cal button.today { border-color: var(--rm-primary); }
.rm-cal button.on {
    background: var(--rm-primary); color: var(--rm-primary-ink); border-color: var(--rm-primary);
}

/* ---- 6e. Time grid ----------------------------------------- */
/* Hours as a 6-column grid, minutes as quarter chips: any quarter hour is two
   taps, where the spinner it replaces needed up to fifty. A custom field covers
   the rare non-quarter minute. */
.rm-hours { display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; }
.rm-hours button {
    min-height: 44px; border-radius: var(--rm-r-sm);
    border: 1px solid var(--rm-line-strong); background: var(--rm-surface);
    color: var(--rm-ink-2); font-family: var(--rm-font-num);
    font-size: .85rem; font-weight: 700; cursor: pointer;
    padding: 0 2px;
}
.rm-hours button:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: 1px; }
.rm-hours button.on { background: var(--rm-primary); border-color: var(--rm-primary); color: var(--rm-primary-ink); }
.rm-hours button .ampm { font-size: .58rem; opacity: .75; margin-left: 1px; }

/* ---- 6f. Entity picker rows -------------------------------- */
/* Two lines, because a column of names identifies nobody: the seed fleet alone
   carries several Rahmans. The second line holds what disambiguates them — a
   phone number, a registration plate. */
.rm-pick-search { position: sticky; top: 0; z-index: 1; background: var(--rm-surface); padding-bottom: 8px; }
.rm-pick-list { display: flex; flex-direction: column; }
.rm-pick-row {
    display: flex; align-items: center; gap: 12px; width: 100%;
    min-height: 56px; padding: 8px 4px;
    border: none; border-bottom: 1px solid var(--rm-line);
    background: none; text-align: left; cursor: pointer; font-family: inherit;
}
.rm-pick-row:focus-visible { outline: 2px solid var(--rm-primary); outline-offset: -2px; }
.rm-pick-row.on { background: var(--rm-primary-soft); }
.rm-pick-mark {
    width: 36px; height: 36px; flex: none; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--rm-surface-2); color: var(--rm-ink-2);
    font-weight: 800; font-size: .85rem;
}
.rm-pick-row.on .rm-pick-mark { background: var(--rm-primary); color: var(--rm-primary-ink); }
/* Flex column, not two bare spans: spans are inline, so the name and the phone
   number rendered as one run — "Kabir Mia+8801800057175" — which is worse than
   the single-line list this row was built to replace. */
.rm-pick-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rm-pick-name { font-weight: 700; color: var(--rm-ink); font-size: .95rem; overflow-wrap: anywhere; }
.rm-pick-sub {
    font-size: .78rem; color: var(--rm-ink-3);
    font-family: var(--rm-font-num); overflow-wrap: anywhere;
}
.rm-pick-empty { padding: 28px 8px; text-align: center; color: var(--rm-ink-3); font-size: .9rem; }

/* ============================================================
   7. Text that must not be cut in half

   Every rule here comes from one measured failure on an SM-A256E (384px CSS
   viewport, Android font scale applied inside the WebView, so the rendered base
   is 18.2px against a 16px stylesheet). The pattern behind all of them is the
   same: a box sized for desktop English meets a longer string — বাংলা, a company
   name, a scaled-up font — and the overflow is hidden rather than accommodated.
   ============================================================ */

/* ---- 7a. Lifecycle rail: stack it on a phone ----------------
   Four segments across a 304px card leaves each label about 28px of usable
   width. Measured need: 87–108px for the Bangla stage names on /guide
   ("বুকিং হয়েছে", "চালক পৌঁছেছে"), so every one of them ellipsised down to a
   syllable and the rail said nothing at all.

   Stacked rather than shrunk: the rail's job is to name the stage a trip is in,
   and a horizontal rail that cannot show a stage name has no job left. The
   left-hand dot column and the done/now colouring carry over unchanged, so it
   still reads as one rail rather than four cards. */
@media (max-width: 820px) {
  .rm-rail { flex-direction: column; }
  .rm-rail .seg {
    padding: 9px 12px 9px 26px;
    border: 1px solid var(--rm-line);
    border-bottom-width: 0;
  }
  .rm-rail .seg:first-child {
    border-radius: var(--rm-r-md) var(--rm-r-md) 0 0;
    padding-left: 26px;
  }
  .rm-rail .seg:last-child {
    border-radius: 0 0 var(--rm-r-md) var(--rm-r-md);
    border-bottom-width: 1px;
  }
  /* The chevron between segments points right, which is meaningless once they
     are stacked. */
  .rm-rail .seg::after { display: none; }
  .rm-rail .seg .dot { top: 15px; left: 10px; }
  /* The whole point: let the stage name occupy the width it now has. */
  .rm-rail .seg .v { white-space: normal; overflow: visible; text-overflow: clip; }
}

/* ---- 7b. Radzen dropdown labels lose their descenders -------
   Measured on fourteen screens: the label box is 26px while the text needs 31px,
   so overflow:hidden clips the bottom of every descender — "Select or search
   customer" lost the tails of its p and y. Radzen sizes this line for a 16px
   base; the device renders 18.2px.

   Height rather than font-size: shrinking the text to fit would be solving a
   too-small box by making the content harder to read. */
/* Specificity note, learned the hard way: a bare .rz-dropdown-label.rz-inputtext
   rule matches but still loses, because Radzen sets this box inside its own
   @media block and a later top-level rule of equal specificity does not outrank a
   media-query rule of equal specificity that comes after it in the cascade. The
   parent selector buys the extra id-less weight, and the media query keeps the
   change on the widths that need it. */
@media (max-width: 820px) {
  .rz-dropdown .rz-dropdown-label.rz-inputtext,
  .rz-form-field-content .rz-dropdown-label.rz-inputtext {
    height: auto;
    min-height: 2.1em;
    line-height: 1.5;
  }
}

/* ---- 7c. Floating form-field labels ------------------------
   Radzen stamps .rz-text-truncate on the floating label, which on /vehicles/create
   cut "Make" to a 19px box needing 58px — the label of a required field, unreadable.
   The label sits above its input with the full field width available, so there is
   nothing for it to collide with; the truncation was never earning anything. */
.rz-form-field-label.rz-text-truncate {
  overflow: visible;
  text-overflow: clip;
  max-width: none;
}

/* ---- 7d. Small Radzen buttons in toggle groups -------------
   The board's Day/Range selector: 50px box, 56px of text. These are a segmented
   control, so they cannot ellipsise into "Ra…" without losing the only thing that
   distinguishes them. */
.rz-button.rz-button-sm { min-width: 0; padding-left: .55rem; padding-right: .55rem; }
.rz-button.rz-button-sm .rz-button-text { overflow: visible; text-overflow: clip; }

/* ---- 7e. Debtor rows: give the name its own line -----------
   The rule above (§ "Debtor row") makes the customer name ellipsise so the amount
   and the days-overdue never break. That trade was the right call for the row as
   built, but on a 384px phone it left 82px for "Grameenphone Ltd", which needs
   158px — every debtor on the landing screen read as "Grameenph…", and a debtor
   you cannot name is not a debtor you can chase.

   Two lines rather than a narrower amount: the constraint the original comment
   cared about is that the *amount* must not break, and stacking honours that
   completely — the amount keeps its own unbroken line and the name gets the full
   card width above it. Phone only; the desktop row has the width to stay on one
   line and is left alone. */
@media (max-width: 820px) {
  .rm-debtor-row { flex-wrap: wrap; }
  .rm-debtor-name {
    flex: 1 0 100%;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  .rm-debtor-amount { flex: 1 0 auto; }
}

/* ---- 7f. Select bars must show their whole option ----------
   RadzenSelectBar renders each option as a fixed-size small button; measured on
   the booking board, "Day" had 48px for 50px of text and "Range" 69px for 73px.
   A segmented control whose segments are cut is worse than a wide one — the label
   is the only thing distinguishing the segments from each other. Let the bar wrap
   to a second line instead of trimming the words. */
@media (max-width: 820px) {
  /* Targeted at the button element itself rather than at a container class:
     RadzenSelectBar's root class differs between versions, and the measured
     clipping ("Day" 48px for 50px, "Range" 69px for 73px) is on the item box. */
  .rz-button.rz-button-sm.rz-button-text-only {
    width: auto;
    min-width: 0;
    overflow: visible;
    padding-left: .5rem;
    padding-right: .5rem;
  }
  .rz-button.rz-button-sm.rz-button-text-only .rz-button-text {
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
  }
}
