/* ===== Modal Component (hardened) ===== */

/* --- Root container --- */

.ui-modal-root {
    position: fixed;
    inset: 0;
    z-index: var(--modal-root-z-index, 1000);
    pointer-events: none;
}

.ui-modal-root.open,
.ui-modal-root.closing {
    pointer-events: auto;
}

.ui-modal-root.closed {
    pointer-events: none;
}

/* --- Overlay (scrim) --- */

.ui-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--modal-overlay-z-index, 1000);
    background: var(--modal-overlay, color-mix(in srgb, var(--meb-palette-neutral-black) 50%, transparent));
    opacity: 0;
    transition: opacity 180ms ease;
}

.ui-modal-root.open .ui-modal-overlay {
    opacity: 1;
}

.ui-modal-root.closing .ui-modal-overlay {
    opacity: 0;
}

/* --- Wrapper (positioning layer) --- */

.ui-modal-wrapper {
    position: fixed;
    inset: 0;
    z-index: var(--modal-panel-z-index, 1001);
    /* Bug 11291: reserve the cookie-consent banner's measured height (published on :root by
       consent-banner.js, absent once answered and in MAUI) so a dialog or sheet never sits under it. */
    bottom: var(--consent-banner-height, 0px);
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* --- Dialog surface --- */

.ui-modal {
    position: relative;
    display: flex;
    flex-direction: column;
    max-height: calc(90vh - var(--consent-banner-height, 0px));
    background: var(--modal-background, var(--meb-surface-modal, var(--meb-surface-card)));
    color: var(--modal-foreground, var(--meb-text-modal, var(--meb-text-default)));
    border: 1px solid var(--modal-border, var(--meb-border-modal, var(--meb-border-subtle)));
    border-radius: var(--meb-radius-lg);
    box-shadow: var(--modal-shadow, var(--meb-shadow-modal, var(--meb-shadow-lg)));
    pointer-events: auto;
    opacity: 0;
    transform: translateY(10px) scale(0.985);
    transition:
        opacity 180ms ease,
        transform 180ms ease;
    will-change: opacity, transform;
}

.ui-modal-root.open .ui-modal {
    opacity: 1;
    transform: translateY(0) scale(1);
}

.ui-modal-root.closing .ui-modal {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
}

.ui-modal:focus-visible {
    /* Transparent, not none: forced-colors mode strips the box-shadow ring and repaints this outline in a
       system colour (ADO 11309). */
    outline: var(--meb-focus-ring-width) solid transparent;
    outline-offset: var(--meb-focus-ring-offset);
    box-shadow: var(--focus-ring-shadow, var(--meb-button-primary-focus-shadow));
}

/* --- Header --- */

.ui-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    font-weight: var(--weight-semibold);
    font-size: var(--text-lg);
    border-bottom: 1px solid var(--modal-border, var(--meb-border-modal, var(--meb-border-subtle)));
}

.ui-modal-header-content {
    flex: 1 1 auto;
    min-width: 0;
}

/* --- Close button --- */

.ui-modal-close {
    appearance: none;
    border: none;
    background: transparent;
    font-size: var(--text-xl);
    line-height: var(--font-lineheight-display);
    cursor: pointer;
    color: var(--modal-foreground, var(--meb-text-modal, var(--meb-text-default)));
    padding: 4px 8px;
    margin: -4px -8px -4px 12px;
    border-radius: 4px;
    flex-shrink: 0;
}

.ui-modal-close:hover {
    opacity: 0.7;
}

.ui-modal-close:focus-visible {
    /* Transparent, not none: forced-colors mode strips the box-shadow ring below and repaints a
       transparent outline in a system colour, so the Close button still shows focus (ADO #11308). */
    outline: var(--meb-focus-ring-width, 3px) solid transparent;
    outline-offset: var(--meb-focus-ring-offset, 2px);
    box-shadow: var(--focus-ring-shadow, var(--meb-button-primary-focus-shadow));
}

/* Hover dims the whole button (and its shadow); a focused button keeps an opaque ring so it stays at 3:1. */
.ui-modal-close:hover:focus-visible {
    opacity: 1;
}

/* --- Body --- */

.ui-modal-body {
    padding: 20px;
    overflow-y: auto;
    flex: 1 1 auto;
    /* This is a flex child of the dialog (.ui-modal max-height:90vh, less the consent banner's
       height while it shows). overflow-y:auto
       already resolves its flex min-size to 0, so on normal screens the dialog's 90vh
       (minus the header) -- not the cap below -- governs, letting the popup use the
       available height instead of clipping early. Was 70vh, which clipped well short of
       90vh. min-height:0 is explicit insurance; 85vh is a safety cap that only binds on
       very tall monitors. Body still scrolls past whichever limit applies. */
    min-height: 0;
    max-height: 85vh;
}

/* --- Footer --- */

.ui-modal-footer {
    padding: 16px 20px;
    display: flex;
    justify-content: flex-end;
    border-top: 1px solid var(--modal-border, var(--meb-border-modal, var(--meb-border-subtle)));
}

/* --- Loading overlay (covers dialog surface, no layout shift) --- */

.ui-modal-loading {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--meb-surface-modal, var(--meb-surface-card)) 60%, transparent);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    pointer-events: all;
    border-radius: inherit;
}

.ui-modal-spinner {
    width: 32px;
    height: 32px;
    border: 3px solid color-mix(in srgb, var(--meb-palette-neutral-black) 20%, transparent);
    border-top-color: color-mix(in srgb, var(--meb-palette-neutral-black) 70%, transparent);
    border-radius: 50%;
    animation: ui-modal-spin 0.8s linear infinite;
}

@keyframes ui-modal-spin {
    to {
        transform: rotate(360deg);
    }
}

/* --- Busy state: disable footer actions --- */

.ui-modal-root.busy .ui-modal-footer button {
    pointer-events: none;
    opacity: 0.6;
}

.ui-modal-root.busy .ui-modal-close {
    pointer-events: none;
    opacity: 0.4;
}

/* --- Variant: Sm (small, 400px) --- */

.ui-modal-wrapper.variant-sm .ui-modal {
    width: 400px;
    max-width: calc(100vw - 32px);
}

/* --- Variant: Default (500px) --- */

.ui-modal-wrapper.variant-default .ui-modal {
    width: 500px;
    max-width: calc(100vw - 32px);
}

/* --- Variant: Lg (large, 720px) --- */

.ui-modal-wrapper.variant-lg .ui-modal {
    width: 720px;
    max-width: calc(100vw - 32px);
}

/* --- Variant: Xl (extra-large, 90vw) --- */

.ui-modal-wrapper.variant-xl .ui-modal {
    width: 90vw;
    max-width: 90vw;
}

/* --- Variant: FitContent (grows with content) --- */

.ui-modal-wrapper.variant-fitcontent .ui-modal {
    width: fit-content;
    max-width: calc(100vw - 32px);
}

/* --- Variant: Centered --- */

.ui-modal-wrapper.variant-centered {
    display: flex;
    align-items: center;
    justify-content: center;
}

.ui-modal-wrapper.variant-centered .ui-modal {
    width: 500px;
    max-width: calc(100vw - 32px);
}

/* --- Variant: Fullscreen --- */

.ui-modal-wrapper.variant-fullscreen .ui-modal {
    width: 100vw;
    height: calc(100vh - var(--consent-banner-height, 0px));
    max-height: calc(100vh - var(--consent-banner-height, 0px));
    border-radius: 0;
}

/* --- Reduced motion --- */

@media (prefers-reduced-motion: reduce) {

    .ui-modal-overlay,
    .ui-modal {
        transition: none;
    }
}

/* --- Scroll lock (applied to body via JS) --- */

body.modal-scroll-lock {
    overflow: hidden;
}

/* Keep the classic scrollbar gutter while the lock hides the body scrollbar so the page does not
   shift sideways when a dialog opens (10960). modalInterop.js adds modal-scroll-lock-gutter only
   when a classic scrollbar was showing at lock time, so non-scrolling pages gain no gutter.
   No-op on overlay-scrollbar mobile/WebView. */
html:has(body.modal-scroll-lock-gutter) {
    scrollbar-gutter: stable;
}

/* --- Variant: QR (Neo-Brutalist / 6138) --- */

.ui-modal-wrapper.variant-qr .ui-modal {
    width: var(--meb-layout-qr-modal-width);
    max-width: calc(100vw - var(--meb-space-container-md) * 2);
    border: var(--meb-border-width-sm) solid var(--meb-border-default);
    border-radius: var(--meb-border-radius-xxl);
    box-shadow: var(--meb-shadow-qr-modal);
    background: var(--meb-surface-qr-background);
    overflow: hidden;
}

.ui-modal-wrapper.variant-qr .ui-modal-header {
    border-bottom: none;
    justify-content: center;
    padding: var(--meb-space-stack-xl) var(--meb-space-stack-xl) var(--meb-space-stack-sm) var(--meb-space-stack-xl);
}

.ui-modal-wrapper.variant-qr .ui-modal-header-content {
    color: var(--meb-surface-qr-title-foreground);
    font-size: var(--font-size-h1-mobile);
    font-weight: var(--font-weight-bold);
    text-align: center;
    text-transform: none;
    letter-spacing: var(--meb-font-letter-spacing-tighter);
}

.ui-modal-wrapper.variant-qr .ui-modal-header .ui-modal-close {
    position: absolute;
    top: var(--meb-space-stack-md);
    right: var(--meb-space-stack-md);
    font-size: var(--font-size-h1-mobile);
    font-weight: var(--font-weight-bold);
    margin: var(--meb-space-stack-none);
}

.ui-modal-wrapper.variant-qr .ui-modal-body {
    padding: 0 var(--meb-space-stack-xl) var(--meb-space-stack-xl) var(--meb-space-stack-xl);
    max-height: 80vh;
}

.ui-modal-wrapper.variant-qr .ui-modal-footer {
    border-top: none;
    padding: var(--meb-space-stack-md) var(--meb-space-stack-xl) var(--meb-space-stack-xl) var(--meb-space-stack-xl);
    justify-content: center;
}

/* --- Variant: Sheet (#11128, Surveys board Main) ---
   Bottom-anchored, full width, top-only radius, safe-area bottom padding, max-height near 90vh with
   the body scrolling. Its max-height subtracts the consent banner's height (Bug 11291), like the
   base rule and the fullscreen variant, because the wrapper above is already lifted by it. */

.ui-modal-wrapper.variant-sheet {
    align-items: flex-end;
}

.ui-modal-wrapper.variant-sheet .ui-modal {
    width: 100%;
    max-width: 100%;
    max-height: calc(90vh - var(--consent-banner-height, 0px));
    border-inline: none;
    border-bottom: none;
    border-radius: var(--meb-border-radius-xl) var(--meb-border-radius-xl) 0 0;
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--meb-space-stack-sm));
    /* No scale-and-nudge entrance: without the motion preference the sheet simply appears. The
       slide-up lives in the no-preference block below. */
    transform: none;
}

.ui-modal-root.open .ui-modal-wrapper.variant-sheet .ui-modal,
.ui-modal-root.closing .ui-modal-wrapper.variant-sheet .ui-modal {
    transform: none;
}

.ui-modal-wrapper.variant-sheet .ui-modal-header {
    padding: var(--meb-space-stack-sm) var(--meb-space-stack-sm) var(--meb-space-stack-sm) var(--meb-space-stack-md);
}

/* The title takes programmatic focus on open (tabindex="-1"); it is not interactive, so no ring. */
.ui-modal-wrapper.variant-sheet .ui-modal-header:focus {
    outline: none;
}

.ui-modal-wrapper.variant-sheet .ui-modal-header-content {
    font-size: var(--text-lg);
    font-weight: var(--font-weight-semibold);
    line-height: var(--font-lineheight-body);
}

.ui-modal-wrapper.variant-sheet .ui-modal-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--meb-size-touch-target);
    min-height: var(--meb-size-touch-target);
    padding: var(--meb-space-stack-none);
    margin: var(--meb-space-stack-none);
}

.ui-modal-wrapper.variant-sheet .ui-modal-body {
    padding: var(--meb-space-stack-md);
    max-height: none;
}

/* Tap guard: modalInterop.js sets data-meb-tap-guard on the root for the first 400 ms after a sheet
   opens (Modal.SheetTapGuardMs). The root swallows the tap; the dialog and overlay never see it, so
   a tap meant for a bingo square can neither answer nor close the sheet. Keyboard is unaffected. */
.ui-modal-root[data-meb-tap-guard] {
    pointer-events: auto;
}

.ui-modal-root[data-meb-tap-guard] .ui-modal-overlay,
.ui-modal-root[data-meb-tap-guard] .ui-modal {
    pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {

    .ui-modal-wrapper.variant-sheet .ui-modal,
    .ui-modal-root.closing .ui-modal-wrapper.variant-sheet .ui-modal {
        transform: translateY(100%);
        transition:
            opacity 180ms ease,
            transform var(--meb-anim-slidein, 300ms) var(--meb-ease-overlay, ease-out);
    }

    .ui-modal-root.open .ui-modal-wrapper.variant-sheet .ui-modal {
        transform: translateY(0);
    }
}

/* Bug 11291: dvh follows the visible viewport on mobile browsers whose toolbar collapses (the terms
   sheet, a default-variant dialog, already relied on it). It sits in @supports because a declaration
   holding var() is accepted at parse time and, on an engine without dvh, would turn into "none" at
   computed-value time and drop the vh cap above. These rules come after every variant rule so they win
   on equal specificity. */
@supports (height: 100dvh) {

    .ui-modal {
        max-height: calc(90dvh - var(--consent-banner-height, 0px));
    }

    .ui-modal-wrapper.variant-sheet .ui-modal {
        max-height: calc(90dvh - var(--consent-banner-height, 0px));
    }

    .ui-modal-wrapper.variant-fullscreen .ui-modal {
        height: calc(100dvh - var(--consent-banner-height, 0px));
        max-height: calc(100dvh - var(--consent-banner-height, 0px));
    }
}

/* #11157: Back pressed on a participating dialog. modalHistory.js sets data-meb-back-closing on the
   root as soon as it consumes the Back, before the .NET close round trip returns, so the press is
   acknowledged at once. Same exit styles, duration and reduced-motion behaviour as .closing; a
   refused Back removes the attribute. A data attribute, not a class: Blazor owns the class list.
   These rules come last so they win over the equally specific .open rules above. */
.ui-modal-root[data-meb-back-closing] .ui-modal-overlay {
    opacity: 0;
}

.ui-modal-root[data-meb-back-closing] .ui-modal {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
}

@media (prefers-reduced-motion: no-preference) {

    .ui-modal-root[data-meb-back-closing] .ui-modal-wrapper.variant-sheet .ui-modal {
        transform: translateY(100%);
    }
}
