/* ═══════════════════════════════════════════════════════════════════════════
   MAXIMUS — /login
   Decisions/Maximus-Janus-Login-Remember-Me-2026-09-27.md

   WHY THIS FILE DECLARES ITS OWN :root INSTEAD OF LINKING style.css
   ----------------------------------------------------------------
   style.css is 238 KB and it styles a fixed-viewport dashboard shell —
   `body { overflow: hidden }`, a tab rail, a chat dock. The login page is one
   centred card that must render before anything else on a phone on cellular,
   and it is the FIRST paint of a Home Screen app launch. Pulling the whole
   dashboard sheet in would both slow that paint and start a fight with layout
   rules written for a different page.

   So this sheet redeclares only the ~20 tokens the card uses. That is a
   deliberate duplication and duplication drifts, so it is checked rather than
   trusted: `scripts/test_login_tokens.py` asserts that every token declared
   here with the same name as one in style.css has the SAME VALUE, and fails if
   one moves. The two tokens at the bottom exist only here, and the test knows
   that and says why.

   Every colour is a token because qa/css_token_gate.py scans frontend/css/*.css
   and refuses a hex literal outside :root, a font-size that is not var(--fs-*),
   and a padding/margin/gap length that is not a var(). That guard is the reason
   the design system is worth anything (Decisions/A-Guard-Must-Prove-It-Can-Refuse.md).
   ═══════════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "Orbitron";
  src: url("/static/fonts/orbitron-700.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* Janus's wordmark face, used only when this page is wearing Janus's colours.
   The URL is absolute because janus.css's own `url("fonts/...")` resolves
   against /janus/, and this sheet is served from /static/css/. The path is in
   auth_routes.OPEN_PREFIXES: a font needed to RENDER the login screen cannot
   itself require a session. */
@font-face {
  font-family: "Syncopate";
  src: url("/janus/fonts/syncopate-700.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

:root {
  /* surfaces */
  --bg: #050506;
  --surface-1: #0c0c0e;
  --surface-2: #141417;
  --surface-3: #1c1c20;
  --border: #212126;
  --border-strong: #2e2e35;

  /* text */
  --text: #e7e7ea;
  --text-dim: #9a9aa3;
  --text-faint: #8f8f9a;

  /* accent — brand, the one primary action, and focus. Nothing else. */
  --accent: #ff7a1a;
  --accent-bright: #ff944d;
  --accent-soft: rgba(255, 122, 26, 0.10);
  --accent-glow: rgba(255, 122, 26, 0.35);
  --on-accent: #000;

  /* semantic */
  --ok: #3ddc84;
  --warn: #ffb224;
  --err: #ff5252;

  /* type */
  --fs-micro: 11px;
  --fs-caption: 12px;
  --fs-label: 12px;
  --fs-body: 13px;
  --fs-subtitle: 15px;
  --fs-title: 18px;
  /* iOS Safari zooms the page when a focused input's font-size is under 16px.
     On this page EVERY input is one Ryan taps on a phone, so the touch size is
     unconditional here rather than behind a media query. */
  --fs-input-touch: 16px;
  --font-ui: "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-display: "Orbitron", var(--font-ui);

  /* spacing — the 4px grid */
  --sp-hair: 2px;
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px;

  /* motion */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast: 120ms;

  --radius: 2px;

  /* DECLARED ONLY HERE. It mirrors the --warn-soft that style.css already
     carries, for the one tinted surface this page needs: the error banner.
     Written as a token rather than inlined at the rule because an rgba() in a
     rule is the same defect as a hex in a rule — it is just one the gate's
     regex cannot see, which makes it worse, not better.
     Only this page's tokens live here, and only the ones it uses:
     scripts/test_login_tokens.py refuses a declaration nothing references,
     because an unused copy of a shared value is the one that goes stale. */
  --err-soft: rgba(255, 82, 82, 0.10);

  /* ── Janus ──────────────────────────────────────────────────────────────
     One origin, one login (the decision's words), so Janus does not get a
     second login page to drift out of step with this one — it gets this page
     wearing Janus's colours, chosen from `next`. Every value below is copied
     from frontend/janus/janus.css and checked against it by
     scripts/test_login_tokens.py, which is what stops "Janus pink" here
     quietly becoming a different pink from Janus pink there.
     Lane 2 owns those files; nothing here writes to them. */
  --j-bg: #0a0a0c;
  --j-surface-1: #131316;
  --j-surface-2: #1b1b1f;
  --j-surface-3: #232329;
  --j-border: #26262c;
  --j-border-strong: #2c2c33;
  --j-text: #ececf0;
  --j-text-dim: #a0a0ab;
  --j-text-faint: #8a8a96;
  --j-action: #ff5fa8;
  --j-action-hover: #ff85bd;
  --j-action-soft: #2a1521;
  --j-action-edge: #3a2231;
  --j-ok: #3ddc84;
  --j-warn: #ffb224;
  --j-err: #ff5a5a;
  --j-err-soft: #2a1414;
  /* The wordmark face only. Janus also ships Manrope for body text and Sora
     for headings; they are deliberately NOT loaded here. This is one card on
     a cold cellular start, the mark and the pink are what anyone recognises,
     and two more variable fonts would cost more than the recognition is
     worth. The fallback chain is janus.css's own. */
  --font-mark: "Syncopate", "Sora", system-ui, sans-serif;
}

/* Arrived from Janus. Only the TOKENS change — every rule below is already
   written against them, so there is no second set of Janus rules to maintain
   and no way for the two themes to grow apart. */
:root.janus {
  --bg: var(--j-bg);
  --surface-1: var(--j-surface-1);
  --surface-2: var(--j-surface-2);
  --surface-3: var(--j-surface-3);
  --border: var(--j-border);
  --border-strong: var(--j-border-strong);
  --text: var(--j-text);
  --text-dim: var(--j-text-dim);
  --text-faint: var(--j-text-faint);
  --accent: var(--j-action);
  --accent-bright: var(--j-action-hover);
  --accent-soft: var(--j-action-soft);
  --accent-glow: var(--j-action-edge);
  /* janus.css draws its primary button as action-on-background, not on black. */
  --on-accent: var(--j-bg);
  --ok: var(--j-ok);
  --warn: var(--j-warn);
  --err: var(--j-err);
  --err-soft: var(--j-err-soft);
  --font-display: var(--font-mark);
}

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

html { background: var(--bg); }

body {
  margin: 0;
  /* Centres the card, and the safe-area insets are what keep it clear of the
     notch and the home indicator when iOS runs this WITHOUT browser chrome —
     which is the whole case Ryan reported broken. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
  padding-top: max(var(--sp-5), env(safe-area-inset-top));
  padding-bottom: max(var(--sp-5), env(safe-area-inset-bottom));
  padding-left: max(var(--sp-4), env(safe-area-inset-left));
  padding-right: max(var(--sp-4), env(safe-area-inset-right));
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

/* ── the card ───────────────────────────────────────────────────────────── */

.card {
  width: 100%;
  max-width: 380px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
}

.brand {
  display: flex;
  align-items: baseline;
  gap: var(--sp-hair);
  margin: 0 0 var(--sp-2);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-title);
  letter-spacing: 0.08em;
  color: var(--text);
}

/* The one accent mark on the page above the button: the wordmark's period.
   Identity, not status. */
.brand .dot { color: var(--accent); }

/* janus.css:350 sets its wordmark at 0.16em. Matching it is the difference
   between "Janus colours" and "Janus". */
:root.janus .brand { letter-spacing: 0.16em; }

/* Maximus has a voice everywhere else and had none in its own UI (see the
   maximus-design skill). This is that voice, and it is also the answer to the
   question Ryan actually had: why am I being asked again? */
.greeting {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-subtitle);
  color: var(--text);
}

.sub {
  margin: 0 0 var(--sp-5);
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--text-dim);
}

/* ── the banner ─────────────────────────────────────────────────────────── */

.banner {
  display: none;
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3);
  border: 1px solid var(--border-strong);
  border-left: 2px solid var(--text-dim);
  border-radius: var(--radius);
  background: var(--surface-2);
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--text);
}

.banner.show { display: block; }
.banner.bad { border-left-color: var(--err); background: var(--err-soft); }
.banner.wait { border-left-color: var(--warn); }
.banner.good { border-left-color: var(--ok); }

/* An error that only reports a limitation is the defect the design skill names.
   Every banner state carries its next action in this line. */
.banner .next {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-caption);
  color: var(--text-dim);
}

/* ── the form ───────────────────────────────────────────────────────────── */

.field { margin: 0 0 var(--sp-4); }

label {
  display: block;
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-micro);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-faint);
}

input[type="text"], input[type="password"] {
  width: 100%;
  padding: var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-input-touch);
  transition: border-color var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}

input[type="text"]:focus, input[type="password"]:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Wraps the input ALONE, never the label — the Show button is absolutely positioned
   against this box, and a label inside it makes the button centre over both and sit
   above the field. iOS also draws its Passwords / Face ID key inside the input, which
   is what the right-hand padding keeps the typed characters clear of. */
.pw { position: relative; display: block; }
.pw input { padding-right: var(--sp-6); }

.reveal {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  padding: 0 var(--sp-3);
  border: 0;
  background: none;
  color: var(--text-faint);
  font-family: var(--font-ui);
  font-size: var(--fs-caption);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}

.reveal:hover { color: var(--text-dim); }
.reveal:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }

button[type="submit"] {
  width: 100%;
  /* 48px of height comes from the padding plus the 16px line — above Apple's
     44px minimum tap target, checked at 390px wide. */
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-ui);
  font-size: var(--fs-input-touch);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}

button[type="submit"]:hover:not(:disabled) {
  background: var(--accent-bright);
  border-color: var(--accent-bright);
}

button[type="submit"]:focus-visible { box-shadow: 0 0 0 3px var(--accent-glow); }

/* bcrypt at cost 14 takes about 1.3 seconds, so the pressed state is not
   decoration — without it the page looks broken for longer than a user waits. */
button[type="submit"]:disabled {
  background: var(--surface-3);
  border-color: var(--border-strong);
  color: var(--text-faint);
  cursor: default;
}

.foot {
  margin: var(--sp-4) 0 0;
  font-size: var(--fs-caption);
  line-height: 1.6;
  color: var(--text-faint);
}

.foot a { color: var(--text-dim); }

/* ── phones ─────────────────────────────────────────────────────────────── */

@media (max-width: 420px) {
  body { padding-left: max(var(--sp-3), env(safe-area-inset-left));
         padding-right: max(var(--sp-3), env(safe-area-inset-right)); }
  .card { padding: var(--sp-5) var(--sp-4) var(--sp-4); }
}

/* A short landscape viewport is the keyboard-is-up case on a phone: centring
   then pushes the card's top off-screen, so stop centring and let it scroll. */
@media (max-height: 560px) {
  body { align-items: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}
