/* dialog.css — The passcode gate and the auth dialog.
   Cascade position 8 of 18. Order is load-bearing and is asserted by
   verify-fixtures.mjs; index.html links these explicitly (never @import) because Caddy's
   try_files turns a missing file into a 200 text/html SPA shell.
   Owner after Step 1: section (d).

   Both surfaces are native <dialog> elements opened with showModal(). A <dialog>
   without the `open` attribute is display:none by the UA stylesheet and the UA gives
   it `display: block` when it opens, so the only display rule here is the one that
   turns that block into the grid the layout needs. Nothing sets `display` on the
   closed state — that is item 91's family, and `[hidden] { display: none !important }`
   already lives in css/base.css.

   The element itself is the full-viewport positioning layer (transparent); the scrim
   is ::backdrop, and .gate__card is the card. */

/* ── The positioning layer ───────────────────────────────────────────────────── */
.gate {
  position: fixed; inset: 0;
  width: 100%; max-width: none; height: 100%; max-height: none;
  margin: 0; padding: var(--sn-space-5); border: 0;
  background: transparent; color: var(--sn-ink);
  overflow-y: auto; overscroll-behavior: contain;
}
.gate[open] { display: grid; justify-items: center; align-items: start; }
/* item 31: the scrim is translucent so the dashboard stays identifiable behind it — the
   card now only ever appears over work in progress, because the sign-in FRONT door moved
   to /app/login/ (section r) and this dialog re-authenticates an expired session in
   place. REFERENCE-STUDY §12 measured shadcn at 10% black + blur(4px)
   and GitHub Primer at 40% of a slate; DESIGN-SPEC §4.22's .78 is heavier than anything
   measured, so this starts from the measured end. */
.gate::backdrop {
  background: rgb(var(--sn-surface-0-rgb) / .3);
  backdrop-filter: blur(6px);
  animation: gate-fade var(--sn-dur-fast) var(--sn-ease-out);
}
.gate[open] .gate__card { animation: gate-rise var(--sn-dur-fast) var(--sn-ease-out); }
@keyframes gate-fade { from { opacity: 0 } }
@keyframes gate-rise { from { opacity: 0; transform: scale(.97) } }

/* ── The card ────────────────────────────────────────────────────────────────── */
/* Top-anchored, not vertically centred, so a mode switch grows the card downward
   instead of sliding every shared field up the screen (item 34). */
.gate__card {
  position: relative;
  align-self: start; margin-top: min(9vh, 84px);
  width: min(400px, calc(100vw - 32px)); padding: var(--sn-space-5);
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 24px); overflow-y: auto;
  border: 1px solid var(--sn-line-raised); border-radius: var(--sn-radius-md);
  background: var(--sn-surface-3);
  box-shadow: var(--sn-shadow-overlay);
  display: grid; gap: var(--sn-space-4);
  text-align: center;
}
.gate__mark {
  width: 48px; height: 48px; margin: 0 auto; display: block;
}
.gate__mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
.gate h1 { font-size: var(--sn-text-lg); line-height: var(--sn-lh-lg); }
/* item 34 again: the blurb is the only child above the fields whose height depends on the
   mode, so it is locked to two lines. Without the lock the sign-up blurb wraps one line
   longer than the sign-in blurb and pushes the Email field 18 px down on a mode switch,
   which is the same defect the top-anchored card was supposed to remove. */
.gate__blurb {
  font-size: var(--sn-text-xs); line-height: 1.5; color: var(--sn-ink-dim);
  min-height: calc(var(--sn-text-xs) * 1.5 * 2);
}
.gate__note { font-size: var(--sn-text-2xs); color: var(--sn-ink-mute); }
/* The fields, the reserved error slot and the submit share ONE wrapper with NO gap, so
   the 18 px slot is the whole resting distance between them (item 38). The card's own
   16 px gap sits outside this wrapper, never inside it. */
.gate__form { display: grid; text-align: left; }

/* ── Fields — DESIGN-SPEC §4.21, REFERENCE-STUDY §5.3 ────────────────────────── */
.gate__card .auth__field { display: grid; gap: var(--sn-space-2); text-align: left; }
.gate__card label {
  display: block; font-size: var(--sn-text-xs); font-weight: 600; color: var(--sn-ink);
}
.auth__wrap { position: relative; display: flex; }
/* item 47: this file kills no outline anywhere. Focus is the shared
   :focus-visible ring from css/base.css, which does not fire on a mouse click. */
.gate input {
  width: 100%; height: 40px; padding: 0 var(--sn-space-3);
  border: 1px solid var(--sn-line); border-radius: var(--sn-radius-sm);
  background: var(--sn-surface-2); color: var(--sn-ink);
  /* 16px, not --sn-text-md's 15px, and it is deliberate: below 16px iOS zooms the page
     on focus. REFERENCE-STUDY §5.1 measured GitHub and Vercel both accepting the same
     mismatch with the rest of their UI for exactly this reason. */
  font: inherit; font-size: 16px;
  transition: border-color var(--sn-dur-fast) var(--sn-ease-out),
              background-color var(--sn-dur-fast) var(--sn-ease-out);
}
.gate input::placeholder { color: var(--sn-ink-placeholder); }
/* MEASURED 2026-09-05: without the :not(), hover out-specifies the invalid rule below
   (0,3,1 against 0,2,0) and a field the user is being told is wrong loses its error
   border the moment the pointer rests on it — which is most of the time, because they
   just clicked it. Items 26, 27. */
.gate input:hover:not(:disabled):not([aria-invalid="true"]) { border-color: var(--sn-line-strong); }
.auth__wrap:has(.auth__reveal) input { padding-right: 44px; }
/* items 26, 27: an invalid field is in the error family at rest AND focused — never in
   the brand green, which is this app's success colour. The 3px ring is REFERENCE-STUDY
   §11.3's shipped shadcn recipe; the border change is DESIGN-SPEC §4.21's addition, and
   it is what makes the field identifiable without focus and without reading the message.
   --sn-bad measures 7.69:1 against the input fill. */
.gate [aria-invalid="true"] {
  border-color: var(--sn-bad); border-width: 1.5px;
  background: var(--sn-bad-tint);
  box-shadow: 0 0 0 3px rgb(var(--sn-bad-rgb) / .35);
}
.gate [aria-invalid="true"]:focus-visible { outline-color: var(--sn-bad); }

/* item 29: a reveal in every mode that has a password field, including reset. */
.auth__reveal {
  position: absolute; right: 4px; top: 4px;
  width: 32px; height: 32px; min-height: 32px; min-width: 32px; padding: 0;
}
.auth__reveal svg { width: 16px; height: 16px; }
.auth__reveal .auth__eye-off, .auth__reveal[aria-pressed="true"] .auth__eye { display: none; }
.auth__reveal[aria-pressed="true"] .auth__eye-off { display: block; }
.auth__reveal[aria-pressed="true"] {
  background: var(--sn-surface-3); border-color: var(--sn-line); color: var(--sn-ink);
}

/* item 28: the message for a field renders under that field, inside its own group, so
   nothing unrelated can get between them. It is not a live region — focus moves to the
   field and its aria-describedby carries the text, which is one announcement, not two
   (item 45). */
.auth__err {
  font-size: var(--sn-text-xs); line-height: 1.35; font-weight: 500;
  color: var(--sn-bad); text-align: left;
}
.auth__err:empty { display: none; }
.auth__hint {
  display: block; font-size: var(--sn-text-xs); line-height: 1.35;
  color: var(--sn-ink-mute); text-align: left;
}
/* item 38 + item 49: the shared slot for server-level failures is RESERVED, not removed,
   so the first error shifts nothing — but it is only 18px and it carries no gap of its
   own, so the resting distance from the last field to the button is that 18px.
   mocks/NOTES B5 measured 50px when the wrapper's gap sat on both sides of it. */
.gate__err {
  min-height: 18px; font-size: var(--sn-text-xs); line-height: 1.35;
  color: var(--sn-bad); text-align: left;
}
/* The route out of an auth error. It sits inside the error slot on both the
   login page and the dialog, so clearing the sentence clears the control. */
.auth__offer {
  display: inline-flex; align-items: center; min-height: 32px;
  margin-top: var(--sn-space-1);
  padding: 0; border: 0; background: none;
  font: inherit; font-size: var(--sn-text-xs); font-weight: 600;
  color: var(--sn-ink); text-decoration: underline; text-underline-offset: 3px;
}
.auth__offer:hover:not(:disabled) { color: var(--sn-accent); }

/* ── The auth dialog on top of the gate's grammar ────────────────────────────── */
.auth { z-index: 101; }
.auth .gate__card {
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 24px);
  overflow-y: auto; scroll-padding-block: 12px;
}
/* item 33: the × is the ONE dismissal control, plus Escape and a backdrop click.
   The old Cancel button — full width, bordered, and placed below the mode-switch links, i.e. the
   largest and last thing on the card doing the least valuable job — is deleted.
   REFERENCE-STUDY §12: 28 x 28, inset 8px from the top-right corner. */
.auth__close {
  position: absolute; top: 8px; right: 8px;
  width: 28px; height: 28px; min-height: 28px; min-width: 28px; padding: 0;
}
.auth__close svg { width: 14px; height: 14px; }
/* items 32, 44: the eyebrow carries the product name, which the title cannot, and it is
   one line at 390px. It used to read "ACCOUNT / SIGN IN" over an H1 reading "Sign in". */
.auth__cue {
  font-size: var(--sn-text-2xs); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--sn-ink-mute);
}
.auth__fields { display: grid; gap: var(--sn-space-4); }
/* Inside the zero-gap form wrapper, so it carries its own separation. */
.auth__note {
  margin-top: var(--sn-space-4);
  font-size: var(--sn-text-xs); line-height: 1.45; color: var(--sn-accent); text-align: left;
}
/* item 48: the mode switches are the dialog's navigation. `padding: 0` put the shared
   focus ring straight on the glyphs, and 4px of padding cannot lift a 12.5px link to the
   32px floor either — mocks/NOTES B6 measured 125 x 27. The min-height does it. */
.auth__alt { display: flex; gap: var(--sn-space-3); justify-content: center; flex-wrap: wrap; }
.auth__alt button {
  display: inline-flex; align-items: center; min-height: 32px;
  padding: var(--sn-space-1) var(--sn-space-2);
  border: 0; border-radius: var(--sn-radius-sm); background: none;
  font-size: var(--sn-text-xs); font-weight: 600;
  color: var(--sn-ink-dim); text-decoration: underline; text-underline-offset: 3px;
  transition: color var(--sn-dur-fast) var(--sn-ease-out);
}
.auth__alt button:hover:not(:disabled) { color: var(--sn-ink); }
.auth__alt button:disabled { color: var(--sn-ink-mute); cursor: not-allowed; }
/* item 42: loading is a state, not a fade. The label swaps to the present participle and
   [data-busy] in css/base.css draws the spinner; the 55% opacity is gone. */
.gate button:disabled { cursor: wait; }
.gate input:disabled { color: var(--sn-ink-mute); cursor: not-allowed; }

/* item 43: at 1280 x 700 in sign-up mode the card used to scroll inside itself and put
   the last control below its own fold. 700 px is a very common laptop viewport once the
   browser chrome is subtracted, and deleting the Cancel button was not enough on its own,
   so the card compacts before it reaches that height rather than at it. */
@media (max-height: 800px) {
  .gate { padding: var(--sn-space-3); }
  .gate .gate__card { margin-top: var(--sn-space-3); gap: var(--sn-space-3); }
  .gate .gate__mark { width: 36px; height: 36px; }
  .gate .auth__fields { gap: var(--sn-space-3); }
}
@media (max-height: 680px) {
  .gate { padding: 8px 12px; }
  .gate .gate__card {
    margin-top: 8px;
    max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px);
    padding: var(--sn-space-4);
    gap: var(--sn-space-2);
  }
  .gate .gate__mark { width: 32px; height: 32px; }
  .gate .auth__fields { gap: var(--sn-space-2); }
}

/* ══ UI Quiet: "Add my own printer" / "Add my own filament" (choice-steps lane) ══════════
   Placement decision (23 Sep): the two custom forms open in a dialog, not inline. Native
   <dialog> + showModal(), so focus trap, Escape and the inert page come from the UA, as for
   the gate above. Surface-3 card, radius 12, overlay shadow (DESIGN §2); the form inside
   keeps every field, id and validation it had. */
.sn-dialog {
  width: min(520px, calc(100vw - 32px)); max-height: calc(100dvh - 48px);
  margin: auto; padding: 0; border: 1px solid var(--sn-line-raised); border-radius: var(--sn-radius-md);
  background: var(--sn-surface-3); color: var(--sn-ink);
  box-shadow: var(--sn-shadow-overlay);
  overflow-y: auto; overscroll-behavior: contain;
}
.sn-dialog[open] { animation: gate-rise var(--sn-dur-fast) var(--sn-ease-out); }
.sn-dialog::backdrop {
  background: rgb(var(--sn-surface-0-rgb) / .5);
  backdrop-filter: blur(4px);
  animation: gate-fade var(--sn-dur-fast) var(--sn-ease-out);
}
.sn-dialog__head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sn-space-3);
  padding: var(--sn-space-4) var(--sn-space-3) var(--sn-space-2) var(--sn-space-5);
  background: var(--sn-surface-3);
}
.sn-dialog__title {
  font-family: var(--sn-font-display); font-weight: 700;
  font-size: var(--sn-text-md); line-height: var(--sn-lh-md);
}
.sn-dialog__x { width: 32px; padding: 0; }
.sn-dialog__body { margin: 0; padding: 0 var(--sn-space-5) var(--sn-space-5); }
