/* overlays.css — frontend-overlays (F2). ONE sheet for every overlay family
 * (architecture §3, §8 "Alternative F"): .enr-modal*, .enr-drawer*,
 * .enr-popover*, .enr-toast*, .enr-scrim, body.enr-scroll-locked, and each
 * family's own [hidden]{display:none!important} guard (§6 "Stacking
 * realization" point 3 — [hidden] is a user-agent rule any author display:
 * beats). SP3 fills in the MODAL half only; SP4/SP5/SP6 append the
 * drawer/popover/toast halves below — do not split this into five sheets
 * (no bundler; five requests for one shared scrim/tier/guard is worse).
 *
 * Tokens only (--enroute-*, --bg-*, --fg-*, --border-*, --radius-*,
 * --shadow-*, --focus-ring, --z-*, --dur-*, --ease-*). No literals.
 *
 * Replaces framework/modal.css (deleted this phase) — same visual
 * vocabulary, one behavioural change: the scrim is no longer painted on
 * `.enr-modal` itself (a background + flex-center per overlay). It is now
 * the single `.enr-scrim` element `overlay-scrim.js` owns (contract "One
 * scrim and one reference-counted scroll lock" — two stacked dialogs must
 * not double the dimming).
 *
 * Markup contract (docs/contracts/frontend-overlays-contract.md 1.0.1):
 *   <div class="enr-modal" id="..." hidden>
 *     <div class="enr-modal__dialog" role="dialog" aria-modal="true"
 *          aria-labelledby="...">
 *       <div class="enr-modal__head">
 *         <h2 class="enr-modal__title" id="...">…</h2>
 *         <button class="enr-modal__close btn btn--ghost btn--icon"
 *                 data-enr-close aria-label="…">…</button>
 *       </div>
 *       <div class="enr-modal__body">…</div>
 *       <div class="enr-modal__foot">…actions…</div>     (optional)
 *     </div>
 *   </div>
 */

/* The single scrim (overlay-scrim.js owns the element + the ref count).
 * Opacity-transitioned rather than display-toggled so the fade reads —
 * `.m-scrim`'s established pattern (mobile.css), reused for consistency. */
.enr-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-scrim);
  background: rgba(10, 13, 11, 0.48); /* --enroute-ink-900 @ 48% scrim */
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

.enr-scrim.is-open {
  opacity: 1;
  pointer-events: auto;
}

/* overlay-scrim.js adds this to <body> while its reference count is > 0. */
body.enr-scroll-locked {
  overflow: hidden;
}

/* ---------------------------------------------------------------------
   .enr-modal
   --------------------------------------------------------------------- */

.enr-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  pointer-events: none; /* the scrim, not this element, catches the dismiss click */
}

.enr-modal[hidden] {
  display: none !important;
}

.enr-modal__dialog {
  width: 100%;
  max-width: 460px;
  max-height: calc(100vh - 32px);
  display: flex;
  flex-direction: column;
  background: var(--bg-paper);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  pointer-events: auto; /* re-enable interaction inside the dialog itself */
}

.enr-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-1);
}

.enr-modal__title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg-1);
}

.enr-modal__close {
  margin: -10px -10px -10px 0; /* keep the visual box tight while the hit area stays 44px */
}

.enr-modal__body {
  padding: var(--space-5);
  overflow-y: auto;
  color: var(--fg-2);
  font-size: 14px;
  line-height: 1.5;
}

.enr-modal__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-1);
  background: var(--bg-canvas);
}

/* doc-preview-release-boxes: the document PREVIEW modal is wide — it frames
   a PDF or image and should take up a good portion of the screen while
   keeping scrim margins. Relative units so it scales to the viewport;
   capped so it never grows absurd on ultra-wide displays. */
.enr-modal--preview .enr-modal__dialog { max-width: min(1400px, 94vw); }
/* schedule-board draft modal: wider than the 460px default so the
   deliveries repeater (location · product · volume · remove) fits on one
   row without clipping (schedule-board-contract 1.0.3). Board-local layout
   lives in pages/calendar.css. */
.enr-modal--draft .enr-modal__dialog { max-width: min(660px, 94vw); }
/* frontend-overlays SP7 (D7): the confirm is a pure yes/no, so it stays
   narrow at every viewport width — same token-free `min()` pattern as
   --preview/--draft above. */
.enr-modal--confirm .enr-modal__dialog { max-width: min(440px, 94vw); }
.enr-modal--confirm .enr-modal__body p { margin: 0 0 var(--space-1) 0; }
/* A titleless confirm hides its <h2>, leaving the close control as the head's
   only flex child — `space-between` then parks it at the START. Seen live on
   the fleet-units Delete confirm (2026-09-14). Keep it at the trailing edge
   whether or not a title is present. */
.enr-modal--confirm .enr-modal__close { margin-inline-start: auto; }

/* Small screens: let the dialog fill the width comfortably. */
@media (max-width: 520px) {
  .enr-modal { padding: var(--space-3); align-items: flex-end; }
  .enr-modal__dialog { max-width: 100%; }
  .enr-modal--preview .enr-modal__dialog { max-width: 100%; }
}

/* ---------------------------------------------------------------------
   .enr-drawer — frontend-overlays SP4 (architecture §13 SP4; C27; D13).
   ONE tag, no author-supplied variant: a side panel at `frontend-contract`'s
   `shell` breakpoint and above (1025px+), a bottom sheet at 1024px and
   below, from the same markup (contract "The drawer's presentation
   switches at frontend-contract's shell boundary exactly").

   UNLIKE `.enr-modal`, there is no separate inner `__dialog` wrapper: the
   root element IS the positioned panel (markup contract:
   docs/contracts/frontend-overlays-contract.md 1.0.1):
     <aside class="enr-drawer enr-drawer--end" id="..." hidden
            role="dialog" aria-modal="true" aria-labelledby="...">
       <div class="enr-drawer__hd">
         <h2 class="enr-drawer__title" id="...">…</h2>
         <button class="enr-drawer__close btn btn--ghost btn--icon"
                 data-enr-close aria-label="…">…</button>
       </div>
       <div class="enr-drawer__body">…</div>
       <div class="enr-drawer__ft">…actions…</div>          (optional)
       <div class="enr-drawer__grip"></div>    (always present; visible
                                                 only in the sheet
                                                 presentation, below)
     </aside>

   Default rules below are the SIDE PANEL (desktop-first, matching this
   sheet's modal half above and the codebase's general convention of
   desktop-base + a `max-width` override for the phone case — e.g.
   price-book.css, mobile.css). The bottom-sheet override lives in the one
   `@media (max-width: 1024px)` block at the end of this section — the
   EXACT boundary C27/C-AC10 assert (1024 sheet, 1025 panel). --------- */

.enr-drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  z-index: var(--z-drawer);
  display: flex;
  flex-direction: column;
  width: min(420px, 100vw);
  max-width: 100vw;
  background: var(--bg-paper);
  box-shadow: var(--shadow-md);
  /* Final-review blocking finding 2: open/close is the boolean `hidden`
     attribute below, and a transition cannot run across a display flip —
     `[hidden]` stays the a11y guarantee (tab-order/AT), `.is-open` (added
     alongside it by drawer.js, never instead of it) is what actually
     drives the motion. Same shape as `.enr-scrim.is-open` above. */
  transition: transform var(--dur-base) var(--ease-out);
}

.enr-drawer[hidden] {
  display: none !important;
}

.enr-drawer--end {
  right: 0;
  border-left: 1px solid var(--border-1);
  transform: translateX(100%); /* closed state: off-screen to the right */
}

.enr-drawer--start {
  left: 0;
  border-right: 1px solid var(--border-1);
  transform: translateX(-100%); /* closed state: off-screen to the left */
}

/* `none`, NOT `translateX(0)`/`translate3d(0,0,0)`: an identity transform
   still establishes a containing block for `position: fixed` descendants
   and would re-anchor the custom-select popup to the drawer's own box
   instead of the viewport (same trap as `main`'s `.drawer.open`,
   `shell.css` ~916-922 — verified there by a popup landing ~800px
   off-screen on desktop and ~130px below its field on mobile). */
.enr-drawer.is-open {
  transform: none;
}

.enr-drawer__hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-1);
}

.enr-drawer__title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg-1);
}

.enr-drawer__close {
  margin: -10px -10px -10px 0; /* keep the visual box tight while the hit area stays 44px */
}

.enr-drawer__body {
  flex: 1;
  padding: var(--space-5);
  overflow-y: auto;
  color: var(--fg-2);
  font-size: 14px;
  line-height: 1.5;
  /* A column flex container, not just a scroll box: matches the pre-migration
     house `.drawer__body` (shell.css) every migrated consumer's CSS was
     written against — e.g. price-book's `.plr-edwrap{flex:none}` (D13's
     rate-editor) needs a flex-column ancestor for that declaration to mean
     anything, and relies on it to keep a tall table from collapsing its
     scroll box to zero height (the original bug that rule fixes). */
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.enr-drawer__ft {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-1);
  background: var(--bg-canvas);
}

/* `{% ui_drawer_footer variant="filters" %}` — frontend-forms SP-D12
   (frontend-overlays-contract 2.1.0, D12). The filter-drawer footer: two
   ordinary buttons filling the row edge-to-edge, each at the 44px tap floor
   (ADR 0005). Promoted from the two hand-written page wrappers this replaces
   — the page-owned footer wrappers in pages/price-book.css and
   pages/fuel-terms.css, both `display:flex; gap:10px; width:100%` +
   `.btn{flex:1; justify-content:center; min-height:44px}` (F2 final review
   finding 11: a near-duplicate is the promotion signal; lane M-E retires
   them). Their 10px gap was off the 4px scale; the modifier
   snaps it to `--space-3` (12px). The default `__ft` above stays the
   right-justified action row every other drawer had. */
.enr-drawer__ft--filters {
  justify-content: stretch;
  gap: var(--space-3);
}

.enr-drawer__ft--filters .btn {
  flex: 1;
  justify-content: center;
  min-height: var(--space-8); /* 44px */
}

/* Present ALWAYS (markup contract: "`.enr-drawer__grip` present always"),
   but only VISIBLE in the sheet presentation below — a side panel has
   nothing to drag. */
.enr-drawer__grip {
  display: none;
}

/* Bottom-sheet presentation — `frontend-contract`'s `shell` boundary,
   1024px and below (C27, C-AC10: sheet at 1024, panel at 1025). */
@media (max-width: 1024px) {
  .enr-drawer {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-sheet);
    width: auto;
    max-height: 84vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: var(--shadow-sheet);
  }

  /* Both sides slide UP as a sheet at this breakpoint, not sideways — the
     side-panel translateX above is wrong once the presentation switches.
     `.enr-drawer.is-open`'s `transform: none` (specificity 0,2,0) beats a
     single-class selector regardless of source order or which side of the
     cascade it sits on, so it still wins here with no extra rule needed. */
  .enr-drawer,
  .enr-drawer--start,
  .enr-drawer--end {
    transform: translateY(105%);
  }

  /* `side` has no effect below `shell` (contract "ui_drawer side"): both
     modifiers collapse onto the same full-width bottom sheet. */
  .enr-drawer--start,
  .enr-drawer--end {
    left: 0;
    right: 0;
    border-left: none;
    border-right: none;
  }

  .enr-drawer__grip {
    display: block;
    position: absolute;
    top: 7px;
    left: 50%;
    transform: translateX(-50%);
    width: 36px;
    height: 4px;
    border-radius: var(--radius-pill);
    background: var(--border-2);
  }

  .enr-drawer__hd {
    padding-top: calc(var(--space-4) + 8px); /* clear the grip */
  }

  .enr-drawer__ft {
    padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
  }
}

/* ---------------------------------------------------------------------
   .enr-popover — frontend-overlays SP5 (architecture §13 SP5; C5, C8, C28;
   drift ruling D12). The canonical anchored panel, replacing the four
   hand-rolled ones — the order list's, the price book's and fuel terms'
   facet panels, the schedule board's menu, the order-actions menu's chrome
   and `.od-adv__menu` / `.od-dmenu`'s chrome.

   Markup contract (docs/contracts/frontend-overlays-contract.md 1.0.1) —
   CHROME ONLY (R7: "the popover owns the panel, not its insides"; no
   option, item or list class is published, and every migrated consumer
   keeps its own):
     <div class="enr-popover" id="..." role="dialog" aria-label="..."
          data-enr-placement="bottom-start" hidden>
       <div class="enr-popover__body">…the caller's own options…</div>
     </div>

   GEOMETRY IS THE SCRIPT'S, NOT THIS SHEET'S. `overlay-position.js` writes
   `left`/`top` in viewport coordinates after flipping and clamping to an
   8px inset (C28's guarantee), so there is deliberately no `top:`/`left:`
   default here — a CSS default would be visible for one frame before the
   first measurement and would fight the clamp on every reposition. What
   this sheet owns is the box: tier, surface, radius, shadow, and the size
   caps that make an 8px inset achievable at 375px in the first place.

   `max-width` is what keeps C28's "never causes horizontal page scroll"
   true rather than merely attempted: the clamp cannot fit a panel wider
   than the viewport, so the panel is capped below it. --------------- */

.enr-popover {
  position: fixed;
  z-index: var(--z-popover);
  width: max-content;
  max-width: min(320px, calc(100vw - 16px));
  max-height: calc(100vh - 16px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-paper);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.enr-popover[hidden] {
  display: none !important;
}

.enr-popover__body {
  padding: var(--space-2);
  color: var(--fg-2);
  font-size: var(--text-sm);
  line-height: 1.45;
}

/* A menu rendered into <body> is a SIBLING of an open dialog, not a
   descendant of it, so it must not sit below `--z-dialog` — architecture
   §6 "Stacking realization" point 2 records that putting a context menu on
   `--z-menu` (120) would hide it behind an open modal and fail C9.
   `overlays/menu.js` adds this modifier to every menu it opens, so the rung
   lives in one place instead of being re-remembered in each page
   stylesheet (it was `order-actions-menu.css:8` before this). Private: the
   contract publishes `.enr-popover` and `.enr-popover__body` only. */
.enr-popover--menu {
  z-index: var(--z-dialog);
  padding: var(--space-1) 0;
}

.enr-popover--menu .enr-popover__body {
  padding: 0;
}

/* ---------------------------------------------------------------------
   .enr-toast  (SP6 — moved out of shell.css:1269-1294; D10)
   --------------------------------------------------------------------- */

/* One floating status toast, fixed to the bottom-right: it overlays the page
   and displaces nothing (client-view ruling 3, 2026-08-18). `overlays/toast.js`
   builds and owns the single element and nothing server-rendered carries this
   family — which is why there is no toast tag (contract: "There is no toast tag
   and no confirm tag"). The drain bar is decorative; the message is announced
   through role=status.

   THE ONE RULE THAT IS NOT A VERBATIM MOVE — the z-rung.
   Pre-migration `.enr-toast` sat on `--z-toast` (90), below `--z-dialog`
   (1000). That was safe only while its single caller (`load-detail-route.js`)
   never raised a toast over a dialog. D10 folds in two families that both
   outrank it deliberately: `.plr-toast` at `calc(var(--z-dialog) + 100)` and
   `.sb-toast` at `calc(var(--z-drag) + 10)`. The price book's own comment
   (price-book.css:309-315, deleted with the rule) records why, and it is a
   measured regression, not a preference: "The toast is the only channel for an
   error carrying neither row nor field detail — a refused export, a 413, an
   expired session on Write — and every one of those is raised while a drawer or
   the export modal is still open. At 80 it painted underneath both, so at ≤640,
   where each covers the full width, the message never reached the user."
   `tests/e2e/test_price_book_page.py::test_export_refusal_message_is_visible_over_the_open_modal`
   samples the pixels for exactly that, and C22 makes "a toast with a modal open"
   a supported state. So the merged family keeps the highest of the three rungs —
   byte-for-byte the one `.plr-toast` already used. tokens.css is NOT renumbered
   (architecture §3), and `calc()` on a named rung is the sub-rung idiom
   tokens.css itself names. `frontend-overlays-contract` says the toast consumes
   `--z-toast`; that sentence needs the same kind of PATCH as Finding 3's
   `--z-menu` sentence — an SP10 item, recorded in the SP6 report. */
.enr-toast {
  position: fixed;
  right: 16px;
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: calc(var(--z-dialog) + 100);
  display: flex;
  align-items: center;
  gap: 10px;
  /* §7's oversized-content row: the toast must never cover the viewport,
     however long the message is. */
  max-width: min(360px, calc(100vw - 32px));
  padding: 11px 14px;
  background: var(--enroute-ink-900);
  color: var(--fg-on-ink);
  font-size: 13px;
  font-weight: 600;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

/* §6: this family sets `display`, so `[hidden]` needs the !important guard in
   the same scope. The toast's own state class is `.is-shown` and the element is
   deliberately left in the document between messages (a live region must exist
   before its text changes to be announced), so nothing in this component sets
   `hidden` on it — the guard is for any consumer that does. */
.enr-toast[hidden] {
  display: none !important;
}

.enr-toast.is-shown {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.enr-toast__tick {
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: 50%;
  background: var(--enroute-green-500);
  color: var(--fg-on-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.enr-toast__tick svg {
  width: 11px;
  height: 11px;
}

.enr-toast--error .enr-toast__tick {
  background: var(--enroute-rose-500);
}

.enr-toast--warning .enr-toast__tick {
  background: var(--enroute-amber-500);
}

.enr-toast__msg {
  min-width: 0;
}

.enr-toast__x {
  appearance: none;
  border: 0;
  background: transparent;
  color: rgba(255, 255, 255, 0.7); /* --fg-on-ink @ 70% on the ink-900 pill */
  cursor: pointer;
  display: inline-flex; /* centres the 16 px registry icon (SP8: was a text glyph) */
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
  padding: 2px;
}

.enr-toast__x:hover {
  color: var(--fg-on-ink);
}

.enr-toast__x:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* Decorative. `overlays/toast.js` sets its animation-duration to match the
   toast's own duration and hides it outright when duration is 0, so the bar
   never claims a deadline the timer is not keeping. */
.enr-toast__bar {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--enroute-green-500);
}

.enr-toast.is-shown .enr-toast__bar {
  animation: enr-toast-drain 5s linear forwards;
}

@keyframes enr-toast-drain {
  from {
    width: 100%;
  }
  to {
    width: 0;
  }
}

@media (max-width: 640px) {
  /* clear the fixed bottom action bar */
  .enr-toast {
    bottom: calc(76px + env(safe-area-inset-bottom));
  }
}

@media (prefers-reduced-motion: reduce) {
  .enr-toast {
    transition: none;
  }

  .enr-toast.is-shown .enr-toast__bar {
    animation: none;
    width: 0;
  }

  .enr-drawer {
    transition: none;
  }
}
