/* Dialog chrome — Shell-owned, split out of shared.css (Sprint 9.5 Story 22,
   architecture/SHARED-CSS-LAYER-SPLIT.md). Dialog/modal placement, one dialog chrome (.app-modal*),
   the Add/Edit dialog chrome, dialog form vertical rhythm, and dialog controls — moved here as ONE
   contiguous block, order unchanged, so the two intra-file order dependencies the plan names (the
   Add/Edit dialog's specificity-vs-Syncfusion comment, and .app-modal__actions' sticky-footer comment)
   both survive untouched. Linked once in App.razor's <head>, reaching every Blick's DOM the same way
   shared.css already did. */

/* ── Dialog/modal placement (--app-dialog-top-offset / .app-add-edit-dialog) ────────────────────────────────
   Brian, 2026-08-19: "make dialogs placements consistent. keep them close to the top just a bit lower." One
   offset, shared by every dialog family in the app: Syncfusion Add/Edit dialogs (.app-add-edit-dialog, below)
   position via `top`; the custom backdrop-flex modals below (.app-modal-backdrop) consume it directly since
   that family now lives here too (D-0183, below). 14vh is a modest step below the platform's top-10-percent
   default — near the top, not centered — chosen to satisfy Brian's "just a bit lower" without drifting toward
   the vertically-centered posture this replaces.

   🔴 `vh`, NOT `%` — Will's measurement (D-0183 follow-up sweep): a `%` value is fine for `.app-add-edit-dialog`'s
   `top` (percentages on `top`/`bottom` already resolve against the containing block's HEIGHT), but the exact
   same `%` value fed to `.app-modal-backdrop`'s `padding-top` below resolved against the backdrop's WIDTH
   instead — the CSS spec's rule for ALL padding percentages, regardless of which physical side they pad. On a
   1920-wide, 950-tall window that put the dialog 269px down; on a 960-wide window of the same height, 134px
   down — the offset tracked window WIDTH, which this value was never meant to depend on. On a wide, short
   window (1920×700) that could burn over a third of the window's own height on empty backdrop before the
   dialog even started. `vh` is a length, not a percentage, so it resolves identically against height no matter
   which property consumes it — one value, one meaning, in both consumers below. Pre-existing in all five
   backdrops before the hoist; fixed once here is the reason the hoist happened.

   This rule was previously CCP.CatalogContext.Blick's own .app-add-edit-dialog override (2026-08-16, "dialog is
   too high"), scoped to one Blick because no house posture existed yet to match. That's no longer true — Brian
   just created one — so the rule moved here instead of leaving a one-Blick copy to drift from it. */
:root {
    --app-dialog-top-offset: 14vh;
}

.app-add-edit-dialog {
    top: var(--app-dialog-top-offset) !important;
    max-height: 85vh !important;
}

.app-add-edit-dialog .e-dlg-content {
    max-height: calc(85vh - 120px);
    overflow-y: auto;
}

/* Slice B REBUILD (Francie, 2026-09-05) — Will's FAIL 1: the rule above caps and scrolls `.e-dlg-content` as a
   whole, but B3.CRUD's AddOne/EditOne render the Cancel/Save row (`<div class="mt-4 d-flex justify-content-
   between">`, EditOne.razor:258 / AddOne.razor:256 — identical markup in both, the one stable hook every
   Add/Edit dialog in this app shares) INSIDE that same scrolling element, as the form's own last child. Capping
   `.e-dlg-content` therefore scrolled the actions row away with everything else, and nothing on screen said so
   — measured worse than the platform default this replaced (which at least left Save reachable by scrolling the
   PAGE, not an invisible inner region). The fix pins that one row to the bottom of the scrolling container
   instead of letting it scroll with the fields: `position: sticky; bottom: 0` keeps it glued to the visible
   bottom edge of `.e-dlg-content` as the fields above it scroll underneath, so Save is on screen at every
   scroll position, on every dialog, at every viewport height — not just the ones short enough to fit under
   85vh. The opaque background stops scrolled-past fields showing through; the top border is the "this is a
   fixed region, not just where the content happened to end" signal FAIL 1 also asked for. Scoped under
   `.app-add-edit-dialog .e-dlg-content` so this never reaches an unrelated `mt-4 d-flex justify-content-between`
   elsewhere in the app.
   ⚠ FIXED (Will's/Carrie's FAIL, 2026-09-05 inspection round): the first version fought the scroll container's
   OWN `padding-bottom: var(--brand-space-6)` with `margin-bottom: calc(-1 * var(--brand-space-6))` on this
   element, trying to pull the sticky box down over that padding strip. `position: sticky`'s `bottom: 0` offset
   is measured against the scrolling ancestor's PADDING BOX, not its border box — a negative margin shifts the
   element's flow footprint but does not reliably drag its STUCK visual position past the container's own
   padding, so a ~24px strip of that padding (with whatever scrolled field was last in the flow still visible
   through it — the "opaque fill" was never actually painted over that strip, it only ever covered the
   element's own border box) rendered BELOW the supposedly-flush footer. Root cause fixed at the source instead:
   `.e-dlg-content` above now carries NO bottom padding at all, so there is no trailing strip for `bottom: 0` to
   fall short of — the same 24px of breathing room the container used to reserve is now this element's own
   `padding-bottom`, inside the box the sticky positioning and the opaque background both already cover
   correctly. No negative margin anywhere. */
.app-add-edit-dialog .e-dlg-content .mt-4.d-flex.justify-content-between {
    position: sticky;
    bottom: 0;
    background: var(--brand-surface);
    border-top: 1px solid var(--brand-border);
    padding-top: var(--brand-space-3);
    padding-bottom: var(--brand-space-6);
    z-index: 1;
}

/* ── One dialog chrome (.app-modal-backdrop / .app-modal / .app-modal--wide / .app-modal__field / .app-modal__actions) ──
   D-0183 (Duane, 2026-08-25, ruling on the 2026-08-25 conformance sweep, item 3): dialog chrome HOISTS here.
   Five Blicks each declared their own backdrop-and-dialog pair and the copies had already diverged four
   separate ways; per-Blick dialog chrome is now a styling fork under DESIGN-SYSTEM.md §4.32 — no new dialog family may be
   declared in a Blick's blick.css. All five original families — CCP.CustomerContext.Blick's .cust-email-modal,
   CCP.QuoteContext.Blick's .qw-modal, CCP.WorkManagementContext.Blick's .wo-modal, CCP.FinanceContext.Blick's
   .fin-modal and CCP.InventoryContext.Blick's .inv-modal — have converged onto this family; every local
   backdrop-and-dialog rule is deleted from those Blicks' own stylesheets.

   The baseline carries the fix built for CCP.FinanceContext.Blick's invoice-detail dialog (2026-08-25): three
   of the five families declared no `max-height` and no `overflow-y` at all, so content taller than the window
   was cut off with no scrollbar and the actions row unreachable — the exact clipping defect Finance hit and
   fixed. 🔴 max-height: 100%, NOT a viewport unit, is the whole fix: the backdrop is `position: fixed; inset:
   0`, so its BORDER box is the viewport but its CONTENT box is the viewport minus its own padding. A
   viewport-unit max-height measures against the full viewport instead, so it never subtracts the backdrop's
   own top offset — on a short window the height budget exceeds the space actually left, the internal
   scrollbar never triggers, and everything past the bottom edge — including the way to close the dialog — is
   simply cut off. As a percentage of the backdrop's content box, 100% IS the space actually available,
   whatever --app-dialog-top-offset resolves to. */
.app-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    align-items: flex-start;
    padding-top: var(--app-dialog-top-offset);
    /* The matching bottom edge: a dialog tall enough to use its whole height budget would otherwise sit
       flush against the bottom of the window. Padding on the backdrop, not a margin on the dialog, so it is
       part of the same content box .app-modal's max-height: 100% measures against. */
    padding-bottom: var(--brand-space-6);
    justify-content: center;
    z-index: 1000;
}

.app-modal {
    background: var(--brand-surface);
    border-radius: var(--brand-radius-xl);
    box-shadow: var(--brand-shadow-modal);
    padding: var(--brand-space-6);
    padding-bottom: 0;
    max-width: 480px;
    width: 100%;
    max-height: 100%;
    overflow-y: auto;
}

/* Will's visual inspection: every converged dialog carried app.css's `.content h2` rule (2.25rem top margin
   plus a 2px top border) into its own title — a stray half-width rule and ~36px of dead space above it. That
   rule is meant for a section heading on a page, not a dialog title sitting at the top of its own box.
   Finance's invoice-detail dialog already found and fixed this exact defect for its own `.fin-modal__header
   h2` (title beside a dismiss button, not a direct child of `.app-modal`) before the hoist; this is the same
   fix generalized to the family, same selector shape as the existing `.card h2:first-child` precedent
   (app.css) for the same reason: an h2 that opens its own container, not one continuing a page. */
.app-modal h2:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

/* A dialog's own title-plus-dismiss-button header row (the invoice-detail dialog, and any future dialog with
   the same shape) — the other converged dialogs put their dismiss control in the actions row instead, but this
   layout is still shared family chrome, not a Blick-owned `*-modal` class (D-0183 §3 bans those outright). */
.app-modal__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--brand-space-3);
}

/* Same fix as `.app-modal h2:first-child` above, scoped to a header row's own title so it wins over
   app.css's `.content h2` top-rule/margin without needing `:first-child` (the header row's title sits beside
   the dismiss button, not as the row's only child). */
.app-modal__header h2 {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

.app-modal__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: var(--brand-radius-md);
    background: var(--brand-primary-lighter);
    color: var(--brand-primary);
    font-size: var(--brand-font-size-lg);
    vertical-align: middle;
    margin-right: var(--brand-space-3);
}

/* The flow rules below are Finance's own, carried over unchanged from the invoice-detail dialog that this
   modifier was proven against: a dialog with several stacked sections (a header, a detail grid, a line-item
   table, a totals strip, a payment history, a notes timeline) sits flush block-against-block without them —
   the section headings inside carry margin: 0 for the same reason. A --wide dialog with only one content
   block (the Customer email compose dialog) is unaffected: flex-direction: column with one child lays out
   exactly as block flow would. */
.app-modal--wide {
    max-width: 860px;
    display: flex;
    flex-direction: column;
    gap: var(--brand-space-5);
}

.app-modal__field {
    margin-bottom: var(--brand-space-3);
}

.app-modal__field label {
    display: block;
    font-weight: var(--brand-font-weight-medium);
    margin-bottom: var(--brand-space-1);
}

/* The way out must not scroll away. Once the height budget above is honest the dialog scrolls its own
   content, and on a short window the actions row — the only always-visible way out a mouse user has — would
   scroll off the bottom with it. Pinned to the bottom of the scrollport, with its own surface and a rule so
   content passing underneath does not read through it. */
.app-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--brand-space-2);
    position: sticky;
    bottom: 0;
    background: var(--brand-surface);
    border-top: 1px solid var(--brand-border);
    padding-top: var(--brand-space-3);
    padding-bottom: var(--brand-space-6);
}
/* A dialog whose actions row ever carries a long inline banner beside its buttons (e.g. IssueInvoiceConfirmDialog's
   "customer/job details could not be loaded" refusal) must not be able to crush the buttons down to a wrapped,
   sub-usable target -- flex's default shrink algorithm lets a <button> shrink below its own label's width before
   a sibling <div> gives up any space. Buttons get a floor; the banner shrinks/wraps instead (Will, 2026-09-02:
   Go Back measured at ~58px, wrapped onto two lines). */
.app-modal__actions button {
    flex-shrink: 0;
}

/* Will's measurement: on both email dialogs (Customer's and Quote's, D-0183's --wide family) the PRIMARY
   action — Send — belongs to the platform B3.Communication.UI.Components.NewEmail package and sits outside
   .app-modal__actions above, which carries only our own Close button. NewEmail exposes no slot or public
   method for its Send button (checked its source: SubmitNewEmail is private, no BelowForm render fragment
   exists next to AboveForm) — it cannot be relocated into .app-modal__actions without forking the platform
   component, which is out of bounds. What CAN be done from our side of that boundary: NewEmail's own
   stylesheet names its Send button `compose-email-form__btn-submit` (a stable, intentionally-exposed class —
   see GetCssClass in NewEmail.razor.cs), and Blazor CSS isolation scopes a component's OWN stylesheet, not
   external ones, so a plain, unscoped selector here still reaches it. Pinning it the same way our own actions
   row pins Close keeps the primary action reachable without a scroll, without touching NewEmail's markup. */
.app-modal .compose-email-form__btn-submit {
    position: sticky;
    bottom: var(--brand-space-6);
}

/* ── Add/Edit dialog chrome — spec §5: visually indistinguishable from .app-modal (line ~432).
   `.app-add-edit-dialog` already wraps both Add and Edit dialogs (line ~388, top-offset rule) —
   reused as the scoping selector rather than guessing the mode-specific `e-edit-dialog` /
   `e-add-dialog` class Syncfusion assigns. Where Syncfusion's structure forces two selectors
   instead of `.app-modal`'s one div, both carry the same declarations (spec §5 note). ── */
.app-add-edit-dialog.e-dialog {
    border-radius: var(--brand-radius-xl) !important;
    box-shadow: var(--brand-shadow-modal) !important;
}
.app-add-edit-dialog .e-dlg-header-content {
    background: var(--brand-surface);
    padding: var(--brand-space-6) var(--brand-space-6) 0;
    border-bottom: none;
}
.app-add-edit-dialog .e-dlg-header-content .e-dlg-custom-header {
    font-size: var(--brand-font-size-lg);
    font-weight: var(--brand-font-weight-semibold);
    color: var(--brand-text);
}
/* §3b: the header's own Save is Syncfusion's adaptive-dialog apply button — a second live
   control for the same action as the footer Save. The footer stays (house .app-modal__actions
   convention); this one goes. Both drive the same save pipeline, so hiding it loses nothing. */
.app-add-edit-dialog .e-dlg-header-content .e-res-apply-btn {
    display: none;
}
/* Bottom padding moved onto the sticky footer itself (below) rather than living here — see that rule's
   comment (Will's/Carrie's FAIL, 2026-09-05) for why padding on the SCROLL CONTAINER can never be covered
   by a `position: sticky` child's opaque fill.
   ⚠ Second FAIL found on re-inspection (2026-09-05): this rule's specificity (two classes) loses the cascade
   to Syncfusion's own `.e-bigger .e-responsive-dialog .e-dlg-content { padding: 0px 16px 16px; }` in
   bootstrap5.css (three classes) — every dialog here carries both `e-bigger` and `e-responsive-dialog`, so
   Syncfusion's 16px bottom padding was winning outright and this rule's `padding-bottom: 0` never actually
   applied. `!important` on padding alone is the fix, matching the pattern already used against Syncfusion
   defaults elsewhere in this block (`.app-add-edit-dialog { top: ...; max-height: ... !important; }` above). */
.app-add-edit-dialog .e-dlg-content {
    padding: 0 var(--brand-space-6) 0 !important;
    background: var(--brand-surface);
}

/* ── Dialog form vertical rhythm — spec §5, the biggest gap. Today every field sits inside
   EditOne's bordered .fieldset with heavy padding and .mb-3 rows; this is the fix. ── */
/* §3a: the empty square in the top-left of the Edit dialog is EditOne's fieldset legend
   (`<div class="fieldset"><h1><span></span></h1>`) rendered with no title — not a checkbox,
   not a field (probe: zero checkboxes in the dialog). Hidden outright: no CCP screen supplies
   legend text, and EditOne's own injected <style> block cannot be reached to un-border it any
   other way (root cause is platform-side, B3 backlog item, spec §8 item 1). */
.b3-addone-editone .fieldset {
    border: none;
    margin-top: 0;
    padding: 0;
}
.b3-addone-editone .fieldset h1 {
    display: none;
}
.b3-addone-editone .editone-row {
    margin-bottom: var(--brand-space-3);
}
.b3-addone-editone label.font-weight-bold,
.b3-addone-editone .form-label {
    font-weight: var(--brand-font-weight-medium);
    font-size: var(--brand-font-size-sm);
    color: var(--brand-text);
    margin-bottom: var(--brand-space-1);
}

/* T6, Sprint 8.5 (DESIGN-SYSTEM.md §4.17, Duane 2026-09-03): AddressBlock.razor draws its own header as
   <label id="label_AddressBlock-{ComponentID}" class="font-weight-bold">, which the rule above would
   otherwise flatten to an ordinary field label (medium/sm) — exactly the same rule scope that already flattens
   the Add dialog's own "Company Address" header today. This rule keys on the id prefix, not the class, so it
   wins the specificity tie and the three headers (CustomerEditor.razor and CustomerDirectory.razor's Add
   dialog) read as section headers again. */
.b3-addone-editone label[id^="label_AddressBlock-"] {
    font-size: var(--brand-font-size-base);
    font-weight: var(--brand-font-weight-semibold);
    color: var(--brand-text);
    display: block;
    margin-bottom: var(--brand-space-2);
}

/* ── Dialog controls — spec §5 table. §3c: the footer Save/Cancel are Bootstrap's own
   `.btn-primary`/`.btn-light`, never Syncfusion's `.e-btn.e-primary` — the existing Syncfusion
   mapping (line ~823) simply never matches them; this is a different component family, not a
   lost cascade (spec §3c, closes conformance-sweep UNVERIFIED #2). Mapped onto the same tokens
   `.e-btn.e-primary` already uses. ── */
.b3-addone-editone .form-control {
    border: 1px solid var(--brand-border);
    border-radius: var(--brand-radius-md);
    background: var(--brand-surface);
    color: var(--brand-text);
}
.b3-addone-editone .form-control::placeholder {
    color: var(--brand-text-subtle);
}
.b3-addone-editone .form-control:focus {
    border-color: var(--brand-primary) !important;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand-primary) 25%, transparent) !important;
}
.b3-addone-editone .form-control:disabled {
    background: var(--brand-neutral-bg);
}
.b3-addone-editone .form-control.border-danger {
    border-color: var(--brand-danger) !important;
}
.b3-addone-editone .invalid-feedback,
.b3-addone-editone .text-danger {
    color: var(--brand-danger);
    font-size: var(--brand-font-size-xs);
}

/* Dropdowns (Category, Type — B3.UI.Components.DefinedListDropDownSF, Syncfusion .e-ddl) — same
   box as text inputs; chevron muted. Also covers the customer portal's custom estimate form
   (.rfq-custom-estimate, CustomerRequestForQuoteForm). */
.b3-addone-editone .e-ddl .e-input-group,
.b3-addone-editone .e-ddl.e-input-group,
.rfq-custom-estimate .e-ddl .e-input-group,
.rfq-custom-estimate .e-ddl.e-input-group {
    border: 1px solid var(--brand-border) !important;
    border-radius: var(--brand-radius-md) !important;
    background: var(--brand-surface);
}
.b3-addone-editone .e-ddl .e-input-group-icon,
.rfq-custom-estimate .e-ddl .e-input-group-icon {
    color: var(--brand-text-muted) !important;
}

/* Multi-value chips (DefinedListMultiSelectSF, Syncfusion .e-multi-select-wrapper) — pill fill
   per Badges.png shape, replacing the dark grey-navy default. */
.b3-addone-editone .e-multi-select-wrapper {
    border: 1px solid var(--brand-border) !important;
    border-radius: var(--brand-radius-md) !important;
}
.b3-addone-editone .e-chips-collection .e-chips {
    background: var(--brand-primary) !important;
    color: var(--brand-text-on-brand) !important;
    border-radius: var(--brand-radius-pill) !important;
    padding: var(--brand-space-1) var(--brand-space-2) !important;
}
.b3-addone-editone .e-chips-collection .e-chips-close {
    color: inherit;
}
.b3-addone-editone .e-chips-collection .e-chips-close:hover {
    color: var(--brand-text-on-brand) !important;
    background: var(--brand-primary-active) !important;
    border-radius: 50%;
}

/* Prefixed input ($ Base Cost/unit — CurrencyField.cs's own Bootstrap .input-group). */
.b3-addone-editone .input-group .input-group-text {
    background: var(--brand-neutral-bg);
    color: var(--brand-neutral-text);
    border: 1px solid var(--brand-border);
    border-right: none;
}
.b3-addone-editone .input-group .form-control {
    border-left: none;
}

/* Footer buttons (EditOne's own `div.mt-4.d-flex.justify-content-between`, not Syncfusion's
   `.e-footer-content` — the probe found no buttons under that selector). Cancel = house
   secondary; Save = house primary, same mapping as `.e-btn.e-primary` (line ~823-836). */
.b3-addone-editone .btn-light {
    background: var(--brand-surface) !important;
    border: 1px solid var(--brand-border) !important;
    color: var(--brand-text) !important;
}
.b3-addone-editone .btn-primary {
    background: var(--brand-primary-hover) !important;
    border-color: var(--brand-primary-hover) !important;
    color: var(--brand-surface) !important;
}
.b3-addone-editone .btn-primary:hover,
.b3-addone-editone .btn-primary:focus,
.b3-addone-editone .btn-primary:active {
    background: var(--brand-primary-active) !important;
    border-color: var(--brand-primary-active) !important;
}

/* Validation banner ("Sorry, we couldn't save…", EditOne/AddOne's own `div.error.text-danger`
   wrapping `ErrorMessage`) — theme.css alert family, not bare red text. */
.b3-addone-editone .error.text-danger {
    background: var(--brand-danger-bg);
    color: var(--brand-danger-text);
    border-radius: var(--brand-radius-md);
    padding: var(--brand-space-3);
    margin-bottom: var(--brand-space-3) !important;
}

