/* ============================================
   Modal Shell (canonical centered dialog) — ADR-131
   One shared shell for every hand-rolled Bootstrap `.modal`: void/approve/
   payout confirms, add-skill/attachment/competency forms, and friends. It
   skins Bootstrap's `.modal` chrome with design tokens so every dialog shares
   one elevation, radius, and z-index stack, and it kills the two backdrop
   idioms these modals used to hand-roll (a sibling `.modal-backdrop` element vs
   an inline `background: rgba(0,0,0,.5)`).

   Rendered by the {% modal %} block tag → shared/components/_modal.html. No JS:
   the tag ships a tiny inline Alpine `dismiss($el)` closer that walks to the
   modal root (`[data-global-modal-root]`) and removes the whole fragment; the close
   button, the backdrop click, and the Escape key all call it. Walking the DOM
   (not `$root`) keeps dismiss correct when a slot declares its own `x-data`
   (a payout form with live money-math) — `$root` would resolve to that nested
   scope and orphan the backdrop. The fragment is HTMX-injected only when it
   should be shown, so the shell is always visible (no Bootstrap modal JS
   toggling `display`).

   TWO PARADIGMS, ONE CHROME. The block tag above is for HTMX-injected fragments:
   they appear the moment they're injected, so `.global-modal` forces `display:block`
   to beat Bootstrap's `.modal { display:none }`. But a handful of dialogs are
   *persistent* — hidden until a trigger shows them, via Bootstrap modal JS
   (`data-bs-toggle`/`data-bs-dismiss`) or an Alpine state flag. Those can't wear
   `.global-modal` (its `display:block` would defeat the hide). So the visual chrome —
   the panel's border/radius/shadow — is split out onto `.global-modal-chrome`, which
   a persistent modal adds to its `.modal` element to share the exact same panel
   look while keeping its own show/hide + backdrop. `.global-modal` = injected shell
   (visibility + chrome); `.global-modal-chrome` = chrome only, for the persistent ones.

   `.global-modal` is a locked singleton (test_css_standards.py) — a second base
   definition fails the build. Variants: `--md` (640px, two panels side by side), `--lg` (wide dialog).

   Deliberate exception to the sibling components: drawer.css / confirm-dialog.css
   are framework-free, but this shell *skins* Bootstrap's `.modal` / `.modal-dialog`
   / `.modal-content` / `.modal-backdrop` classes — because the ~23 dialogs it
   replaces already speak that markup. That's why it depends on loading after
   Bootstrap (see below), unlike its siblings.
   ============================================ */

/* Base def — loaded after Bootstrap so this beats `.modal { display:none }` at
   equal specificity. This is the one bare `.global-modal {` rule (the singleton). */
.global-modal {
    display: block;
    z-index: var(--z-modal);
}

/* Every injected dialog sits dead-center of the viewport (Bootstrap's default
   top-anchored dialog reads too high). The flex column centers short dialogs;
   one taller than the viewport tops out at the margin and scrolls INSIDE
   itself — see the .modal-content cap below. */
.global-modal .modal-dialog {
    display: flex;
    align-items: center;
    min-height: calc(100% - 3.5rem);
    margin: var(--spacing-lg) auto;
}

/* A dialog taller than the viewport scrolls its body, not the whole panel:
   the header (title + the X that is sometimes the only way out) and any footer
   stay frozen while the content moves under them (Nick, 08-17). Capping
   .modal-content — which Bootstrap already lays out as a flex column — makes
   the body the one flexible row; its own overflow makes it shrinkable, and
   overflow:hidden keeps the scrolled content clipped to the rounded panel.
   3.5rem mirrors the dialog's top+bottom margins above. Injected shell only:
   persistent `.global-modal-chrome` modals keep Bootstrap's own behavior. */
.global-modal .modal-content {
    max-height: calc(100vh - 3.5rem);
    overflow: hidden;
}

/* The slot between .modal-content and .modal-body is one wrapper — a <form>,
   or a plain div when there is nothing to post — except the top-up modal's
   pending face, which renders body + footer bare. Without this rule the
   wrapper's flex min-height:auto keeps it content-sized, the cap above clips
   it, and the body's own overflow never engages: content below the fold is
   unreachable. Flexing the wrapper (min-height:0 so it may shrink) hands the
   constraint down to the body. The :not() list is load-bearing: a bare
   .modal-footer must keep Bootstrap's row direction (column re-aims
   align-items and centers its buttons), a bare .modal-body already shrinks
   and scrolls on its own (flex: 1 1 auto + the overflow below), and
   json_script drops a <script> sibling into one slot, so a bare :not()
   would un-hide its JSON. */
.global-modal .modal-content > form,
.global-modal .modal-content > div:not(.modal-header):not(.modal-body):not(.modal-footer) {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.global-modal .modal-body {
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Shared panel chrome — worn by BOTH the injected shell (`.global-modal`) and the
   persistent modals that opt in with `.global-modal-chrome`, so every dialog on the
   platform lands the same tokenized elevation/radius, not Bootstrap defaults. */
.global-modal .modal-content,
.global-modal-chrome .modal-content {
    border: none;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-modal);
}

/* Wide dialog (e.g. an editable line-item table). */
.global-modal--lg .modal-dialog {
    max-width: 800px;
}

/* Between the two: a dialog that lays two panels side by side (the calendar
   connect explainer) but has no table to justify the full 800. */
.global-modal--md .modal-dialog {
    max-width: 640px;
}

/* Backdrop element the tag renders as a sibling of the dialog. Sits one rung
   below the panel on the shared elevation ladder. */
.global-modal__backdrop {
    z-index: var(--z-modal-backdrop);
}

/* ── Beside an open drawer (ADR-125) ───────────────────────────────────────
   A dialog opened FROM a drawer (Adjust amount, Void, the sync match picker)
   used to land BEHIND the panel: both wear var(--z-modal), and the drawer's
   container sits later in the page than the modal container, so the drawer won
   the tie. These rules lift the dialog and its backdrop over the panel — still
   under var(--z-popover), so autocompletes inside the dialog stay on top.

   In push mode the drawer leaves a free strip between the sidebar and the
   panel's left edge, so the dialog centers THERE instead of in the whole
   viewport (which read as "hiding under the drawer"). Padding does the work,
   not a width cap: the dialog's own `margin: auto` re-centers inside the padded
   box, and a dialog wider than the strip shrinks to fit it. Below the push line
   the drawer overlays the page and there is no free strip, so only the layering
   applies and centering stays normal. */
.global-app-layout.is-drawer-open .global-modal {
    z-index: calc(var(--z-modal) + 2);
}

.global-app-layout.is-drawer-open .global-modal__backdrop {
    z-index: calc(var(--z-modal) + 1);
}

@media (min-width: 1200px) {
    .global-app-layout.is-drawer-open .global-modal {
        padding-left: var(--sidebar-width, 0px);
        padding-right: var(--drawer-width);
    }

    /* The collapsed sidebar is a 56px icon rail whose width lives in the rule,
       not in --sidebar-width (main.css), so the free strip is re-measured. */
    .global-app-layout.is-drawer-open.is-sidebar-collapsed .global-modal {
        padding-left: 56px;
    }
}
