/* ============================================================
   Dialog — the modal card the site opens things in, and the sheet
   it becomes on a phone.

   Figma draws every one of these in a single "Handover 2 / Dialog"
   frame (35055:986), side by side, because they are one object: a
   400px white card, 24px radius, on a 25% black scrim, with a ✕ in
   its own row along the top. Only the body differs. This file owns
   everything they share — the scrim, the card, the ✕, the opening
   motion and the mobile sheet — so a change to any of it lands on
   all of them at once.

     <div class="dialog">                    ← the scrim
       <div class="dialog__card">
         <div class="dialog__head">
           <button class="dialog__close">✕</button>
         </div>
         <div class="dialog__body">  …the part that differs…  </div>
       </div>
     </div>

   Built and driven by scripts/dialog.js — no dialog is written into
   a page's source, since each exists once and only on demand.

   WHO USES IT
     · styles/passgate.css   — the four-digit password gate
     · styles/quote-dialog.css — the full text behind a quote card
   A new one supplies a body and nothing else.

   THE CARD DOES NOT FOLLOW THE DARK THEME. Its white/black are pinned
   rather than taken from --surface/--fg: the design is a white card,
   and a page running `data-theme="dark"` inverts the raw scale
   (styles/color.css), which would flip it to black. Everything that is
   a *shade* still comes from the scale, because those read the same
   either way.

   RADIUS 24px IS OFF-SYSTEM. The DLS has exactly one corner radius
   (--radius, 8px) plus fully-round. The Figma card is 24px, so it is
   set here as a single component constant rather than silently
   minting a new global token — flagged for a decision, not absorbed.
   ============================================================ */

.dialog {
  position: fixed;
  inset: 0;
  z-index: 100;                        /* above the sticky nav */

  display: grid;
  place-items: center;
  padding: var(--space-24);            /* never let the card touch the edge */

  background: rgb(0 0 0 / 0.25);       /* Figma scrim */
}

/* [hidden] must beat `display: grid`, or the dialog never hides. */
.dialog[hidden] { display: none; }

.dialog__card {
  --dialog-radius: 24px;               /* off-system, see the header note */

  width: 400px;
  max-width: 100%;
  box-sizing: border-box;
  border-radius: var(--dialog-radius);

  /* Pinned, not tokens — see the header note on the dark theme. */
  background: #ffffff;
  color: #000000;

  display: flex;
  flex-direction: column;
  align-items: center;
  /* 16 / 24 / 48. Figma draws the gate's card at 16px inline and the
     quote's at 24 — read as drift rather than intent: the two are the
     same card in the same frame, differing by 8px in the one place
     both of them show it (the ✕), with nothing in either body asking
     for the tighter measure. Unified on the wider value, which the
     quote's running text needs and the gate's centred content doesn't
     mind. */
  padding: var(--space-16) var(--space-24) var(--space-48);
}

/* --- Close ------------------------------------------------------
   Its own full-width row so the ✕ sits hard right on the card's
   padding, exactly as in Figma, without crowding the body below. */
.dialog__head {
  display: flex;
  justify-content: flex-end;
  width: 100%;
}

.dialog__close {
  display: grid;
  place-items: center;
  width: var(--space-24);
  height: var(--space-24);
  padding: 0;
  border: 0;
  background: none;
  color: #1f1f1f;                      /* Figma glyph */
  cursor: pointer;
}
.dialog__close svg { display: block; width: 100%; height: 100%; fill: currentColor; }
.dialog__close:focus-visible { outline: 2px solid #000; outline-offset: 2px; border-radius: 2px; }

/* --- Body -------------------------------------------------------
   One 16px rhythm down the middle. A dialog that wants something else
   overrides this from its own file. */
.dialog__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-16);
}

/* --- Opening -----------------------------------------------------
   Every dialog animates in, on every screen size. Following the
   Airbnb login modal: the scrim fades, and the card comes up a few
   pixels while growing the last 4% into place — enough to read as
   "this opened from the page" without a bounce or a wait. The same
   on the way out, so a mis-click doesn't cost half a second.

   Two curves on purpose, and both are DLS motion tokens (see the note
   in styles/motion.css): travel runs on --motion-ease, opacity on a
   plain `ease`, because the eye reads a fade as linear and an eased
   one reads as a stall.

   scripts/dialog.js adds `.dialog--open` one frame after unhiding,
   and waits --motion-base before hiding again, so the card is never
   yanked off screen mid-travel.
   ---------------------------------------------------------------- */
.dialog {
  opacity: 0;
  transition: opacity var(--motion-fast) ease;
}
/* SCOPED to a real dialog on purpose. The closed state is what a card
   looks like before it opens — a bare `.dialog__card` dropped into a
   page as a static reference (components.html does exactly that) is
   not closed, it's just a card, and must not start invisible. Every
   rule below that describes a STATE is scoped the same way; the plain
   `.dialog__card` rules above describe the object itself. */
.dialog .dialog__card {
  opacity: 0;
  transform: translateY(8px) scale(0.96);
  transform-origin: center;
  transition: opacity var(--motion-fast) ease,
              transform var(--motion-base) var(--motion-ease);
}
.dialog--open { opacity: 1; }
.dialog--open .dialog__card { opacity: 1; transform: none; }

/* NEVER TALLER THAN THE SCREEN. A body can be long — a full LinkedIn
   recommendation runs to three paragraphs — and on a short window that
   would run off the bottom of the card with no way to reach it, since
   the page behind is scroll-locked while the dialog is open. Cap it at
   the viewport less the scrim's own 24px at each end, and let the body
   scroll inside. `min-height: 0` is what lets a flex child shrink below
   its content and actually take the overflow. */
.dialog .dialog__card { max-height: calc(100dvh - var(--space-48)); }
.dialog .dialog__body { min-height: 0; overflow-y: auto; }

@media (prefers-reduced-motion: reduce) {
  /* No travel and no scale — the dialog is simply there. The scrim
     still fades, which is a change in brightness rather than motion. */
  .dialog .dialog__card { transform: none; transition: opacity var(--motion-fast) ease; }
}

/* --- Mobile: the card becomes a sheet ----------------------------
   Under 640px a 400px card leaves two thin slivers of scrim at the
   sides and floats in the middle of the screen. Same card, different
   frame: it spans the width, sits on the bottom edge, keeps its 24px
   radius on the top corners only, and slides up from below. The strip
   of dimmed page left showing above it is what makes it read as a
   sheet over the site rather than a new page.

   IT IS AS TALL AS ITS CONTENT by default, capped so it can never
   cover that strip, with the body scrolling if it reaches the cap.
   A dialog with a text input wants `.dialog--keyboard` instead — see
   the note on that modifier.

   IT SLIDES, IT DOES NOT SCALE. A full-bleed sheet that scales shows
   the page through the gap down its edges.
   ---------------------------------------------------------------- */
@media (max-width: 640px) {
  .dialog {
    box-sizing: border-box;            /* the inset comes out of the height */
    height: 100dvh;                    /* the visible screen, toolbar excluded */
    place-items: end stretch;          /* bottom edge, full width */
    padding: 0;
  }

  .dialog .dialog__card {
    width: 100%;                       /* over the base rule's 400px */
    /* Never taller than the screen less the strip of page above it. */
    max-height: calc(100dvh - var(--space-48) - env(safe-area-inset-top));
    /* Top corners only — the bottom two sit on the edge of the screen. */
    border-radius: var(--dialog-radius) var(--dialog-radius) 0 0;
    padding: var(--space-16) var(--space-24)
             calc(var(--space-24) + env(safe-area-inset-bottom));

    transform: translateY(100%);       /* parked below the edge until open */
    opacity: 1;                        /* it arrives by moving, not fading */
    transition: transform var(--motion-base) var(--motion-ease);
  }
  .dialog--open .dialog__card { transform: translateY(0); }

  /* Long content scrolls; the ✕ stays put. */
  .dialog .dialog__body { overflow-y: auto; }

  /* --- .dialog--keyboard: the sheet runs the full screen ---------
     For a dialog with a text input in it. A sheet that stops short of
     the bottom has to be lifted out from under the keyboard, and iOS
     gives no honest number to lift it by: position:fixed is pinned to
     the layout viewport, which on Safari runs on behind the bottom
     toolbar, so the toolbar reads as "covered by the keyboard" too and
     the sheet floats above the edge. Full height with the content up
     top has no bottom edge to defend — the keyboard covers empty white
     and the fields never move.

     HEIGHT IS 100dvh, NOT inset: 0. Same reason: `inset: 0` reaches
     under the toolbar, which leaves a strip of the page showing below
     the sheet. dvh tracks the height actually on screen as the toolbar
     comes and goes. */
  .dialog--keyboard {
    place-items: stretch;              /* the card takes what's left */
    /* The page stays visible above the sheet — iOS leaves the same
       sliver. env() is 0 unless the page opts into viewport-fit:cover;
       it costs nothing and keeps the gap honest if it ever does. */
    padding: calc(var(--space-48) + env(safe-area-inset-top)) 0 0;
  }
  .dialog--keyboard .dialog__card {
    max-height: none;
    justify-content: flex-start;       /* content at the top, not centred */
    padding-bottom: calc(var(--space-16) + env(safe-area-inset-bottom));
  }
  .dialog--keyboard .dialog__body {
    /* In the upper third: clear of the keyboard on the shortest phone,
       and low enough not to crowd the ✕. */
    margin-top: var(--space-64);
    overflow-y: visible;
  }
}

@media (max-width: 640px) and (prefers-reduced-motion: reduce) {
  /* No slide — the sheet is simply there. `.dialog--open` still sets
     the resting transform, it just arrives in one step. */
  .dialog .dialog__card { transition: none; }
}
