
/* Inter Tight and IBM Plex Mono, served by THIS origin out of
   apps/accounts/assets/fonts (brand-assets.ts) — never fonts.googleapis.com.
   `font-display: swap` so a font that is slow or blocked shows the system
   fallback immediately: the sign-in form must never be invisible waiting on a
   typeface. The unicode-ranges are Google's own subset boundaries, kept
   verbatim so the browser downloads latin-ext only when a page actually
   contains one of those code points.

   Inter Tight is served as its VARIABLE font (one file per subset, weight
   axis 400-600) rather than three static instances: the range on
   @font-face tells the browser which weights this face answers for, and it
   interpolates the rest from that one download. A fixed `font-weight: 400`
   here would be wrong — the design uses 500 and 600 too. */
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/inter-tight-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/inter-tight-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  color-scheme: dark;
  --bg: #05060f;
  /* The card. The DESIGN specifies #090D1E at 51% alpha over the background
     plate; this ships the same colour at 90%.
     That is a deliberate, load-bearing deviation, not a taste change. With a
     translucent card the effective text contrast depends on whatever pixels
     of the plate happen to sit behind it — and this plate is not a quiet
     gradient, it is a mural with white cloud highlights and bright cyan
     linework directly behind where the card sits. Over one of those the
     design's own #eef3ff body text lands near 2.6:1: a WCAG failure that
     moves with the artwork. At 90% the worst case (pure white behind) still
     computes 10.4:1 for body text and 4.6:1 for the muted lede, so every
     pairing below holds no matter what the plate does. The blur, the inner
     highlight and the border all survive; only the guesswork is gone. */
  --surface: rgba(9, 13, 30, 0.90);
  --surface-hover: rgba(255, 255, 255, 0.16);
  --surface-subtle: rgba(255, 255, 255, 0.09);
  --border: rgba(255, 255, 255, 0.16);
  --border-strong: rgba(255, 255, 255, 0.24);
  --field: rgba(4, 8, 22, 0.72);
  --text: #eef3ff;
  --text-muted: #b6c2dc;
  --text-faint: #93a0bb;
  --accent: #1d7cf2;
  --accent-text: #ffffff;
  --accent-hover: #3a90ff;
  --accent-active: #166ad3;
  --link: #7db8ff;
  --link-hover: #a9d2ff;
  --danger: #ff9a9a;
  --danger-bg: rgba(80, 20, 28, 0.72);
  --danger-border: rgba(214, 90, 100, 0.55);
  --focus: #4a9cff;
  --sans: 'Inter Tight', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--bg);
  background-image: url('/assets/bg.webp');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  color: var(--text);
  font-family: var(--sans);
  line-height: 1.5;
  padding: 3rem 7vw;
}

/* The design's dimming layer over the plate. A pseudo-element rather than a
   second background layer so it can sit above the image and below the card,
   and `position: fixed` so a page tall enough to scroll (the two-factor page
   on a short viewport) dims all of it. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background: radial-gradient(60% 70% at 50% 50%, rgba(4, 6, 18, 0.34) 0%, rgba(4, 6, 18, 0.18) 100%);
  pointer-events: none;
}

main {
  position: relative;
  width: 100%;
  max-width: 26.25rem;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 2.125rem 2.125rem 1.625rem;
  backdrop-filter: blur(26px) saturate(135%);
  -webkit-backdrop-filter: blur(26px) saturate(135%);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

header.brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.625rem;
}

img.lockup { display: block; height: 30px; width: auto; }

h1 {
  font-size: 1.3125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 0.25rem;
}

/* The one-line lede under the heading (page-shell.ts). */
p.lede {
  margin: 0 0 1.5rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--text-muted);
}

h2 {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text-muted);
  margin: 1.75rem 0 0.75rem;
  padding-top: 1.375rem;
  border-top: 1px solid var(--border);
}

p { color: var(--text-muted); margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

strong { color: var(--text); font-weight: 600; }

a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); }

form { display: flex; flex-direction: column; gap: 0.875rem; margin-top: 0.25rem; }

/* The design's field label: small, uppercase, tracked. */
label {
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

input[type="text"], input[type="password"] {
  width: 100%;
  /* 44px, not the design's 42px: the minimum touch target this surface has
     always held (WCAG 2.5.8). 1rem, not the design's 14px: iOS Safari zooms
     the whole page when a focused input is under 16px, which on a login form
     means the field jumps out from under the person typing into it. */
  min-height: 44px;
  padding: 0.625rem 0.8125rem;
  font-size: 1rem;
  font-family: inherit;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text);
  background: var(--field);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
}
input::placeholder { color: rgba(226, 235, 255, 0.42); }

/* The design's focus treatment is a blue glow. It is applied ON TOP of the
   outline, never instead of it: a box-shadow is not an outline in forced-
   colors mode and disappears entirely there, which would leave a keyboard
   user with no focus indicator at all. */
input:focus, a:focus, button:focus,
input:focus-visible, a:focus-visible, button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
input:focus { border-color: var(--focus); box-shadow: 0 0 0 3px rgba(74, 156, 255, 0.22); }

button {
  min-height: 44px;
  padding: 0.625rem 1rem;
  font-size: 0.9375rem;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: 0.01em;
  color: var(--accent-text);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
}
button:hover { background: var(--accent-hover); }
button:active { background: var(--accent-active); }

/* consent-page.ts's Deny action reads as a secondary control, by VALUE —
   no extra class needed, so a future third decision button (there is none
   today) would fall back to the primary look rather than silently matching
   nothing. */
button[name="decision"][value="deny"] {
  color: var(--text);
  background: var(--surface-subtle);
  border-color: var(--border-strong);
}
button[name="decision"][value="deny"]:hover { background: var(--surface-hover); }

.error {
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger-border);
  border-radius: 8px;
  padding: 0.625rem 0.8125rem;
  font-size: 0.875rem;
  margin: 0 0 0.25rem;
}

code {
  font-family: var(--mono);
  font-size: 0.85em;
  background: rgba(4, 8, 22, 0.6);
  padding: 0.1em 0.35em;
  border-radius: 4px;
  /* consent-page.ts reflects a caller-supplied redirect_uri (and
     oauth_query's client_id) into a <code> — an unbroken URL is exactly the
     kind of string a browser will NOT wrap on its own, which forced a
     horizontal scrollbar at 375px width until this was added. */
  overflow-wrap: break-word;
  word-break: break-word;
}

ul { padding-left: 1.25rem; color: var(--text-muted); }
ul li { margin: 0.4rem 0; }

/* The design's "oder" rule between the password form and the alternative
   credential methods. A real separator element, not a border on the list, so
   it can carry the label. */
.divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.375rem 0 1rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* sign-in-page.ts's passkey alternative: a real <ul> (kept extensible for a
   future second credential method — see that file's own doc comment), but
   styled as a proper secondary action, never a visual bullet. */
ul.alt {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}
ul.alt li { margin: 0; }
ul.alt a {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.5rem 1rem;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface-subtle);
  color: var(--text);
  font-weight: 500;
  font-size: 0.8125rem;
}
ul.alt a:hover { background: var(--surface-hover); color: var(--text); }
/* ACC-719: a social entry is a <form> (it carries the CSRF token), so its
   control is a <button> — styled as the same secondary action as the passkey
   link rather than the page's primary accent button. */
ul.alt form { margin: 0; }
ul.alt button {
  width: 100%;
  border: 1px solid var(--border-strong);
  background: var(--surface-subtle);
  color: var(--text);
  font-weight: 500;
  font-size: 0.8125rem;
}
ul.alt button:hover, ul.alt button:active { background: var(--surface-hover); }

noscript .error { margin-top: 1rem; }

@media (prefers-reduced-motion: no-preference) {
  button, ul.alt a { transition: background-color 120ms ease; }
}

/* The backdrop-filter is the single most expensive thing on this page, and on
   a low-end phone it is the difference between the form appearing instantly
   and a visible stall. Below the breakpoint the card goes fully opaque, which
   also removes the last of the contrast guesswork on the smallest screens. */
@media (max-width: 30rem) {
  body { padding: 1rem 0.75rem; }
  main {
    padding: 1.5rem 1.25rem;
    border-radius: 14px;
    background-color: #0b1020;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
