/* Billington Mobile Site Styles */

/* ============================================
   DESIGN TOKENS — semantic colour layer
   Contract & governance: docs/development/design-tokens-and-theming.md (§2)
   ---------------------------------------------------------------------------
   Two orthogonal axes: brand (data-brand on <html>) × colour-scheme
   (data-bs-theme, native Bootstrap). gm* components / views consume --gm-*
   ONLY — never a raw hex, brand name, or Bootstrap colour class.

   :root below IS the `legacy-2024` theme: today's shipped look captured
   verbatim so the cutover to holdings-2026 (plan A4) is a default-flip with
   instant rollback. It renders pixel-identical to the pre-token CSS — the
   token layer adds indirection, not colour changes (rollout §5 step 1).
   ============================================ */
:root {
  /* ---- Legacy brand palette (retained; other rules still reference these
          directly. New work must use the --gm-* tokens below instead.) ---- */
  --billington-red: rgb(195, 32, 40);      /* Pantone 200c, #C32028 */
  --billington-blue: rgb(34, 22, 70);      /* Pantone 275c, #221646 */

  /* Brand Blue Tints (from brand guidelines) */
  --billington-blue-80: rgb(78, 69, 107);  /* 80% tint - #4E456B */
  --billington-blue-60: rgb(122, 115, 144); /* 60% tint - #7A7390 */

  /* Brand Red Tints (from brand guidelines) */
  --billington-red-80: rgb(207, 77, 83);   /* 80% tint - #CF4D53 */
  --billington-red-60: rgb(219, 121, 126); /* 60% tint - #DB797E */

  /* Brand Blue Light Tints (for backgrounds) */
  --billington-blue-10: rgb(233, 232, 238); /* 10% tint - #E9E8EE */

  /* Neutral Grays (from GMISBlazor) */
  --g-light-grey: rgb(193, 193, 193);      /* #C1C1C1 */
  --g-medium-grey: rgb(160, 160, 160);     /* #A0A0A0 */
  --g-dark-grey: rgb(76, 76, 76);          /* #4C4C4C */

  /* Brand Typography (from brand guidelines - Digital Font) */
  --brand-font: Calibri, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --brand-text-color: #212529;

  /* ---- gm-* semantic tokens — legacy-2024 values (theming doc §2) ---------
     Every §2 slot is present. Tokens marked "inert" have no consumer yet
     (the gm* component that will read them lands in plan A2/A3); their value
     still reflects today's look so the eventual wiring is a no-op. */
  --gm-chrome:          #C32028;                    /* brand app bar fill (red) — registry §4 legacy row */
  --gm-chrome-text:     #FFFFFF;                    /* text/icons on chrome */
  --gm-accent:          #C32028;                    /* legacy has no distinct accent; red doubles as brand mark. Inert. */
  --gm-action:          var(--billington-blue-80);  /* #4E456B — today's primary button (btn-brand-80) */
  --gm-action-text:     #FFFFFF;                    /* text on primary buttons */
  --gm-action-hover:    rgb(60, 52, 86);            /* today's btn-brand-80 :hover/:focus derivation */
  --gm-action-disabled: #B9B6C4;                    /* §2 value; inert (Bootstrap opacity handles disabled today) */
  --gm-canvas:          var(--billington-blue-10);  /* #E9E8EE — page background */
  --gm-surface:         #FFFFFF;                    /* card/sheet background */
  --gm-hairline:        #DEE2E6;                    /* dividers/borders (= Bootstrap --bs-border-color) */
  --gm-text:            var(--brand-text-color);    /* #212529 — primary text */
  --gm-text-secondary:  #6C757D;                    /* metadata/labels (Bootstrap secondary) */
  --gm-link:            var(--billington-blue);     /* #221646 — row titles/links/brand blue */
  --gm-focus:           rgba(34, 22, 70, .25);      /* brand-blue focus ring; inert */
  --gm-radius:          0;                          /* cards are squared today; inert until gm* consume it */

  /* ---- Status tokens — theme-INVARIANT across every brand (§2 rule 3).
          Safety signals in a gate-check tool; never theme per company.
          Values equal Bootstrap's success/warning/danger defaults, so the
          --bs-danger bridge below is a no-op today. ---- */
  --gm-status-success:      #198754;   /* valid / compliant (white text on fill) */
  --gm-status-success-text:  #198754;  /* success as TEXT/value/icon on a surface (= fill on light; a lighter variant in dark — see the dark block) */
  --gm-status-warning:      #FFC107;   /* expiring (fill with dark text only) */
  --gm-status-warning-text: #8A5A00;   /* amber as text/icon on light (raw #FFC107 on white fails AA) */
  --gm-status-danger:       #DC3545;   /* expired / overdue / destructive */
  --gm-status-danger-text:   #DC3545;  /* danger as TEXT/value/icon on a surface (= fill on light; lighter in dark) */
  --gm-status-workflow:     #221646;   /* neutral workflow states — FILL (with white text) */
  --gm-status-workflow-text: #221646;  /* workflow as TEXT/value on a surface (= fill on light; lighter in dark) */
  --gm-status-neutral:      #E9ECEF;   /* unknown/unavailable — fill */
  --gm-status-neutral-text: #333333;   /* text on neutral fill */
  --gm-status-on-fill:         #FFFFFF; /* text/icon ON success/danger/workflow fills (scheme-invariant — the fills don't flip) */
  --gm-status-warning-on-fill: #332701; /* text ON the amber fill (white fails AA on #FFC107) */

  /* ---- Bootstrap bridge (theming doc §2): stock BS components inherit the
          theme without per-component overrides. -----------------------------
     NOTE: legacy sources --bs-primary from --gm-link (#221646 brand blue),
     NOT --gm-action. Today the primary BUTTON is a tint (#4E456B) while
     text-primary, the login navbar and .bg-primary use full brand blue —
     mapping bs-primary to gm-action here would recolour every text-primary
     icon. holdings-2026 collapses button and brand blue into one value and so
     applies the canonical --bs-primary ← --gm-action bridge. */
  --bs-primary: var(--gm-link);
  --bs-primary-rgb: 34, 22, 70;
  --bs-body-bg: var(--gm-canvas);
  /* Bootstrap's own secondary/tertiary surfaces are theme-aware but off-palette (a bluish #2b3035
     in dark). Bridging them to --gm-canvas makes bg-body-secondary / bg-body-tertiary both
     theme-aware AND on-palette, so a recessed panel inside a card is canvas-on-surface in either
     scheme. This is what .bg-light was reaching for; .bg-light is a fixed grey and never flips. */
  --bs-secondary-bg: var(--gm-canvas);
  --bs-tertiary-bg: var(--gm-canvas);
  /* .bg-body-* utilities read the -rgb triplet, not the colour above; it must be restated for each
     canvas value. legacy canvas = #E9E8EE. */
  --bs-secondary-bg-rgb: 233, 232, 238;
  --bs-tertiary-bg-rgb: 233, 232, 238;
  --bs-body-font-family: var(--brand-font);
  --bs-body-color: var(--gm-text);
  --bs-danger: var(--gm-status-danger);
}

/* ============================================
   THEME: holdings-2026 — ADOPTED default (theming doc §4)
   Values = theming doc §2 defaults / design review §6 specs as amended by the
   2026-07-06 decision round (navy chrome on ALL screens D2, solid navy action /
   tints retired D1, red is content accent only D3). Status tokens are invariant
   (inherited from :root).

   hoardit is grouped in here because it INHERITS Holdings-2026 wholesale
   (theming doc §4, plan A5): the shared indigo chrome + action, the neutral
   canvas, every status colour. Its sole brand divergence is --gm-accent, set in
   the hoardit-only block below. Do NOT build components against legacy-2024 —
   it is deleted after this theme's bake-in.
   ============================================ */
[data-brand="holdings-2026"],
[data-brand="hoardit"] {
  --gm-chrome:          #221646;   /* navy app bar on ALL screens incl. project context (D2) */
  --gm-chrome-text:     #FFFFFF;
  --gm-accent:          #C32028;   /* content-level only; never chrome (D2), never danger (D3) */
  --gm-action:          #221646;   /* solid navy — tints retired (D1) */
  --gm-action-text:     #FFFFFF;
  --gm-action-hover:    #1D133C;   /* ~15% black overlay on --gm-action */
  --gm-action-disabled: #B9B6C4;
  --gm-canvas:          #F5F5F5;   /* app-owned neutral (replaces dropped blue-10) */
  --gm-surface:         #FFFFFF;
  --gm-hairline:        #E5E5E5;
  --gm-text:            #111111;
  --gm-text-secondary:  #6E6E6E;   /* metadata/label TEXT: nudged from the 2026 Grey #737373 (which is 4.35:1 on the #F5F5F5 canvas, under AA) to pass the contrast gate on both surface (5.1:1) and canvas (4.7:1); #737373 stays the fill/border grey */
  --gm-link:            #221646;
  --gm-focus:           rgba(34, 22, 70, .35);
  --gm-radius:          8px;

  /* Bootstrap bridge — canonical §2 mapping (gm-action == gm-link here). */
  --bs-primary: var(--gm-action);
  --bs-primary-rgb: 34, 22, 70;
  --bs-body-bg: var(--gm-canvas);
  /* Bootstrap's own secondary/tertiary surfaces are theme-aware but off-palette (a bluish #2b3035
     in dark). Bridging them to --gm-canvas makes bg-body-secondary / bg-body-tertiary both
     theme-aware AND on-palette, so a recessed panel inside a card is canvas-on-surface in either
     scheme. This is what .bg-light was reaching for; .bg-light is a fixed grey and never flips. */
  --bs-secondary-bg: var(--gm-canvas);
  --bs-tertiary-bg: var(--gm-canvas);
  /* holdings-2026 canvas = #F5F5F5 */
  --bs-secondary-bg-rgb: 245, 245, 245;
  --bs-tertiary-bg-rgb: 245, 245, 245;
  --bs-body-color: var(--gm-text);
  --bs-danger: var(--gm-status-danger);
}

/* ============================================
   THEME: hoardit — first subsidiary proof (theming doc §4, plan A5)
   Everything except --gm-accent is inherited from the grouped holdings-2026
   block above, so the §6 contrast gate result is identical to holdings-2026:
   accent is NOT a gated pairing — it is a content-level / logo-field colour
   only (never chrome, never a text colour, never a white-text button fill).

   --gm-accent below is DERIVED, not copied from the guide: the Hoard-it PDF
   misprints its secondary hex as #C32028 (Holdings red — a leftover template
   value; brand contract §1 errata). The real secondary is the orange that is
   the guide's single most-used colour build (CMYK 0/60/100/0), the same orange
   family as Easi-Edge. #FF6600 is the sRGB of that CMYK build; the guide's
   printed RGB line (unreadable via text extraction — subset-encoded glyphs)
   should confirm the exact value — treat as provisional pending that check.

   Logo: Hoard-it currently inherits the group (Holdings) logo. A Hoard-it
   asset at /images/brand/hoardit/logo.svg (+ logo-white.svg) is a follow-up
   (theming doc §2 convention); no logo asset is fabricated here.
   ============================================ */
[data-brand="hoardit"] {
  --gm-accent: #FF6600;   /* Hoard-it secondary — DERIVED orange (see note). Fills/logo only. */
}

/* ============================================
   COLOUR SCHEME: dark  (theming doc §4/§5, plan A7)
   The SECOND orthogonal axis — data-bs-theme="dark" on <html>, native
   Bootstrap 5.3. NOT a brand fork: this block overrides only the
   surface/text/canvas/hairline/focus tokens plus the status variants that
   fail on a dark surface. Brand chrome/action/accent are NOT touched here —
   they flow from the data-brand block, so any brand × dark just works.

   Source order matters: this sits AFTER the brand blocks. All three selectors
   have equal specificity (one attribute), so on <html data-brand=… data-bs-theme=dark>
   the later rule wins — dark's surface/text tokens override the brand's, while
   the brand's chrome/action/accent (which dark does NOT set) survive untouched.

   Contrast: every pairing below clears WCAG AA 4.5:1 for BOTH brands crossed
   with dark (holdings-2026 and hoardit share these values; hoardit's only
   divergence is --gm-accent, a non-gated fills/logo slot). Gate numbers are
   recorded in design-tokens-and-theming.md §6.
   ============================================ */
[data-bs-theme="dark"] {
  /* Surfaces & text — the dark counterparts (theming doc §2 slots). */
  --gm-canvas:          #121212;   /* page background — near-black neutral */
  /* dark canvas = #121212 — the -rgb triplet cannot resolve through var(), so restate it here or
     .bg-body-secondary keeps Bootstrap's bluish #343a40 default. */
  --bs-secondary-bg-rgb: 18, 18, 18;
  --bs-tertiary-bg-rgb: 18, 18, 18;
  --gm-surface:         #1E1E1E;   /* elevated card/sheet background */
  --gm-hairline:        #333333;   /* dividers/borders on dark */
  --gm-text:            #E6E6E6;   /* primary text — near-white (13.4:1 on surface) */
  --gm-text-secondary:  #A6A6A6;   /* metadata/labels (6.85:1 surface / 7.70:1 canvas) */
  --gm-link:            #A99CE0;   /* lightened brand indigo — navy is illegible on dark (6.76:1 surface) */
  --gm-focus:           rgba(169, 156, 224, .55);   /* lavender focus ring, visible on dark & on navy buttons */

  /* Brand ACTION/PRIMARY relit for dark (2026-07-08). The light-theme navy
     #221646 fails on dark surfaces: as a button fill it is 1.13:1 vs the canvas
     (WCAG 1.4.11 non-text wants >=3:1, so edges read as indistinct) and as an
     icon it is 1.01:1 (invisible — the pin was barely visible). Per Material-3
     dark-theme practice, the brand colour is LIGHTENED on dark; fill and icon
     diverge by role (as the status fill/-text tokens do):
       - button fill: mid indigo, white text still >=4.5:1, edges >=3:1 both surfaces
       - text-primary (icons like the pin, links): the light icon indigo (--gm-link) */
  --gm-action:          #6A5CB8;   /* button fill — white 5.47:1; vs canvas 3.42 / surface 3.05 */
  --gm-action-hover:    #7468BF;   /* lightens on hover (dark elevation) — white 4.69:1 */
  --bs-primary:         var(--gm-link);   /* text-primary icons/text = #A99CE0 (6.76:1) — NOT the button fill */
  --bs-primary-rgb:     169, 156, 224;

  /* Chrome (#221646) is deliberately kept: the app bar is a large filled region
     with white content (16.5:1), reads as chrome, and is the D2 brand anchor. */

  /* Status — invariant SEMANTICS, dark-surface-safe VALUES (theming doc rule 3).
     Fills whose light value already clears white/dark text on a dark surface are
     kept identical (success, warning, danger); only workflow (navy → invisible)
     and neutral (a light chip → glaring) get dark fills. Every status used as
     TEXT/value/icon gets a lightened *-text variant (navy/mid green/red fail as
     text on #1E1E1E). */
  --gm-status-success-text:  #4CC38A;   /* success as text (7.53:1 surface) — fill stays #198754 */
  --gm-status-warning-text:  #F0B44E;   /* amber as text (9.01:1 surface) — fill stays #FFC107 + #332701 */
  --gm-status-danger-text:   #F1707B;   /* danger as text (5.83:1 surface) — fill stays #DC3545 */
  --gm-status-workflow:      #5648A0;   /* workflow FILL relit for dark (white text 7.42:1; navy would vanish) */
  --gm-status-workflow-text: #A99CE0;   /* workflow as text (6.76:1 surface) */
  --gm-status-neutral:       #343A40;   /* neutral chip relit for dark (a light chip glares) */
  --gm-status-neutral-text:  #CED4DA;   /* text on the dark neutral chip (7.70:1) */
}

/* Dark-scheme component nudges: a handful of overlays hardcode a light-mode
   assumption (a black hover wash is invisible on a dark row). Kept minimal and
   scoped — the token flips above do the heavy lifting. */
[data-bs-theme="dark"] .menu-nav-item:hover {
  background-color: rgba(255, 255, 255, 0.06);   /* black wash is invisible on a dark drawer */
}
[data-bs-theme="dark"] .menu-nav-item:active {
  background-color: rgba(255, 255, 255, 0.10);
}

/* Brand Buttons */
.btn-brand {
  background-color: var(--billington-blue);
  border-color: var(--billington-blue);
  color: white;
}

.btn-brand:hover,
.btn-brand:focus {
  background-color: #1a1240;
  border-color: #1a1240;
  color: white;
}

.btn-brand:active {
  background-color: #110d2e;
  border-color: #110d2e;
  color: white;
}

/* 80% tint button — the app's primary action button.
   Consumes the --gm-action token set (theming doc §2). Legacy value is the
   blue-80 tint #4E456B; holdings-2026 flips it to solid navy at cutover with
   no change to this rule. :active keeps the blue-60 pressed tint (no token). */
.btn.btn-brand-80 {
  background-color: var(--gm-action);
  border-color: var(--gm-action);
  color: var(--gm-action-text);
}

.btn.btn-brand-80:hover,
.btn.btn-brand-80:focus {
  background-color: var(--gm-action-hover);
  border-color: var(--gm-action-hover);
  color: var(--gm-action-text);
}

.btn.btn-brand-80:active,
.btn.btn-brand-80:active:focus,
.btn.btn-brand-80.active {
  background-color: var(--billington-blue-60);
  border-color: var(--billington-blue-60);
  color: white;
}

/* Red button (for cancel/secondary actions) - 100% with 80% active */
.btn.btn-red-80 {
  background-color: var(--billington-red);
  border-color: var(--billington-red);
  color: white;
}

.btn.btn-red-80:hover,
.btn.btn-red-80:focus {
  background-color: rgb(175, 29, 36);
  border-color: rgb(175, 29, 36);
  color: white;
}

.btn.btn-red-80:active,
.btn.btn-red-80:active:focus {
  background-color: var(--billington-red-80);
  border-color: var(--billington-red-80);
  color: white;
}

html {
  font-size: 16px; /* Consistent size - prevents iOS zoom on form inputs */
  position: relative;
  min-height: 100%;
}

/* Body styles removed - using Bootstrap flex utilities for sticky footer */

/* Navbar Logo - Maintains navbar height (for unauthenticated users) */
.navbar-logo {
  height: 20px;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}

/* App Context Bar - Persistent header for authenticated users
   Shows brand (red) or project context (blue) with integrated hamburger menu */
.app-context-bar {
  position: sticky;
  top: 0;
  z-index: 1020;                   /* Below modals (1055), above content */
  color: white;
  padding: 0.5rem 0.75rem;         /* 8px 12px - compact padding */
  min-height: 48px;                /* Slightly taller to accommodate button */
  display: flex;
  align-items: center;
  justify-content: space-between;  /* Push menu button to right */
  font-size: 1.25rem;              /* 20px - matches card headers */
  font-weight: 500;
  line-height: 1.3;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Content wrapper - allows text truncation while button stays fixed */
.app-context-bar .context-bar-content {
  display: flex;
  align-items: center;
  min-width: 0;                    /* Allow flex item to shrink below content size */
  flex: 1;
}

/* Hamburger menu button - integrated into context bar.
   gm-allow-small-target: the painted square stays 40px because the bar is 48px tall with 8px of
   vertical padding — a 44px button would push the bar to 60px on every page. The ::after below
   takes the HIT AREA to 44px with no visual change at all. */
.context-bar-menu-btn {
  width: 40px;
  height: 40px;
  min-width: 40px;
  padding: 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 6px;
  background-color: rgba(255, 255, 255, 0.15);
  color: white;
  font-size: 1.5rem;
  margin-left: 0.75rem;
  flex-shrink: 0;
  transition: background-color 0.2s ease;
}

/* Hit-area extension: 40px painted + 2px on every side = the 44px touch floor. Transparent and
   absolutely positioned, so it paints nothing and takes no space in the flex row. Taps on it
   land on the button because it is the button's own pseudo-element. */
.context-bar-menu-btn::after {
  content: "";
  position: absolute;
  inset: -2px;
}

.context-bar-menu-btn:hover {
  background-color: rgba(255, 255, 255, 0.25);
}

.context-bar-menu-btn:active {
  background-color: rgba(255, 255, 255, 0.35);
  transform: scale(0.96);
}

.context-bar-menu-btn:focus-visible {
  outline: 2px solid white;
  outline-offset: 2px;
}

/* Single app bar (D2): both states consume --gm-chrome, so the bar is one colour on every
   screen — red in legacy-2024, navy in holdings-2026. Project context is signalled by the
   CODE—Name text strip, not by bar colour. */
.app-context-bar--brand {
  background-color: var(--gm-chrome);
  color: var(--gm-chrome-text);
}

.app-context-bar--brand .context-bar-logo {
  height: 20px;
  width: auto;
}

.app-context-bar--project {
  background-color: var(--gm-chrome);
  color: var(--gm-chrome-text);
}

.app-context-bar--project .project-code {
  font-weight: 700;
  flex-shrink: 0;
}

.app-context-bar--project .project-separator {
  opacity: 0.7;
  flex-shrink: 0;
}

.app-context-bar--project .project-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Menu Drawer - Modern minimal navigation */
.menu-drawer {
  background-color: var(--gm-surface);
}

/* Drawer header band (atlas): consumes --gm-chrome to match the app bar. */
.menu-drawer-header {
  background-color: var(--gm-chrome);
  color: var(--gm-chrome-text);
  padding: 0.25rem 0.5rem;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  min-height: 48px;
}

/* Header buttons - shared styles */
.menu-header-btn {
  width: 44px;
  height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background-color: transparent;
  color: white;
  font-size: 1.25rem;
  text-decoration: none;
  transition: background-color 0.2s ease;
}

.menu-header-btn:hover {
  background-color: rgba(255, 255, 255, 0.15);
  color: white;
}

.menu-header-btn:active {
  background-color: rgba(255, 255, 255, 0.25);
}

.menu-header-btn:focus-visible {
  outline: 2px solid white;
  outline-offset: 2px;
}

/* Navigation list */
.menu-nav-list {
  display: flex;
  flex-direction: column;
  padding: 0.5rem 0;
}

/* Navigation items */
.menu-nav-item {
  display: flex;
  align-items: center;
  padding: 0.875rem 1.25rem;
  margin: 0 1rem;
  color: var(--gm-text);
  text-decoration: none;
  border-bottom: 1px solid var(--gm-hairline);
  transition: background-color 0.15s ease;
}

/* (hover: hover) — on touch, :hover applies on tap and STICKS. The menu is outside the boosted
   swap, so without this a tapped row keeps its hover tint after the drawer closes and reopens. */
@media (hover: hover) {
  .menu-nav-item:hover {
    background-color: rgba(0, 0, 0, 0.04);
    color: var(--gm-text);
    text-decoration: none;
  }
}
.menu-nav-item:hover {
  color: var(--gm-text);
  text-decoration: none;
}

.menu-nav-item:active {
  background-color: rgba(0, 0, 0, 0.08);
}

/* Page content section - full width available, no hamburger concerns */
.page-content {
  /* Content can use full width - positioned below header */
}

/* Mobile-optimised max width - keeps layout comfortable on desktop */
body {
  background-color: var(--g-dark-grey);
}

.app-shell {
  max-width: 576px;
  margin-left: auto;
  margin-right: auto;
  background-color: var(--bs-body-bg);
}

/* Constrain offcanvas menu to app column on desktop viewports */
@media (min-width: 577px) {
  .offcanvas.offcanvas-end {
    right: calc(50% - 288px);
    transition: none;
  }

  .offcanvas.offcanvas-end.hiding {
    visibility: hidden;
  }
}

/* Page Bottom Spacing - Ensures breathing room at bottom of all pages */
main.flex-fill > .container-fluid {
  padding-bottom: 1.5rem;
}

/* Mobile-first responsive utilities */
.box-shadow {
  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}

/* Preserves user-entered line breaks — used by five detail/update views (Incidents, NCRs, Variations) for multi-line text. */
.white-space-pre-wrap {
  white-space: pre-wrap;
}

/* Our own :active pressed states now fire on touch (site.js registers the touchstart listener
   iOS requires). The OS's translucent grey tap-flash would paint on top of them — two competing
   pressed effects per tap — so it is suppressed in favour of ours. */
* {
  -webkit-tap-highlight-color: transparent;
}

/* Busy indicators — 500ms delay, no layout shift.
 *
 * htmx applies .htmx-request to the element that ISSUED the request, and to any element named by
 * that element's hx-indicator. It never applies it to both a spinner AND its button. The previous
 * rules here required `.btn .htmx-indicator.htmx-request` — the class on the spinner span AND a
 * .btn ancestor — a combination nothing in the app ever produced, so every indicator was dead:
 *   - the credential submit spinner sits in a .gm-btn (gmButton emits gm-btn, never .btn) and the
 *     <form> is what receives .htmx-request;
 *   - gmFileUpload's progress row does receive .htmx-request via hx-indicator, but has no .btn ancestor;
 *   - the RAMS accept button has .btn, but the button receives the class, not its spinner span.
 * Found in the 2026-07-09 runtime testing pass; see
 * docs/assessments/2026-07-09-runtime-ux-remediation-plan.md (W1-3, W1-4).
 *
 * The contract below has three triggers, matching the three ways a control goes busy:
 *   1. .htmx-indicator.htmx-request  — hx-indicator names the element itself (file upload progress)
 *   2. .htmx-request <descendant>    — the button, or the form owning it, is in flight
 *   3. .is-busy                      — a native (non-htmx) form submit; set by site.js
 * Rule 2 is scoped to the button spinner classes on purpose: an unscoped `.htmx-request .htmx-indicator`
 * would also reveal gmFileUpload's "Uploading…" row whenever the surrounding form posts.
 */

/* Hide indicator completely by default */
.htmx-indicator {
  display: none;
}

/* Button setup for loading state */
.btn,
.gm-btn {
  position: relative;
  min-width: var(--btn-min-width, auto);
}

/* (1) Self-targeted indicator — the element named by hx-indicator. */
.htmx-indicator.htmx-request {
  display: inline-flex;
}

/* Position button spinners absolutely so they never affect layout */
.btn .htmx-indicator,
.gm-btn .gm-btn__spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: none;
  align-items: center;
  gap: 0.25rem;
  white-space: nowrap;
}

/* (2) + (3) Reveal the button spinner, initially invisible, shown after 500ms.
   `.htmx-request .gm-btn__spinner` already covers a busy .gm-btn (it is its own spinner's
   .htmx-request ancestor), so no separate .gm-btn.htmx-request rule is needed. */
.btn.htmx-request .htmx-indicator,
.btn.is-busy .htmx-indicator,
.htmx-request .gm-btn__spinner,
.gm-btn.is-busy .gm-btn__spinner {
  display: inline-flex;
  opacity: 0;
  animation: showAfterDelay 0s step-end 0.5s forwards;
}

/* Show indicator instantly after 500ms delay */
@keyframes showAfterDelay {
  to {
    opacity: 1;
  }
}

/* Keep text visible and properly aligned */
.btn .btn-text,
.gm-btn .gm-btn__label {
  display: inline-flex;
  align-items: center;
}

/* Fade the label on the same 500ms beat. Opacity, not display: the button keeps its width. */
.btn.htmx-request .btn-text,
.btn.is-busy .btn-text,
.htmx-request .gm-btn__label,
.gm-btn.is-busy .gm-btn__label {
  animation: hideAfterDelay 0s step-end 0.5s forwards;
}

/* Hide text instantly after 500ms delay */
@keyframes hideAfterDelay {
  to {
    opacity: 0;
  }
}

/* Lock the control while it is in flight. Belt and braces alongside hx-disabled-elt / the
   site.js re-entry guard, both of which also stop a second submit reaching the server.
   Descendant, not child: htmx stamps .htmx-request on the <form>, and the submit button is
   usually nested inside a gmActionBar rather than being a direct child of the form. */
.btn.htmx-request,
.btn.is-busy,
.gm-btn.htmx-request,
.gm-btn.is-busy,
.htmx-request .gm-btn[type="submit"] {
  pointer-events: none;
  cursor: wait;
}

/* Enhanced pressed state for better tactile feedback */
.btn:active:not(:disabled):not(.htmx-request):not(.is-busy),
.gm-btn:active:not(:disabled):not(.htmx-request):not(.is-busy) {
  transform: scale(0.98);
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}

/* Ensure Bootstrap spinner sizing in buttons */
.btn .spinner-border-sm,
.gm-btn .spinner-border-sm {
  width: 1rem;
  height: 1rem;
  border-width: 0.15em;
  vertical-align: middle;
}

/* The .offcanvas-header.bg-primary rules that used to sit here were dead: the drawer markup in
   _HamburgerMenu.cshtml uses .menu-drawer-header with a .menu-header-btn.menu-close-btn, and
   nothing in the app has emitted .offcanvas-header or .btn-close-white since. Removed 2026-07-31
   after TouchTargetGuardTests flagged the close button at 1.5em (24px) and the investigation
   found the real close button is .menu-header-btn, already on the 44px floor. */

/* ============================================
   CUSTOM COMPONENTS (gm* Tag Helpers)
   ============================================ */

/* Cards and list items sit on the surface token (flips with the colour scheme). */
.card,
.list-group-item {
  background-color: var(--gm-surface);
}

/* Square corners on cards */
.card {
  border-radius: 0;
}

/* gmCard - Reusable card component with clickable styling
   Usage: <gmCard title="..." icon="..." clickable="true">content</gmCard>
   Wrap in HTMX div or anchor tag for interactive behavior
*/
.card-clickable {
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

/* Hover state - card lifts and shadow increases. Gated on a real hover device: on touch,
   an unwrapped :hover sticks after tap until the next tap elsewhere. */
@media (hover: hover) {
  .card-clickable:hover {
    transform: translateY(-2px);
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  }
}

/* Active state - card returns to normal position with feedback */
.card-clickable:active {
  transform: translateY(0);
  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

/* Prevent child elements from interfering with click events */
.card-clickable * {
  pointer-events: none;
}

/* Touch-friendly active state for mobile devices */
@media (hover: none) {
  .card-clickable:active {
    background-color: rgba(0, 0, 0, 0.02);
  }
}

/* Wizard card - fills available vertical space with buttons at bottom
   Usage: <gmCard card-class="wizard-card" body-class="wizard-body">
          <div class="wizard-content">...step content...</div>
          <partial name="_WizardNavButtons" />
          </gmCard>
*/
.wizard-card {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 130px);      /* fallback */
  height: calc(100dvh - 130px);     /* iOS Safari tab: tracks the collapsing URL bar */
  max-height: calc(100vh - 130px);
  max-height: calc(100dvh - 130px);
}

.wizard-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow: hidden; /* Prevent body from overflowing */
}

.wizard-content {
  flex: 1;
  overflow-y: auto; /* Scroll when content overflows */
  /* A scroll container clips at its padding box, which was cropping the 0.25rem focus-ring halo
     on the controls inside (visible on Windows, and the same on iOS/Android — the clip is CSS,
     not platform). Give the halo that much room inside the clip box and pull the box back out
     with matching negative margins, so nothing visibly moves. Bottom keeps its larger padding. */
  padding: 0.25rem 0.25rem 0.5rem;
  margin: -0.25rem -0.25rem 0;
}

/* gmTitleBar - Section header bar below navbar
   Usage: <gmTitleBar title="..." back-url="..." header-action="..." />
   Displays: [back button] [title] [action buttons]
*/
.titlebar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.5rem;
  padding: 0;
  height: 44px;
  background-color: var(--gm-surface);
  border-bottom: 1px solid var(--gm-hairline);
  margin-bottom: 0.75rem;
}

.titlebar-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--gm-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* The title sits in a 1fr grid track whose automatic minimum is min-content — the full width of
     the text. Without this the track expands and pushes the action button off the edge instead of
     ellipsising, which a long document filename does immediately. */
  min-width: 0;
}

/* Unified text color for cards */
.card-header {
  color: var(--gm-text);
}

/* Default gmCard header — token-based surface so it flips with data-bs-theme dark
   (replaces the hardcoded Bootstrap bg-white the component used to emit). */
.card-header.gm-card-header {
  background-color: var(--gm-surface);
  color: var(--gm-text);
}

.card-header h5 {
  font-weight: 600;
}

.card-body {
  color: var(--gm-text);
}

/* Section headers for unified list containers - matches gmCard header style */
.section-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  font-weight: 600;
  font-size: 1.25rem;
  color: var(--gm-text);
  background-color: var(--gm-surface);
  border-bottom: 1px solid var(--gm-hairline);
}

.section-header i {
  font-size: 1.25rem;
}

/* Add top border to section headers that follow a list-group (divider between sections) */
.list-group + .section-header {
  border-top: 1px solid var(--bs-border-color);
}

/* Collapsible section headers */
.section-header[data-bs-toggle="collapse"] {
  cursor: pointer;
}

.section-header[data-bs-toggle="collapse"]:hover {
  background-color: var(--bs-gray-100);
}

.section-header .section-chevron {
  transition: transform 0.2s ease;
}

.section-header[aria-expanded="false"] .section-chevron {
  transform: rotate(-90deg);
}

.section-header[aria-expanded="false"] {
  border-bottom: none;
}

.titlebar-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--gm-text);   /* was #495057 — raw dark grey, near-invisible on a dark surface */
  font-size: 1.25rem;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  border-radius: 0.25rem;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.titlebar-btn:hover {
  background-color: rgba(0, 0, 0, 0.1);
}

.titlebar-btn:active {
  color: var(--billington-red, #dc3545);
}

.titlebar-actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

/* ============================================
   FEATURE-SPECIFIC STYLES
   ============================================ */

/* Consistent button sizing - applied at all screen sizes for visual consistency */
.btn {
  min-height: 44px;
  padding: 0.75rem 1rem;
}

.btn-lg {
  min-height: 48px;
  font-size: 1.1rem;
}

/* ============================================
   Login Page Styles
   ============================================ */

/* Login Page Layout */
.login-page {
  min-height: 100vh;   /* fallback */
  min-height: 100dvh;  /* iOS Safari tab: tracks the collapsing URL bar */
  background-color: var(--gm-canvas);
  display: flex;
  flex-direction: column;
}

.login-holdings-logo {
  max-width: 280px;
  width: 100%;
  height: auto;
}

.login-content {
  flex: 1;
  padding: 2rem;
  max-width: 400px;
  margin: 0 auto;
  width: 100%;
}

.login-title {
  text-align: center;
  margin-bottom: 1.5rem;
  color: var(--gm-link);
  font-weight: 600;
}

.login-footer {
  text-align: center;
  color: var(--gm-text-secondary);
  font-size: 0.875rem;
  margin-top: 1.5rem;
  margin-bottom: 0;
}

/* Microsoft Sign-In Button - Billington Red */
.btn-microsoft {
  background-color: var(--billington-red);
  border-color: var(--billington-red);
  color: white;
  font-weight: 500;
}

.btn-microsoft:hover {
  background-color: rgb(175, 29, 36);
  border-color: rgb(175, 29, 36);
  color: white;
}

.btn-microsoft:active,
.btn-microsoft:focus {
  background-color: var(--billington-red-80);
  border-color: var(--billington-red-80);
  color: white;
  box-shadow: 0 0 0 0.25rem rgba(195, 32, 40, 0.25);
}

/* Login Divider - "OR" separator */
.login-divider {
  display: flex;
  align-items: center;
  text-align: center;
  margin: 1.5rem 0;
  color: var(--g-medium-grey);
}

.login-divider::before,
.login-divider::after {
  content: '';
  flex: 1;
  border-bottom: 1px solid var(--g-light-grey);
}

.login-divider span {
  padding: 0 1rem;
  font-weight: 500;
  font-size: 0.875rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Touch-friendly form elements — iOS zoom prevention + 44px touch floor.
   UNCONDITIONAL (A3): the 16px/44px rule applies at every viewport, not only mobile, so the
   field ergonomics hold on tablets/desktop too. Compensating inline styles are being deleted as
   forms migrate to the gmField family. */
.form-control, .form-select {
  min-height: 44px;
  font-size: 16px; /* CRITICAL: Prevents iOS zoom on focus */
  padding: 0.5rem 0.75rem; /* Comfortable touch padding */
}

/* Large variants for emphasis screens */
.form-control-lg, .form-select-lg {
  min-height: 48px;
  font-size: 16px; /* Prevents zoom on iOS */
}

/* Ensure textarea also prevents zoom */
textarea.form-control {
  font-size: 16px;
}

/* Date and time inputs need explicit sizing */
input[type="date"].form-control,
input[type="time"].form-control,
input[type="datetime-local"].form-control {
  font-size: 16px;
  min-height: 44px;
}

/* iOS: native date/time inputs size to fit-content and ignore width:100%, overflowing their
   container on the right on real WebKit (desktop emulation only fakes the viewport, not the
   UA rendering, so it looks fine there). Stripping the native appearance makes them obey the
   box model like every other .gm-input. Empty-value height is held by .gm-input's min-height. */
input[type="date"].gm-input,
input[type="time"].gm-input,
input[type="datetime-local"].gm-input,
input[type="date"].form-control,
input[type="time"].form-control,
input[type="datetime-local"].form-control {
  -webkit-appearance: none;
  appearance: none;
  min-width: 0;      /* let flex/grid parents shrink it instead of overflowing */
  max-width: 100%;
}

/* Once appearance is stripped, iOS centres the value; left-align to match text inputs. */
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value,
input[type="datetime-local"]::-webkit-date-and-time-value {
  text-align: left;
}

/* Same family, resting state: iOS paints its own chrome on un-normalised controls (inner top
   shadow on text fields, native height/padding on selects), so every .gm-input control gets the
   appearance reset — real WebKit only; desktop emulation can't show the difference. */
input.gm-input,
textarea.gm-input,
select.gm-input {
  -webkit-appearance: none;
  appearance: none;
}

/* Stripping appearance removes the select's native arrow — supply our own (Bootstrap 5.3's
   chevron, sized/positioned as .form-select does, so gm-input and form-select dropdowns match). */
select.gm-input {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 16px 12px;
  padding-right: 2.25rem;
}

[data-bs-theme="dark"] select.gm-input {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* Progress indicators (emitted by gmFileUpload) */
.upload-progress {
  padding: 1rem;
  background-color: var(--gm-canvas);   /* was #f8f9fa — raw light panel, glaring on dark */
  border-radius: 0.5rem;
  border: 1px solid var(--gm-hairline); /* was #dee2e6 */
}

/* Mobile-friendly alerts */
@media (max-width: 576px) {
  .alert {
    font-size: 0.9rem;
  }
  
  .alert .btn-close {
    font-size: 1.2rem;
  }
}

/* Improved touch targets for mobile */
.list-group-item {
  min-height: 80px;
}

.btn-group .btn {
  min-width: 44px;
  min-height: 44px;
}

/* Loading states for better UX */
.htmx-request .spinner-border,
.htmx-request .progress-bar-animated {
  animation-duration: 0.75s;
}

/* Ensure proper spacing on mobile */
@media (max-width: 576px) {
  .container-fluid {
    padding-left: 15px;
    padding-right: 15px;
  }
  
  .card-body {
    padding: 1rem 0.75rem;
  }
}

/* Timeline styles - Required for activity timeline visualization */
.timeline {
  position: relative;
  padding-left: 2rem;
}

.timeline::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 0;
  height: 100%;
  width: 2px;
  background: var(--gm-hairline);   /* was #dee2e6 — raw light grey, invisible-context on dark */
}

.timeline-item {
  position: relative;
}

.timeline-item::before {
  content: '';
  position: absolute;
  left: -26px;
  top: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gm-text-secondary);
  border: 2px solid var(--gm-surface);
  box-shadow: 0 0 0 1px var(--gm-hairline);
}

.timeline-icon {
  margin-left: -2rem;
}

@media (max-width: 768px) {
  .timeline {
    padding-left: 1.5rem;
  }
}


/* ================================================================
   gm-* DESIGN SYSTEM UTILITIES & COMPONENT STYLES  (plan A2)
   Token-only: every colour/size below reads a --gm-* variable
   (theming doc §2). NO raw hexes, NO !important. Values match the
   design-review §6 specs; the legacy-2024 :root keeps today's look,
   holdings-2026 is a token flip — never a rule change here.
   ================================================================ */

/* --- Touch & input floors (utilities; the global form rule stays
       media-query-gated until gmField lands in A3) ---------------- */
.gm-touch {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The shared field control (gmField / gmSelect / gmDate / gmTextArea / gmSearchBar). White
   background, grey border, 16px/48px, navy focus ring (replaces the hard-coded #0d6efd). */
.gm-input {
  display: block;
  width: 100%;
  min-height: 48px;
  font-size: 16px; /* iOS zoom floor */
  padding: 0.5rem 0.75rem;
  color: var(--gm-text);
  background-color: var(--gm-surface);
  border: 1px solid var(--gm-text-secondary);
  border-radius: var(--gm-radius);
  line-height: 1.4;
}

.gm-input:focus {
  outline: none;
  border-color: var(--gm-link);
  box-shadow: 0 0 0 3px var(--gm-focus);
}

.gm-input.is-invalid {
  border-color: var(--gm-status-danger);
}

.gm-input.is-invalid:focus {
  box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.25);
}

.gm-input:disabled {
  background-color: var(--gm-canvas);
  color: var(--gm-text-secondary);
}

textarea.gm-input {
  min-height: 96px;
  resize: vertical;
}

/* --- gmSpinner ------------------------------------------------- */
.gm-spinner-lg {
  width: 3rem;
  height: 3rem;
}

/* --- gmEmptyState --------------------------------------------- */
.gm-empty-state {
  position: relative;
  overflow: hidden;
  padding: 2rem 1rem;
}

.gm-empty-state-icon {
  display: block;
  font-size: 48px;
  line-height: 1;
  color: var(--gm-text-secondary);
  margin-bottom: 0.75rem;
}

.gm-empty-state-title {
  font-size: 17px;
  font-weight: 600;
  color: var(--gm-text);
  margin-bottom: 0.25rem;
}

.gm-empty-state-caption {
  font-size: 14px;
  color: var(--gm-text-secondary);
}

.gm-empty-state-action {
  margin-top: 1rem;
}

/* D5 watermark device: pale-grey brand-icon ghost bleeding off one
   card edge, never angled/flipped. Off unless watermark="true". */
.gm-empty-state-watermark {
  position: absolute;
  right: -0.5rem;
  bottom: -1.5rem;
  font-size: 8rem;
  line-height: 1;
  color: var(--gm-text-secondary);
  opacity: 0.06;
  pointer-events: none;
}

/* --- gmStatusBadge (locked colour grammar, design review §6) --- */
.gm-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  border-radius: 999px;
  padding: 4px 10px;
  min-height: 24px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
}

.gm-badge--success {
  background-color: var(--gm-status-success);
  color: var(--gm-status-on-fill);
}

.gm-badge--warning {
  background-color: var(--gm-status-warning);
  color: var(--gm-status-warning-on-fill); /* dark text on amber fill (§6) */
}

.gm-badge--danger {
  background-color: var(--gm-status-danger);
  color: var(--gm-status-on-fill);
}

.gm-badge--workflow {
  background-color: var(--gm-status-workflow);
  color: var(--gm-status-on-fill);
}

.gm-badge--neutral {
  background-color: var(--gm-status-neutral);
  color: var(--gm-status-neutral-text);
}

/* --- Status-coloured select (SOR item status, W3-2) ----------- */
/* The select wears the tone of its CURRENT value; site.js swaps the modifier on change from the
   selected option's data-gm-status. Same grammar as the badges above (OK success / ActionRequired
   warning / Issue danger). Border uses the fill token (visible in both schemes); text uses the
   *-text token, which the dark block relights — no dark-mode overrides needed here. Deliberately
   text + border, not a filled control: a solid amber select field reads as an error state, and
   the filled form is reserved for badges.

   The solid dot before the text (owner, 2026-07-10) is what carries the status's TRUE colour:
   the amber-as-text token is darkened to #8A5A00 for AA contrast on white and reads olive there,
   so the dot shows the raw fill (#FFC107 etc.) that text never legibly can. Drawn as a second
   background layer beside Bootstrap's chevron — an inline element cannot sit inside a <select>.
   Neutral (NA) gets a muted dot so the text never jumps between states. */
.gm-status-select {
  --gm-select-dot: var(--bs-secondary-color);
  background-image: var(--bs-form-select-bg-img), radial-gradient(circle, var(--gm-select-dot) 5px, transparent 6px);
  background-position: right 0.75rem center, left 0.875rem center;
  background-size: 16px 12px, 12px 12px;
  background-repeat: no-repeat;
  padding-left: 2.25rem;
}

.gm-status-select--success {
  --gm-select-dot: var(--gm-status-success);
  border-color: var(--gm-status-success);
  color: var(--gm-status-success-text);
  font-weight: 500;
}

.gm-status-select--warning {
  --gm-select-dot: var(--gm-status-warning);
  border-color: var(--gm-status-warning);
  color: var(--gm-status-warning-text);
  font-weight: 500;
}

.gm-status-select--danger {
  --gm-select-dot: var(--gm-status-danger);
  border-color: var(--gm-status-danger);
  color: var(--gm-status-danger-text);
  font-weight: 500;
}

/* The dropdown's option list must NOT inherit the select's tone — without these rules, picking
   "Immediate Action" painted every option red in the open list (owner screenshots, 2026-07-10).
   Each option carries its own status colour instead, keyed on the same data-gm-status the JS
   reads, so the list previews each choice's severity. Chromium and Firefox honour colour on
   <option>; iOS shows a native picker and ignores it, which is a harmless fallback. */
.gm-status-select option {
  color: var(--bs-body-color);
  font-weight: 400;
}

.gm-status-select option[data-gm-status="success"] { color: var(--gm-status-success-text); }
.gm-status-select option[data-gm-status="warning"] { color: var(--gm-status-warning-text); }
.gm-status-select option[data-gm-status="danger"]  { color: var(--gm-status-danger-text); }

/* Amber as TEXT on a surface — the AA-safe token, never Bootstrap's .text-warning (#FFC107 fails
   AA on white). Scheme-aware (the token lightens in dark). Use where a warning status shows as text. */
.gm-text-warning { color: var(--gm-status-warning-text); }

/* SOR summary rows are tap-to-edit (jump back to that checklist item). Cursor + hover make the
   affordance visible; the pencil icon in the row states it. */
.gm-sor-summary-row { cursor: pointer; }
.gm-sor-summary-row:active { background-color: rgba(0, 0, 0, 0.04); }
@media (hover: hover) {
  .gm-sor-summary-row:hover { background-color: rgba(0, 0, 0, 0.04); }
}

/* --- gm-toast (transient action confirmation) ----------------- */
/* Shown by site.js for the gmToast event (server GmToast() helper). Bottom-centred pill in the
   chrome colours, clear of the iOS home bar. pointer-events none: it must never intercept a tap.
   z-index above the modal overlay (1060) — a delete's toast fires as its confirm dialog closes. */
.gm-toast {
  position: fixed;
  left: 50%;
  bottom: calc(1.5rem + env(safe-area-inset-bottom));
  transform: translate(-50%, 0.5rem);
  max-width: calc(100vw - 2rem);
  padding: 0.55rem 1.1rem;
  border-radius: 999px;
  background-color: var(--gm-chrome);
  color: var(--gm-chrome-text);
  font-size: 0.9rem;
  /* Flex row so the sticky variant's spinner and the message are truly centre-aligned
     (baseline alignment sat the 1rem spinner slightly low beside 0.9rem text). The
     ellipsis lives on the inner .gm-toast-text — a flex container can't ellipsize an
     anonymous text child. */
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 1080;
}

.gm-toast .gm-toast-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gm-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

@media (prefers-reduced-motion: reduce) {
  .gm-toast {
    transition: opacity 0.18s ease;
    transform: translate(-50%, 0);
  }
}

/* --- gmMetaChip (outline; never a status colour) -------------- */
.gm-meta-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--gm-text-secondary);
  background-color: transparent;
  border: 1px solid var(--gm-hairline);
  white-space: nowrap;
}

/* --- gmListGroup / gmListItem (canonical interactive row §6) --- */
.gm-list-group {
  display: flex;
  flex-direction: column;
}

.gm-list-item {
  display: flex;
  align-items: stretch;
  background-color: var(--gm-surface);
  border-bottom: 1px solid var(--gm-hairline);
}

.gm-list-group > .gm-list-item:last-child {
  border-bottom: 0;
}

.gm-list-item__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 64px;
  padding: 16px;
  color: inherit;
  text-decoration: none;
}

.gm-list-item--navigates .gm-list-item__main {
  cursor: pointer;
}

.gm-list-item--navigates .gm-list-item__main:active {
  background-color: rgba(0, 0, 0, 0.04);
}
@media (hover: hover) {
  .gm-list-item--navigates .gm-list-item__main:hover {
    background-color: rgba(0, 0, 0, 0.04);
  }
}

.gm-list-item__body {
  flex: 1 1 auto;
  min-width: 0;
}

.gm-list-item__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--gm-link);
}

.gm-list-item__meta {
  font-size: 14px;
  color: var(--gm-text-secondary);
}

.gm-list-item__badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}

.gm-list-item__aside {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
}

.gm-list-item__date {
  font-size: 13px;
  color: var(--gm-text-secondary);
}

.gm-list-item__chevron {
  font-size: 16px;
  color: var(--gm-text-secondary);
}

.gm-list-item__action {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-right: 8px;
}

.gm-list-item__action-btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: var(--gm-text-secondary);
  cursor: pointer;
}

/* --- gmButton utility classes (used now by modal footers;
       gmButton component in A3 reuses these) ------------------- */
.gm-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35em;
  min-height: 48px;
  padding: 0.5rem 1rem;
  border-radius: var(--gm-radius);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
}

.gm-btn--block {
  width: 100%;
}

/* Compact size modifier for inline controls inside banners/alerts (e.g. the staleness "Retry").
   Deliberately below the 48px default; not for primary form actions.
   gm-allow-small-target: the painted button stays 36px so it sits inline in a one-line banner
   without inflating it; the ::after below takes the HIT AREA to 44px with no visual change. */
.gm-btn--sm {
  min-height: 36px;
  padding: 0.25rem 0.75rem;
  font-size: 14px;
  position: relative;
}

.gm-btn--sm::after {
  content: "";
  position: absolute;
  inset: -4px;
}

.gm-btn[aria-disabled="true"] {
  pointer-events: none;
  opacity: 0.65;
}

.gm-btn--primary {
  background-color: var(--gm-action);
  border-color: var(--gm-action);
  color: var(--gm-action-text);
}

.gm-btn--primary:hover,
.gm-btn--primary:active {
  background-color: var(--gm-action-hover);
  border-color: var(--gm-action-hover);
  color: var(--gm-action-text);
}

.gm-btn--secondary {
  background-color: var(--gm-surface);
  border-color: var(--gm-text-secondary);
  color: var(--gm-link);
}

.gm-btn--danger {
  background-color: var(--gm-status-danger);
  border-color: var(--gm-status-danger);
  color: var(--gm-status-on-fill);
}

.gm-btn:disabled {
  background-color: var(--gm-action-disabled);
  border-color: var(--gm-action-disabled);
  cursor: default;
}

/* --- gmModal / gmConfirmModal (design review §6) -------------- */
.gm-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1050;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background-color: rgba(0, 0, 0, 0.5);
}

.gm-modal {
  display: flex;
  flex-direction: column;
  width: 90%;
  max-height: 90vh;
  background-color: var(--gm-surface);
  border-radius: 12px;
  box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.3);
  overflow: hidden;
}

.gm-modal--sm { max-width: 400px; }
.gm-modal--md { max-width: 500px; }
.gm-modal--lg { max-width: 640px; }

.gm-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--gm-hairline);
}

.gm-modal__title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--gm-text);
}

.gm-modal__close {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: var(--gm-text-secondary);
  cursor: pointer;
}

.gm-modal__body {
  padding: 1.5rem;
  overflow-y: auto;
  font-size: 15px;
  line-height: 1.5;
  color: var(--gm-text);
}

.gm-modal__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--gm-hairline);
}

/* ================================================================
   gm-* DESIGN SYSTEM — WAVE 2 (plan A3)
   Token-only; no raw hexes, no !important. Values match design
   review §6. legacy-2024 keeps today's look; holdings-2026 flips.
   ================================================================ */

/* --- gmField family (label/control/validation) --------------- */
.gm-field {
  margin-bottom: 1rem;
}

.gm-field__label {
  display: block;
  font-size: 15px;
  font-weight: 600;
  color: var(--gm-link);
  margin-bottom: 0.35rem;
}

.gm-field__required {
  color: var(--gm-status-danger-text);
}

.gm-field__helper {
  font-size: 13px;
  color: var(--gm-text-secondary);
  margin-top: 0.25rem;
}

.gm-field__error {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 13px;
  color: var(--gm-status-danger-text);
  margin-top: 0.25rem;
}

/* --- gmSearchBar --------------------------------------------- */
.gm-search {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--gm-hairline);
}

.gm-search__field {
  position: relative;
}

.gm-search__icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--gm-text-secondary);
  pointer-events: none;
}

.gm-search__input {
  height: 48px;
  padding-left: 40px;
  padding-right: 44px;
}

/* The component has its own clear-X; suppress WebKit's native one so iOS
   doesn't show two side by side on type="search" inputs. */
.gm-search__input::-webkit-search-cancel-button,
.gm-search__input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.gm-search__clear {
  position: absolute;
  right: 2px;
  top: 50%;
  transform: translateY(-50%);
  border: 0;
  background: transparent;
  color: var(--gm-text-secondary);
}

/* Revealed by the .htmx-indicator.htmx-request rule (gmSearchBar's hx-indicator names this
   element), which sets display: inline-flex — centre it explicitly rather than relying on the
   text-align it carried while it was a never-revealed block. */
.gm-search__spinner {
  width: 100%;
  justify-content: center;
  margin-top: 0.5rem;
}

/* --- gmLoadMore ---------------------------------------------- */
.gm-load-more {
  text-align: center;
  padding: 1rem;
  border-top: 1px solid var(--gm-hairline);
  color: var(--gm-text-secondary);
  font-size: 0.875rem;
  cursor: pointer;
}

.gm-load-more:hover,
.gm-load-more:focus-visible {
  background-color: rgba(0, 0, 0, 0.03);
}

/* --- gmFileUpload -------------------------------------------- */
.gm-file-upload__gallery {
  margin-bottom: 0.75rem;
}

.gm-file-upload__count {
  margin-bottom: 0.5rem;
  color: var(--gm-text-secondary);
}

.gm-file-upload__items {
  border: 1px solid var(--gm-hairline);
  border-radius: var(--gm-radius);
}

.gm-file-upload__empty {
  text-align: center;
  padding: 0.75rem;
  background-color: var(--gm-canvas);
  color: var(--gm-text-secondary);
}

/* The placeholder is a sibling of the hx-swap="beforeend" target, not a child of it, so an upload
   cannot leave it stranded above the file it just added. Hide it the moment the container holds
   anything; show it again if every item is removed.
   :empty is exact only while nothing writes stray text nodes into the container — which is why
   every upload response is whitespace-free. See PartialRenderingExtensions.RenderPartialToStringAsync.
   The ~ arm hides the placeholder while an upload is in flight, so the "Uploading…" row takes its
   place rather than sitting beneath a message saying there is nothing here. */
.gm-file-upload__items:not(:empty) ~ .gm-file-upload__empty,
.gm-file-upload__progress.htmx-request ~ .gm-file-upload__empty {
  display: none;
}

/* Remove-file buttons live inside the gallery, one per item, across nine feature partials. htmx
   stamps .htmx-request on the button itself, so scoping to the gallery gives every one of them a
   busy state with no markup change. The existing .btn.htmx-request rule already locks the button. */
.gm-file-upload__items .btn.htmx-request {
  position: relative;
}

.gm-file-upload__items .btn.htmx-request > i {
  visibility: hidden;
}

.gm-file-upload__items .btn.htmx-request::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.875rem;
  height: 0.875rem;
  margin: -0.4375rem 0 0 -0.4375rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: gmSpin 0.75s linear infinite;
}

@keyframes gmSpin {
  to { transform: rotate(360deg); }
}

/* Shown by the .htmx-indicator.htmx-request rule (hx-indicator names this element), which sets
   display: inline-flex — so centre it explicitly rather than relying on the text-align it had
   while it was a never-revealed block. */
.gm-file-upload__progress {
  padding: 0.5rem 0;
  width: 100%;
  justify-content: center;
  align-items: center;
  gap: 0.5rem;
  color: var(--gm-text-secondary);
}

.gm-file-upload__hint {
  display: block;
  margin-top: 0.5rem;
  font-size: 13px;
  color: var(--gm-text-secondary);
}

/* Local (offline-captured) gallery items — built client-side by gm-offline.js when an upload is
   captured with no signal. The thumbnail is an object-URL preview of the on-device blob; the chip
   states plainly that the file is not yet with the office (PRD §6.1 "on this phone"). */
.gm-file-upload__local-thumb {
  width: 2rem;
  height: 2rem;
  object-fit: cover;
  flex-shrink: 0;
}

.gm-file-upload__local-chip {
  background-color: var(--gm-canvas);
  color: var(--gm-text-secondary);
  border: 1px solid var(--gm-hairline);
  font-weight: 500;
}

/* Attachment remove buttons in both galleries (offline upload + SOR capture) render as Bootstrap
   btn-sm, which is below the 44px touch minimum. Restore the target without changing their look. */
[data-gm-offline-remove],
.gm-sor-gallery-item .btn-outline-danger {
  min-width: 44px;
  min-height: 44px;
}

/* --- Outbox UX (offline-tiers M4, PRD §4/§9) ------------------
   The /Outbox page rows, the drawer count badge, the per-list pending strip and the item
   controls. All built client-side by gm-offline.js (GmOffline.ux); token-only, no !important. */

/* Drawer count badge on the Outbox nav row. */
.gm-outbox-badge {
  background-color: var(--gm-status-danger);
  color: var(--gm-status-on-fill);   /* text ON the danger fill — the -text token equals the fill in light mode (invisible) */
  font-size: 12px;
  font-weight: 600;
  min-width: 1.25rem;
}

/* An outbox row — a compact card per queued/sending/received/needs-attention submission. */
.gm-outbox-item {
  background-color: var(--gm-surface);
  border: 1px solid var(--gm-hairline);
  border-radius: var(--gm-radius, 8px);
  padding: 0.85rem 1rem;
  margin-bottom: 0.75rem;
}

.gm-outbox-item__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.gm-outbox-item__kind {
  font-size: 16px;
  font-weight: 600;
  color: var(--gm-text);
}

.gm-outbox-item__summary {
  margin-top: 0.25rem;
  font-size: 14px;
  color: var(--gm-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gm-outbox-item__meta {
  margin-top: 0.25rem;
  font-size: 13px;
  color: var(--gm-text-secondary);
}

.gm-outbox-item__error {
  margin-top: 0.4rem;
  font-size: 13px;
  color: var(--gm-status-danger);
}

.gm-outbox-item__controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* Item action buttons are compact overrides of the shared gm-btn (44px touch target). */
.gm-outbox-item__btn {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0.5rem 0.9rem;
  font-size: 14px;
}

/* The state chip on each outbox row — a filled pill echoing the status grammar. */
.gm-outbox-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.gm-outbox-chip--queued {
  background-color: var(--gm-status-neutral);
  color: var(--gm-status-neutral-text);
}
.gm-outbox-chip--sending {
  background-color: var(--gm-status-workflow);
  color: var(--gm-status-on-fill);   /* text ON the fill — the -text token equals the fill in light mode (invisible) */
}
.gm-outbox-chip--received {
  background-color: var(--gm-status-success);
  color: var(--gm-status-on-fill);   /* text ON the fill — the -text token equals the fill in light mode (invisible) */
}
.gm-outbox-chip--attention {
  background-color: var(--gm-status-warning);
  color: var(--gm-status-warning-on-fill);   /* dark text ON the amber fill (matches .gm-badge--warning) */
}

/* The per-list pending banner (F6): PROMINENT, so unsent work is front-and-centre where the user
   looks for it — using the app's amber "waiting/attention" grammar (a solid left accent + tinted
   surface). Still a single, honest pointer to the Outbox (a bold count, a reassuring subtitle and a
   tap-through chevron), never a synthesised confirmed list row (PRD §4 visual-distinction). */
.gm-outbox-strip {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  margin: 0 0 0.75rem;
  color: var(--gm-text);
  background-color: rgba(255, 193, 7, 0.12);   /* translucent amber — reads on light and dark surfaces */
  border: 1px solid var(--gm-status-warning);
  border-left-width: 4px;
  border-radius: var(--gm-radius, 8px);
  text-decoration: none;
}
.gm-outbox-strip__icon {
  font-size: 1.35rem;
  line-height: 1;
  color: var(--gm-status-warning-text);
  flex-shrink: 0;
}
.gm-outbox-strip__body {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  min-width: 0;
}
.gm-outbox-strip__title {
  font-size: 15px;
  font-weight: 700;
  color: var(--gm-text);
}
.gm-outbox-strip__sub {
  font-size: 12px;
  color: var(--gm-text-secondary);
}
.gm-outbox-strip__chevron {
  font-size: 1.1rem;
  line-height: 1;
  color: var(--gm-text-secondary);
  flex-shrink: 0;
}
.gm-outbox-strip:active {
  background-color: rgba(255, 193, 7, 0.2);
}
@media (hover: hover) {
  .gm-outbox-strip:hover {
    background-color: rgba(255, 193, 7, 0.2);
  }
}

/* --- gmDetailList / gmDetailRow (stacked label/value) -------- */
.gm-detail-list {
  margin: 0;
}

.gm-detail-list dd {
  margin-left: 0;
}

.gm-detail-row__label {
  font-size: 13px;
  color: var(--gm-text-secondary);
  margin-bottom: 2px;
}

.gm-detail-row__value {
  font-size: 16px;
  font-weight: 600;
  color: var(--gm-text);
  margin: 0 0 12px 0;
}

.gm-detail-list > .gm-detail-row__value:last-child {
  margin-bottom: 0;
}

/* --- gmDocThumb + gmListItem media slot ---------------------- */
.gm-doc-thumb {
  width: 50px;
  height: 50px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--gm-radius);
  overflow: hidden;
  flex-shrink: 0;
}

.gm-doc-thumb__img {
  width: 50px;
  height: 50px;
  object-fit: cover;
  border: 1px solid var(--gm-hairline);
  border-radius: var(--gm-radius);
}

.gm-doc-thumb i {
  font-size: 1.75rem;
}

.gm-doc-thumb--pdf i {
  color: var(--gm-status-danger-text);
  font-size: 2rem;
}

.gm-doc-thumb--file i {
  color: var(--gm-text-secondary);
}

.gm-list-item__media {
  flex: 0 0 auto;
  margin-right: 12px;
  display: flex;
  align-items: center;
}

/* --- gmStatTile + grid --------------------------------------- */
.gm-stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.gm-stat-tile {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px;
  background-color: var(--gm-surface);
  border: 1px solid var(--gm-hairline);
  border-radius: var(--gm-radius);
  text-decoration: none;
  color: inherit;
  min-height: 44px;
}

.gm-stat-tile--tap {
  cursor: pointer;
}

.gm-stat-tile--tap:hover,
.gm-stat-tile--tap:focus-visible {
  background-color: rgba(0, 0, 0, 0.02);
}

.gm-stat-tile__icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--gm-status-on-fill);
  flex-shrink: 0;
  font-size: 1.1rem;
}

.gm-stat-tile__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.gm-stat-tile__value {
  font-size: 28px;
  font-weight: 700;
  line-height: 1.1;
}

.gm-stat-tile__label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  color: var(--gm-text-secondary);
  letter-spacing: 0.03em;
}

.gm-stat-tile--success .gm-stat-tile__icon { background-color: var(--gm-status-success); }
.gm-stat-tile--success .gm-stat-tile__value { color: var(--gm-status-success-text); }
.gm-stat-tile--warning .gm-stat-tile__icon { background-color: var(--gm-status-warning); color: var(--gm-status-warning-on-fill); }
.gm-stat-tile--warning .gm-stat-tile__value { color: var(--gm-status-warning-text); }
.gm-stat-tile--danger .gm-stat-tile__icon { background-color: var(--gm-status-danger); }
.gm-stat-tile--danger .gm-stat-tile__value { color: var(--gm-status-danger-text); }
.gm-stat-tile--workflow .gm-stat-tile__icon { background-color: var(--gm-status-workflow); }
.gm-stat-tile--workflow .gm-stat-tile__value { color: var(--gm-status-workflow-text); }
.gm-stat-tile--neutral .gm-stat-tile__icon { background-color: var(--gm-status-neutral); color: var(--gm-status-neutral-text); }
.gm-stat-tile--neutral .gm-stat-tile__value { color: var(--gm-text); }

/* --- gmActionBar --------------------------------------------- */
.gm-action-bar {
  display: flex;
  gap: 12px;
}

.gm-action-bar--stack {
  flex-direction: column;
}

.gm-action-bar--stack > * {
  width: 100%;
}

.gm-action-bar--end {
  flex-direction: row;
  justify-content: flex-end;
  flex-wrap: wrap;
}

/* --- nav drawer footer row (Logout) -------------------------- */
.menu-nav-item--footer {
  border-top: 1px solid var(--gm-hairline);
}

/* Footer row whose control sits UNDER its label (haptic strength): five segments
   do not fit beside a label at drawer width. */
.menu-nav-item--stack {
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
}

/* The stacked row is a container, not a target: its segments carry the selection
   colour themselves, so the row-level hover/pressed wash every .menu-nav-item
   gets would just flash the whole section grey around a segment tap. Same
   specificity as the washes it neutralises; later in the file, so it wins. */
.menu-nav-item--stack:hover,
.menu-nav-item--stack:active,
[data-bs-theme="dark"] .menu-nav-item--stack:hover,
[data-bs-theme="dark"] .menu-nav-item--stack:active {
  background-color: transparent;
}

/* --- gm-segmented: single-choice segment group (haptic strength row) --------
   Not Bootstrap's btn-group + btn-check: that colours its checked state from
   --bs-primary and would need per-theme overrides to follow the gm tokens;
   this is the token-native form. Selected fill is --gm-chrome (the app-bar
   colour), which every theme defines with AA-contrast --gm-chrome-text. */
.gm-segmented {
  display: flex;
  border: 1px solid var(--gm-hairline);
  border-radius: var(--gm-radius);
  overflow: hidden;
}

.gm-segmented__btn {
  flex: 1 1 0;
  min-height: 44px;
  padding: 0 0.25rem;
  border: 0;
  background-color: transparent;
  color: var(--gm-text-secondary);
  font-size: 0.8125rem;
  transition: background-color 0.15s ease;
}

.gm-segmented__btn + .gm-segmented__btn {
  border-left: 1px solid var(--gm-hairline);
}

.gm-segmented__btn.is-selected {
  background-color: var(--gm-chrome);
  color: var(--gm-chrome-text);
  font-weight: 600;
}

/* Drawer footer note: copyright + build version on ONE line at drawer width.
   Non-interactive text, so the 16px input floor does not apply. */
.menu-footer-note {
  font-size: 11px;
  white-space: nowrap;
}

/* --- gmDraft: sessionStorage draft-protection cues (plan A8) ---
   There is no "Draft saved" toast. Saving is silent: the draft is local and
   session-scoped, and announcing it implies the entry reached the server.
   Only the restore banner is shown, at the moment the user needs to know why
   the form is pre-filled. */

/* Dismissible "Draft restored" banner, injected at the top of the form. */
.gm-draft-restored {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 1rem;
  padding: 0.25rem 0.25rem 0.25rem 0.75rem;
  font-size: 13px;
  color: var(--gm-text-secondary);
  background-color: var(--gm-canvas);
  border: 1px solid var(--gm-hairline);
  border-radius: var(--gm-radius);
}

.gm-draft-restored__dismiss {
  flex: none;
  min-width: 44px;
  min-height: 44px;
  padding: 4px;
  border: 0;
  background: transparent;
  color: var(--gm-text-secondary);
  line-height: 1;
}

/* --- gm-doc-viewer (in-app document viewer) --------------------------------
   The app is an installed PWA, so a document must render inside the app shell:
   navigating to the raw bytes leaves standalone mode with no chrome and no back.
   Fills the viewport below the sticky app bar + title bar. */
.gm-doc-viewer {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 8rem);
  min-height: calc(100dvh - 8rem);  /* iOS Safari tab */
  background-color: var(--gm-canvas);
}

.gm-doc-viewer__image {
  max-width: 100%;
  max-height: calc(100vh - 8rem);
  max-height: calc(100dvh - 8rem);  /* iOS Safari tab */
  object-fit: contain;
}

/* pdf.js paints one canvas per page into this column. It scrolls with the page rather than in its
   own box, so the phone's momentum scrolling and the browser's URL-bar hiding both behave normally. */
.gm-doc-viewer__pdf {
  width: 100%;
  align-self: flex-start;
}

.gm-doc-viewer__page {
  margin-bottom: 0.5rem;
  background-color: #fff;         /* a PDF page is white regardless of app theme */
  box-shadow: 0 1px 4px rgba(0, 0, 0, .18);
}

.gm-doc-viewer__page canvas {
  display: block;   /* kill the inline-element baseline gap under each page */
  width: 100%;
  height: auto;
}

/* Occupies the viewer while pdf.js loads, so the page does not sit empty on a slow connection. */
.gm-doc-viewer__status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 3rem 1rem;
  color: var(--gm-text-secondary);
}

/* Shown for unpreviewable types, and swapped in by site.js on Apple devices, whose PDF viewer
   renders only a frozen first page rather than failing outright. */
.gm-doc-viewer__fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 2rem 1.5rem;
  text-align: center;
  width: 100%;
  max-width: 24rem;
}

/* display:flex above beats the [hidden] UA rule, so restate it at equal specificity. Without this
   the fallback card renders on every device, underneath the embedded PDF. */
.gm-doc-viewer__fallback[hidden] {
  display: none;
}

.gm-doc-viewer__fallback-icon {
  font-size: 3rem;
  color: var(--gm-text-secondary);
}

/* --- gm-nav-progress: boosted-navigation progress bar -------------------------
   hx-boost replaces a full page load with a body swap, which means the browser
   no longer paints its own progress. Without this a tap on a nav row looks
   ignored on a slow connection. Shown only for boosted requests (see site.js).

   Indeterminate on purpose: htmx gives no byte progress for a navigation, and a
   bar that pretends to know how far along it is would be a lie. */
.gm-nav-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 100%;
  z-index: 2000;
  transform: scaleX(0);
  transform-origin: 0 50%;
  /* The bar overlays the app bar, so it must contrast with --gm-chrome — which is navy for
     holdings-2026 and red for the legacy brand, and does not flip between colour schemes.
     --gm-chrome-text is the token defined to sit ON that fill, so it is the only value that is
     legible for every brand in both schemes: 16.52:1 on navy, 5.90:1 on legacy red.

     Two colours have been tried here and both were wrong; do not try a third without measuring.
       --gm-status-workflow: in light mode it resolves to #221646, the exact navy of the app bar, so
         the bar was invisible. It only showed in dark mode because the token is relit there
         ("navy would vanish", per its own comment).
       --gm-accent (brand red): 2.80:1 on holdings navy, below the 3:1 WCAG minimum for a UI
         component — and on the LEGACY brand --gm-accent IS --gm-chrome (both #C32028), so the bar
         would be 1.0:1, invisible again. The tokens also declare accent "content-level only; never
         chrome (D2)". Owner decision 2026-07-09: keep white. */
  background-color: var(--gm-chrome-text);
  opacity: 0;
  pointer-events: none;
}

/* In flight: creep towards 90% and wait there. The remaining 10% is run by .is-done, which is
   what actually finishes the bar — removing .is-loading alone would make it vanish at whatever
   width it had reached, which is why it appeared to stop halfway.

   The curve front-loads deliberately: a navigation that takes 300ms should still show visible
   travel, so most of the distance is covered early and the tail creeps. */
.gm-nav-progress.is-loading {
  opacity: 1;
  animation: gmNavProgress 6s cubic-bezier(0, 0.6, 0.2, 1) forwards;
}

@keyframes gmNavProgress {
  0%   { transform: scaleX(0); }
  20%  { transform: scaleX(0.45); }
  50%  { transform: scaleX(0.72); }
  100% { transform: scaleX(0.9); }
}

/* Completion. site.js freezes the current scale inline, then sets scaleX(1); the transition below
   carries it to the end and fades it out just behind. The delay on opacity lets the bar be seen
   reaching 100% before it disappears. */
.gm-nav-progress.is-done {
  opacity: 0;
  transition: transform 0.16s ease-out, opacity 0.22s ease-out 0.14s;
}

@media (prefers-reduced-motion: reduce) {
  .gm-nav-progress.is-loading {
    animation: none;
    transform: scaleX(1);
  }
}

/* --- menu action row (a <button> wearing .menu-nav-item) -----------------------
   .menu-nav-item was written for anchors. A button brings its own background,
   border, font and centred text, all of which must be cleared so the Refresh row
   is indistinguishable from the links above it. */
.menu-nav-item--action {
  width: calc(100% - 2rem);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--gm-hairline);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.menu-nav-item--action[aria-busy="true"] {
  cursor: wait;
  opacity: 0.7;
}
