/*
 * Modal shell — CR-A116-30 (PRJ-A116 INV-10 Law 1: "data entry is always a
 * modal"). Values are verbatim from the frozen card
 * (kpm/products/ynetplus/cr/attachments/modal-shell.html) and its own
 * BUILD NOTES block. The card's own demo markup uses shorthand class names
 * for its mockup page only (`.modal`, `.m-head`, `.m-step-dot`, ...) — the
 * names below are the CR's own Scope section, the production contract.
 *
 * Ships with NO consumer in this diff (CR-A116-30's own "one component, no
 * consumers yet"). CR-A116-35 (New Order) and CR-A116-36 (Add job site) are
 * the first callers — this file is loaded (this stylesheet link only) but
 * nothing in the app invokes assets/js/shared/modal.js yet.
 *
 * Sizes are a CLOSED SET — s/420 · m/560 · l/720. A surface picks one; it
 * never sets its own width. If a surface does not fit `l`, that is a signal
 * the surface is a page, not an entry modal — escalate rather than adding a
 * size (BUILD NOTES).
 *
 * 2026-08-17 (Drew, live at localhost:3004 — direct design directive):
 * `l` widened 720 -> 900px and the height budget tightened from
 * `min(86vh,760px)` to `min(80vh,680px)` specifically to stop the New Order
 * wizard's Step 1/2/3 content from needing to scroll the modal body at
 * common desktop viewports. `s`/`m` are UNCHANGED — only `l` moved, and only
 * for this reason. A tablet breakpoint (`max-width:900px`) was added between
 * the existing desktop and phone (480px) rules so the jump isn't as abrupt.
 * The frozen card (modal-shell.html) still states the old 720px value — NOT
 * updated by this fix (that is a Claude-Design-side edit, Drew's own, out of
 * scope here); flagged, not silently drifted past.
 *
 * [[BF-515]] 2026-08-18 (Drew, verbatim: *«میشه اندازه مودال تغییر نکنه به
 * اکشن‌های یوزر؟ مثل سرچ میکنه جاب سایت رو کوچیک یا بزرگ نشه مودال … ولی سایز
 * مودال نباید تغییر کند.»* — "can the modal's size stop changing in response
 * to user actions? e.g. searching a job site shouldn't shrink or grow it").
 * `l` gains a fixed `height` equal to the `max-height` it already had, so the
 * wizard frame is a constant rather than something that shrink-wraps whatever
 * the current step happens to be rendering. `s`/`m` are UNCHANGED and
 * deliberately so — Drew scoped the ruling to this wizard (*«فعلا این
 * ویزارد»*), and those two are single-question/short-form surfaces where
 * forcing a tall frame around two lines of content would look worse, not
 * better. See the `.modal-shell.l` rule for the mechanics.
 *
 * Tokens (--dur-1, --ease, --navy, --blue, --line, --field, --bg, --muted,
 * --mono, --r-lg, --r-md, --blue-soft) all come from tokens.css, loaded
 * before this file in index.html.
 */

.modal-scrim {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(15, 23, 42, .55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease);
}
.modal-scrim.modal-open { opacity: 1; }

.modal-shell {
    display: flex;
    flex-direction: column;
    background: var(--bg);
    border-radius: var(--r-lg);
    box-shadow: 0 25px 50px -12px rgba(15, 23, 42, .35);
    max-height: min(86vh, 760px);
    overflow: hidden;
    transform: scale(.96);
    opacity: 0;
    transition: transform var(--dur-1) var(--ease), opacity var(--dur-1) var(--ease);
}
.modal-shell.modal-open {
    transform: scale(1);
    opacity: 1;
}

/* Sizes — a closed set. A surface picks one; it never sets its own width. */
.modal-shell.s { width: 420px; }
.modal-shell.m { width: 560px; }
/* `l` widened + shortened 2026-08-17 (Drew directive, see header) — a
   dedicated max-height budget only for `l`, since only its content (the New
   Order wizard) had the overflow problem.

   [[BF-515]] 2026-08-18 — `height` added, deliberately IDENTICAL to the
   max-height above it. The shell only ever had a max-height, so with
   `flex-direction: column` it shrink-wrapped its content and grew until it
   hit the cap: every content change moved the frame. Searching a job site
   resized the modal on each keystroke, and Step 2's own search-first change
   (nothing rendered until the user types) made it worst of all — the modal
   collapsed on arrival and jumped on the first character.

   Setting `height` to the value the frame was already allowed to reach means
   the wizard opens at its previous MAXIMUM size and stays there: no step, no
   result count, and no empty state can move it. `min(80vh, 680px)` is not a
   new number — it is Drew's own 2026-08-17 budget, tuned live against this
   wizard's content, promoted from a ceiling to a constant. Keeping both
   declarations (rather than `height` alone) leaves the cap explicit for any
   future rule that raises the height.

   `.modal-body` is already the only scrolling element (`overflow-y: auto;
   flex: 1; min-height: 0`), so the surplus lands there: short steps get slack
   inside a stable frame, tall ones scroll inside it. Head and foot are
   `flex-shrink: 0`, so neither is squeezed by a step that overflows.

   The phone block at the bottom of this file re-declares `.modal-shell.l`
   with `height: 100vh` — same specificity, later in the file, so the
   full-screen sheet still wins under 480px. Verified, not assumed. */
.modal-shell.l {
    width: 900px;
    height: min(80vh, 680px);
    max-height: min(80vh, 680px);
}

/* Tablet-ish reflow — narrower than `l`'s 900px but well above the 480px
   phone breakpoint below, so the shell doesn't jump straight from
   comfortably-wide to full-screen-phone. Only `l` needs this: `s`/`m` (420/
   560) already fit inside this range untouched. */
@media (max-width: 900px) {
    .modal-shell.l {
        width: min(92vw, 720px);
    }
}

@media (prefers-reduced-motion: reduce) {
    /* The 120ms scale-in is dropped entirely — opacity only. */
    .modal-shell {
        transform: none;
        transition: opacity var(--dur-1) ease;
    }
    .modal-shell.modal-open {
        transform: none;
        opacity: 1;
    }
}

/* ============================= head ==================================== */

.modal-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 18px;
    border-bottom: 1px solid var(--line);
    flex-shrink: 0;
}

.modal-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--navy);
    margin: 0;
    flex: 1;
    line-height: 1.3;
}
.modal-title:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: 4px;
    border-radius: 2px;
}

.modal-steps {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
.modal-steps[hidden] { display: none; }

.modal-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--line);
}
.modal-dot.done { background: var(--navy); }
.modal-dot.now {
    background: var(--blue);
    box-shadow: 0 0 0 3px var(--blue-soft);
}

.modal-step-label {
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--muted);
    margin-left: 4px;
    white-space: nowrap;
}

/* 32px visual box, 44px touch target via negative margin — same technique
   as payments.css .panel-close (INV-9a: 44px stays, consistent everywhere;
   the card's own 32px is the VISUAL size, not the tappable area). */
.modal-close {
    width: 32px;
    height: 32px;
    min-width: 44px;
    min-height: 44px;
    margin: -6px;
    border-radius: var(--r-md);
    border: 1px solid var(--line);
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--muted);
    cursor: pointer;
    padding: 0;
    font: inherit;
    appearance: none;
    -webkit-appearance: none;
    transition: transform 160ms ease-out, background 120ms ease;
}
.modal-close svg {
    width: 15px;
    height: 15px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
}
@media (hover: hover) {
    .modal-close:hover { background: var(--field); }
}
.modal-close:active { transform: scale(.96); }
.modal-close:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: 2px;
}

/* ============================= body ===================================== */

/* The ONLY scrolling element in the shell. */
.modal-body {
    padding: 18px;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}

/* 1px top border that appears only once scrolled (JS toggles this class on
   the body's own scroll event — scrollTop > 0). */
.modal-body.scrolled {
    border-top: 1px solid var(--line);
    box-shadow: inset 0 6px 6px -6px rgba(15, 23, 42, .12);
}

/* ============================= foot ===================================== */

.modal-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    padding: 14px 18px;
    border-top: 1px solid var(--line);
    background: var(--field);
    flex-shrink: 0;
}
/* Utility for a "Back ... spacer ... Next" split footer (card §3) — a
   consumer drops this between a leading and trailing action. */
.modal-foot-spacer { flex: 1; }

/* ============================= phone ===================================== */

/* Under 480px the shell becomes the screen: full width and height, radius
   0, footer buttons stretch. */
@media (max-width: 480px) {
    .modal-scrim { padding: 0; }

    .modal-shell,
    .modal-shell.s,
    .modal-shell.m,
    .modal-shell.l {
        width: 100vw;
        max-width: 100vw;
        height: 100vh;
        max-height: 100vh;
        border-radius: 0;
    }

    .modal-foot > button,
    .modal-foot > .btn {
        flex: 1;
        justify-content: center;
    }
}
