/* Select Technicians picker — take up most of the screen width and a fixed height */
.select-technicians-dialog.mud-dialog {
    width: 90vw;
    max-width: 90vw;
    height: 90vh;
    max-height: 90vh;
}

/* ---------------------------------------------------------------------------
   New Appointment dialog — three-column layout (form / day timeline / route map).

   The dialog is sized near-fullscreen so all three columns fit; each column owns
   its own scroll so the form can be long without pushing the timeline or map
   off-screen. Below BreakpointLg (1280px) the map column is dropped and the
   middle column reverts to the Appointments/Route toggle; below 960px the
   columns stack.
   --------------------------------------------------------------------------- */
.appointment-dialog.mud-dialog {
    width: 96vw;
    max-width: 1560px;
    height: 92vh;
    max-height: 92vh;
    overflow: hidden;
}

/* Below BreakpointMd the dialog is opened with FullScreen=true (AppointmentDialogOptions), but
   MudBlazor's .mud-dialog-fullscreen is a single class with no !important — the two-class rule
   above out-specificities it and the dialog stays at 96vw/92vh. Restate it at matching weight,
   or the fullscreen option is silently discarded on every tablet and phone. */
.appointment-dialog.mud-dialog.mud-dialog-fullscreen {
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: none;
    margin: 0;
    border-radius: 0;
}

/* The modal wraps MudDialog in an EditForm, so MudBlazor's title/content/actions end up
   inside a plain <form> that sits between them and the flex dialog. Left as display:block
   it swallows the height chain and every column grows to its content instead of to the
   dialog. Make it the flex column the dialog expects. */
.appointment-dialog > form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

/* Beat the generic `:has(.register-modal-content)` rule below — this dialog scrolls
   per-column, not as one block, and its content must fill the dialog height. */
.appointment-dialog .mud-dialog-content {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow: hidden;
    padding: 0;
}

.appt-shell {
    display: grid;
    grid-template-columns: 400px minmax(0, 1fr) 440px;
    height: 100%;
    min-height: 0;
    background: var(--mud-palette-background-gray);
}

.appt-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    background: var(--mud-palette-surface);
}

.appt-col--form {
    border-right: 1px solid var(--mud-palette-lines-default);
}

/* Only the field list scrolls; the context header above it stays pinned. */
.appt-form-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: visible;
    padding: 16px 20px;
}

.appt-form-header {
    flex: 0 0 auto;
    padding: 14px 20px 12px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.appt-col--timeline {
    background: var(--mud-palette-background-gray);
}

.appt-col--map {
    border-left: 1px solid var(--mud-palette-lines-default);
}

.appt-panel-header {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
}

.appt-timeline-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    padding: 12px 16px 16px;
}

.appt-map-body {
    flex: 0 0 auto;
    padding: 0;
}

/* Route stop list beneath the map. */
.appt-stop-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.appt-stop {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.appt-stop-order {
    flex: none;
    width: 20px;
    height: 20px;
    border-radius: 999px;
    background: var(--mud-palette-text-primary);
    color: var(--mud-palette-surface);
    font-size: 10px;
    font-weight: 500;
    line-height: 20px;
    text-align: center;
}

.appt-stop-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The appointment being created — mirrors the blue "NEW" row in the design. */
.appt-stop--new {
    background: var(--mud-palette-primary-hover);
    border-left: 3px solid var(--mud-palette-primary);
}

.appt-stop--new .appt-stop-order {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

.appt-stop--new .appt-stop-label,
.appt-stop--new .appt-stop-time {
    color: var(--mud-palette-primary);
    font-weight: 500;
}

/* The preview block for the appointment being created, so it reads as "the new one"
   against the existing blocks around it. */
.appt-new-block {
    outline: 2px solid var(--mud-palette-primary-darken);
    outline-offset: -2px;
}

/* ---------------------------------------------------------------------------
   Narrow layout — the three columns become tabs.

   Set on the shell by the component below BreakpointMd, where there is no width
   for a second column. Only one panel is in the grid at a time (the form stays
   mounted and is hidden, so its state survives a tab switch), so the shell is
   two rows: the tab strip, then the panel filling everything left.
   --------------------------------------------------------------------------- */
.appt-shell--tabs {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
}

.appt-col--hidden {
    display: none;
}

.appt-tabs {
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
}

/* House tab treatment: muted at rest, primary when active, and the active underline drawn as an
   inset shadow so selecting a tab never reflows the strip. */
.appt-tab {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 44px;
    padding: 6px 24px;
    border: none;
    background: none;
    cursor: pointer;
    color: var(--mud-palette-text-secondary);
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.75;
    letter-spacing: 0.02857em;
}

.appt-tab .mud-icon-root {
    color: var(--mud-palette-text-secondary);
}

.appt-tab--active {
    color: var(--mud-palette-primary);
    box-shadow: inset 0 -2px 0 0 var(--mud-palette-primary);
}

.appt-tab--active .mud-icon-root {
    color: var(--mud-palette-primary);
}

.appt-tab:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

/* Warning count for the form, so the signal survives on the Day and Route tabs — the detail
   itself stays inline against the fields that caused it. */
.appt-tab__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 6px;
    border: 1px solid;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
}

.appt-tab__badge--error {
    background: var(--status-error-bg);
    border-color: var(--status-error-border);
    color: var(--status-error-fg);
}

.appt-tab__badge--warning {
    background: var(--status-warning-bg);
    border-color: var(--status-warning-border);
    color: var(--status-warning-fg);
}

.appt-tab__badge--info {
    background: var(--status-info-soft-bg);
    border-color: var(--status-info-soft-border);
    color: var(--status-info-soft-fg);
}

/* In the tabbed layout the panel owns the full grid row, so the form's own scroller finally has
   a bounded parent to scroll inside. */
.appt-shell--tabs .appt-col--form {
    border-right: none;
}

/* The route panel scrolls as one piece here — map, then stop list — rather than pinning a fixed
   map above an independently scrolling list in a viewport that may be only 400px tall. */
.appt-shell--tabs .appt-col--map {
    border-left: none;
    overflow-y: auto;
}

.appt-shell--tabs .appt-stop-list {
    flex: 0 0 auto;
    overflow-y: visible;
}

/* Touch: the stop rows and the day timeline are read, but the tab strip is the primary control at
   this width and must clear 44px even where the type is short. */
@media (pointer: coarse) {
    .appt-tab {
        min-height: 48px;
    }
}

@media (max-width: 1279px) {
    .appt-shell {
        grid-template-columns: minmax(360px, 1fr) minmax(0, 1fr);
    }
}

@media (max-width: 959px) {
    /* Single column at this width whether or not the component has applied --tabs yet (it does so
       from the measured viewport width, which is 0 until the first measurement). Height stays 100%:
       an auto height here would overflow .mud-dialog-content, which is overflow:hidden, and the
       bottom of the form would be clipped with nothing on the page able to scroll to it. */
    .appt-shell {
        grid-template-columns: 1fr;
        height: 100%;
    }

    /* Degrades the stacked case gracefully if the shell ever renders more than one column here
       (the tab class comes from the measured viewport width, which the CSS cannot see): each
       column takes an equal share of the height and scrolls inside itself, instead of the stack
       overflowing an overflow:hidden parent. Ignored in the tabbed layout, whose rows are explicit. */
    .appt-shell {
        grid-auto-rows: minmax(0, 1fr);
    }

    .appt-col--form {
        border-right: none;
    }
}

/* Phone: pull the form gutter in, and keep the actions clear of the home indicator. Type is not
   stepped down — the 14px floor holds and the form wraps instead. */
@media (max-width: 599px) {
    .appt-form-scroll {
        padding: 16px;
    }

    .appt-form-header,
    .appt-panel-header,
    .appt-stop {
        padding-left: 16px;
        padding-right: 16px;
    }

    .appt-tab {
        padding: 6px 8px;
    }

    .appointment-dialog .mud-dialog-actions {
        padding-bottom: max(8px, env(safe-area-inset-bottom));
    }
}

/* Global styles for Register modal dialogs */
/* Constrain the dialog-content so the actions stay pinned, and scroll there (single scrollbar). */
.mud-dialog-content:has(.register-modal-content) {
    max-height: calc(100dvh - 200px);
    overflow-y: auto;
    overflow-x: hidden;
    /* Square, and it must be squared HERE rather than on the ground inside it. MudBlazor gives
       .mud-dialog-content top-left/top-right radii of --mud-default-borderradius (the same stock
       rule mobile-detail.css:2104 cancels for fullscreen), and the overflow above makes this a
       clipping box — so an 8px curve here crops the ground's top corners back to the dialog's
       white surface no matter what radius the ground itself carries. Measured: content computed
       `8px 8px 0 0` with `overflow: hidden auto` while the ground underneath was already 0.
       The dialog's own 18px + overflow:hidden supplies all the rounding this stack needs. */
    border-radius: 0;
}

/* The modal ground. Was `var(--mud-palette-background-gray)`; see the Design D block at the
   foot of this file for why that resolved to a flat grey well inside a dialog and what
   replaced it. */
.register-modal-content {
    background:
        radial-gradient(520px 320px at 12% -8%, rgba(21, 93, 252, 0.10), rgba(21, 93, 252, 0)),
        radial-gradient(420px 300px at 100% 0%, rgba(150, 180, 240, 0.16), rgba(150, 180, 240, 0)),
        var(--defex-page-ground);
    /* Square. The shell is a MudPaper, so it carries --mud-default-borderradius (8px) — invisible
       while this surface was a flat grey well, but once it paints the ground the four corners cut
       back to the dialog's white surface and read as notches. The ground is edge-to-edge bleed
       (the shell's ma-n4); its only rounding is the dialog's own overflow clip. */
    border-radius: 0;
}

/* Register-modal layout that docks the SetupStructureRail beside the form. The form flexes to
   fill; the rail is a fixed-width aside that sticks to the top of the scroll area. Below the
   large-dialog fullscreen breakpoint the rail is hidden so the form keeps the full width. */
.setup-rail-layout {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}

.setup-rail-main {
    flex: 1 1 auto;
    min-width: 0;
}

.setup-rail-aside {
    /* Same radius as the MudPaper form card beside it, so the two cards read as a pair. */
    border-radius: var(--mud-default-borderradius);
    flex: 0 0 400px;
    width: 400px;
    position: sticky;
    top: 0;
    align-self: flex-start;
    /* utilities.css carries a global `aside { box-shadow: … !important }`; the rail is a flat
       bordered card, so it opts out. Needs !important to beat that rule. */
    box-shadow: none !important;
}

@media (max-width: 960px) {
    .setup-rail-aside {
        display: none;
    }
}

.mud-dialog-content:has(.todotask-page-dialog) {
    height: calc(100dvh - 200px);
    max-height: calc(100dvh - 200px);
    overflow-y: auto;
}

/* Keep the ToDoTask dialog at a single, fixed height regardless of which tab is active.
   Inner tab content components (Notes, Conversations) hard-code their own 100dvh-based
   heights for use on full pages — neutralize those inside the dialog so the dialog
   content's own scroll container is the only one. */
.todotask-page-dialog .mud-tabs,
.todotask-page-dialog .mud-tabs-panels,
.todotask-page-dialog .mud-tab-panel {
    height: auto !important;
}

.todotask-page-dialog .mud-tab-panel > .mud-paper,
.todotask-page-dialog .notes-corkboard-wrap {
    height: auto !important;
    max-height: none !important;
    overflow: visible !important;
}

/* Shared styles for all confirmation-type dialogs */
.confirm-dialog .mud-dialog-title,
.confirm-dialog .mud-dialog-content,
.confirm-dialog .mud-dialog-actions {
    border: none !important;
    border-top: none !important;
    border-bottom: none !important;
}

.confirm-dialog .mud-divider {
    display: none !important;
}

.confirm-dialog-icon-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    min-width: 40px;
    min-height: 40px;
    border-radius: 50%;
    margin-right: 16px;
}

.confirm-dialog-icon-wrap--warning {
    background-color: #fff3e0;
    border: 1px solid #ff9800;
}

.confirm-dialog-icon-wrap--error {
    background-color: #ffebee;
    border: 1px solid #ef5350;
}

.confirm-dialog-icon-wrap--default {
    background-color: #f5f5f5;
    border: 1px solid #bdbdbd;
}

.confirm-dialog-icon-wrap--success {
    background-color: #e8f5e9;
    border: 1px solid #4caf50;
}

.confirm-dialog-icon-wrap--primary {
    background-color: #e3f2fd;
    border: 1px solid #2196f3;
}

.confirm-dialog-icon-wrap--info {
    background-color: #e1f5fe;
    border: 1px solid #03a9f4;
}

.confirm-dialog-icon {
    font-size: 1.25rem;
}

.confirm-dialog-message {
    color: rgba(0, 0, 0, 0.6);
    font-size: 1rem;
    flex: 1;
}

/* Remove horizontal divider lines in Question and DeleteConfirmation dialogs */
.question-dialog .mud-dialog-title,
.question-dialog .mud-dialog-content,
.question-dialog .mud-dialog-actions,
.delete-confirmation-dialog .mud-dialog-title,
.delete-confirmation-dialog .mud-dialog-content,
.delete-confirmation-dialog .mud-dialog-actions {
    border: none !important;
    border-top: none !important;
    border-bottom: none !important;
}

.question-dialog .mud-divider,
.delete-confirmation-dialog .mud-divider {
    display: none !important;
}

/* Quotes Created confirmation dialog - match design: bold title, gray icon, primary black YES */
.quotes-created-dialog .mud-dialog-title {
    font-weight: bold;
    font-size: 1.2rem;
    color: rgba(0, 0, 0, 0.87);
}

.quotes-created-dialog .question-dialog-icon {
    color: rgba(0, 0, 0, 0.38);
}

.quotes-created-dialog .question-dialog-message {
    color: rgba(0, 0, 0, 0.6);
    font-size: 1rem;
}

/* Delete confirmation dialog - light red border, circular trash icon, CANCEL/DELETE buttons */
.delete-confirmation-dialog.mud-dialog .mud-paper {
    border: 1px solid #ffdddd;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.delete-confirmation-dialog .mud-dialog-title {
    font-weight: bold;
    color: rgba(0, 0, 0, 0.87);
}

.delete-confirmation-dialog .delete-dialog-icon-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    min-width: 40px;
    min-height: 40px;
    border-radius: 50%;
    background-color: #ffebee;
    border: 1px solid #ef5350;
    margin-right: 16px;
}

.delete-confirmation-dialog .delete-dialog-icon {
    font-size: 1.25rem;
}

.delete-confirmation-dialog .delete-dialog-message {
    color: rgba(0, 0, 0, 0.6);
    font-size: 1rem;
    flex: 1;
}

.delete-confirmation-dialog .delete-dialog-actions {
    padding: 16px 24px;
    gap: 12px;
}

.delete-confirmation-dialog .delete-dialog-actions .mud-button-root {
    text-transform: uppercase;
    font-weight: 500;
    border-radius: 6px;
}

.delete-confirmation-dialog .delete-dialog-btn-cancel {
    border-color: rgba(0, 0, 0, 0.23);
    color: rgba(0, 0, 0, 0.87);
}

.delete-confirmation-dialog .delete-dialog-btn-cancel:hover {
    border-color: rgba(0, 0, 0, 0.87);
    background-color: rgba(0, 0, 0, 0.04);
}

/* Publish confirmation dialog - primary-colored icon, CANCEL/PUBLISH buttons */
.publish-confirmation-dialog.mud-dialog .mud-paper {
    border: 1px solid #bbdefb;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.publish-confirmation-dialog .mud-dialog-title {
    font-weight: bold;
    color: rgba(0, 0, 0, 0.87);
}

.publish-dialog-icon-wrap {
    background-color: #e3f2fd;
    border: 1px solid var(--mud-palette-primary);
}

/* Fullscreen toggle button — sits in the title bar immediately left of the close button.
   The numbers are keyed to MudBlazor's own close button, which is 48px square at `top: 8px;
   right: 8px`, so it occupies 8-56px in from the trailing edge. Butting up against it means
   right: 56px, and matching `top: 8px` puts the two icons on the same centre line — provided the
   toggle is also rendered at the default 48px (see DialogFullscreenToggle.razor). */
.mud-dialog-title .mud-button-fullscreen {
    top: 8px;
    right: 56px;
    position: absolute;
}

/* Must out-specify MudBlazor's own `.mud-dialog .mud-dialog-title:has(.mud-button-close)`, which
   reserves only 64px — enough for the close button alone, so a long title would run underneath the
   toggle. 104px = 56px offset + the toggle's own 48px. */
.mud-dialog .mud-dialog-title:has(.mud-button-fullscreen) {
    padding-inline-end: 104px;
}

/* ===== Drag-to-resize dialogs ===== */
/* Opt in by putting `defex-dialog-resizable` on the MudDialog's Class (DialogOptions has no class
   hook — see the note further down this file). The grip is the browser's own `resize` handle, so
   there is no JS module and nothing injected into the dialog's DOM.

   Three MudBlazor rules have to be released for a drag to actually get anywhere:
     .mud-dialog          max-height: calc(100dvh - appbar-height)  -> clamps vertical drag
     .mud-dialog-width-*  max-width: 600/960/1280px                 -> clamps horizontal drag
     .mud-dialog          overflow-y: auto                          -> pinned to hidden here, so the
                          shell never grows its own scrollbar; .mud-dialog-content is already
                          flex: 1 1 auto; overflow: auto and does the scrolling. */
.mud-dialog.defex-dialog-resizable {
    resize: both;
    overflow: hidden;
    max-width: none;
    max-height: calc(100dvh - 32px);
    min-width: 360px;
    min-height: 320px;
}

/* Top-anchor a resizable dialog instead of centring it. MudBlazor centres on both axes, which means
   a growing dialog keeps re-centring and the grip trails the cursor at half rate. Anchoring the top
   makes the vertical drag exact. The horizontal axis is fixed by dialogDrag.js, which pins the
   dialog to `position: fixed` once it has opened — this rule is what it falls back to if that
   module never loads. */
.mud-dialog-container:has(> .defex-dialog-resizable) {
    align-items: flex-start;
}

.mud-dialog.defex-dialog-resizable {
    margin-top: 6vh;
}

/* Full screen has to beat two lots of inline style: the width/height the native resize grip writes,
   and the position/left/top dialogDrag.js pins. MudBlazor's .mud-dialog-fullscreen sets width and
   height WITHOUT !important, so inline would otherwise win and full screen would land at the
   dragged size and offset. */
.mud-dialog.defex-dialog-resizable.mud-dialog-fullscreen {
    position: fixed !important;
    inset: 0 !important;
    width: 100% !important;
    height: 100% !important;
    max-width: 100% !important;
    max-height: none !important;
    margin: 0 !important;
    resize: none;
}

/* ===== Non-modal dialogs ===== */
/* Add `defex-dialog-nonmodal` to make a dialog float over a still-usable page. Three things
   otherwise make a MudDialog modal, and DialogOptions exposes no `Modal` flag to turn any of them
   off (9.1.0 has only Position/MaxWidth/BackdropClick/CloseOnEscapeKey/CloseOnNavigation/NoHeader/
   CloseButton/FullScreen/FullWidth/BackgroundClass/DefaultFocus):

     1. .mud-dialog-container is a fixed, full-viewport sheet with pointer-events: auto.
     2. .mud-overlay is the scrim, also fixed and full-viewport, at z-index 1401.
     3. .mud-focus-trap — which measurement showed does NOT need disabling: focus moved to the app
        bar behind an open dialog and stayed there.

   The container is raised above the standard dialog z-index (1400) so this dialog also stays above
   any ordinary modal opened later, while remaining below the snackbar layer at 1500.

   Note this is only half the job: the dialog must also be opened with CloseOnNavigation = false
   (see UIConstants.AskDexDialogOptions), or MudBlazor closes it on the next route change. */
.mud-dialog-container:has(> .defex-dialog-nonmodal) {
    pointer-events: none;
    z-index: 1450;
}

.mud-dialog-container:has(> .defex-dialog-nonmodal) > .mud-overlay {
    display: none;
}

.mud-dialog.defex-dialog-nonmodal {
    pointer-events: auto;
}

/* Add `defex-dialog-above-nonmodal` to a dialog the FLOATING WINDOW ITSELF opens — the Ask DEX
   drill-downs that list the records behind a figure or a next-action count.

   The 1450 above is a rule about ownership, not about importance: a chat you are still using must
   not be buried by whatever the page opens behind it. A dialog Dex opened is the opposite case —
   it IS the conversation continuing, so it belongs on top of the window that asked for it.
   Otherwise clicking a number appears to do nothing, because the answer opened behind the chat.

   1460 keeps it below the snackbar layer (1500). The scrim rides along because the overlay is a
   child of the container, so raising the container raises both — which is what we want: reading
   the records behind a number deserves the focus. */
.mud-dialog-container:has(> .defex-dialog-above-nonmodal) {
    z-index: 1460;
}

/* The title bar is the drag handle (dialogDrag.js), but its buttons must still read as buttons. */
.mud-dialog.defex-dialog-nonmodal .mud-dialog-title {
    cursor: move;
    touch-action: none;
}

.mud-dialog.defex-dialog-nonmodal .mud-dialog-title button {
    cursor: pointer;
}

/* A docked window is positioned by the dock, not by the drag. */
.mud-dialog.defex-dialog-docked .mud-dialog-title {
    cursor: default;
}

/* ===== Docked dialogs ===== */
/* Driven by dialogDock.js. These override the inline left/top/width/height dialogDrag.js pins for
   the floating window rather than clearing them, so floating again restores the previous position
   and size for free.

   `top` clears the app bar deliberately: the Ask DEX pill and the global search live up there, and
   a dock that covered them would trap the user. Full-screen still wins over this — its rule carries
   one more class, so it out-specifies these at equal !important weight, and exiting full screen
   drops the window back into its dock. */
.mud-dialog.defex-dialog-docked {
    position: fixed !important;
    top: var(--app-bar-offset, 65px) !important;
    bottom: 0 !important;
    height: auto !important;
    max-height: none !important;
    width: var(--dex-dock-width, 380px) !important;
    max-width: none !important;
    /* Release the resizable dialog's 360px floor. A width, even !important, still loses to
       min-width, so without this the 48px collapsed rail renders 360px wide and dragging the dock
       narrower than 360 moves the page padding while the panel itself refuses to follow. */
    min-width: 0 !important;
    margin: 0 !important;
    border-radius: 0 !important;
    resize: none;
}

.mud-dialog.defex-dialog-docked-right {
    right: 0 !important;
    left: auto !important;
    border-left: 1px solid var(--mud-palette-table-lines);
}

/* --dex-dock-inset is the nav drawer's current width, published by dialogDock.js from the layout's
   own computed margin. Docking flush to left: 0 would bury the navigation. */
.mud-dialog.defex-dialog-docked-left {
    left: var(--dex-dock-inset, 0px) !important;
    right: auto !important;
    border-right: 1px solid var(--mud-palette-table-lines);
}

/* Reflow the page into what's left, so the chat never covers what it is being asked about.
   Padding, not margin: MudBlazor owns .mud-main-content's margin-left for the nav drawer and
   rewrites it as the drawer opens, closes and crosses breakpoints. Both paddings are 0 by default,
   so this is free real estate. */
[data-dex-dock="right"] .mud-main-content {
    padding-right: var(--dex-dock-width, 0px) !important;
}

[data-dex-dock="left"] .mud-main-content {
    padding-left: var(--dex-dock-width, 0px) !important;
}

/* Drag handle on the dock's inner edge. Native CSS `resize` only grows right and down, so it can
   serve a left dock but never a right one — both get this instead, so they behave the same. */
.defex-dock-resizer {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 6px;
    cursor: ew-resize;
    z-index: 2;
    background: transparent;
    transition: background-color .15s ease;
}

.defex-dock-resizer[data-side="left"] {
    right: 0;
}

.defex-dock-resizer[data-side="right"] {
    left: 0;
}

.defex-dock-resizer:hover {
    background-color: var(--mud-palette-primary);
}

/* Collapse toggle — leftmost of the four trailing controls, continuing the 8 / 56 / 104 run.
   Must come after the `.mud-button-dock` rules above: both `:has()` selectors carry the same
   specificity, so source order is what decides the reserved padding. */
.mud-dialog-title .dex-dock-collapse-btn {
    top: 8px;
    right: 152px;
    position: absolute;
}

.mud-dialog .mud-dialog-title:has(.dex-dock-collapse-btn) {
    padding-inline-end: 200px;
}

/* Docked, the full-screen toggle isn't rendered (ChatAgent.razor), so the row closes up to
   8 / 56 / 104 — close, dock, collapse — and reclaims its 48px for the heading. Leaving the
   original offsets would open a hole where the toggle used to be. */
.mud-dialog.defex-dialog-docked .mud-dialog-title .mud-button-dock {
    right: 56px;
}

.mud-dialog.defex-dialog-docked .mud-dialog-title .dex-dock-collapse-btn {
    right: 104px;
}

.mud-dialog.defex-dialog-docked .mud-dialog-title:has(.dex-dock-collapse-btn) {
    padding-inline-end: 152px;
}

/* A docked panel is ~380px and the title bar spends 200px of it on the trailing button row, so the
   greeting has ~130px to live in and wraps. Clamp it to two lines: unclamped it runs to three and
   the header eats height the transcript needs. Docked only — the floating window is wide enough. */
.mud-dialog.defex-dialog-docked .mud-dialog-title .mud-typography-h6 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ===== Collapsed dock (rail) ===== */
/* Set by dialogDock.js alongside the docked classes. The panel folds to --dex-dock-width (48px,
   published by the module) and the page keeps that much padding, so the chat reads as parked
   rather than closed. Everything is hidden with CSS and stays mounted — ChatCore holds the
   transcript in a component field, so unmounting it would end the conversation.
   Only the chevron and the vertical wordmark survive the fold — the orb, the heading and the rest
   of the control row all need a width the rail hasn't got. */
.mud-dialog.defex-dialog-dock-collapsed .mud-dialog-content,
.mud-dialog.defex-dialog-dock-collapsed .dex-dock-titlebar,
.mud-dialog.defex-dialog-dock-collapsed .mud-button-dock,
.mud-dialog.defex-dialog-dock-collapsed .mud-button-fullscreen,
.mud-dialog.defex-dialog-dock-collapsed .mud-button-close {
    display: none !important;
}

/* The title bar becomes the whole rail: the content is display:none, so it takes the height the
   transcript used to have and the vertical wordmark has somewhere to run. The reserved
   padding-inline-end has to go with the button row it was reserving for, or it pushes the rail's
   own content off a 48px panel. */
.mud-dialog.defex-dialog-dock-collapsed .mud-dialog-title {
    padding: 8px 0 !important;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    overflow: hidden;
}

/* Back into the flow: the row it was absolutely positioned within no longer exists. */
.mud-dialog.defex-dialog-dock-collapsed .dex-dock-collapse-btn {
    position: static !important;
    flex: 0 0 auto;
}

/* Vertical wordmark down the rail, so a folded panel still says what it is. Mirrors the
   Relationship Explorer's collapsed panel (relexplorer.css → .rx-collapse-rail-text). */
.dex-dock-rail-label {
    flex: 1 1 auto;
    border: none;
    background: transparent;
    padding: 0;
    cursor: pointer;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--mud-palette-text-secondary);
}

.dex-dock-rail-label:hover {
    color: var(--mud-palette-text-primary);
}

/* Dock menu — sits left of the fullscreen toggle, which sits left of MudBlazor's close button.
   Each control is 48px, so the row runs 8 / 56 / 104 in from the trailing edge. */
.mud-dialog-title .mud-button-dock {
    top: 8px;
    right: 104px;
    position: absolute;
}

.mud-dialog .mud-dialog-title:has(.mud-button-dock) {
    padding-inline-end: 152px;
}

/* Below md these dialogs already open full screen, and a drag grip is meaningless on touch. */
@media (max-width: 959px) {
    .mud-dialog.defex-dialog-resizable {
        resize: none;
        margin-top: 0;
    }
}

/* Ask DEX — default size, replacing the inline 70vh that used to sit on the inner MudPaper.
   The dialog needs an explicit height for the chat to be able to fill it.
   78vh, not 70vh: the old 70vh was the height of the chat *inside* the dialog, so measuring the
   whole dialog at 70vh would have handed the user a shorter transcript than before. 78vh restores
   the previous overall size once the title bar is counted. */
.mud-dialog.ask-dex-dialog {
    width: 960px;
    height: 78vh;
}

/* ===== Global dialog overrides (extracted from MainLayout) ===== */
.mud-dialog .mud-dialog-title {
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

.mud-dialog .mud-dialog-actions {
    border-top: 1px solid var(--mud-palette-table-lines);
}

.mud-dialog .mud-dialog-content {
    padding: 16px;
    margin: 0px;
}

/* ===== SetupStructureRail — docked "Structure" rail (design 2a) ===== */
/* A light card that draws the Client → Contract → Property tree live inside a register modal.
   Kept in this global sheet (not component-isolated) so the styles apply to markup rendered via
   the component's templated node delegate. Icons use MudIcon Color.Inherit, so their colour comes
   from the `color` of the scoped wrapper element.

   Every colour is derived from the palette rather than hard-coded, so the rail follows the app
   theme. It cannot use a `.defex-theme-dark` override: the host stamps that marker on
   .main-container and MudDialog renders through a portal outside it, so the selector would never
   match here. The color-mix-against-surface approach mirrors the --status-* tokens in
   utilities.css, which exist for exactly this reason. */

.ssr-rail {
    --ssr-surface: var(--mud-palette-surface);
    --ssr-well: var(--mud-palette-background-gray);
    --ssr-border: var(--mud-palette-table-lines);
    --ssr-text: var(--mud-palette-text-primary);
    --ssr-text-muted: var(--mud-palette-text-secondary);
    --ssr-accent: var(--wgt-status-info);
    --ssr-accent-bg: color-mix(in srgb, var(--ssr-accent) 8%, var(--mud-palette-surface));
    --ssr-accent-border: color-mix(in srgb, var(--ssr-accent) 35%, var(--mud-palette-surface));
    --ssr-link-muted: var(--mud-palette-table-lines);
    --ssr-slot-border: var(--mud-palette-table-lines);
    background: var(--ssr-surface);
    border: 1px solid var(--ssr-border);
    border-radius: var(--mud-default-borderradius);
    padding: 18px 16px;
    color: var(--ssr-text);
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
}

.ssr-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
}

.ssr-head-icon {
    display: inline-flex;
    color: var(--ssr-accent);
}

.ssr-head-title {
    font-weight: 600;
    font-size: 12px;
    line-height: 1;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ssr-text);
}

.ssr-sub {
    font-size: 11px;
    color: var(--ssr-text-muted);
    margin-bottom: 14px;
}

/* Zoom/pan viewport — a fixed window onto the (larger) computed tree canvas. The tree is scaled to
   fit by default and can be zoomed/panned. */
.ssr-viewport {
    position: relative;
    height: 230px;
    max-width: 100%;
    overflow: hidden;
    border-radius: 8px;
    background: var(--ssr-well);
    border: 1px solid var(--ssr-border);
    touch-action: none;
    cursor: grab;
}

.ssr-viewport.is-dragging {
    cursor: grabbing;
}

/* Transform surface holding the SVG connectors + positioned nodes. Sized to the computed canvas;
   translate/scale applied inline from component state. */
.ssr-canvas {
    position: absolute;
    top: 0;
    left: 0;
}

.ssr-links {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* Connector strokes are themed here rather than emitted as SVG stroke attributes from the
   component, so they follow the palette with the rest of the rail. */
.ssr-links path.is-present {
    stroke: var(--ssr-accent);
}

.ssr-links path.is-muted {
    stroke: var(--ssr-link-muted);
}

/* Node base — absolutely positioned from inline left/top/width computed by the layout pass. */
.ssr-node {
    position: absolute;
    box-sizing: border-box;
    border-radius: 6px;
    padding: 8px 10px;
    display: flex;
    align-items: center;
    gap: 9px;
    transition: box-shadow 0.2s ease, background 0.2s ease;
}

/* Filled — an established parent / relation. */
.ssr-node.is-filled {
    background: var(--ssr-accent-bg);
    border: 1.5px solid var(--ssr-accent-border);
    color: var(--ssr-text);
}

/* Draft — the entity being created in this modal, highlighted. */
.ssr-node.is-draft {
    background: var(--ssr-surface);
    color: var(--ssr-text);
    box-shadow: 0 0 0 2px var(--ssr-accent), 0 2px 6px color-mix(in srgb, var(--ssr-accent) 22%, transparent);
}

/* Empty — nothing added yet; a dashed placeholder slot. */
.ssr-node.is-empty {
    background: var(--ssr-surface);
    border: 1.5px dashed var(--ssr-slot-border);
    color: var(--ssr-text-muted);
    justify-content: center;
    text-align: center;
}

.ssr-node-icon {
    display: inline-flex;
    color: var(--ssr-accent);
    flex: none;
}

.ssr-node-text {
    min-width: 0;
}

.ssr-node-title {
    font-weight: 600;
    font-size: 12px;
    line-height: 1.15;
    color: var(--ssr-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ssr-node-sub {
    font-weight: 400;
    font-size: 9.5px;
    line-height: 1.2;
    color: var(--ssr-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-top: 2px;
}

.ssr-slot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    color: inherit;
}

.ssr-slot span {
    font-weight: 500;
    font-size: 10px;
    line-height: 1.3;
    color: inherit;
}

/* Zoom controls — a small pill of icon buttons under the viewport. */
.ssr-zoom-controls {
    display: flex;
    justify-content: center;
    gap: 4px;
    margin-top: 8px;
}

.ssr-zoom-btn {
    color: var(--ssr-text-muted) !important;
    background: color-mix(in srgb, var(--ssr-accent) 8%, transparent) !important;
    padding: 4px !important;
}

.ssr-zoom-btn:hover {
    color: var(--ssr-accent) !important;
    background: color-mix(in srgb, var(--ssr-accent) 18%, transparent) !important;
}

/* Register modals (Client / Property / Service Agreement) open at an in-between
   width — wider than MudBlazor's Large (1280px) but narrower than ExtraLarge
   (1920px). Applied via the marker class on each <MudDialog>, so it holds no
   matter which MaxWidth the caller passes. Two classes + !important beat
   .mud-dialog-width-lg/xl on the teleported dialog paper. */
.mud-dialog.dialog-register-wide {
    max-width: 1500px !important;
}

/* New Contact opens a step wider than the default Small (600px) so its two preference
   panels — Reports to receive / Notifications to receive — can sit side by side instead of
   stacking into a very tall scroll. Deliberately narrower than .dialog-register-wide: the
   content is two checkbox columns plus an eight-field form, and 1500px would leave both
   stranded. Applied via the marker class on <MudDialog> so it holds no matter which
   MaxWidth the caller passes (this modal is opened from six places, most with
   RegisterDialogOptions = Small). Excludes full screen, where MudBlazor's own
   max-width: 100% carries no !important and would otherwise lose. */
.mud-dialog.dialog-register-contact:not(.mud-dialog-fullscreen) {
    max-width: 1100px !important;
}

/* The two preference panels. Equal columns with matched heights (grid stretch), collapsing
   to a single stack below MudBlazor's md breakpoint — which is also where the dialog itself
   goes full screen on a phone. */
.contact-prefs-split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    align-items: stretch;
}

@media (max-width: 959.98px) {
    .contact-prefs-split {
        grid-template-columns: 1fr;
    }
}

/* Restore the theme's dialog-title role across the web host.
   mobile-detail.css carries `.mud-dialog-title .mud-typography { font-size: .875rem !important }`
   with no host scoping, and index.html links that sheet AFTER this one, so every web dialog
   title was clamped to 14px/600 — below the 18px .section-header rows inside the dialog it
   introduces. (.confirm-dialog already carried a local workaround for the same clamp.)

   The clamp cannot simply be deleted or moved: 12 mobile dialogs render Typo.h6 into the real
   .mud-dialog-title slot and depend on it, `.md-page` sits INSIDE the dialog body so it can't
   scope a title selector, and mobile dialogs share no common class. Opting the web out here is
   the available seam — the MAUI host links fonts/MudBlazor/aos/mobile-detail/app.css and does
   not link this file, so this rule reaches every browser dialog and no MAUI dialog.

   Targets .mud-typography-h6 specifically rather than all typography in the title, so dialogs
   that put a subtitle, chip or body text in their title bar keep the sizes they declared.
   The extra `.mud-dialog` lifts specificity to 0,3,0 so it beats the later-loading clamp. */
.mud-dialog .mud-dialog-title .mud-typography.mud-typography-h6 {
    font-size: var(--mud-typography-h6-size) !important;
    font-weight: var(--mud-typography-h6-weight) !important;
    letter-spacing: var(--mud-typography-h6-letterspacing);
    line-height: var(--mud-typography-h6-lineheight);
}
/* ============================================================
   DESIGN D IN A DIALOG — register modals ("Glass Cohesion", utilities.css:1)
   Applies to every RegisterModalContentShell consumer (~297 razor files) via the
   base .register-modal-content class. The shell's `Sectioned` parameter is a
   separate, narrower opt-in — see the note on the frame below.

   Why a dialog needed its own block at all. Design D redefines
   --mud-palette-background-gray to transparent on .main-container so the body
   gradient becomes the page ground — but MudDialogProvider renders OUTSIDE
   .main-container (see utilities.css:412), so that override never reaches a
   dialog and .register-modal-content resolved to the literal #F4F6FA. Meanwhile
   .custom-border IS global, so the cards inside had already taken D's translucent
   --defex-card-bg. The result was alpha-white cards over flat grey: the one
   surface in the app wearing half the new system.

   The model here is the PAGE, not the app bar: dialog paper = chrome panel,
   content area = ground, section papers = cards. Deliberately NO backdrop-filter
   anywhere in this block. A dialog sits on MudBlazor's dark scrim, and blurring
   a near-flat colour returns that colour (the same reasoning that keeps blur off
   .page-header, utilities.css:1428) — it would cost a compositing layer per
   dialog and render as grey.

   THE FRAME. The shell draws a .custom-border frame around its content by default
   and that stays: only 43 of the ~297 consumers supply their own section papers,
   and the other 254 would otherwise drop bare fields straight onto the ground with
   no card at all. Those 43 pass Sectioned="true" so their own papers become the
   cards — without it, D's nesting reset (utilities.css:1274) renders each one as a
   grey well inside a near-white card. The two modes converge for single-section
   content: one card on the ground either way.

   Light theme only, matching the tokens it consumes. Note the usual web-host
   constraint: MudThemeProvider never stamps .mud-theme-dark, and MainLayout's
   defex-theme-dark lands on .main-container, which a dialog is outside of — so
   there is no marker available to scope a dark variant here. When --defex-* grows
   dark values this block follows for free; until then it must not be given
   literals that only work in light.

   THE GROUND itself is declared up at .register-modal-content, beside the rule it
   replaced, rather than down here — it is the surface's own fill, not a modifier.
   ============================================================ */

/* THE CHROME PANEL. Same identity as .page-header so a modal launched from a page
   reads as the same material: panel radius and the glass rim. The FILL stays the
   opaque surface — --defex-glass-panel is white-at-0.66 and there is nothing but
   the scrim behind it, so translucency here would only dull the dialog.

   overflow: hidden clips the square-cornered ground to the 18px radius. Safe with
   MudBlazor's popovers (selects, date pickers, the LookupDropdown) because those
   are portalled to MudPopoverProvider, not rendered inside the dialog. */
.mud-dialog:has(.register-modal-content) {
    border: 1px solid var(--defex-glass-edge);
    border-radius: var(--defex-radius-panel);
    box-shadow: var(--defex-card-shadow);
    overflow: hidden;
}

/* Register modals open FullScreen below the compact breakpoint (UIConstants.IsCompactView).
   MudBlazor's .mud-dialog-fullscreen is (0,1,0) and the rule above is (0,2,0), so without
   this the rounded corners and rim survive into fullscreen and float over the screen edges
   — the same specificity trap already documented for .appointment-dialog above. */
.mud-dialog.mud-dialog-fullscreen:has(.register-modal-content) {
    border: 0;
    border-radius: 0;
}

/* Title and actions are the chrome bracketing the ground, so they keep the surface fill and
   are separated by the house hairline rather than by a shadow. --defex-rule, not
   --mud-palette-table-lines: that token also draws DataGrid row separators and is pitched
   to stay legible at full strength, which reads as a hard seam here. */
.mud-dialog:has(.register-modal-content) .mud-dialog-title {
    border-bottom: 1px solid var(--defex-rule);
}

.mud-dialog:has(.register-modal-content) .mud-dialog-actions {
    border-top: 1px solid var(--defex-rule);
}

/* The docked rail becomes a card like everything else on the ground. It opts out of the
   card SHADOW deliberately (see .setup-rail-aside above, which kills the global aside
   shadow) — the rail is a tall sticky element and a large soft cast under it tracks the
   scroll, which reads as a smear rather than as lift. Rim and radius are enough to pair
   it with the section cards beside it.

   dialogs.css is linked BEFORE utilities.css (index.html), so an equal-specificity rule
   would lose to .ssr-rail's own declarations; the descendant selector takes this to
   (0,2,0) and wins on specificity instead of order. */
.register-modal-content .ssr-rail {
    background: var(--defex-card-bg);
    border-color: var(--defex-card-edge);
    border-radius: var(--defex-radius-panel);
}

.register-modal-content .setup-rail-aside {
    border-radius: var(--defex-radius-panel);
}

/* ============================================================
   DESIGN D CHROME — dialogs generally
   Extends the panel identity above from register modals to every other dialog, so a
   confirm prompt and a form modal read as the same material as the page header that
   launched them. Built from an audit of all 83 non-shell dialogs in AI.Client.

   WHAT THIS RULE DELIBERATELY DOES NOT SET: overflow.

   The register-modal rule above can afford `overflow: hidden` because it has a
   guaranteed inner scroller — .mud-dialog-content:has(.register-modal-content) is
   pinned to overflow-y:auto (top of this file). Dialogs at large have no such
   guarantee: MudBlazor's base `.mud-dialog { overflow-y: auto; max-height: calc(100dvh
   - appbar) }` IS the fallback scroller, and a good number of dialogs rely on it —
   the ClientPortal list dialogs size their tables in vh and overrun the paper, and the
   SpreadsheetBulkEditDialog caps itself in dvh. Replacing that with a clip
   would silently cut off pagers and Close buttons on a short window. The clip is
   therefore applied ONLY where a full-bleed body makes it necessary, below.

   EXCLUSIONS. Two kinds, both audited rather than guessed:
     - dialogs that already own a bespoke block in this file or their own stylesheet
       (.appointment-dialog rebuilds its whole height chain; .delete-confirmation-dialog
       sets its own radius and shadow; ChatAgent's docked/non-modal/resizable variants
       force border-radius:0 and draw single edges; .qc-dialog needs its in-flow chip
       popovers to escape; TrainingGuide and the map/photo dialogs draw their own
       edge-to-edge chrome);
     - dialogs that own their layout and would be damaged by a rim changing their
       content box or a radius cutting full-bleed content. Those carry the shared
       .defex-dialog-raw opt-out, added to 10 files that had no marker class of their
       own. Grep that class to find them.

   .register-modal-content is excluded because the block above already gives it the
   same chrome PLUS the clip it can safely afford — without this, the two rules tie at
   (0,1,0) vs (0,2,0) and the weaker one would just be dead weight.
   ============================================================ */
.mud-dialog:not(.defex-dialog-raw):not(.appointment-dialog):not(.delete-confirmation-dialog):not(.defex-dialog-resizable):not(.defex-dialog-docked):not(.defex-dialog-nonmodal):not(.ask-dex-dialog):not(.qc-dialog):not(.timesheet-entry-map-dialog):not(.defect-photo-dialog):not(.learn-lesson-dlg):not(.select-technicians-dialog):not(:has(.register-modal-content)) {
    border: 1px solid var(--defex-glass-edge);
    border-radius: var(--defex-radius-panel);
    box-shadow: var(--defex-card-shadow);
}

/* THE FULLSCREEN COMPANION — universal, and the reason this needs saying twice.
   MudBlazor's .mud-dialog-fullscreen is (0,1,0) and carries no !important, but
   index.html links MudBlazor.min.css BEFORE dialogs.css, so ANY same-weight rule here
   wins on source order and the radius and rim survive into fullscreen, floating over
   the screen edges. That hits more dialogs than the mobile breakpoint suggests:
   BackgroundJobRunsDialog and IntegrationLogsDialog both toggle fullscreen at RUNTIME
   from their title bars, so they flip in and out of this state while open.

   Left unqualified by the exclusion chain on purpose — zeroing the radius on a dialog
   that never had one is a no-op, and the alternative is repeating that whole chain. */
.mud-dialog.mud-dialog-fullscreen {
    border: 0;
    border-radius: 0;
}

/* THE CLIP, where a full-bleed body makes it necessary. The three entity overview dialogs and
   MyToDoTask open with ContentClass="pa-0" Gutters="false" and paint a grey body edge to
   edge, so a radius without a clip cuts square notches at the content corners — the
   same failure the ground hits at .register-modal-content above. Paired with a content
   scroller so the clip cannot swallow an overflowing narrative, which is exactly the
   arrangement that makes the register-modal clip safe. */
.mud-dialog.defex-dialog-fullbleed {
    overflow: hidden;
}

.mud-dialog.defex-dialog-fullbleed .mud-dialog-content {
    max-height: calc(100dvh - 200px);
    overflow-y: auto;
    /* Same stock top-corner radius as the register-modal case above, and the same reason: the
       overflow makes this a clipping box, so the full-bleed body would notch at the top corners. */
    border-radius: 0;
}

/* Those bodies paint --mud-palette-background-gray inline, which outside
   .main-container resolves to the flat #F4F6FA well rather than the ground (see the
   block above). Give them the same ground the register modals get, so every dialog
   with a full-bleed body reads the same way. Inline styles outrank class rules, hence
   the !important — the fill is declared in the razor's style attribute. */
.mud-dialog.defex-dialog-fullbleed .mud-dialog-content > .mud-paper {
    background:
        radial-gradient(520px 320px at 12% -8%, rgba(21, 93, 252, 0.10), rgba(21, 93, 252, 0)),
        radial-gradient(420px 300px at 100% 0%, rgba(150, 180, 240, 0.16), rgba(150, 180, 240, 0)),
        var(--defex-page-ground) !important;
}

/* New User lays its fields out two-per-row (see RegisterUserModal.razor), which needs more than
   the caller's Small default (600px) — at 600px the md breakpoint never fires and the pairs
   stack anyway. 900px is enough for two comfortable field columns without stranding the
   full-width Guest-user row. Applied via the marker class on <MudDialog> so it holds no matter
   which MaxWidth the caller passes (this modal is opened from the Users list and the Get Started
   hub, both with RegisterDialogOptions = Small). Excludes full screen, where MudBlazor's own
   max-width: 100% carries no !important and would otherwise lose. */
.mud-dialog.dialog-register-user:not(.mud-dialog-fullscreen) {
    max-width: 900px !important;
}

/* Get Started > "Set up Users" / "Set Technician Costs". Both host a full management grid
   (search, many columns, bulk bar) inside a dialog, so they take a near-fullscreen shell
   rather than a register width. Opened with MaxWidth.False, which hands sizing here -
   DialogOptions has no class hook, so the class comes from each dialog's MudDialog Class. */
.setup-wide-dialog.mud-dialog {
    width: 96vw;
    max-width: 1800px;
    height: 92vh;
    max-height: 92vh;
}

/* Same specificity trap documented for .appointment-dialog: below the compact breakpoint these
   open with FullScreen=true, and MudBlazor's single-class .mud-dialog-fullscreen would lose to
   the two-class rule above. Restate it at matching weight or fullscreen is silently discarded. */
.setup-wide-dialog.mud-dialog.mud-dialog-fullscreen {
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: none;
    margin: 0;
    border-radius: 0;
}

/* The hosted page scrolls inside the dialog, so the content area fills the height instead of
   sitting at its natural size and leaving the dialog part-empty. */
.setup-wide-dialog .mud-dialog-content {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
}
