/* Modal theme + global modal behavior */

/* Prevent background scroll when Bootstrap modals are open */
body.modal-open {
  overflow: hidden;
  overscroll-behavior: none;
}

/*
 * Modal scrim: use the shared backdrop token (neutral dark, slight green tint in
 * light theme). Avoid painting the backdrop with --bg-secondary (nav chrome) —
 * that made open modals look dark green instead of dimmed.
 * Force opacity:1 because --backdrop-scrim already includes alpha (same as sidebar).
 *
 * Stack above the fixed top navbar (z-index 1100). Bootstrap defaults (1050/1055)
 * leave the nav on top of the scrim, which forced a nav-height top pad and made
 * centered dialogs sit too low. Covering chrome is the normal overlay pattern.
 */
.modal-backdrop {
  --bs-backdrop-bg: var(--backdrop-scrim);
  --bs-backdrop-opacity: 1;
  --bs-backdrop-zindex: 1110;
  z-index: var(--bs-backdrop-zindex);
  background-color: var(--backdrop-scrim);
}

/*
 * Inset for notches / home indicator only — not the app navbar (covered by
 * the raised modal stack). Keeps dialogs optically centered in the viewport.
 */
.modal {
  --bs-modal-zindex: 1120;
  --modal-viewport-inset-top: max(var(--bs-modal-margin, 0.5rem), env(safe-area-inset-top, 0px));
  --modal-viewport-inset-bottom: max(var(--bs-modal-margin, 0.5rem), env(safe-area-inset-bottom, 0px));
  z-index: var(--bs-modal-zindex);
  padding-top: var(--modal-viewport-inset-top);
  padding-bottom: var(--modal-viewport-inset-bottom);
}

.modal-dialog-centered {
  min-height: calc(100% - var(--modal-viewport-inset-top) - var(--modal-viewport-inset-bottom));
}

/* Baseline modal theme — neutral surface + chrome (brand stays in buttons/links).
 * Keep Bootstrap's --bs-modal-* radius tokens in sync so header/footer corners match.
 * Fullscreen variants (e.g. modal-fullscreen-sm-down) zero radius inside their own
 * max-width media queries — do not override that here without a matching breakpoint. */
.modal-content {
  --bs-modal-border-radius: var(--radius-lg);
  --bs-modal-inner-border-radius: calc(var(--radius-lg) - 1px);
  background: var(--bg-surface);
  border: 1px solid var(--border-primary);
  border-radius: var(--bs-modal-border-radius);
  color: var(--text-primary);
  box-shadow: var(--shadow-xl);
}

/*
 * Scrollable + centered: keep short dialogs optically centered, but cap tall
 * content so .modal-body (or turbo-frame > .modal-body) is the scrollport.
 * Without a viewport max-height, swapped turbo-frame content can grow the
 * dialog past the screen; body { position: fixed } then leaves no place to
 * scroll (calendar detail after close → reopen).
 */
.modal-dialog-scrollable.modal-dialog-centered {
  max-height: calc(100% - var(--modal-viewport-inset-top) - var(--modal-viewport-inset-bottom));
}

.modal-dialog-scrollable.modal-dialog-centered .modal-content {
  max-height: min(
    100%,
    calc(100dvh - var(--modal-viewport-inset-top) - var(--modal-viewport-inset-bottom) - 1rem)
  );
  overflow: hidden;
}

.modal-header,
.modal-footer {
  background: var(--card-chrome-bg);
  border-color: var(--border-primary);
}

.modal-body {
  background: var(--bg-surface);
  color: var(--text-primary);
}

/*
 * Bootstrap modal-dialog-scrollable only constrains a .modal-body that is a direct
 * flex child of .modal-content. Several screens wrap body (and sometimes footer)
 * in <form> so the sticky footer can use form="…". Without this, the form grows
 * with its content and the dialog never scrolls (add-bulava invite, kurin/hurtok
 * add/edit, proba warning resolve, …).
 *
 * Preferred markup still keeps .modal-body as a direct child of .modal-content
 * (see shared/modal + roster camper). This rule makes the form-wrapper pattern safe.
 */
.modal-dialog-scrollable .modal-content > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.modal-dialog-scrollable .modal-content > form > .modal-body {
  overflow-y: auto;
}

/*
 * Shared People / calendar modals: turbo-frame wraps .modal-body (and sometimes
 * footer). Make the frame the scrollport — flex percentage heights on the inner
 * body were unreliable after close→reopen content swaps.
 */
.modal-dialog-scrollable .modal-content > turbo-frame {
  display: block;
  flex: 1 1 auto;
  min-height: 0;
  max-height: calc(100vh - var(--modal-viewport-inset-top) - var(--modal-viewport-inset-bottom) - 4.5rem);
  max-height: calc(100dvh - var(--modal-viewport-inset-top) - var(--modal-viewport-inset-bottom) - 4.5rem);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.modal-dialog-scrollable .modal-content > turbo-frame > .modal-body {
  /* Body scrolls with the frame; keep Bootstrap's overflow rule from fighting. */
  overflow: visible;
}

/*
 * wrap_body:false hosts that ship .modal-body + .modal-footer inside the frame
 * (calendar detail/editor, person modals with sticky chrome). Keep the footer
 * pinned and scroll the body — and drop the frame max-height so an empty
 * loading shell with a footer does not fill the viewport (tall flash).
 */
.modal-dialog-scrollable .modal-content > turbo-frame:has(> .modal-footer) {
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-height: none;
  overflow: hidden;
}

.modal-dialog-scrollable .modal-content > turbo-frame:has(> .modal-footer) > .modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.modal-dialog-scrollable .modal-content > turbo-frame:has(> .modal-footer) > .modal-footer {
  flex-shrink: 0;
}

/*
 * Loading shells are often one line of copy. Without a floor the dialog opens
 * much thinner/shorter than the loaded panel, then jumps. fillTurboFrameModalLoading
 * toggles .is-loading; clear it on turbo:frame-load. Override per dialog via
 * --modal-frame-loading-min-height on .modal-dialog when needed.
 */
.modal-dialog {
  --modal-frame-loading-min-height: 16rem;
}

.modal-dialog.modal-lg {
  --modal-frame-loading-min-height: 14rem;
}

.modal-dialog-scrollable .modal-content > turbo-frame.is-loading > .modal-body {
  min-height: var(--modal-frame-loading-min-height);
}

.trophy-modal-dialog {
  width: calc(100% - var(--space-4) * 2);
  max-width: min(26rem, calc(100vw - 1.5rem));
  margin-left: auto;
  margin-right: auto;
}

.modal .btn-close {
  filter: none;
  opacity: 0.55;
}

.modal .btn-close:hover {
  opacity: 1;
}

/*
 * Global mobile rule forces .btn { width: 100% }. Modal chrome (footer actions,
 * approve/close, dismiss) should stay intrinsic so pairs don't stack full-bleed.
 */
@media (max-width: 768px) {
  .modal .btn {
    width: auto;
    max-width: 100%;
  }
}

/*
 * Role and custom title sit beside a button. width:auto lets the text field
 * use its intrinsic minimum, which on a phone is wider than the dialog.
 * Basis 0 plus min-width 0 keeps the field inside the modal, like the other
 * full-width controls.
 */
.regional-role-inline > :is(.form-control, .form-select) {
  flex: 1 1 0;
  width: 1%;
  min-width: 0;
  max-width: 100%;
}

.regional-role-inline > .btn {
  flex-shrink: 0;
}

/* Permission / org-access review with a permission matrix: usable width, content-sized height. */
.org-access-request-review-modal.modal-dialog,
.permission-request-review-modal.modal-dialog {
  width: calc(100% - 1.5rem);
  max-width: min(42rem, calc(100vw - 1.5rem));
  margin-left: auto;
  margin-right: auto;
}

