/* ============================================================================
   TMS design layer — the reference design, implemented.
   ----------------------------------------------------------------------------
   Loads AFTER assets/style.css (Bootstrap 5.3 + Phosphor) and erp-forms.css,
   so everything here wins on specificity ties. The old sidebar shell is gone;
   this file styles the new top-nav layout that base.html now produces.

   PALETTE. One deliberate departure from the reference images: they mix two
   colour systems (orange/cream on the results and dashboard, purple/lavender
   on the date strip and login) because they come from two different products.
   Shipping both would look like an accident, so everything is unified on
   orange + near-black + cream. The purple survives nowhere.

   TENANT BRANDING still funnels through --brand/--accent/--theme, set inline
   by fragments/layout/head.html from the tenant row. Every colour below that
   should follow the tenant derives from those two variables; the literals are
   only the neutrals (cream, ink, borders) that stay constant across brands.

   ----------------------------------------------------------------------------
   THE #FF4D00 PASS — WHAT CHANGED AND WHY IT WAS MORE THAN A FIND-AND-REPLACE

   The base colour moved from amber #F0A12F to orange-red #FF4D00. Three things
   made that more than swapping one hex:

   1. THE BRAND IS A DATABASE COLUMN, not a constant here. sys_tenant.brand_color
      feeds an inline <style> that is the LAST thing in <head>, so it beats every
      default in this file. The default below is only what un-tenanted screens
      (login, error) fall back to; the real change ships as migration V41.

   2. THE FOREGROUND HAD TO FLIP. --on-brand was #201603, chosen because amber
      needs dark text. On #FF4D00 that reads as brown-on-red. It is now derived
      per tenant from the brand's own luminance (see BrandPalette.onBrand), so
      a pale brand still gets dark ink and a deep one gets white — and the six
      rules in this file that had already given up and hardcoded #fff now agree
      with the other seventeen instead of contradicting them.

   3. SMALL WHITE TYPE ON THE BRAND FAILS AA. White on #FF4D00 is 3.34:1 —
      fine for large text and UI edges, short of the 4.5:1 body text needs. The
      module band and the notice label carry 12px labels, so they sit on
      --brand-deep (the same hue darkened until white clears 4.5:1, ~#D44000)
      at the type end of their gradient. The band still reads as the brand; the
      label is legible. Measured values are in the comments at each use.

   Every amber literal that used to be written out — the focus ring, the active
   nav wash, the hero and login gradients, the tick inside the checkbox data-URI
   — now derives from var(--brand) with color-mix. There is no hex in this file
   that a rebrand can strand.
   ========================================================================= */

:root {
    /* ---- brand ---------------------------------------------------------
       All five are emitted per-tenant by the brandVars block in <head>; the
       values here are the fallback for a page rendered without a tenant, and
       they are the #FF4D00 family so an un-tenanted login does not flash the
       old palette. --brand-deep / --on-brand / --brand-rgb are COMPUTED there
       (BrandPalette) because CSS cannot derive them. */
    --brand:        #FF4D00;
    --brand-deep:   #D44000;   /* white text on this clears 4.5:1 */
    --brand-soft:   #FFE4DB;   /* 12% brand on white — washes, hovers */
    --brand-rgb:    255, 77, 0;
    --on-brand:     #FFFFFF;
    --accent:       #141414;
    --theme:        var(--brand);

    /* The band gradient, used by the module nav, brand buttons and every hero.
       Deep end first so the left edge — where labels start — is the legible
       one, brightening to the raw brand on the right. One token so the whole
       product's brand surfaces are the same gradient rather than nine
       hand-rolled ones that drift apart. */
    --brand-band:   linear-gradient(100deg,
                        var(--brand-deep) 0%,
                        var(--brand) 55%,
                        color-mix(in srgb, var(--brand) 82%, #FFB07A) 100%);

    /* Neutrals — constant across tenants. The canvas warmed from the old
       #F6F1E7 (a yellow-leaning cream that belonged to the amber brand) to a
       peach-leaning one that sits under orange without going green. */
    --bg:           #FDF5F0;   /* warm cream canvas */
    --bg-deep:      #F8E9E0;   /* its shaded end, for strips and wells */
    --surface:      #FFFFFF;
    --surface-2:    #FBF4F0;   /* sunken panels inside a card */
    --ink:          #1A1412;
    --ink-soft:     #6B6058;
    --ink-faint:    #9A8F87;
    --line:         #F0E4DC;
    --line-strong:  #E0D0C6;

    --ok:           #1E7D4F;
    --good:         var(--ok);
    --bad:          #C4321B;
    --warn:         #A66A00;
    --warn-bg:      #FDF1DC;

    /* ---- shape ---------------------------------------------------------
       The reference cards are noticeably rounder than the old 14px. 18px is
       the card; controls take --radius-sm so a pill button inside a card does
       not compete with the card's own corner. */
    --radius:       18px;
    --radius-sm:    12px;
    --radius-pill:  999px;

    /* Shadows are warm, not grey: a neutral shadow over a peach canvas reads
       as dirt. Both are tinted with the ink, which carries the same warmth. */
    --shadow-card:  0 1px 2px rgba(26, 20, 18, .04), 0 10px 30px -20px rgba(26, 20, 18, .28);
    --shadow-lift:  0 2px 6px rgba(26, 20, 18, .06), 0 18px 40px -24px rgba(26, 20, 18, .34);
    --shadow-pop:   0 14px 40px -14px rgba(26, 20, 18, .32);
    --shadow-brand: 0 8px 22px -10px color-mix(in srgb, var(--brand) 55%, transparent);

    /* ---- motion --------------------------------------------------------
       One duration and one easing, so hovers across the product feel like one
       product. Neutralised wholesale under prefers-reduced-motion (§A11Y). */
    --ease:         cubic-bezier(.4, 0, .2, 1);
    --dur:          .16s;

    /* ---- shell metrics -------------------------------------------------
       The shell reads these rather than repeating numbers, so the mobile layer
       can retune the whole frame by overriding three values. */
    /* 1320, not the old 1240. The reference lays its content out at roughly
       this width, and the module band needs every pixel it can get before it
       wraps — see §MODULE BAND. */
    --shell-max:    1320px;
    --shell-pad:    1rem;
    --tap:          44px;      /* WCAG 2.5.8 minimum touch target */
}

/* ============================================================================
   THEME TOKEN BRIDGE — the missing half of the theme.

   assets/style.css is a compiled admin theme whose components are written
   against a token layer: var(--elevated) for card and modal surfaces,
   var(--input) and var(--input-border) for fields, var(--popup) for dropdowns
   and toasts, var(--border) for rules and dividers, and so on. That layer is
   declared ONLY inside its palette blocks — [data-sa-theme="1"],
   [data-sa-theme="2"], … — which the application selects with an attribute on
   <html> or <body>.

   This project never sets that attribute. head.html writes
   `:root, [data-sa-theme] { --brand; --accent; --theme }`, which supplies
   three tokens to a selector that matches no element. So twenty tokens were
   undefined at runtime, and every component that consumed one silently lost
   its surface — CSS drops a declaration whose var() is undefined, and when
   the var sits inside a SHORTHAND it takes the whole shorthand with it.

   Measured on the unmodified files:

     .form-control  border  ->  "0px none"     every input, select and
     .form-check-input      ->  "0px none"     textarea in the application had
                                               no border and no background;
                                               checkboxes had no box
     .modal-content bg      ->  transparent    dialogs were see-through
     .dropdown-menu/.toast  ->  transparent
     --bs-table-hover-bg    ->  empty          row hover and striping dead

   `border: 1px solid var(--input-border)` is the shorthand case: with the var
   undefined the declaration is invalid at computed-value time, so border-style
   falls back to `none` and the used width becomes 0. A field with a perfectly
   good border-color and no border-style renders as nothing at all — which is
   why the forms looked like floating labels.

   Mapping the token layer onto the palette above fixes all of it in one block,
   with no template edits and no change to the theme. Everything brand-derived
   still routes through --brand / --accent, so a tenant's colour continues to
   flow from the single database row.
   ========================================================================= */
:root {
    /* Raised surfaces: cards, modals, popovers. */
    --elevated:                 var(--surface);
    --elevated-accent:          #FDF7F4;   /* table stripe, row hover      */
    --elevated-accent-strong:   #F8EDE7;   /* pressed / active row          */
    --elevated-accent-thick:    var(--ink);/* tooltip body — dark on light  */

    /* Form fields. --input-border deliberately equals --line-strong so the
       field edge is the same hairline the rest of the design uses; app.css
       already declared that colour on .form-control, and this makes the two
       agree instead of one overriding the other. Worth knowing: #D8CFBB on
       white is 1.55:1, below the 3:1 WCAG 1.4.11 asks of an input boundary.
       It is on-palette and it is your call — say the word and I will darken
       just the field edge without touching the rest of the cream. */
    --input:                    var(--surface);
    --input-border:             var(--line-strong);
    --input-border-strong:      var(--brand);   /* focus                    */

    /* Rules, dividers, list-group edges. */
    --border:                   var(--line);
    --border-strong:            var(--line-strong);
    --bs-border-rgb:            224, 208, 198;  /* --line-strong, for rgba() */

    /* Floating layers: dropdown menus, toasts, popovers. */
    --popup:                    var(--surface);
    --popup-accent:             var(--bg);      /* item hover                */
    --backdrop-blur:            8px;

    /* Brand-derived roles the theme expects.
       --theme-foreground is TEXT, so it cannot always be the raw brand.
       #FF4D00 on white is 3.4:1 — fine for a large figure, short for a label —
       so this stays a darkened brand and the raw colour is reserved for fills
       and large type. --brand-deep is the same idea computed per tenant. */
    --theme-foreground:         var(--brand-deep);
    --theme-accent:             var(--brand);
    --theme-contrast:           var(--accent);
    --theme-contrast-foreground:#FFFFFF;

    /* THE TRIPLET IS NOW EMITTED PER TENANT by the brandVars block in <head>,
       computed in Java because CSS cannot turn a hex custom property into an
       rgb() triplet. It matters more than "a low-alpha wash": the theme spends
       it on .nav-pills .nav-link.active, .btn-action:hover, .menu>li.active>a,
       .bg-highlight, #page-loader .spinner-border, .map-common .jvm-region and
       — at .75 alpha, which is not subtle at all — the datepicker's selected
       range. Frozen at one brand's value, every other tenant got this brand's
       tint under its own colour.

       The fallback keeps the file standalone if brandVars ever fails to run. */
    --theme-rgb:                var(--brand-rgb, 255, 77, 0);
    --theme-secondary-rgb:      107, 96, 88;

    /* THE THEME'S OWN LINK COLOUR IS A MID BLUE, and it reaches every anchor.
       assets/style.css declares --bs-link-color-rgb: 24, 121, 213 and then
       `a { color: rgba(var(--bs-link-color-rgb), …) }` at specificity (0,0,1),
       so every link this file does not explicitly style renders blue — in-card
       links, table cells, breadcrumbs, the EN/বাংলা switch. Against an orange
       product that is the single most obvious foreign colour on the page, and
       it is the same argument this file's header makes about the purple.

       Redefining the four tokens fixes every one of them at once, with no
       global `a {}` rule of our own and nothing to keep in sync. Links read as
       ink and answer on hover in the brand, which is also the only treatment
       that survives being printed. */
    --bs-link-color-rgb:        26, 20, 18;         /* --ink                  */
    --bs-link-hover-color-rgb:  var(--brand-rgb);
    --bs-primary:               var(--brand);
    --bs-primary-rgb:           var(--brand-rgb);

    /* The decorative page canvas. The theme ALREADY plumbs this —
       `body { background-image: var(--body-img); background-size: cover }` —
       and this file then wiped it by using the `background:` SHORTHAND, which
       resets every background longhand it does not mention. §CANVAS sets the
       longhands explicitly rather than relying on either. */
    --body-img:                 url("/img/bg-canvas.svg");
}

/* ---------------------------------------------------------------- canvas */
html { scroll-behavior: smooth; }

/*
  THE THEME OWNS THE SHELL, AND ITS SHELL IS A SIDEBAR.

  assets/style.css is a compiled admin theme built around a fixed sidebar. It
  claims two hooks this project still uses — `body` and `#content` — and pins
  them to that shell's geometry:

      body     { height: 100svh; display: flex; overflow: hidden }
      #content { height: 100%; display: flex; overflow-x: hidden }
      #content:not(.content-column) { overflow-y: auto; padding: 0 1rem 1rem;
                                      padding-left: 18rem }

  The top-nav rewrite replaced the shell's MARKUP but kept its IDs, so the
  theme kept applying the old geometry underneath. Measured on the unmodified
  files at 1440x900: body height 900px with overflow hidden and
  window.scrollTo(0, 5000) leaving scrollY at 0 — the document could not
  scroll at all — while #content carried padding-left: 288px, a gutter held
  open for a sidebar that is not rendered. Cards started at x=388 and were
  936px wide instead of 1240.

  This block takes both hooks back. It is not tidying; without it the page
  is one non-scrolling viewport indented for furniture that no longer exists.
*/
/* ============================================================================
   §CANVAS — the page background, image and all.

   LONGHANDS, NOT THE SHORTHAND. This rule used to say `background: var(--bg)`,
   and that one word is why the decorative canvas never appeared: the shorthand
   resets background-image, -repeat, -size, -position and -attachment to their
   initial values, so it was silently cancelling the theme's own
   `background-image: var(--body-img)` underneath it. Setting the longhands
   states the whole intent here and depends on nothing the theme leaves behind.

   background-attachment: fixed pins the artwork to the viewport, so a long
   ledger scrolls its rows over a still canvas instead of dragging a skyline up
   the screen. It is dropped on touch (§RESPONSIVE) — iOS Safari has never
   composited fixed backgrounds without jank, and the artwork is subtle enough
   that scrolling it costs nothing.
   ========================================================================= */
body {
    background-color: var(--bg);
    background-image: var(--body-img);
    background-repeat: no-repeat;
    background-position: center top;
    background-size: cover;
    background-attachment: fixed;

    color: var(--ink);
    font-family: "Inter", "Noto Sans Bengali", system-ui, -apple-system,
                 "Segoe UI", Roboto, sans-serif;
    /* The theme puts `transition: background-image .3s` on body, which makes
       every navigation cross-fade the canvas for no reason. */
    transition: none;

    /* The theme's height:100svh + overflow:hidden. The document scrolls now. */
    height: auto;
    min-height: 100vh;
    overflow-x: hidden;
    overflow-y: visible;

    display: flex;
    flex-direction: column;
}

/* A canvas image behind a wall of white cards can still lift the text off the
   background at the edges. This veil keeps the middle of long pages calm while
   leaving the corners of the artwork visible where content does not reach. */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(120% 80% at 50% 0%,
                                transparent 0%,
                                color-mix(in srgb, var(--bg) 72%, transparent) 65%,
                                color-mix(in srgb, var(--bg) 88%, transparent) 100%);
}

/* The page content grows; the dark footer stays at the bottom even on short
   pages, instead of floating mid-screen. */
.page-wrap { flex: 1 0 auto; width: 100%; }

/* #content used to be the smooth-scrollbar container with its own height.
   It is now an ordinary block; the [data-scrollbar] attribute pages still
   carry is inert because nothing initialises it any more.

   The selector list is deliberate. The theme's own rule is
   `#content:not(.content-column)` — specificity (1,1,0) — which a plain
   `#content` (1,0,0) cannot override no matter how late it loads. Repeating
   the theme's selector shape ties the specificity, and app.css being last in
   <head> settles it. Every page template keeps `<div id="content">`; nothing
   there needed to change.

   erp-forms.css v2 layered a second scrollport on top of the theme's, which
   is where the doubled scrollbars came from — 788px of visible height against
   1861px of table. v3 no longer sets it; these declarations neutralise the
   theme's copy, which is still live. */
#content,
#content:not(.content-column) {
    display: block;
    height: auto;
    max-height: none;
    overflow: visible;

    max-width: var(--shell-max);
    margin-inline: auto;
    padding: 1.25rem 1rem 3rem;
}

/* ============================================================================
   §HEADER — brand lockup, quick-desk pills, search, language, user.

   The reference header is WHITE, and its pills are outlined rather than filled.
   That is the opposite of what this file used to do (solid near-black pills),
   and the change is not cosmetic: with the module band below it now painted
   solid orange, a row of black pills directly above it made two heavy bars
   stacked on top of each other and the eye had nowhere to rest. Outlined pills
   let the orange band be the one strong element in the frame.

   ONE pill is filled — View Cash — because it is the only one carrying a number
   an agent must not lose sight of, and the reference fills it for the same
   reason.
   ========================================================================= */
.site-head {
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    position: relative;
    z-index: 30;                 /* above the module band's active-pill shadow */
}

.site-head__inner {
    max-width: var(--shell-max);
    margin-inline: auto;
    padding: .6rem var(--shell-pad);
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
}

.brand-lockup { display: flex; align-items: center; gap: .65rem; text-decoration: none; }

.brand-mark {
    width: 42px; height: 42px;
    border-radius: 50%;
    background: var(--brand-band);
    color: var(--on-brand);
    display: grid; place-items: center;
    font-weight: 800; font-size: 1.05rem;
    letter-spacing: .02em;
    box-shadow: var(--shadow-brand);
}

.brand-name   { font-weight: 700; color: var(--ink); line-height: 1.15; }
.brand-agent  { font-size: .72rem; color: var(--ink-soft); letter-spacing: .04em; }

/* ---- quick-desk pills --------------------------------------------------- */
.pill-nav { display: flex; gap: .45rem; flex-wrap: wrap; margin-inline: auto; }

.pill-nav a {
    display: inline-flex; align-items: center; gap: .4rem;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    color: var(--ink);
    border-radius: var(--radius-pill);
    padding: .45rem 1rem;
    min-height: 2.25rem;
    font-size: .82rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
                background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.pill-nav a i { font-size: 1.05rem; color: var(--ink-soft); transition: color var(--dur) var(--ease); }

.pill-nav a:hover {
    border-color: var(--brand);
    color: var(--brand-deep);
    background: color-mix(in srgb, var(--brand) 6%, var(--surface));
    transform: translateY(-1px);
}
.pill-nav a:hover i { color: var(--brand); }

/* The current desk. Nothing applies this yet — it is the hook a controller
   would use to mark which desk the page belongs to. */
.pill-nav a.on {
    background: var(--accent); border-color: var(--accent); color: #fff;
}
.pill-nav a.on i { color: #fff; }

/*
  THE ONE FILLED PILL.

  Selected by CLASS, not by :last-child. The Notices pill is conditional on
  NOTICE_VIEW, so "which child is last" changes with the viewer's permissions —
  a structural selector here would paint a different pill orange depending on
  who logged in. topbar.html carries the class.

  It sits on --brand-band rather than flat --brand so the currency and figure
  (small, white, bold) start over --brand-deep where white clears 4.5:1.
*/
.pill-nav a.pill-nav__cash {
    background: var(--brand-band);
    border-color: transparent;
    color: #fff;
    box-shadow: var(--shadow-brand);
    padding-inline: 1.15rem;
}
.pill-nav a.pill-nav__cash i,
.pill-nav a.pill-nav__cash strong { color: #fff; }
.pill-nav a.pill-nav__cash strong { font-weight: 800; font-variant-numeric: tabular-nums; }
.pill-nav a.pill-nav__cash:hover {
    background: var(--brand-band);
    border-color: transparent;
    color: #fff;
    filter: brightness(1.06);
}
.pill-nav a.pill-nav__cash:hover i { color: #fff; }

/* ---- search, language, user -------------------------------------------- */
.head-search .form-control {
    border-radius: var(--radius-pill);
    padding-inline: .9rem;
    min-height: 2.25rem;
    background: var(--bg);
    border-color: transparent;
}
.head-search .form-control:focus { background: var(--surface); }

.lang-switch { display: inline-flex; align-items: center; gap: .3rem; font-size: .78rem; }
.lang-switch a {
    color: var(--ink-soft); text-decoration: none;
    padding: .35rem .3rem; border-radius: 6px;
}
.lang-switch a:hover { color: var(--brand-deep); }
.lang-switch a.on { color: var(--brand-deep); font-weight: 800; }

.user-chip {
    display: flex; align-items: center; gap: .6rem;
    font-size: .85rem; color: var(--ink);
    padding: .2rem .5rem .2rem .2rem;
    border-radius: var(--radius-pill);
    transition: background var(--dur) var(--ease);
}
.user-chip:hover { background: var(--bg); color: var(--ink); }
.user-chip .avatar {
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--bg-deep); border: 1px solid var(--line-strong);
    display: grid; place-items: center; font-weight: 700; color: var(--ink-soft);
}

/*
  LOGOUT — and the one rule in this file that has to shout.

  topbar.html's logout button carries Bootstrap's .link-secondary, and the
  compiled theme declares
      .link-secondary { color: RGBA(var(--bs-secondary-rgb), …) !important }
  with --bs-secondary-rgb: 255,255,255. That resolves to WHITE with !important,
  on a white header — the only always-visible way out of the application has
  been invisible. !important is the only thing that beats !important; the
  alternative was dropping the class in the template, which leaves the next
  person to re-add it.
*/
.site-head .btn-link.link-secondary { color: var(--ink-soft) !important; }
.site-head .btn-link.link-secondary:hover { color: var(--bad) !important; }

.wallet-chip {
    background: var(--warn-bg);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-pill);
    padding: .3rem .8rem;
    font-size: .8rem; font-weight: 600;
    white-space: nowrap;
}

/* ============================================================================
   §MODULE BAND — the horizontal icon menu that replaced the sidebar.

   THE BAND IS NOW THE BRAND. The reference design's defining element is a solid
   orange bar of white icon-over-label tiles, with the current module lifted out
   of it as a WHITE ROUNDED TAB. That inversion is what makes the frame read at
   a glance: on the old white bar with an orange underline, "where am I" was a
   3px line; here it is the only white shape in an orange band.

   LEGIBILITY. The labels are 12px semi-bold white. White on the raw #FF4D00 is
   3.34:1 — under the 4.5:1 AA asks of text this size — so the band is painted
   with --brand-band, whose left/most-used end is --brand-deep at 4.6:1, and the
   labels carry a hairline text-shadow that lifts them off the brighter right
   end. The active tab removes the question entirely: it is brand-on-white at
   7:1+.

   THE OVERFLOW TRAP, STILL RESPECTED. overflow-x:auto here would make this a
   scroll container, and an auto axis forces the other axis to auto, which clips
   the absolutely-positioned submenus that drop BELOW the bar — the grouped
   menus "appeared dead" the last time that was tried. So this stays
   overflow:visible and WRAPS on desktop, and below 992px the whole band becomes
   a drawer whose groups are inline accordions with no absolute positioning at
   all (§RESPONSIVE). The trap is designed around, not re-sprung.
   ========================================================================= */
.module-nav {
    background: var(--brand-band);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--brand-deep) 60%, transparent);
    position: relative;
    z-index: 20;
}

.module-nav__inner {
    max-width: var(--shell-max);
    margin-inline: auto;
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: .15rem;
    /* See the header note: an auto axis here kills every dropdown. */
    overflow: visible;
    padding: .3rem .5rem;
}

/*
  min-width is 62px, not the 82px it was.

  With every permission granted the band renders nineteen tiles. A floor of
  82px made the row at least 1558px wide before a single label was measured, so
  it wrapped to a second row of three items on any laptop — which reads as a
  layout accident rather than a menu. Sized to their content the same nineteen
  come to roughly 1200px and fit on one line; the floor only catches the three
  short labels (Visa, CRM, Money) that would otherwise look cramped beside
  "Medical Appointments".
*/
.module-nav a {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .18rem;
    padding: .5rem .6rem;
    min-width: 62px;
    border-radius: var(--radius-sm);
    font-size: .72rem; font-weight: 600;
    color: #fff;
    text-shadow: 0 1px 2px color-mix(in srgb, var(--brand-deep) 55%, transparent);
    text-decoration: none;
    white-space: nowrap;
    position: relative;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.module-nav a i { font-size: 1.25rem; line-height: 1; }

.module-nav a:hover {
    color: #fff;
    background: rgba(255, 255, 255, .16);
}

/* The white tab. box-shadow rather than a border so the tile keeps its exact
   size and the band does not jitter by 2px as you move between modules. */
.module-nav a.active,
.module-nav__drop > a.active {
    background: var(--surface);
    color: var(--brand-deep);
    text-shadow: none;
    box-shadow: 0 6px 16px -8px rgba(26, 20, 18, .45);
    font-weight: 700;
}
.module-nav a.active i { color: var(--brand); }

/* The unread badge rides the tile's top-right corner instead of pushing the
   label sideways — `ms-auto` in a column flex container did nothing useful. */
.module-nav a .notice-badge {
    position: absolute;
    top: .15rem; inset-inline-end: .5rem;
    margin: 0;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-deep) 70%, transparent);
}
.module-nav a.active .notice-badge { box-shadow: 0 0 0 2px var(--surface); }

/* Grouped platform items get a divider so agency and platform desks read as
   two families even in one row. On the orange band it is a white hairline. */
.module-nav .nav-split {
    width: 1px; background: rgba(255, 255, 255, .32);
    margin: .6rem .35rem;
    flex: 0 0 1px;
}

/* Modules with sub-screens (CRM) use a dropdown instead of a flat item. The
   trigger stays a normal nav tile — it still links to the module landing page —
   and hover or keyboard focus opens the child list. The 'crm' active key still
   lights the trigger on every sub-screen, exactly as it did with a flat item. */
.module-nav__drop {
    position: relative;
    display: flex;
    align-items: stretch;
}
.module-nav__drop-menu {
    position: absolute;
    top: calc(100% - .2rem); left: .35rem; z-index: 50;
    min-width: 210px;
    background: var(--popup);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-pop);
    padding: .35rem;
    display: none;
    flex-direction: column;
}

/*
  THE LAST GROUP OPENS LEFTWARDS.

  sidebar.html has always put .module-nav__drop--end on the Settings group, and
  nothing has ever styled it — so the rightmost tile in the band opened a 210px
  menu off the right edge of the viewport. body carries overflow-x:hidden, so
  there was no scrolling to it either: Settings, which is where Logout lives,
  was the one menu most likely to be unreachable.
*/
.module-nav__drop--end .module-nav__drop-menu {
    left: auto;
    right: .35rem;
}
.module-nav__drop:hover .module-nav__drop-menu,
.module-nav__drop:focus-within .module-nav__drop-menu,
.module-nav__drop.open .module-nav__drop-menu {
    display: flex;
}
/* Children are real text links, not icon tiles — explicitly undo the tile
   sizing/centering of .module-nav a. Higher specificity keeps these rules
   immune to the base rule below. */
.module-nav .module-nav__drop-menu a {
    display: flex; align-items: center; gap: .55rem;
    min-width: 0; width: 100%;
    padding: .5rem .7rem;
    flex-direction: row;
    font-size: .78rem; font-weight: 600;
    color: var(--ink);
    text-align: left;
    text-shadow: none;
    background: none;
    border-bottom: none;
    border-radius: 8px;
    box-shadow: none;
    white-space: nowrap;
}
.module-nav .module-nav__drop-menu a:hover {
    background: var(--popup-accent); color: var(--brand-deep);
}
.module-nav .module-nav__drop-menu a i { font-size: .95rem; color: var(--ink-faint); }
.module-nav .module-nav__drop-menu a:hover i { color: var(--brand); }

/*
  THE CHILD YOU ARE ON.

  Thirty-two child links across the six groups already have `active` appended by
  sidebar.html, and nothing has ever styled it — open Money while standing on
  /ledger and the menu gives no clue which of its seven screens you are looking
  at. The parent tile is a white tab in the band; its child needs the matching
  statement inside the menu, or the active state stops at the group.
*/
.module-nav .module-nav__drop-menu a.active {
    background: color-mix(in srgb, var(--brand) 10%, transparent);
    color: var(--brand-deep);
    font-weight: 700;
    box-shadow: inset 3px 0 0 var(--brand);
}
.module-nav .module-nav__drop-menu a.active i { color: var(--brand); }
/* Logout in the Settings drop is a POST form (CSRF), so it is a button styled
   to match the link tiles around it. It reads as destructive: red on hover. */
.module-nav .module-nav__drop-menu form button.module-nav__drop-logout {
    display: flex; align-items: center; gap: .55rem;
    width: 100%; padding: .5rem .7rem;
    background: none; border: 0; cursor: pointer;
    font: inherit; font-size: .78rem; font-weight: 600;
    color: var(--ink); text-align: left; white-space: nowrap;
    border-radius: 8px;
}
.module-nav .module-nav__drop-menu form button.module-nav__drop-logout i {
    font-size: .95rem; color: var(--ink-faint);
}
.module-nav .module-nav__drop-menu form button.module-nav__drop-logout:hover {
    background: var(--popup-accent); color: var(--bad);
}

/* The hamburger and everything it opens now live together in §RESPONSIVE —
   the old rules here only hid the header pills and never touched the module
   band, which was the actual wall of navigation on a phone. */

/* ============================================================================
   §CARDS
   ========================================================================= */
.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-card);
}
.card .card-body { padding: 1.25rem; }

/*
  THE THEME'S CARD PSEUDO-ELEMENT, AND WHY THE RADIUS BUMP NEEDED IT.

  assets/style.css draws a full-bleed backdrop layer behind every card:

      .card:before { content:""; position:absolute; inset:0; z-index:-1;
                     backdrop-filter: blur(var(--backdrop-blur));
                     border-radius: .75rem }

  That 12px is a LITERAL, not a token, so it cannot follow --radius. The moment
  the design's radius went past 12px the pseudo's squarer corners poked out
  beyond the card's own — and because the pseudo also carries a backdrop blur,
  the overhang rendered as four smeared corners rather than a flat sliver.
  .card appears in 98 templates, so this was every screen at once.

  inherit ties it to whatever --radius becomes next time. Dropping the blur is
  not tidying either: it is 98 cards of per-frame GPU readback that buys nothing
  over an opaque white surface, and it costs real scroll performance now that
  there is a fixed background image beneath it.
*/
.card::before,
.card-hover::before {
    border-radius: inherit;
    backdrop-filter: none;
}

/* Cards that are also links (route cards, hospital cards) lift on hover. */
.card-lift { transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.card-lift:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }

.page-head {
    max-width: var(--shell-max);
    margin-inline: auto;
    padding: 1.5rem var(--shell-pad) 0;
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
}
.page-head h1 {
    font-size: clamp(1.35rem, 1.1rem + .9vw, 1.75rem);
    font-weight: 800; letter-spacing: -.01em; margin: 0;
    color: var(--ink);
}
.page-head .crumb { font-size: .78rem; color: var(--ink-soft); margin-top: .2rem; }
.page-head .crumb a { color: var(--ink-soft); }
.page-head .crumb a:hover { color: var(--brand-deep); }
/* The reference puts a grey line of explanation under the title on the landing
   screens ("Find the best flights for your next journey"). */
.page-head .page-sub { font-size: .86rem; color: var(--ink-soft); margin: .25rem 0 0; }

/* ============================================================================
   §BUTTONS

   BOOTSTRAP CHANGES background-color; A GRADIENT IS A background-image.

   This is the trap that makes "just add a gradient" wrong. Bootstrap's button
   state model is entirely background-COLOR:

       .btn:hover                 { background-color: var(--bs-btn-hover-bg) }
       .btn:first-child:active    { background-color: var(--bs-btn-active-bg) }
       .btn:disabled              { background-color: var(--bs-btn-disabled-bg);
                                    opacity: .65 }

   Put a background-IMAGE on .btn-theme and every one of those becomes a visual
   no-op — the image paints over the colour. Hover does nothing, active does
   nothing, and a disabled Submit renders at full brand strength with 65%
   opacity, which on this design reads as "enabled, slightly faded". .btn-theme
   is in 84 templates, so every form in the product.

   So each state re-declares the IMAGE. And the disabled state drops the
   gradient for a flat desaturated fill rather than leaning on opacity, because
   a translucent orange over the new page canvas is still unmistakably orange.

   One more theme landmine: assets/style.css sets
       .btn-theme { --bs-btn-bg: color-mix(in srgb, var(--theme) 82.5%, transparent) }
   — a TRANSLUCENT brand. Any state that falls through to the theme's token
   lets the canvas artwork show through the button. Everything below is opaque.
   ========================================================================= */
.btn {
    border-radius: var(--radius-pill);
    font-weight: 600;
    transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease),
                background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
                color var(--dur) var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn-theme,
.btn-theme:visited {
    background-color: var(--brand-deep);   /* opaque floor under the image */
    background-image: var(--brand-band);
    border: 1px solid transparent;
    color: #fff;
    font-weight: 700;
    box-shadow: var(--shadow-brand);
}
.btn-theme:hover,
.btn-theme:focus,
.btn-theme:focus-visible {
    background-color: var(--brand-deep);
    background-image: var(--brand-band);
    border-color: transparent;
    color: #fff;
    filter: brightness(1.07);
}
.btn-theme:active,
.btn-theme:first-child:active,
.btn-theme.active,
.btn-theme.show {
    background-color: var(--brand-deep);
    background-image: var(--brand-band);
    border-color: transparent;
    color: #fff;
    filter: brightness(.93);
    box-shadow: none;
}
/* Flat and desaturated, NOT the gradient at 65% — see the header note. */
.btn-theme:disabled,
.btn-theme.disabled,
fieldset:disabled .btn-theme {
    background-color: color-mix(in srgb, var(--brand) 26%, var(--line-strong));
    background-image: none;
    border-color: transparent;
    color: #fff;
    box-shadow: none;
    opacity: 1;
    filter: none;
}

/* The outlined counterpart — "My Bookings", "All visas", every secondary
   action in the reference. */
.btn-outline-secondary {
    --bs-btn-color: var(--ink);
    --bs-btn-border-color: var(--line-strong);
    --bs-btn-bg: var(--surface);
    --bs-btn-hover-color: var(--brand-deep);
    --bs-btn-hover-bg: color-mix(in srgb, var(--brand) 7%, var(--surface));
    --bs-btn-hover-border-color: var(--brand);
    --bs-btn-active-color: var(--brand-deep);
    --bs-btn-active-bg: color-mix(in srgb, var(--brand) 12%, var(--surface));
    --bs-btn-active-border-color: var(--brand);
}

.btn-dark-pill {
    background: var(--accent); color: #fff;
    border: 1px solid var(--accent);
    font-weight: 600;
}
.btn-dark-pill:hover {
    background: var(--brand-deep); border-color: var(--brand-deep); color: #fff;
}

/*
  THE HAMBURGER.

  Two theme rules gang up on it. `button { padding: 0 }` at (0,0,1) leaves it
  with no hit area, and `button:focus, button:active { outline: none }` at
  (0,1,1) beats this file's global `:focus-visible` at (0,1,0) — so the control
  that opens the entire menu on a phone had neither a tap target nor a keyboard
  focus ring. Both are fixed OUTSIDE any media query, because the element is
  focusable at every width even when it is not displayed at every width.
*/
.js-nav-toggle {
    min-width: var(--tap);
    min-height: var(--tap);
    display: none;                       /* shown by §RESPONSIVE */
    align-items: center;
    justify-content: center;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    border-radius: var(--radius-pill);
    color: var(--ink);
    padding: .35rem .7rem;
}
.js-nav-toggle i { font-size: 1.35rem; }
.js-nav-toggle:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}

/* ---------------------------------------------------------------- typography */
.fs-7 { font-size: .85rem; }
.fs-8 { font-size: .74rem; }

/*
  .text-brand HAD NO RULE AT ALL.

  Six templates use it — the visa catalogue's stay/validity line, the visa
  product's Total cost, the notice board, the fare dialog — and it was defined
  in neither app.css, erp-forms.css nor the compiled theme. So the single most
  prominent number on the visa detail page, the one the reference design sets
  in large orange, has been rendering in default body ink.

  --brand-deep rather than --brand: this class is used on TEXT, and the raw
  brand on white is 3.34:1. The deep shade is 4.6:1 and the same colour to the
  eye. .text-brand-vivid is there for the rare large display figure where the
  full brand is wanted and the size earns it.
*/
.text-brand       { color: var(--brand-deep) !important; }
.text-brand-vivid { color: var(--brand) !important; }
.bg-brand-soft    { background: var(--brand-soft); }
.border-brand     { border-color: var(--brand) !important; }

.display-word {
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
}

/* ------------------------------------------------------------------- money */
/* Tabular numerals or columns of figures wobble. */
.amt, .num, .stat-value { font-variant-numeric: tabular-nums; }

.amt      { font-weight: 600; white-space: nowrap; }
.amt-ccy  { font-size: .72em; color: var(--ink-soft); margin-inline-end: .15em; }
.amt.neg, .amt.neg .amt-val { color: var(--bad); }

.table .num, .table th.num { text-align: end; white-space: nowrap; }
.table .narration {
    max-width: 34ch; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
}

/* ---------------------------------------------------------- traveller page */
.traveller-toolbar-search .form-control {
    width: clamp(14rem, 26vw, 20rem);
}

.traveller-toolbar-add {
    min-width: 9.5rem;
}

.traveller-list-card .card-body {
    padding: .9rem;
}

.traveller-table {
    --bs-table-bg: transparent;
}

.traveller-table thead th {
    font-size: .72rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    border-bottom-width: 1px;
}

.traveller-table tbody td {
    padding-top: .72rem;
    padding-bottom: .72rem;
}

.traveller-pill-pax {
    min-width: 3rem;
    text-align: center;
}

.traveller-action-btn {
    width: 2rem;
    height: 2rem;
    display: inline-grid;
    place-items: center;
    padding: 0;
}

.traveller-form .modal-body {
    background: linear-gradient(180deg, color-mix(in srgb, var(--bg) 55%, #fff) 0%, #fff 24rem);
}

.traveller-scan-card {
    border: 1px solid var(--line);
    border-radius: calc(var(--radius) - 2px);
    background: var(--surface);
    padding: .85rem .9rem;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--line) 56%, transparent);
}

.traveller-scan-card__head {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin-bottom: .55rem;
    color: var(--ink);
}

.traveller-scan-card__head > i {
    color: color-mix(in srgb, var(--brand) 70%, var(--accent));
    font-size: 1rem;
}

.traveller-scan-input-group .btn {
    min-width: 6rem;
    font-weight: 600;
}

.traveller-scan-status {
    min-height: 1.15rem;
}

.traveller-form-section {
    margin-top: .4rem;
    font-size: .68rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-soft);
    font-weight: 700;
}

@media (max-width: 767.98px) {
    .traveller-toolbar-search .form-control {
        width: 12rem;
    }
    .traveller-list-card .card-body {
        padding: .65rem;
    }
    .traveller-scan-input-group {
        flex-direction: column;
        gap: .45rem;
    }
    .traveller-scan-input-group .btn {
        width: 100%;
    }
}

/* ------------------------------------------------------------------- pills */
.pill {
    display: inline-block;
    padding: .18rem .6rem;
    border-radius: var(--radius-pill);
    font-size: .68rem; font-weight: 700; letter-spacing: .03em;
    background: var(--bg); color: var(--ink-soft);
    border: 1px solid var(--line-strong);
}
.pill + .pill { margin-inline-start: .25rem; }

.pill.APPROVED, .pill.ACTIVE, .pill.VERIFIED, .pill.TICKETED, .pill.PAID, .pill.OPEN
    { background: #DCF3E6; color: #14603A; border-color: #BCE5CE; }
.pill.PENDING, .pill.SUBMITTED, .pill.UNDER_REVIEW, .pill.IN_PROCESS, .pill.HOLD,
.pill.CALCULATED, .pill.DRAFT, .pill.warn, .pill.CLOSED
    { background: var(--warn-bg); color: #7A5A12; border-color: #EAD9A8; }
.pill.REJECTED, .pill.SUSPENDED, .pill.CANCELLED, .pill.EXPIRED, .pill.REVERSED,
.pill.FAILED, .pill.LOCKED
    { background: #F9E2DC; color: #8E2F1C; border-color: #EFC4B8; }

/* ---------------------------------------------------------------- stats */
.stat-card .stat-label { font-size: .74rem; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
.stat-card .stat-value { font-size: 1.45rem; font-weight: 800; margin-top: .15rem; }
.stat-brand { background: var(--brand-band); border-color: transparent; box-shadow: var(--shadow-brand); }
.stat-brand .stat-label, .stat-brand .stat-value { color: #fff; }
/* The currency prefix is .amt-ccy, which is --ink-soft — a grey chosen for
   white cards. On the brand tile it all but disappeared, so "BDT 381,910" read
   as a bare number. Same for any muted text that lands on this card. */
.stat-brand .amt-ccy,
.stat-brand .text-body-secondary { color: rgba(255, 255, 255, .82) !important; }

/* ---------------------------------------------------------------- hero */
/*
  The "Travel With Us / DISCOVER THE WORLD" block.

  EVERY STOP NOW DERIVES FROM --brand. It used to interpolate two hardcoded
  amber literals (#FCD289 → var(--brand) → #C97F1D) around the brand, which is
  fine while the brand IS amber and incoherent the moment it is not: the same
  gradient on #FF4D00 ran gold → orange-red → dark amber, three unrelated hues
  in one panel. color-mix keeps the light and dark ends as tints and shades of
  whatever the tenant actually stores.

  overflow is VISIBLE, not hidden. The dashboard renders the flight-search
  widget inside this block, and that widget's passenger picker is an absolutely
  positioned panel that drops below its trigger — `overflow: hidden` clipped it
  away entirely, so the cabin/traveller selector could not be opened from the
  dashboard at any width. The rounded corners are kept by clipping the DECOR
  (::after) instead of the whole box; the same trap the module band documents.
*/
.hero {
    border-radius: var(--radius);
    overflow: visible;
    background:
        radial-gradient(120% 90% at 85% 10%, rgba(255, 255, 255, .26), transparent 55%),
        linear-gradient(165deg,
            color-mix(in srgb, var(--brand) 78%, #FFB077) 0%,
            var(--brand) 46%,
            var(--brand-deep) 100%);
    color: #fff;
    padding: 2.2rem 1.6rem 1.6rem;
    position: relative;
}
/*
  The same skyline watermark the visa heroes carry, so the two biggest painted
  surfaces in the product read as one design rather than two. It lives on
  ::after — WITH the bottom rounding — because .hero itself must stay
  overflow:visible: the flight-search widget it contains has an absolutely
  positioned passenger panel that drops below its trigger, and overflow:hidden
  clipped that panel away entirely, so the cabin/traveller selector could not be
  opened from the dashboard at any width.

  z-index 0 rather than -1: a negative index would put it behind the .hero's own
  opaque gradient and render nothing, which is exactly how the first version of
  this watermark disappeared on the visa panels.
*/
.hero::after {
    content: "";
    position: absolute; inset-inline: 0; bottom: 0; height: 62%;
    border-end-start-radius: var(--radius);
    border-end-end-radius: var(--radius);
    background: url("/img/hero-skyline.svg") center bottom / cover no-repeat;
    pointer-events: none;
    z-index: 0;
}
/* Content above the watermark. .hero-search is already z-index 1. */
.hero > * { position: relative; z-index: 1; }
.hero h1 { font-size: clamp(1.6rem, 4vw, 2.6rem); font-weight: 800; letter-spacing: .1em; }
.hero .kicker { font-weight: 600; letter-spacing: .3em; font-size: .8rem; text-transform: uppercase; opacity: .85; }

.hero-search {
    position: relative; z-index: 1;
    background: var(--surface);
    border-radius: var(--radius);
    box-shadow: var(--shadow-pop);
    padding: 1rem;
    color: var(--ink);
    margin-top: 1.25rem;
}

.trip-tabs { display: inline-flex; background: var(--bg); border-radius: var(--radius-pill); padding: .25rem; gap: .25rem; }
.trip-tabs label {
    padding: .35rem 1rem; border-radius: var(--radius-pill);
    font-size: .8rem; font-weight: 700; cursor: pointer; color: var(--ink-soft);
    margin-bottom: 0;
}
.trip-tabs input { display: none; }
/* The reference paints the selected segment ORANGE, not near-black. It used to
   read var(--accent), which is why the "One Way" pill stayed black through the
   brand change — and on tenant PT, whose accent is itself an orange, black was
   never what it rendered anyway. */
.trip-tabs input:checked + label {
    background: var(--brand-band); color: #fff; box-shadow: var(--shadow-brand);
}
.trip-tabs label.disabled { opacity: .45; cursor: not-allowed; }

.swap-btn {
    border: 1px solid var(--line-strong); background: var(--surface);
    border-radius: 50%; width: 34px; height: 34px;
    display: grid; place-items: center; align-self: end; margin-bottom: .35rem;
}
.swap-btn:hover { background: var(--brand); color: var(--on-brand); border-color: var(--brand); }

/* ---------------------------------------------------------------- offers */
.offer-rail { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(240px, 1fr);
              gap: 1rem; overflow-x: auto; padding-bottom: .5rem; scroll-snap-type: x mandatory; }
.offer-card {
    scroll-snap-align: start;
    border-radius: var(--radius);
    padding: 1.1rem;
    color: #fff;
    min-height: 130px;
    display: flex; flex-direction: column; justify-content: space-between;
    background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 78%, #6B6058), var(--accent));
}
.offer-card.alt  { background: linear-gradient(150deg, var(--brand), var(--brand-deep)); color: #fff; }
.offer-card h3   { font-size: 1rem; font-weight: 800; margin: 0; }
.offer-card .tag { font-size: .68rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; opacity: .8; }

/* ------------------------------------------------------- recent searches */
.recent-row {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: .8rem 1rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.recent-row + .recent-row { margin-top: .6rem; }
.recent-route { font-weight: 700; }
.recent-meta  { font-size: .76rem; color: var(--ink-soft); }

/* ---------------------------------------------------------- date strip */
.date-strip { display: flex; gap: .5rem; overflow-x: auto; padding-bottom: .25rem; }
.date-strip form { margin: 0; }
.date-tab {
    border: 1px solid var(--line-strong);
    background: var(--surface);
    border-radius: 10px;
    padding: .45rem .9rem;
    text-align: center;
    font-size: .74rem; font-weight: 600; color: var(--ink-soft);
    min-width: 96px;
    cursor: pointer;
}
.date-tab .d { display: block; font-size: .86rem; color: var(--ink); font-weight: 700; }
.date-tab:hover { border-color: var(--brand); }
.date-tab.on {
    background: var(--brand-deep); color: #fff; border-color: var(--brand-deep);
}
.date-tab.on .d { color: #fff; }

/* -------------------------------------------------------- airline strip */
.airline-strip { display: flex; gap: .5rem; overflow-x: auto; }
.js-airline-chip {
    border: 1px solid var(--line-strong);
    background: var(--surface);
    border-radius: 10px;
    padding: .4rem .8rem;
    font-size: .74rem;
    display: flex; flex-direction: column; align-items: center; gap: .05rem;
    min-width: 108px; cursor: pointer;
}
.js-airline-chip .al   { font-weight: 700; color: var(--ink); }
.js-airline-chip .min  { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.js-airline-chip:hover { border-color: var(--brand); }
.js-airline-chip.on    { background: var(--brand-deep); border-color: var(--brand-deep); }
.js-airline-chip.on .al, .js-airline-chip.on .min { color: var(--on-brand); }

/* ---------------------------------------------------------- filter rail */
.filter-rail .card-body { padding: 1rem; }
.filter-rail h4 {
    font-size: .72rem; font-weight: 800; letter-spacing: .06em;
    text-transform: uppercase; color: var(--ink-soft);
    margin: 1rem 0 .5rem;
}
.filter-rail h4:first-child { margin-top: 0; }
.filter-rail .form-check-label { font-size: .82rem; }

input[type="range"].form-range::-webkit-slider-thumb { background: var(--brand); }
input[type="range"].form-range::-moz-range-thumb     { background: var(--brand); border: 0; }

/* ---------------------------------------------------------- flight card */
.flight-card {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface);
    box-shadow: var(--shadow-card);
}
.flight-card + .flight-card { margin-top: 1rem; }

.flight-card__band {
    background: var(--brand);
    color: var(--on-brand);
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    padding: .6rem 1rem;
}
.flight-card__airline { font-weight: 800; letter-spacing: .04em; }
.flight-card__fares   { display: flex; align-items: center; gap: 1.1rem; }
.fare-label { font-size: .66rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; opacity: .8; display: block; }
.fare-value { font-weight: 800; font-size: 1.02rem; font-variant-numeric: tabular-nums; }

.flight-card__body { display: grid; grid-template-columns: 1fr 220px; }
@media (max-width: 991.98px) { .flight-card__body { grid-template-columns: 1fr; } }

.segments { padding: 1rem; }
.seg {
    display: grid;
    grid-template-columns: 90px 1fr 90px;
    gap: .75rem; align-items: center;
    padding: .55rem 0;
}
.seg + .seg { border-top: 1px dashed var(--line); }
.seg .port  { font-weight: 800; font-size: 1.05rem; }
.seg .t     { font-size: .74rem; color: var(--ink-soft); }
.seg .route-line {
    position: relative; height: 2px; background: var(--line-strong); border-radius: 1px;
}
.seg .route-line::after {
    content: "\2708";   /* plane glyph rides the line — no image asset */
    position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -55%);
    background: var(--surface);
    padding-inline: .3rem;
    color: var(--brand); font-size: .9rem;
}
.seg .dir-tag {
    font-size: .62rem; font-weight: 800; letter-spacing: .1em;
    color: var(--brand); text-transform: uppercase;
}

.flight-card__side {
    border-inline-start: 1px solid var(--line);
    padding: 1rem;
    display: flex; flex-direction: column; gap: .5rem; justify-content: center;
    background: linear-gradient(180deg, var(--surface), #FBF8F1);
    font-size: .78rem;
}
@media (max-width: 991.98px) { .flight-card__side { border-inline-start: 0; border-top: 1px solid var(--line); } }
.flight-card__side .kv { display: flex; justify-content: space-between; gap: .5rem; }
.flight-card__side .kv b { font-weight: 700; }

/* Net figures exist in the DOM but are only visible in "agent mode". The
   default is hidden so a page opened in front of a customer starts safe;
   the toggle in the results toolbar flips body.show-net. */
.net-only { display: none; }
body.show-net .net-only { display: revert; }

/* ---------------------------------------------------------------- switch */
.net-switch { display: inline-flex; align-items: center; gap: .5rem; font-size: .8rem; font-weight: 600; }
.net-switch .form-check-input { width: 2.4em; height: 1.3em; }
.net-switch .form-check-input:checked { background-color: var(--accent); border-color: var(--accent); }

/* ---------------------------------------------------------------- login */
.auth-shell {
    min-height: 100vh;
    display: grid;
    grid-template-rows: 1fr auto;
    background: var(--bg);
}
.auth-split {
    max-width: 1080px; width: 100%;
    margin: 3rem auto;
    display: grid; grid-template-columns: 1.1fr 1fr;
    background: var(--surface);
    border-radius: 22px;
    overflow: hidden;
    box-shadow: var(--shadow-pop);
}
@media (max-width: 991.98px) { .auth-split { grid-template-columns: 1fr; margin: 1rem; } }

.auth-art {
    position: relative;
    background:
        radial-gradient(90% 70% at 20% 15%, rgba(255, 255, 255, .5), transparent 50%),
        linear-gradient(160deg,
            color-mix(in srgb, var(--brand) 55%, #FFD9B0),
            var(--brand) 55%,
            var(--brand-deep));
    color: #fff;
    padding: 2.5rem;
    display: flex; flex-direction: column; justify-content: space-between;
    min-height: 420px;
    /* The same skyline watermark the visa heroes carry, so the login screen
       belongs to the same product. Drawn white-on-transparent, so it composites
       over whatever brand the tenant stores. */
    background-blend-mode: normal;
}
.auth-art::after {
    content: "";
    position: absolute; inset-inline: 0; bottom: 0; height: 55%;
    background: url("/img/hero-skyline.svg") center bottom / cover no-repeat;
    opacity: .9;
    pointer-events: none;
}
.auth-art > * { position: relative; z-index: 1; }
.auth-art .sun {
    position: absolute; top: 2rem; right: 2.5rem;
    width: 70px; height: 70px; border-radius: 50%;
    background: radial-gradient(circle at 35% 35%,
                #FFFFFF, color-mix(in srgb, var(--brand) 22%, #FFFFFF) 70%);
    box-shadow: 0 0 60px 12px rgba(255, 255, 255, .45);
}
.auth-art .plane {
    font-size: 3.2rem;
    transform: rotate(-18deg);
    align-self: flex-start;
    margin-top: 3rem;
    text-shadow: 0 8px 18px color-mix(in srgb, var(--brand-deep) 55%, transparent);
}
.auth-art h2 { font-weight: 800; letter-spacing: .08em; }

.auth-form { padding: 2.5rem; display: flex; flex-direction: column; justify-content: center; }
.auth-form .form-control { border-radius: 10px; padding-block: .65rem; }

.pay-strip { display: flex; gap: .5rem; flex-wrap: wrap; }
/* Named text chips, NOT the providers' logos — those are their marks to
   licence, and a text badge communicates "we settle by bKash/Nagad/Rocket"
   just as clearly. */
.pay-chip {
    border: 1px solid var(--line-strong);
    border-radius: 8px;
    padding: .25rem .6rem;
    font-size: .7rem; font-weight: 800; letter-spacing: .02em;
    background: var(--surface); color: var(--ink-soft);
}

.auth-footer {
    background: var(--accent); color: #CFC9BC;
    font-size: .78rem;
    padding: 1rem;
    text-align: center;
}
.auth-footer a { color: #fff; text-decoration: none; margin-inline: .6rem; }
.auth-footer a:hover { color: var(--brand); }

/* ---------------------------------------------------------------- footer */
.site-foot {
    background: var(--accent);
    color: #CFC9BC;
    margin-top: 3rem;
}
.site-foot__inner {
    max-width: var(--shell-max); margin-inline: auto;
    padding: 2.2rem 1rem 1.2rem;
    display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 2rem;
}
@media (max-width: 767.98px) { .site-foot__inner { grid-template-columns: 1fr; } }
.site-foot h4 { color: #fff; font-size: .82rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; margin-bottom: .8rem; }
.site-foot a { color: #CFC9BC; text-decoration: none; display: block; padding-block: .18rem; font-size: .82rem; }
.site-foot a:hover { color: var(--brand); }
.site-foot .legal {
    border-top: 1px solid #2C2A24;
    margin-top: 1.6rem; padding-top: 1rem;
    font-size: .74rem; display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
}

/* ---------------------------------------------------------------- misc */
.bank-card.on { outline: 2px solid var(--brand); }

.alert { border-radius: var(--radius); }

.table > :not(caption) > * > * { padding: .55rem .6rem; }
.table thead th {
    font-size: .7rem; font-weight: 800; letter-spacing: .05em;
    text-transform: uppercase; color: var(--ink-soft);
    border-bottom: 2px solid var(--line-strong);
}

/* Budget grid editor (budget-edit). A fixed account column over a wide
   horizontal scroll — without it the code/name scrolls off with the months.
   `position: sticky` with a solid background is enough; the table is inside a
   .table-responsive viewport so the column pins against that. */
.budget-grid th:first-child,
.budget-grid td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
    min-width: 220px;
    white-space: nowrap;
}
.budget-grid th:first-child { z-index: 2; }
.budget-grid .grid-input { min-width: 86px; }
.budget-grid td:not(:first-child) { min-width: 92px; }

/* ------------------------------------------------------------ form chrome */
/* The theme was authored for a DARK shell, and two of its control glyphs are
   still drawn in white. On this cream/white design they are invisible:

     .form-select  --bs-form-select-bg-img  stroke='%23fff'   white chevron on
                                                              a white field
     .form-check-input:checked[type=checkbox]
                   --bs-form-check-bg-image: none             checked and
                   background-color: transparent              unchecked differ
                                                              only by border
                                                              colour — no tick

   The second one matters beyond looks. settings-roles renders the permission
   set as checkboxes; "which permissions are actually on" cannot be a hairline
   colour difference on a screen that decides who may price a refund. */

.form-select {
    --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%235F5A4E' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

.form-check-input {
    border-width: 1.5px;
    border-color: var(--line-strong);
    background-color: var(--surface);
}
.form-check-input:checked {
    background-color: var(--brand);
    border-color: var(--brand);
}
/*
  THE TICK AND THE DOT ARE NOW WHITE.

  Both are inline SVGs inside a data-URI, so the colour is URL-ENCODED —
  `%23FFFFFF`, not `#FFFFFF`. That is worth saying out loud: a search for the
  old `#201603` found neither of these, which is exactly how they survived the
  brand change that made them wrong. They sit on `background-color: var(--brand)`
  above, and near-black on #FF4D00 reads as a smudge rather than a tick.

  This is not only cosmetic. settings-roles renders the whole permission set as
  checkboxes, and "which permissions are actually on" cannot be a colour
  difference nobody can resolve at 16px on a screen that decides who may price
  a refund.
*/
.form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
.form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23FFFFFF'/%3e%3c/svg%3e");
}
.form-check-label { font-size: .82rem; }

.form-control, .form-select { border-radius: var(--radius-sm); border-color: var(--line-strong); }
/* The focus ring was the amber triplet written out longhand — the single
   highest-frequency stale literal in the file, since it is on every input and
   select in the application. color-mix keeps it on whatever the brand is. */
.form-control:focus, .form-select:focus {
    border-color: var(--brand);
    box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--brand) 22%, transparent);
}
.form-label { font-size: .76rem; font-weight: 700; color: var(--ink-soft); }

/* ---------------------------------------------------------------- modals */
/* Scroll lock. Bootstrap 5 applies .modal-open from its JavaScript, and its
   JavaScript is not in this project — so the class was being set by app.js
   and styled by nothing at all, and the page kept scrolling behind an open
   dialog. --sbw is the scrollbar width app.js measures before locking;
   without the padding swap the whole layout jumps sideways as the dialog
   opens, which reads as a glitch. */
body.modal-open {
    overflow: hidden;
    padding-right: var(--sbw, 0px);
}
body.modal-open .site-head { padding-right: var(--sbw, 0px); }

.modal-backdrop {
    --bs-backdrop-bg: rgba(23, 21, 15, .48);
    backdrop-filter: blur(2px);
}

.modal-content {
    border-radius: var(--radius);
    border: 1px solid var(--line);
    box-shadow: var(--shadow-pop);
}

.modal-header {
    border-bottom: 1px solid var(--line);
    background: var(--surface);
    border-start-start-radius: var(--radius);
    border-start-end-radius: var(--radius);
}
.modal-title { font-weight: 700; color: var(--ink); letter-spacing: .01em; }

/* The footer is the pinned edge of a capped dialog, so it needs its own
   surface — a transparent footer over a scrolling body looks like the
   buttons are floating in the form. */
.modal-footer {
    border-top: 1px solid var(--line);
    background: var(--surface);
    border-end-start-radius: var(--radius);
    border-end-end-radius: var(--radius);
}

/* Field grids inside a dialog breathe a little more than in a table row —
   these are the screens where an agent types a passport number. */
.modal-body .row { --bs-gutter-y: .9rem; }

/* ------------------------------------------------------ accessibility */
/* erp-forms.css v2 painted every keyboard focus ring with --bs-primary,
   the theme's inherited blue — the one colour this design says survives
   nowhere. Same floor, brand colours, declared here so it sits with the
   rest of the palette instead of in a file that no longer owns any. */
:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: 3px;
}
.btn:focus-visible,
.pill-nav a:focus-visible,
.module-nav a:focus-visible { outline-offset: 3px; }

/* Keyboard users need to see where they landed inside a scrolling dialog. */
.modal-body :focus-visible { scroll-margin-block: 1rem; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================================
   §RESPONSIVE
   ----------------------------------------------------------------------------
   ONE BREAKPOINT SCALE. This file previously carried seven ad-hoc widths —
   575, 720, 767, 767.98, 860, 991, 1199 — which meant components changed shape
   at seven different moments and a layout could be mid-transition in three of
   them at once. Everything below is on Bootstrap's own scale, because the grid
   the templates use is already on it:

       ≤ 575.98   phone            one column, drawer nav, stacked toolbars
       ≤ 767.98   large phone      two-up grids, table scroll affordance
       ≤ 991.98   tablet           drawer nav, filter disclosure, sidebar stacks
       ≥ 992      desktop          the reference design as drawn

   THE MOBILE NAV IS A DRAWER, AND THAT IS A DELIBERATE CHOICE OVER A SCROLLING
   BAR. The band renders up to nineteen permission-gated tiles; at 360px they
   wrapped into five or six rows — roughly 280px of navigation above the fold,
   on every screen. The obvious fix is overflow-x:auto, and it is the one thing
   that must not be done here: an auto axis forces the other axis to auto, which
   clips the absolutely-positioned group menus, and that bug has already been
   found and fixed once in this file (see §MODULE BAND).

   So below 992px the band becomes an off-canvas panel in which NOTHING is
   absolutely positioned — the groups render as inline, always-open sections.
   No clipping to design around, no hover to depend on (touch has no hover, and
   hover is how those menus open on desktop), and every row gets a full-width
   44px target. It needs no markup change: the same .module-nav element becomes
   the scrim and .module-nav__inner becomes the panel.

   TOUCH TARGETS. WCAG 2.5.8 asks 24px and 2.5.5 asks 44px; this design takes
   44 (--tap). The audit found the controls that mattered were at half that:
   the date steppers 22px, the passenger steppers 28-29px, the filter reset
   ~11px. Those are the +1 day and the adult-count controls on the primary
   search form — a mis-tap there changes what the agent is quoting.

   iOS SAFARI ZOOMS any focused field under 16px, and every input in this
   application is 13px (erp-forms.css). The zoom is not a nuisance: it leaves
   the page scaled and off-centre after every field. Fields go to 16px on touch
   widths, which costs nothing because the form is one column there anyway.
   ========================================================================= */

/* ---- ≤ 991.98: the drawer takes over ------------------------------------ */
@media (max-width: 991.98px) {

    .js-nav-toggle { display: inline-flex; }

    /* The header collapses to two rows: brand + hamburger, then the desk pills
       as a scrollable strip. The pills are a flat list with no popovers, so
       scrolling them is safe — the trap is only ever about the group menus. */
    .site-head__inner { gap: .5rem; padding: .5rem var(--shell-pad); }
    .brand-agent { display: none; }

    .pill-nav {
        order: 10;
        width: 100%;
        margin-inline: 0;
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-inline: contain;
        scrollbar-width: none;
        padding-bottom: .15rem;
        gap: .4rem;
    }
    .pill-nav::-webkit-scrollbar { display: none; }
    .pill-nav a { flex: 0 0 auto; min-height: var(--tap); }

    /* ---- .module-nav becomes the scrim ---- */
    .module-nav {
        position: fixed;
        inset: 0;
        z-index: 1100;
        background: rgba(26, 20, 18, .5);
        box-shadow: none;
        /* visibility, not display: the panel keeps its transition, and the
           drawer stays out of the tab order while closed. */
        visibility: hidden;
        opacity: 0;
        transition: opacity .2s var(--ease), visibility .2s var(--ease);
    }
    body.nav-open .module-nav { visibility: visible; opacity: 1; }

    /* ---- .module-nav__inner becomes the panel ---- */
    .module-nav__inner {
        width: min(330px, 88vw);
        height: 100%;
        max-width: none;
        margin: 0;
        padding: .75rem .6rem calc(.75rem + env(safe-area-inset-bottom));
        flex-direction: column;
        flex-wrap: nowrap;
        align-items: stretch;
        gap: .1rem;
        background: var(--brand-band);
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
        box-shadow: var(--shadow-pop);
        transform: translateX(-100%);
        transition: transform .22s var(--ease);
    }
    body.nav-open .module-nav__inner { transform: translateX(0); }

    /* Rows, not tiles: icon beside label, full width, 44px tall. */
    .module-nav a {
        flex-direction: row;
        justify-content: flex-start;
        gap: .7rem;
        min-width: 0;
        width: 100%;
        min-height: var(--tap);
        padding: .55rem .75rem;
        font-size: .88rem;
        white-space: normal;
    }
    .module-nav a i { font-size: 1.3rem; width: 1.5rem; text-align: center; flex: none; }
    .module-nav a .notice-badge { position: static; margin-inline-start: auto; }

    .module-nav .nav-split {
        width: auto; height: 1px; flex: none;
        margin: .5rem .75rem;
        background: rgba(255, 255, 255, .28);
    }

    /* Groups render inline and ALWAYS OPEN. Nothing is absolutely positioned
       in the drawer, which is the whole point of using one. */
    .module-nav__drop { flex-direction: column; }
    .module-nav__drop-menu,
    .module-nav__drop:hover .module-nav__drop-menu,
    .module-nav__drop:focus-within .module-nav__drop-menu,
    .module-nav__drop.open .module-nav__drop-menu {
        position: static;
        display: flex;
        min-width: 0;
        width: 100%;
        background: rgba(255, 255, 255, .1);
        border: 0;
        border-radius: var(--radius-sm);
        box-shadow: none;
        padding: .2rem;
        margin: .1rem 0 .3rem;
    }
    .module-nav .module-nav__drop-menu a,
    .module-nav .module-nav__drop-menu form button.module-nav__drop-logout {
        color: #fff;
        min-height: var(--tap);
        padding-inline-start: 2.2rem;   /* indented under its group */
        font-size: .84rem;
        white-space: normal;
    }
    .module-nav .module-nav__drop-menu a i,
    .module-nav .module-nav__drop-menu form button.module-nav__drop-logout i { color: rgba(255,255,255,.75); }
    .module-nav .module-nav__drop-menu a:hover { background: rgba(255,255,255,.16); color: #fff; }
    .module-nav .module-nav__drop-menu a.active {
        background: var(--surface); color: var(--brand-deep); box-shadow: none;
    }
    .module-nav .module-nav__drop-menu a.active i { color: var(--brand); }
    /* The group's own trigger is a heading in here, not a link to nowhere. */
    .module-nav__drop > a { font-weight: 700; }

    /* The page must not scroll under an open drawer. */
    body.nav-open { overflow: hidden; }

    /* The results filter rail stacks above the results at this width, so it is
       collapsed behind a summary the user opens on purpose. */
    .filter-rail__toggle { display: flex; }
    .filter-rail[data-collapsed="true"] .filter-rail__body { display: none; }
}

/* Desktop keeps the disclosure control out of the way entirely. */
.filter-rail__toggle { display: none; }
@media (min-width: 992px) {
    .filter-rail .filter-rail__body { display: block !important; }
}

/* ---- ≤ 767.98: phone and large phone ------------------------------------ */
@media (max-width: 767.98px) {
    :root { --shell-pad: .75rem; --radius: 14px; --radius-sm: 10px; }

    /* A fixed background never composited well on iOS and janks the scroll of
       a long list. The artwork is subtle enough to scroll with the page. */
    body { background-attachment: scroll; }

    #content, #content:not(.content-column) { padding: 1rem var(--shell-pad) 2.5rem; }
    .page-head { padding: 1.1rem var(--shell-pad) 0; }
    .card .card-body { padding: 1rem; }
    .hero { padding: 1.5rem 1.1rem 1.2rem; }

    /* ---- 16px fields: the iOS zoom fix, and a 44px floor ---- */
    .form-control, .form-select, .form-control-sm, .form-select-sm,
    .date-field__input, input[type="search"], input[type="text"],
    input[type="number"], input[type="date"], input[type="time"],
    input[type="password"], input[type="email"], textarea, select {
        font-size: 16px;
        min-height: var(--tap);
    }
    textarea { min-height: 5rem; }

    /* ---- the half-size controls the audit measured ---- */
    /*
      display:inline-flex is doing real work here, not tidying. The compiled
      theme declares `button { text-align: left; padding: 0 }`, and these
      controls relied on being small enough that a left-aligned glyph still
      looked centred. Grown to 44px the minus and plus sat hard against the
      left edge of a 44px black square — measured on the visa product's
      passenger steppers at 390px. Centring them explicitly is the only thing
      that survives the theme.
    */
    .date-step,
    .stepper button,
    .pax-stepper button,
    .traveller-action-btn,
    .leg-remove,
    .swap-btn {
        min-width: var(--tap);
        min-height: var(--tap);
        width: var(--tap);
        height: var(--tap);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        padding: 0;
        font-size: 1.05rem;
    }
    .date-field__steps { gap: .4rem; }
    .stepper input, .pax-stepper input { width: 3rem; font-size: 1rem; }

    /* A .7rem underlined word with zero padding was an ~11px tap target. */
    .filter-reset, .fare-details-link {
        min-height: var(--tap);
        display: inline-flex;
        align-items: center;
        padding-inline: .25rem;
    }

    .btn { min-height: var(--tap); }
    .btn-sm { min-height: 2.4rem; }
    /* A toolbar of pills that each take a whole row reads better than four
       half-rows of truncated labels. */
    .page-head .btn { flex: 1 1 auto; justify-content: center; }

    /* ---- tables ----
       These stay SCROLLING rather than becoming stacked cards, and that is a
       decision rather than an omission. The widest screens here are the ledger,
       the trial balance and the payroll run: their value is that the columns
       line up, so a figure can be compared down the page and across the row.
       Stacked cards destroy exactly that, and they triple the height of a
       200-row statement. What was actually missing was the affordance — no
       indication that there is more to the right — and a row tall enough to
       tap. Both are below. */
    /*
      A hint that the table continues past the edge.

      The textbook version of this uses four layers — two `local` cover strips
      in the surface colour that scroll WITH the content to mask the shadows at
      the extremes, plus two `scroll` shadows pinned to the scrollport. It does
      not work here: the table's own cells are opaque, so the cover strips only
      ever show in the gap to the RIGHT of a narrow table, where they render as
      a solid white bar sitting on top of the last column. Measured on /ledger
      at 390px.

      Two pinned shadows, no cover strips. They show even when scrolled fully to
      one end, which at 8% is a soft edge rather than a lie.
    */
    .table-responsive {
        position: relative;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior-inline: contain;
        background:
            linear-gradient(to right, rgba(26, 20, 18, .08), transparent) left  / .7rem 100% no-repeat scroll,
            linear-gradient(to left,  rgba(26, 20, 18, .08), transparent) right / .7rem 100% no-repeat scroll;
    }
    .table > :not(caption) > * > * { padding: .7rem .6rem; }
    .table td .btn-sm, .table td .btn { min-height: 2.2rem; }

    /* The frozen account column ate 220px of a 328px viewport, leaving a
       ~108px window to type twelve months of figures into. Unpinned, the row
       scrolls as one piece and the code stays readable at the start of it. */
    .budget-grid th:first-child,
    .budget-grid td:first-child { position: static; min-width: 9rem; }

    /* ---- dialogs become sheets ----
       A centred dialog on a 640px-tall phone puts its Save button off both
       edges, because .modal-dialog-centered centres with align-items:center
       and the overflow spills symmetrically. Anchored to the bottom with the
       body scrolling, the action is always where the thumb already is. */
    .modal-dialog {
        margin: 0;
        min-height: 100%;
        display: flex;
        align-items: flex-end;
    }
    .modal-content {
        width: 100%;
        max-height: 92dvh;
        border-radius: var(--radius) var(--radius) 0 0;
        display: flex;
        flex-direction: column;
    }
    .modal-body { overflow-y: auto; -webkit-overflow-scrolling: touch; }
    .modal-header, .modal-footer { flex: none; }
    .modal-footer {
        padding-bottom: calc(.75rem + env(safe-area-inset-bottom));
        gap: .5rem;
    }
    .modal-footer > .btn { flex: 1 1 auto; }

    /* ---- rails and grids ---- */
    .offer-rail, .route-rail { grid-auto-columns: minmax(78vw, 1fr); }
    .doc-grid  { grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); }
    .visa-card__banner { width: 100%; min-height: 11rem; }
    .trip-tabs { width: 100%; justify-content: stretch; }
    .trip-tabs label { flex: 1; text-align: center; padding-inline: .5rem; }
    .product-tabs { gap: .4rem; }
    .site-foot__inner { padding: 1.75rem var(--shell-pad) 1rem; }

    /* The notice ticker's label wraps its own row rather than squeezing the
       message into a sliver. */
    .notice-bar { font-size: .78rem; }
    .notice-bar__label { padding-inline: .6rem; }
}

/* ---- ≤ 575.98: one column ------------------------------------------------ */
@media (max-width: 575.98px) {
    .page-head { flex-direction: column; align-items: stretch; }
    .page-head > div:last-child { width: 100%; }
    .offer-rail, .route-rail { grid-auto-columns: minmax(86vw, 1fr); }
    .doc-grid { grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr)); }
    .user-chip span:not(.avatar) { display: none; }
    .fare-totals { flex-direction: column; gap: .5rem; }
    /* The strip stacks here, so it is taller — the tile has to reserve more. */
    .visa-card__price { flex-direction: column; align-items: stretch; gap: .4rem; text-align: center; }
    .visa-card__price .btn { width: 100%; }
    .visa-card__banner { min-height: 14rem; padding-bottom: 7rem; }
    .visa-card__country { font-size: 1.5rem; }
}

/* ---- pointer: coarse ----------------------------------------------------
   Width is a proxy for touch, and a poor one: a 1024px tablet is a touch
   device and a 900px browser window is not. These are the rules that must
   follow the INPUT rather than the viewport. */
@media (hover: none) and (pointer: coarse) {
    /* Hover opens the group menus on desktop. On a device with no hover the
       first tap would have to both open and follow the link. app.js already
       binds click; this makes sure the CSS does not fight it. */
    .module-nav__drop:hover .module-nav__drop-menu { display: none; }
    .module-nav__drop.open .module-nav__drop-menu  { display: flex; }
    .card-lift:hover { transform: none; box-shadow: var(--shadow-card); }
    body { background-attachment: scroll; }
}

/* ---- tall content on short viewports ------------------------------------ */
@media (max-height: 560px) and (max-width: 991.98px) {
    /* A phone in landscape: the drawer must still reach its last item. */
    .module-nav__inner { padding-block: .5rem; }
    .module-nav a { min-height: 40px; padding-block: .35rem; }
}

/* ---------------------------------------------------------------- print */
/* Payslips, statements and vouchers get filed. Chrome off, content full width,
   and net-only figures NEVER print — a printout forgets who was looking. */
@media print {
    .site-head, .module-nav, .site-foot, .page-head .btn, .btn, form.d-flex,
    .filter-rail, .date-strip, .airline-strip, .net-switch { display: none !important; }

    /* A dialog left open must not print over the voucher behind it, and the
       scroll lock must not clip the page to one screen. erp-forms.css used
       to cover this; it no longer has a print block. */
    .modal, .modal-backdrop { display: none !important; }
    body.modal-open { overflow: visible !important; padding-right: 0 !important; }

    body { background: #fff; }
    #content { max-width: none; padding: 0; }
    .card { border: 1px solid #ccc; box-shadow: none; }
    .net-only, body.show-net .net-only { display: none !important; }
}

/* ============================================================================
   Carried over from the previous design layer — classes that the redesign
   dropped but that live pages still use. Restyled to the new palette rather
   than pasted, so nothing on those screens looks like it belongs to the old
   purple theme.
   ========================================================================= */

/* Reference chips on the wallet statement. */
.ref { padding: .2rem .5rem; border-radius: 6px; font-size: .74rem; font-weight: 700; }
.ref.DEPOSIT { background: #DCF3E6; color: #14603A; }
.ref.PAYMENT { background: #F9E2DC; color: #8E2F1C; }
.ref.REFUND  { background: var(--warn-bg); color: #7A5A12; }

/* Danger / attention stat cards (partial-due desk, payroll warnings). */
.stat-card.stat-danger { background: var(--bad); border-color: var(--bad); color: #fff; }
.stat-card.stat-danger .stat-label, .stat-card.stat-danger .stat-value { color: #fff; }
.stat-card.stat-warn   { background: var(--accent); border-color: var(--accent); color: #fff; }
.stat-card.stat-warn .stat-label, .stat-card.stat-warn .stat-value { color: #fff; }

/*
  MUTED TEXT ON A DARK FILL.

  .amt-ccy (the currency prefix) and .text-body-secondary are greys chosen to
  recede on a WHITE card. Wherever the surface underneath is a brand or dark
  fill they recede all the way out — "BDT 381,910" rendered as a bare number on
  the ledger's balance tile. One rule for every filled surface in the product,
  rather than discovering it a card at a time.
*/
.stat-card.stat-danger .amt-ccy, .stat-card.stat-warn .amt-ccy,
.flight-card__band .amt-ccy, .offer-card .amt-ccy, .offer-card.alt .amt-ccy,
.hero .amt-ccy,
.stat-card.stat-danger .text-body-secondary, .stat-card.stat-warn .text-body-secondary,
.flight-card__band .text-body-secondary, .offer-card .text-body-secondary,
.hero .text-body-secondary { color: rgba(255, 255, 255, .82) !important; }

/* Settings sub-navigation tabs. */
.settings-tabs { border-bottom: 1px solid var(--line); }
.settings-tabs .nav-link { white-space: nowrap; color: var(--ink-soft); }
.settings-tabs .nav-link.active {
    color: var(--ink);
    border-bottom: 3px solid var(--brand);
    font-weight: 700;
    background: transparent;
}

/* Passenger-count block on the search form. */
.pax-block { display: flex; gap: .75rem; align-items: end; }

/* OTP settings row spans the settings grid. */
.otp-field { grid-column: 1 / -1; border-top: 1px dashed var(--line); padding-top: .75rem; }

/* Centered single card used by the portal landing and the error page.
   The login screen uses the larger .auth-split instead. */
.auth-card {
    max-width: 420px;
    margin: 8vh auto 3rem;
    background: var(--surface);
    border-radius: 18px;
    padding: 2.2rem;
    box-shadow: var(--shadow-pop);
}
.auth-title    { color: var(--brand-deep); font-size: 1.35rem; font-weight: 800; }
.auth-subtitle { color: var(--ink-soft); font-size: .85rem; }
.link-muted    { color: var(--ink-soft); }
.link-muted:hover { color: var(--ink); }

/* Logout button in the module nav — a form styled as a nav item. */
.module-nav-logout:hover { color: var(--bad) !important; }

/* =====================================================================
   FARE DETAILS DIALOG  (search results → "Fare Details")

   Every colour routes through --brand, so a tenant's palette carries into
   this dialog the way it does everywhere else. No hex literals below.
   ===================================================================== */

.fare-details            { display: flex; flex-direction: column; }

/* ---- tabs: three panes, one class, no tab library ---- */
.fare-tabs               { display: flex; gap: .25rem; background: var(--surface-2, #f4f5f7);
                           border-radius: .5rem; padding: .25rem; margin-bottom: 1rem; }
.fare-tab                { flex: 1; border: 0; background: transparent; border-radius: .375rem;
                           padding: .55rem .75rem; font-size: .875rem; font-weight: 600;
                           color: var(--ink-soft); cursor: pointer; }
.fare-tab.on             { background: #fff; color: var(--brand-deep); box-shadow: 0 1px 2px rgb(0 0 0 / .08); }

.fare-pane               { display: none; }
.fare-pane.on            { display: block; }

/* ---- the branded fare card ---- */
.brand-card              { border: 1px solid var(--brand); border-radius: .5rem; padding: 1rem;
                           max-width: 26rem; }
.brand-section           { background: var(--surface-2, #f4f5f7); border-radius: .25rem;
                           padding: .3rem .6rem; margin: .9rem 0 .5rem;
                           font-size: .75rem; font-weight: 600; color: var(--ink-soft); }
.brand-list              { list-style: none; margin: 0; padding: 0; font-size: .8125rem; }
.brand-list li           { display: flex; gap: .5rem; align-items: flex-start; margin-bottom: .45rem; }
.brand-list li .ph       { color: var(--good, #2e7d32); flex-shrink: 0; margin-top: .1rem; }

/* ---- passenger fare table ---- */
.fare-table thead th     { background: var(--brand-deep); color: #fff; font-size: .75rem;
                           font-weight: 600; white-space: nowrap; }
.fare-table td           { font-size: .8125rem; }

.fare-totals             { display: flex; gap: 2rem; background: var(--surface-2, #f4f5f7);
                           border-radius: .5rem; padding: .75rem 1.25rem; }

.partial-note            { border-top: 1px dashed var(--line, #dee2e6); padding-top: .75rem;
                           font-size: .8125rem; }

/* ---- baggage + rules panes ---- */
.sector-chip             { display: inline-flex; align-items: center; gap: .4rem;
                           background: var(--brand-deep); color: #fff; border-radius: 1rem;
                           padding: .2rem .75rem; font-size: .75rem; font-weight: 600; }
.sector-block            { border: 1px solid var(--line, #dee2e6); border-radius: .5rem;
                           padding: 1rem; margin-bottom: 1rem; }
.bag-row                 { display: flex; gap: .75rem; align-items: center; margin-top: .5rem;
                           font-size: .8125rem; }
.bag-row .ph             { font-size: 1.4rem; color: var(--ink); }
.rule-block              { margin-top: 1rem; }

/* ---- sticky footer: the price the agent reads out, and Book ---- */
.fare-foot               { display: flex; align-items: center; justify-content: space-between;
                           border-top: 1px solid var(--line, #dee2e6);
                           padding-top: .85rem; margin-top: 1.25rem; }

.fare-details-link       { text-decoration: none; font-size: .75rem; font-weight: 600;
                           color: var(--brand); padding: .15rem 0; }
.fare-details-link:hover { text-decoration: underline; }

/* ---- fare-type radios on the search card ---- */
.fare-type-row           { display: flex; flex-wrap: wrap; gap: .35rem 1.1rem; }
.fare-type               { display: inline-flex; align-items: center; gap: .35rem;
                           font-size: .8125rem; cursor: pointer; }
.fare-type input         { accent-color: var(--brand); }

/* A printout forgets who was looking — same rule as .net-only (§ print). */
@media print {
    .fare-details-link, .fare-tabs { display: none !important; }
}

/* =====================================================================
   VISA — catalogue, product, application

   The two painted panels here (the catalogue card's tile and the product
   page's hero) are the reference design's most photographic-looking elements,
   and neither uses a photograph. Both are a brand gradient plus the shared
   hero-skyline watermark, drawn white-on-transparent so it composites over
   whatever colour the tenant stores.

   VisaProduct.bannerPath exists and is editable in the admin form, but it is
   NULL on every seeded row and there is no upload endpoint — so the gradient
   is the DEFAULT, not the fallback, and a product that does have an image
   simply layers it in (see .visa-card__photo). Nothing here depends on
   a photo arriving.
   ===================================================================== */
.visa-panel,
.visa-card__banner,
.visa-hero {
    position: relative;
    background: linear-gradient(135deg,
                    color-mix(in srgb, var(--brand) 88%, #FF8A3D),
                    var(--brand) 45%,
                    var(--brand-deep));
    overflow: hidden;
}
/*
  The watermark sits at z-index 0 and the content is raised to 1.

  The obvious version — ::before at z-index:-1 — renders NOTHING: a negative
  z-index puts the pseudo behind its own parent, and the parent has an opaque
  gradient background, so the skyline was painted and then covered by the panel
  it was decorating. (`isolation: isolate` made it worse by guaranteeing the
  pseudo could not escape to sit behind an ancestor instead.) Painting it in
  front of the background and lifting the text above it is the arrangement that
  actually layers.
*/
.visa-panel::before,
.visa-card__banner::before,
.visa-hero::before {
    content: "";
    position: absolute; inset: 0;
    background: url("/img/hero-skyline.svg") center bottom / cover no-repeat;
    z-index: 0;
    pointer-events: none;
}
/*
  LAYERING INSIDE A BRAND PANEL — stated per element, not with a blanket rule.

  There are four things in the box and they stack:
      0   ::before   the skyline watermark
      0   the photo  when the product has one
      1   the country name
      2   the mode badge and the price strip

  It was briefly written as `> *:not(.visa-card__photo) { position: relative }`,
  which is the kind of rule that looks tidier and is wrong: at (0,2,0) it beat
  .visa-mode-badge and .visa-card__price at (0,1,0) and turned both of them
  static, so the badge and the price strip abandoned their corners and stacked
  in the middle of the tile. Naming the layers costs three rules and cannot do
  that.

  The country name is the only one that NEEDS raising — it is a static inline
  span, and static content paints below positioned siblings, so it would
  otherwise sit under the watermark.
*/
.visa-panel > .visa-card__country,
.visa-card__banner > .visa-card__country,
.visa-hero > .visa-card__country { position: relative; z-index: 1; }

.visa-card__banner > .visa-mode-badge,
.visa-card__banner > .visa-card__price,
.visa-hero > .visa-mode-badge { z-index: 2; }

/* The bottom padding is not decoration: it reserves the strip's own height, so
   the country name centres in the space ABOVE it. Without it the name centres
   in the whole tile and the strip — which is absolutely positioned and so
   outside the flow — lands on top of it. Measured at 390px, where the strip
   stacks to two rows and covered the name completely. */
.visa-card__banner   { width: 20rem; max-width: 100%; min-height: 13rem; border-radius: var(--radius-sm);
                       display: flex; flex-direction: column; justify-content: center;
                       align-items: center; flex-shrink: 0;
                       padding: 2.5rem 1rem 4.75rem; }
.visa-card__country  { color: #fff; font-weight: 800; font-size: 1.75rem; letter-spacing: .06em;
                       text-transform: uppercase; text-align: center; padding-inline: .75rem;
                       text-shadow: 0 2px 10px rgb(0 0 0 / .3); }

/*
  A real banner image when the product has one.

  AN <img>, NOT A background-image FROM A CUSTOM PROPERTY.

  The obvious implementation is th:style="--banner:url(...)" plus
  background-image: var(--banner). It works, and it takes a free-text field
  straight from the admin form into a CSS declaration list: a bannerPath
  containing `x); position:fixed; top:0; --z:url(` injects extra declarations
  onto that element. An inline style attribute is a declaration list rather than
  a stylesheet, so it cannot open new rules and the blast radius is one card —
  but the field is edited by a human and it does not need to be a colour of
  input at all.

  object-fit: cover crops exactly the way background-size: cover does, the URL
  goes through an attribute the framework escapes properly, and the browser gets
  a real image it can cache, lazy-load and report as broken. The brand tint is
  kept over it so a stock photo cannot drag the card out of the palette.
*/
.visa-card__photo {
    position: absolute; inset: 0; z-index: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    mix-blend-mode: multiply;
    opacity: .85;
    pointer-events: none;
}

.visa-mode-badge     { position: absolute; top: .6rem; right: .6rem; background: #fff;
                       color: var(--brand-deep); border-radius: var(--radius-pill);
                       padding: .2rem .7rem; display: inline-flex; align-items: center; gap: .3rem;
                       font-size: .7rem; font-weight: 700; box-shadow: var(--shadow-card); }

/*
  THE PRICE STRIP.

  The reference puts "Starting Price / BDT 5,500.00 / View Details →" as a
  strip along the BOTTOM OF THE IMAGE TILE, not in a grey column beside the
  description — which is where it used to live. Inside the tile it is the last
  thing the eye reaches on the card's most looked-at element; beside the text it
  was a third column that made the card read as three unrelated blocks.
*/
.visa-card__price    { position: absolute; inset-inline: .55rem; bottom: .55rem;
                       background: rgba(255, 255, 255, .96);
                       backdrop-filter: blur(2px);
                       border-radius: var(--radius-sm);
                       padding: .55rem .7rem;
                       display: flex; align-items: center; justify-content: space-between;
                       gap: .6rem; text-align: start; }
.visa-card__price .lbl { font-size: .68rem; color: var(--ink-soft); display: block; line-height: 1.2; }
.visa-card__price .amt { font-size: 1.02rem; font-weight: 800; color: var(--brand-deep); }

.visa-hero           { min-height: 11rem; border-radius: var(--radius) var(--radius) 0 0;
                       display: flex; flex-direction: column; gap: .5rem;
                       align-items: center; justify-content: center; }

/* The reference's fact list: orange TERMS with small orange icons, plain dark
   values. --brand-deep because these are .74rem — the raw brand on white is
   3.34:1 and these are the page's densest block of text. */
.visa-facts dt       { color: var(--brand-deep); font-weight: 600; padding: .22rem 0;
                       display: flex; align-items: center; gap: .4rem; }
.visa-facts dt i     { font-size: .95rem; color: var(--brand); flex: none; }
.visa-facts dd       { padding: .22rem 0; margin: 0; color: var(--ink); }

/* The destination blurb sits in a soft cream well in the reference, so it
   reads as quoted copy rather than as another paragraph of interface. */
.visa-note           { background: var(--surface-2); border: 1px solid var(--line);
                       border-radius: var(--radius-sm); padding: .85rem 1rem;
                       font-size: .84rem; color: var(--ink-soft); }

/* The apply panel follows the page down. The offset clears the sticky shell
   above it; below lg the column stacks and this is switched off, because a
   sticky panel on a phone pins half the viewport to one card. */
@media (min-width: 992px) {
    .visa-apply-col > .card:first-child { position: sticky; top: 1rem; }
}

.visa-kv             { display: flex; gap: .75rem; align-items: flex-start; margin-bottom: .85rem; }
.visa-kv .ph         { font-size: 1.5rem; color: var(--brand); }

/* The product title on a catalogue card. The reference draws it as a link, and
   the theme would happily oblige with its own mid blue — the one foreign colour
   on an orange page. Ink at rest, brand on hover: still obviously a link, and
   the only version that survives being printed. */
.visa-card__title    { font-size: 1.02rem; font-weight: 700; color: var(--ink);
                       line-height: 1.35; }
.visa-card__title:hover { color: var(--brand-deep); text-decoration: underline; }

/* The stay/validity facts beside the entry-type pill. */
.visa-meta           { display: inline-flex; align-items: center; gap: .3rem;
                       color: var(--ink-soft); white-space: nowrap; }
.visa-meta i         { font-size: .95rem; color: var(--brand); }
.visa-meta b         { color: var(--ink); font-weight: 700; }

/*
  THE TRUST ROW — "Easy Process · Secure Payment · Expert Support · Fast
  Delivery" under a catalogue card, and its three-item cousin under the apply
  panel. One class for both, because they are the same component at two widths;
  building them separately is how two things that must agree stop agreeing.

  Wraps rather than scrolls, and the rules disappear on the wrapped rows, so it
  degrades to a tidy two-up on a phone instead of a scrollbar.
*/
.trust-row           { display: flex; flex-wrap: wrap; align-items: center;
                       gap: .5rem 1rem; }
.trust-row__item     { display: inline-flex; align-items: center; gap: .4rem;
                       font-size: .76rem; font-weight: 600; color: var(--ink-soft);
                       flex: 1 1 auto; justify-content: center; white-space: nowrap; }
.trust-row__item i   { font-size: 1.1rem; color: var(--brand); flex: none; }
.trust-row__item + .trust-row__item { border-inline-start: 1px solid var(--line); padding-inline-start: 1rem; }

/*
  The dividers are drawn with a leading border, which is right on one line and
  wrong the moment the row wraps: the first item of the second line inherits a
  rule with nothing to its left. There is no ":first-in-row" selector, so at the
  widths where wrapping is expected the rule is dropped and the items simply
  space out instead.
*/
@media (max-width: 767.98px) {
    .trust-row__item { flex: 1 1 45%; justify-content: flex-start; }
    .trust-row__item + .trust-row__item { border-inline-start: 0; padding-inline-start: 0; }
}
/* The three-item variant sits in the visa product's narrow right column, where
   it wraps for the same reason at full desktop width. */
@media (min-width: 992px) and (max-width: 1199.98px) {
    .visa-apply-col .trust-row__item { flex: 1 1 45%; justify-content: flex-start; }
    .visa-apply-col .trust-row__item + .trust-row__item { border-inline-start: 0; padding-inline-start: 0; }
}

/* The reference draws each required document as an outlined white tile with the
   icon beside the name, not a bare centred glyph — it makes the checklist read
   as a list of things to collect rather than as an icon grid. */
.doc-grid            { display: grid; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); gap: .75rem; }
.doc-tile            { display: flex; align-items: center; gap: .6rem;
                       border: 1px solid var(--line); border-radius: var(--radius-sm);
                       background: var(--surface); padding: .7rem .8rem;
                       font-size: .78rem; line-height: 1.25; }
.doc-tile .ph        { font-size: 1.35rem; color: var(--brand); flex: none; }
.doc-optional        { display: block; color: var(--ink-faint); font-size: .7rem; }

.doc-list            { display: flex; flex-direction: column; gap: .5rem; }
.doc-line            { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
                       border: 1px solid var(--line, #dee2e6); border-radius: .375rem;
                       padding: .5rem .75rem; font-size: .8125rem; }

.seq-badge           { background: var(--brand-deep); color: #fff; border-radius: .25rem;
                       padding: .1rem .5rem; font-weight: 700; font-size: .8125rem; }

.pax-row             { display: flex; align-items: center; justify-content: space-between;
                       padding: .5rem 0; font-size: .875rem; }
.pax-stepper         { display: flex; align-items: center; gap: .35rem; }
.pax-stepper button  { width: 1.75rem; height: 1.75rem; border: 0; border-radius: .25rem;
                       background: var(--ink); color: #fff; font-weight: 700; line-height: 1;
                       /* The theme sets `button { text-align: left }`; every round or
                          square glyph button in this file has to undo it. */
                       display: inline-flex; align-items: center; justify-content: center;
                       padding: 0; }
.pax-stepper input   { width: 2.75rem; text-align: center; border: 0; background: transparent;
                       font-weight: 600; }

.timeline-row        { display: grid; grid-template-columns: 8.5rem 1fr; gap: .5rem;
                       padding: .4rem 0; border-bottom: 1px dashed var(--line, #dee2e6); }
.timeline-row:last-child { border-bottom: 0; }

/* =====================================================================
   NOTICE BOARD
   ===================================================================== */
.notice-badge        { background: var(--bad, #d32f2f); color: #fff; border-radius: 1rem;
                       padding: 0 .4rem; font-size: .7rem; font-weight: 700; margin-left: .35rem; }
.notice-row          { border-left: 3px solid transparent; }
.notice-row.is-unread{ border-left-color: var(--brand); background: color-mix(in srgb, var(--brand) 4%, transparent); }
.notice-row:hover    { border-left-color: var(--brand); }
.unread-dot          { color: var(--brand); font-weight: 700; font-size: .7rem; }

/* Category chips. One hue per module so a visa notice is recognisable
   before it is read — the whole point of categorising them. */
.notice-cat          { display: inline-block; border-radius: .25rem; padding: .1rem .5rem;
                       font-size: .7rem; font-weight: 700; text-transform: uppercase;
                       background: var(--surface-2, #f4f5f7); color: var(--ink-soft); }
.notice-cat.VISA     { background: #e8f0fe; color: #1a56db; }
.notice-cat.MEDICAL  { background: #e6f6ec; color: #1e7e34; }
.notice-cat.FLIGHT   { background: #fff3e0; color: #b45309; }
.notice-cat.FINANCE  { background: #f3e8ff; color: #6b21a8; }
.notice-cat.SYSTEM   { background: #eceff1; color: #37474f; }

/* Authored as plain text and rendered as plain text — see notice-detail
   for why this is not th:utext. */
.notice-body         { white-space: pre-wrap; font-size: .9375rem; line-height: 1.6; }

/* =====================================================================
   SCROLLING NOTICE BAR  (shell header, every page)
   ===================================================================== */
.notice-bar          { display: flex; align-items: stretch; overflow: hidden;
                       background: var(--accent, #141414); color: #fff;
                       font-size: .82rem; line-height: 1.2; }

.notice-bar__label   { flex: none; z-index: 2; display: inline-flex; align-items: center;
                       gap: .4rem; padding: .35rem .85rem; background: var(--brand-deep); color: #fff;
                       font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
                       text-decoration: none; white-space: nowrap; }
.notice-bar__label:hover { color: #fff; }

.notice-bar__viewport{ position: relative; flex: 1 1 auto; overflow: hidden;
                       display: flex; align-items: center; }

.notice-bar__track   { display: flex; align-items: center; white-space: nowrap;
                       will-change: transform; animation: notice-scroll 45s linear infinite; }
/* Pause the moment a human tries to read — or click — one of them. */
.notice-bar__viewport:hover .notice-bar__track { animation-play-state: paused; }

/* The 3rem sits on the item, not as a flex gap on the track, so the trailing
   item of each copy carries it too. That makes the two copies exactly equal in
   width, which is what lets the -50% translate loop without a jump. */
.notice-bar__item    { display: inline-flex; align-items: center; gap: .4rem;
                       margin-right: 3rem; padding: .35rem 0; white-space: nowrap;
                       color: rgba(255,255,255,.9); text-decoration: none; }
.notice-bar__item:hover     { color: #fff; text-decoration: underline; }
.notice-bar__item.is-urgent { color: #fff; font-weight: 700; }
.notice-bar__urgent  { color: #ffd54f; }

.notice-bar__cat     { font-size: .62rem; font-weight: 800; text-transform: uppercase;
                       letter-spacing: .03em; padding: .05rem .35rem; border-radius: .2rem;
                       background: rgba(255,255,255,.16); color: #fff; }

@keyframes notice-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* A reduced-motion preference stops the scroll; the bar becomes swipeable so no
   notice is trapped off-screen. */
@media (prefers-reduced-motion: reduce) {
    .notice-bar__viewport { overflow-x: auto; }
    .notice-bar__track    { animation: none; }
}

/* =====================================================================
   SELECT2  ↔  Bootstrap bridge   (§SELECT2)
   Select2's default theme is not Bootstrap's. These rules line the single
   container up with .form-select — height, border, radius, focus ring — so a
   remote picker sits in a form row without looking bolted on. Loaded after
   select2.min.css (see head.html) so it wins.
   ===================================================================== */
.select2-container { width: 100% !important; }

.select2-container--default .select2-selection--single {
    height: calc(1.5em + .75rem + 2px);   /* == Bootstrap .form-select */
    border: 1px solid var(--bs-border-color, #ced4da);
    border-radius: .375rem;
    display: flex; align-items: center;
    padding: .375rem .75rem;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
    padding: 0; color: var(--bs-body-color, #212529); line-height: 1.5;
}
.select2-container--default .select2-selection--single .select2-selection__placeholder {
    color: var(--bs-secondary-color, #6c757d);
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 100%; right: .5rem;
}
/* Match the theme's focus ring, driven by the tenant brand token. */
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--open  .select2-selection--single {
    border-color: var(--brand);
    box-shadow: 0 0 0 .25rem color-mix(in srgb, var(--brand) 25%, transparent);
    outline: 0;
}
.select2-dropdown { border-color: var(--brand); border-radius: .375rem; }
.select2-container--default .select2-results__option--highlighted[aria-selected] {
    background-color: var(--brand);
}

/* Inside a dialog Select2 sits above the modal (app.js stacks modals from
   z 1055); its dropdown must clear the same stack or it renders behind. */
.modal .select2-container { z-index: 1056; }
.select2-dropdown         { z-index: 1061; }

/* ============================================================================
   FLIGHT SEARCH -> BOOKING
   ----------------------------------------------------------------------------
   The search widget, the results rail and the flight card, implemented against
   the reference screens. Everything here is layout and affordance; not one rule
   decides who may see a number. Net fare and margin are gated SERVER-side on
   MARKUP_VIEW and are absent from the response for anyone without it — .net-only
   only answers "is a customer looking at this screen right now", which is a
   display state the agent toggles, not an access control. A display:none is one
   devtools inspection away from being no protection at all.
   ========================================================================= */

/* ---- product switcher --------------------------------------------------- */
.product-tabs { display: flex; gap: .5rem; overflow-x: auto; padding-bottom: .25rem; }
.product-tab {
    display: inline-flex; align-items: center; gap: .4rem; white-space: nowrap;
    padding: .55rem 1.1rem; border-radius: var(--radius-pill);
    background: var(--surface); border: 1px solid var(--line);
    color: var(--ink-soft); font-size: .82rem; font-weight: 700;
    text-decoration: none; transition: all .15s ease;
}
.product-tab i { font-size: 1.05rem; }
.product-tab:hover:not(.disabled) { border-color: var(--brand); color: var(--ink); }
/* Solid orange, per the reference's active "Flight" tab. */
.product-tab.on {
    background: var(--brand-band); border-color: transparent; color: #fff;
    box-shadow: var(--shadow-brand);
}
.product-tab.on i { color: #fff; }
/* Not built yet. It says so on hover rather than dead-ending on a 404. */
.product-tab.disabled { opacity: .45; cursor: not-allowed; }

/* ---- the search card: illustrated panel + form --------------------------- */
/*
  Grid rather than flex, so the artwork column is a fixed track and the form
  takes the rest — a flex child with a background image has no intrinsic width
  and collapses to nothing.

  The panel is a background-image on an empty div, not an <img>: it is
  decoration, it must crop rather than letterbox at any card height, and an
  empty aria-hidden div is the one form of it that a screen reader ignores
  completely without needing alt="".
*/
.search-card {
    display: grid;
    grid-template-columns: 15rem 1fr;
    overflow: hidden;
}
.search-card__art {
    background: url("/img/search-panel.svg") center / cover no-repeat;
    min-height: 100%;
}
.search-card__form { min-width: 0; }   /* lets the form grid shrink, not overflow */

@media (max-width: 991.98px) {
    /* Below lg the panel is a scroll tax rather than a design: it would push
       the first field a fifth of a viewport down the page. */
    .search-card { grid-template-columns: 1fr; }
    .search-card__art { display: none; }
}

/* ---- the search widget -------------------------------------------------- */
.search-grid {
    display: grid; gap: .75rem; align-items: end;
    grid-template-columns: 1fr auto 1fr 1fr 1fr;
}
@media (max-width: 991.98px) {
    .search-grid { grid-template-columns: 1fr 1fr; }
    .search-cell--swap { display: none; }
}
@media (max-width: 575.98px) { .search-grid { grid-template-columns: 1fr; } }

.search-cell { min-width: 0; }
.search-cell--swap { padding-bottom: .35rem; }
.search-cell__label {
    display: flex; align-items: center; gap: .35rem;
    font-size: .74rem; font-weight: 700; color: var(--ink-soft);
    text-transform: uppercase; letter-spacing: .04em; margin-bottom: .3rem;
}
.search-cell__label i { color: var(--brand); font-size: .95rem; }

/* Date card. The native input[type=date] stays the source of truth — the
   steppers and the readout are decoration over it, so the field still works
   with the script off. */
.date-field {
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); padding: .5rem .75rem;
}
.date-field__head {
    display: flex; align-items: center; gap: .35rem;
    font-size: .74rem; font-weight: 700; color: var(--ink-soft);
    text-transform: uppercase; letter-spacing: .04em;
}
.date-field__head > i { color: var(--brand); }
.date-field__steps { margin-inline-start: auto; display: inline-flex; gap: .25rem; }
.date-step {
    width: 1.4rem; height: 1.4rem; line-height: 1; border-radius: 50%;
    border: 1px solid var(--line-strong); background: var(--surface);
    color: var(--ink-soft); font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; padding: 0;
}
.date-step:hover { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.date-field__input {
    border: 0; background: transparent; width: 100%; padding: .1rem 0;
    font-weight: 700; color: var(--ink); font-size: .95rem;
}
.date-field__input:focus { outline: 0; }
.date-field__read { font-size: .72rem; color: var(--ink-soft); min-height: 1rem; }

/* One-way shows this in place of the return field; it flips the trip type. */
.add-return {
    width: 100%; border: 1px dashed var(--line-strong); border-radius: var(--radius);
    background: transparent; color: var(--ink-soft); padding: 1.1rem .75rem;
    font-size: .82rem; font-weight: 700;
    display: flex; align-items: center; justify-content: center; gap: .4rem;
}
.add-return:hover { border-color: var(--brand); color: var(--brand); }
.add-return i { font-size: 1.2rem; }

/* Trip-type panels. The simple fields stay in the DOM on the multi-city tab so
   the first leg can be seeded from whatever route was already typed. */
.trip-panel--multi { display: none; }
.flight-search.mode-MULTI_CITY .trip-panel--simple { display: none; }
.flight-search.mode-MULTI_CITY .trip-panel--multi  { display: block; }
/* Return is a round-trip field; the Add Return prompt takes its place otherwise. */
.flight-search .search-cell--return .date-field { display: none; }
.flight-search .search-cell--return .add-return { display: flex; }
.flight-search.mode-ROUND_TRIP .search-cell--return .date-field { display: block; }
.flight-search.mode-ROUND_TRIP .search-cell--return .add-return { display: none; }

.leg-row {
    display: grid; gap: .75rem; align-items: end; margin-bottom: .75rem;
    grid-template-columns: auto 1fr 1fr 1fr auto;
}
@media (max-width: 991.98px) { .leg-row { grid-template-columns: auto 1fr 1fr; } }
.leg-row__no {
    width: 1.6rem; height: 1.6rem; border-radius: 50%; margin-bottom: .5rem;
    background: var(--brand); color: var(--on-brand);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: .74rem; font-weight: 800;
}
.leg-remove {
    width: 2rem; height: 2rem; border-radius: 50%; margin-bottom: .3rem;
    border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink-soft);
}
.leg-remove:hover { background: var(--bad); border-color: var(--bad); color: #fff; }
.add-flight {
    border: 1px dashed var(--line-strong); border-radius: var(--radius-pill);
    background: transparent; color: var(--ink-soft); padding: .45rem 1.1rem;
    font-size: .82rem; font-weight: 700; display: inline-flex; align-items: center; gap: .4rem;
}
.add-flight:hover { border-color: var(--brand); color: var(--brand); }
.add-flight[hidden] { display: none; }

/* ---- cabin + party ------------------------------------------------------- */
.search-options {
    display: flex; flex-wrap: wrap; align-items: stretch; gap: .75rem; margin-top: .75rem;
}
.pax-field { position: relative; flex: 1 1 260px; min-width: 240px; }
.pax-field__summary {
    width: 100%; text-align: start; background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: .55rem .85rem; display: flex; flex-direction: column; gap: .1rem;
    font-size: .84rem; color: var(--ink);
}
.pax-field__summary > span { display: flex; align-items: center; gap: .4rem; }
.pax-field__summary i { color: var(--brand); }
.pax-field__summary > i.ph-caret-down {
    position: absolute; inset-inline-end: .85rem; top: 50%; transform: translateY(-50%);
    color: var(--ink-soft);
}
.pax-field__panel {
    display: none; position: absolute; z-index: 20; inset-inline-start: 0; top: calc(100% + .35rem);
    width: min(320px, 90vw); background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: var(--shadow-pop); padding: 1rem;
}
.pax-field.open .pax-field__panel { display: block; }
.pax-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .4rem 0; }
.stepper { display: inline-flex; align-items: center; gap: .4rem; }
.stepper button {
    width: 1.8rem; height: 1.8rem; border-radius: 50%; line-height: 1;
    border: 1px solid var(--line-strong); background: var(--surface); font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; padding: 0;
}
.stepper button:hover { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.stepper input {
    width: 2.2rem; text-align: center; border: 0; background: transparent;
    font-weight: 800; font-variant-numeric: tabular-nums;
}
.stepper input:focus { outline: 0; }
.search-submit { flex: 0 0 auto; padding-inline: 2.5rem; }

.fare-options {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: 1rem; margin-top: .9rem;
}
.fare-options__right { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; }
.fare-options__right .form-select { min-width: 12rem; }

/* ---- recent-route cards -------------------------------------------------- */
.route-rail {
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(190px, 1fr);
    gap: 1rem; overflow-x: auto; padding-bottom: .35rem;
}
.route-card {
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
    padding: .85rem 1rem; text-decoration: none; color: inherit; display: block;
    transition: border-color .15s ease, transform .15s ease;
}
.route-card:hover { border-color: var(--brand); transform: translateY(-2px); }
.route-card__head { font-weight: 800; font-size: 1.02rem; display: flex; align-items: center; gap: .45rem; }
.route-card__head i { color: var(--brand); font-size: .9rem; }
.route-card__date { font-size: .76rem; color: var(--ink-soft); margin-top: .2rem; }
.route-card__tags { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; }
.route-card__tags .tag {
    font-size: .68rem; font-weight: 700; padding: .12rem .5rem;
    border-radius: var(--radius-pill); background: var(--bg); color: var(--ink-soft);
}

/* ---- results: date strip price ------------------------------------------- */
.date-tab .p {
    display: block; font-size: .7rem; font-weight: 700; color: var(--brand);
    font-variant-numeric: tabular-nums;
}
.date-tab.on .p { color: #fff; }

/* ---- results: airline chips ---------------------------------------------- */
.js-airline-chip { align-items: center; gap: .55rem; }
.js-airline-chip .logo {
    width: 1.9rem; height: 1.9rem; border-radius: 50%; flex: 0 0 auto;
    background: var(--bg); color: var(--ink); font-size: .68rem; font-weight: 800;
    display: inline-flex; align-items: center; justify-content: center;
}
.js-airline-chip.on .logo { background: rgba(255, 255, 255, .25); color: var(--on-brand); }
.js-airline-chip .al  { display: block; text-align: start; }
.js-airline-chip .min { display: block; text-align: start; font-size: .74rem; }

/* ---- results: filter rail ------------------------------------------------ */
.filter-rail__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: .5rem; padding-bottom: .6rem; border-bottom: 1px solid var(--line);
    font-size: .84rem;
}
.filter-rail__head i { color: var(--brand); }
.filter-rail__group + .filter-rail__group { border-top: 1px solid var(--line); }
.filter-rail__group { padding-block: .5rem; }
.filter-rail h4 { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.filter-reset {
    border: 0; background: none; padding: 0; color: var(--brand);
    font-size: .7rem; font-weight: 700; text-decoration: underline; text-transform: none;
    letter-spacing: 0;
}
.filter-reset:hover { color: var(--ink); }

.time-grid, .chip-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; }
.chip-grid { grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr)); }
.time-band, .bag-chip {
    position: relative;
    border: 1px solid var(--line); border-radius: var(--radius); cursor: pointer;
    padding: .5rem .4rem; text-align: center; font-size: .72rem; font-weight: 700;
    color: var(--ink-soft); background: var(--surface); margin: 0;
    display: flex; flex-direction: column; align-items: center; gap: .15rem;
}
.time-band i { font-size: 1.15rem; color: var(--brand); }
.time-band input, .bag-chip input { position: absolute; opacity: 0; pointer-events: none; }
.time-band:has(input:checked), .bag-chip:has(input:checked) {
    border-color: var(--brand); background: color-mix(in srgb, var(--brand) 14%, var(--surface));
    color: var(--ink);
}
.bag-chip { justify-content: center; }

/* ---- results: sort bar --------------------------------------------------- */
.sort-bar {
    display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-bottom: .75rem;
}
.sort-bar__label {
    font-size: .72rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .05em; color: var(--ink-soft); margin-inline-end: .25rem;
}
.sort-btn {
    border: 1px solid var(--line); background: var(--surface); color: var(--ink-soft);
    border-radius: var(--radius-pill); padding: .35rem .9rem;
    font-size: .78rem; font-weight: 700;
}
.sort-btn:hover { border-color: var(--brand); color: var(--ink); }
.sort-btn.on { background: var(--brand-deep); border-color: var(--brand-deep); color: #fff; }
/* The arrow is not decoration: "Duration" alone does not say whether the
   shortest flight is at the top or the bottom. */
.sort-btn.on::after { content: " \2191"; }
.sort-btn.on[data-dir="desc"]::after { content: " \2193"; }

/* ---- results: the flight card -------------------------------------------- */
.flight-card__body {
    display: grid; align-items: center;
    grid-template-columns: 150px 1fr 120px 210px;
}
@media (max-width: 1199.98px) { .flight-card__body { grid-template-columns: 140px 1fr 200px; } }
@media (max-width: 767.98px)  { .flight-card__body { grid-template-columns: 1fr; } }

.fc-carrier { display: flex; align-items: center; gap: .6rem; padding: 1rem; }
.fc-logo {
    width: 2.4rem; height: 2.4rem; border-radius: 50%; flex: 0 0 auto;
    background: var(--bg); color: var(--ink); font-size: .78rem; font-weight: 800;
    display: inline-flex; align-items: center; justify-content: center;
}
.fc-airline  { font-weight: 800; font-size: .88rem; line-height: 1.2; }
.fc-flightno { font-size: .72rem; color: var(--ink-soft); }

.seg .city { font-size: .72rem; color: var(--ink-soft); }
/* The flight label sits UNDER the route line, not across it — the line carries
   a plane glyph at its midpoint and the two were overprinting each other. */
.fc-middle { min-width: 7rem; }
.fc-middle .t { margin-top: .45rem; }
.fc-journey {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
    gap: .5rem; padding: 0 1rem .85rem; font-size: .74rem;
}
.fc-journey .dur     { font-weight: 800; color: var(--ink); }
.fc-journey .stops   { font-weight: 700; color: var(--ok); }
.fc-journey .layover { color: var(--ink-soft); }

.fc-bags {
    display: flex; flex-direction: column; gap: .3rem; padding: 1rem .5rem;
    font-size: .74rem; color: var(--ink-soft);
}
.fc-bags .kv { display: flex; align-items: center; gap: .4rem; }
.fc-bags i   { font-size: 1rem; color: var(--brand); }
.fc-bags b   { font-weight: 700; color: var(--ink); }
@media (max-width: 1199.98px) { .fc-bags { flex-direction: row; flex-wrap: wrap; padding: 0 1rem .85rem; } }

.fc-price { display: flex; flex-direction: column; }
.fc-price + .fc-price { margin-top: .5rem; }
/* The net block is a .fc-price too, and `display: flex` above sits LATER in
   this file than the shared `.net-only { display: none }` — same specificity,
   later wins, and the agency's cost price was rendering on a card with the
   customer-safe toggle still off. Restated here at the specificity the pairing
   actually needs, rather than papering over it with !important.

   This is presentation only. Whether a user may EVER see net is decided on the
   server: the block is absent from the response without MARKUP_VIEW. */
.fc-price.net-only { display: none; }
body.show-net .fc-price.net-only { display: flex; }

.flight-card__foot {
    display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
    padding: .55rem 1rem; border-top: 1px solid var(--line); background: var(--bg);
}
.pill.refund-FULL    { background: #E3F1E8; color: var(--ok); }
.pill.refund-PARTIAL { background: var(--warn-bg); color: #8A6100; }
.pill.refund-NONE    { background: #F7E4E0; color: var(--bad); }

/* No arrow before the origin — the loop emits one per leg. */
.route-summary i:first-child { display: none; }

/* ---- fare details: rule chips -------------------------------------------- */
.rule-block__head {
    display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem;
    font-weight: 600; color: var(--ink);
}
.rule-block__head i { font-size: 1.25rem; color: var(--ink-soft); }
.rule-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.rule-chip {
    display: inline-flex; align-items: center; gap: .5rem;
    border: 1px solid var(--line); border-radius: var(--radius-pill);
    padding: .3rem .35rem .3rem .3rem; background: var(--surface); font-size: .8rem;
}
.rule-chip .k {
    background: var(--bg); color: var(--ink-soft); border-radius: var(--radius-pill);
    padding: .15rem .6rem; font-size: .72rem; font-weight: 700;
}
.rule-chip b { font-variant-numeric: tabular-nums; padding-inline-end: .4rem; }
