/* ============================================================================
   MODAL v3 — canonical modal, supersedes app.css/polish.css/v2/modal.css rules
   ----------------------------------------------------------------------------
   Load AFTER legacy-shim.css. Uses tokens.css + v2/system_tokens.css.
   No !important. Specificity max 0,2,0 (only :hover/:focus-visible + .open).
   No descendant selectors — only single-class rules and pseudo states.
   ============================================================================ */


/* ══════════════════════════════════════════════════════════════════════════
   OVERLAY (scrim) — fixed full-viewport, centers child .modal
   ══════════════════════════════════════════════════════════════════════════ */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition:
    opacity var(--d-base) var(--ease-out),
    visibility 0s linear var(--d-base);
  visibility: hidden;
}

.modal-overlay.open {
  display: flex;
  opacity: 1;
  visibility: visible;
  transition:
    opacity var(--d-base) var(--ease-out),
    visibility 0s linear 0s;
  animation: modalOverlayFadeIn var(--d-base) var(--ease-out) both;
}

@keyframes modalOverlayFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}


/* ══════════════════════════════════════════════════════════════════════════
   MODAL — surface dialog
   ══════════════════════════════════════════════════════════════════════════ */

.modal {
  position: relative;
  z-index: var(--z-modal);

  /* Surface */
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-modal);

  /* Sizing */
  width: 100%;
  max-width: 560px;
  max-height: 90vh;

  /* Layout */
  display: flex;
  flex-direction: column;
  overflow: hidden;

  /* Motion */
  animation: modalSlideUp var(--d-slow) var(--ease-out) both;
}

@keyframes modalSlideUp {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* Size variants */
.modal-sm { max-width: 400px; }
.modal-wide { max-width: 800px; }
.modal-xl { max-width: 1100px; }
.modal-full {
  max-width: calc(100vw - var(--space-7));
  max-height: calc(100vh - var(--space-7));
}


/* ══════════════════════════════════════════════════════════════════════════
   HEADER — title row + close button
   ══════════════════════════════════════════════════════════════════════════ */

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  /* Padding compressed bottom-to-zero so the body's own top padding
   * controls the title→form gap. The previous symmetric 24px+24px
   * created ~48px of dead whitespace below the title (visible in user
   * issue images #28 vs #29 — modal-header version had a huge gap;
   * the legacy direct-title variant looked tight & correct). */
  padding: var(--space-5) var(--space-5) 0;
  /* border-bottom removed — created an unwanted divider line between
   * the title and the form/body across all modals (research, release,
   * feature flag, experiment, NPS, new doc, kickoff detail). */
  flex-shrink: 0;
}

/* Wave 17 — Agent 5 cleanup:
 * Deleted the specificity-hack rules that previously fought
 * legacy_replacement.css `.modal > .modal-header` border + padding.
 * Now that legacy_replacement.css has had its `border-bottom` removed
 * (see legacy_replacement.css line ~178), and the base `.modal-header`
 * rule above carries zero bottom-padding, the cascade is self-consistent
 * and no compensating selectors are needed. */

.modal-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.015em;
  color: var(--text);
  flex: 1 1 auto;
  min-width: 0;
}

.modal-subtitle {
  margin: 4px 0 0;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 400;
  color: var(--muted);
  line-height: 1.4;
}


/* ══════════════════════════════════════════════════════════════════════════
   CLOSE BUTTON — icon button variant, sits in header
   ══════════════════════════════════════════════════════════════════════════ */

/* Positioned absolutely at top-right of the .modal container so partials
   don't need a .modal-header wrapper — one <button.modal-close> as first
   child of .modal is enough. Consumers hit Esc, click the scrim, OR click
   this X (Day 10 restore — accessibility parity with the Esc-close hint). */
.modal-close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
  transition: var(--tr-colors);
}

.modal-close:hover {
  background: var(--bg-hover);
  color: var(--text);
}

.modal-close:focus-visible {
  box-shadow: 0 0 0 var(--ring-offset) var(--surface),
              0 0 0 calc(var(--ring-offset) + var(--ring-width)) var(--ring-color);
}

/* Ensure the modal-title doesn't collide with the absolutely-positioned close
   button (32px + 12px offset = 44px reserved; use space-7 = 48px). */
.modal .modal-title { padding-right: var(--space-7); }

/* Verify the .modal is a positioning context — it already carries a
   `position: relative` via .modal itself (see line ~55 base rule). */


/* ══════════════════════════════════════════════════════════════════════════
   BODY — scrollable content area
   ══════════════════════════════════════════════════════════════════════════ */

.modal-body {
  padding: var(--space-5) var(--space-5);
  overflow-y: auto;
  flex: 1 1 auto;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
}

.modal-body-compact {
  padding: var(--space-4);
}

.modal-body-flush {
  padding: 0;
}


/* ══════════════════════════════════════════════════════════════════════════
   FOOTER — action row, right-aligned by default
   ══════════════════════════════════════════════════════════════════════════ */

.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
}

.modal-footer-start {
  justify-content: flex-start;
}

.modal-footer-between {
  justify-content: space-between;
}


/* ══════════════════════════════════════════════════════════════════════════
   ESC HINT — a11y affordance that replaces the removed X-close button.
   Renders as a muted "Esc to close" string anchored to the left edge of
   the action row, so keyboard + screen-reader users still get a visible
   dismissal cue. Suppressed below 600px where space is tight.
   Wave 17 — Agent 5.
   ══════════════════════════════════════════════════════════════════════════ */

.modal-actions {
  position: relative;
}

.modal-actions::before {
  content: "Esc to close";
  position: absolute;
  left: var(--space-5);
  font-size: 11px;
  color: var(--muted);
  letter-spacing: .04em;
  pointer-events: none;
}

@media (max-width: 600px) {
  .modal-actions::before { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   MOBILE — full-screen below 768px
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  .modal-overlay {
    padding: 0;
    align-items: stretch;
    justify-content: stretch;
  }

  .modal {
    max-width: 100%;
    max-height: 100dvh;
    border-radius: 0;
    border: none;
    height: 100dvh;
  }

  .modal-sm,
  .modal-wide,
  .modal-xl,
  .modal-full {
    max-width: 100%;
    max-height: 100dvh;
    border-radius: 0;
    border: none;
  }

  .modal-header {
    padding: var(--space-4);
  }

  .modal-body {
    padding: var(--space-4);
  }

  .modal-footer {
    padding: var(--space-3) var(--space-4);
  }
}


/* ══════════════════════════════════════════════════════════════════════════
   REDUCED MOTION — disable entrance animations
   ══════════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  .modal-overlay,
  .modal-overlay.open,
  .modal {
    animation: none;
    transition: none;
  }
}


/* ══════════════════════════════════════════════════════════════════════════
   WIRE-UP INSTRUCTIONS:
   Add to static/index.html inside <head>, AFTER legacy-shim.css so this
   file wins the cascade (override priority):

     <link rel="stylesheet" href="/css/v3/modal.css?v=1">

   Recommended placement order (top -> bottom in <head>):
     1. tokens.css
     2. v2/system_tokens.css
     3. (all other v2/*.css and app.css)
     4. v2/legacy-shim.css
     5. v3/components.css
     6. v3/modal.css              <-- here
     7. v3/table.css

   USAGE:
     <div class="modal-overlay open" role="dialog" aria-modal="true">
       <div class="modal modal-wide">
         <div class="modal-header">
           <h2 class="modal-title">Title</h2>
           <button class="modal-close" aria-label="Close">×</button>
         </div>
         <div class="modal-body">…</div>
         <div class="modal-footer">
           <button class="btn btn-ghost">Cancel</button>
           <button class="btn btn-primary">Save</button>
         </div>
       </div>
     </div>
   ══════════════════════════════════════════════════════════════════════════ */


/* ============================================================================
   LEGACY nx-{bl,rl,pr}-* MODAL CLASSES — consolidated (Day 10 lanjutan).
   Sources: page_baselines.js (200 callsites), page_resource_leveling.js
   (76), page_procurement.js (140). HTML/JS still ship the prefixed class
   names; here they share a single ruleset via grouped selectors. Only
   `max-width` differs per-suite (bl=960, rl=720, pr=760) and body density
   for rl (13 px, 60 vh) — everything else was byte-identical duplication.
   Renaming callsites is a follow-up sprint; this ships the visual
   consolidation without touching JS.
   ============================================================================ */

.nx-bl-modal-overlay,
.nx-rl-modal-overlay,
.nx-pr-modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--bg-overlay, rgba(0, 0, 0, 0.45));
  z-index: 9999;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 60px 16px;
  overflow: auto;
}

.nx-bl-modal,
.nx-rl-modal,
.nx-pr-modal {
  background: var(--surface, var(--card-bg, var(--bg, #fff)));
  width: 100%;
  border-radius: 12px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  border: 1px solid var(--border, #e5e7eb);
}
.nx-bl-modal { max-width: 960px; }
.nx-rl-modal { max-width: 720px; }
.nx-pr-modal { max-width: 760px; }

.nx-bl-modal-hd,
.nx-rl-modal-hd,
.nx-pr-modal-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border, #e5e7eb);
}

.nx-bl-modal-hd h3,
.nx-rl-modal-hd h3,
.nx-pr-modal-hd h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
}

.nx-bl-modal-body,
.nx-rl-modal-body,
.nx-pr-modal-body {
  padding: 16px 18px;
  max-height: 70vh;
  overflow: auto;
}
/* Resource-leveling modal has denser content (billing table). */
.nx-rl-modal-body { max-height: 60vh; font-size: 13px; }

.nx-bl-modal-ft,
.nx-rl-modal-ft,
.nx-pr-modal-ft {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 10px 16px;
  border-top: 1px solid var(--border, #e5e7eb);
}

.nx-bl-close,
.nx-rl-close,
.nx-pr-close {
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 22px;
  line-height: 1;
  color: var(--muted, #667);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
