
/* color-scheme tells the browser this UI is already dark. What it verifiably
   buys: native selects, date pickers, scrollbars and autofill backgrounds
   render dark instead of light against a dark page.

   It was originally added believing it explained a reported bug where pages
   went much darker until a refresh. That explanation did NOT hold up — force
   dark inverts lightness, so on a #0d0c0e page it would lighten, not darken,
   and Blink skips pages that are already dark. The line is worth keeping on
   its own merits; the cause of that bug is still open.

   ⚠️ NEVER PREPEND TO THIS FILE. It used to carry a UTF-8 BOM, and inserting
   text above it moved the BOM into the body, where U+FEFF is a valid CSS ident
   character — it fused with the next selector and silently killed that whole
   rule. On landing.css that was the entire design-token :root block. The BOMs
   have been removed; keep these files BOM-free. */
:root { color-scheme: dark; }

/* ══════════════════════════════════════════════════════════════════════════
   THE DOORS — the only two pages a logged-out person can reach:
   login.html (log in / create account / forgot password / check your email /
   two-step verification) and reset.html (set a new password).

   Nothing else links this stylesheet. Grepped: `grep -rl login.css *.html`
   returns login.html and reset.html and nothing else, so class names here are
   free to change as long as those two files change with them.

   WHY THIS FILE DOES NOT LINK shared.css
   ─────────────────────────────────────────
   shared.css is ~72KB and is built around the app shell (fixed sidebar, fixed
   right rail, nav.js). These pages have neither. It also still carries a UTF-8
   BOM at byte 0. So the doors keep their own small stylesheet and re-declare
   the handful of tokens they need — copied verbatim from shared.css's :root so
   the two stay the same colour. If you change the palette in shared.css,
   change it here too. There is no build step that will do it for you.

   THE BUG THIS TOKEN BLOCK FIXES: before it existed, this file had no tokens
   at all, so every `var(--accent)` / `var(--red)` written by login.js resolved
   to nothing and silently inherited body grey — the "we resent your email"
   confirmation and the "that failed" error rendered in the same colour.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* Surfaces — copied from shared.css :root */
  --ink:      #0d0c0e;
  --panel:    #131216;
  --panel-2:  #191720;
  --line:     #232128;
  --line-2:   #2f2d36;
  --dim:      #6b6878;
  --mute:     #8d8a9c;
  --text:     #f1f0f4;

  /* Ramps. -wash = fill, -line = border, -text = readable on dark, -base = the
     saturated brand value. Never put -base on text; it fails contrast. */
  --g-wash: rgba(0, 200, 5, .065);
  --g-line: rgba(0, 200, 5, .26);
  --g-text: #5ce265;
  --g-base: #00C805;

  --a-wash: rgba(232, 163, 61, .075);
  --a-line: rgba(232, 163, 61, .30);
  --a-text: #f0b661;

  --r-wash: rgba(255, 68, 85, .07);
  --r-line: rgba(255, 68, 85, .28);
  --r-text: #ff8b96;

  /* Aliases login.js and reset.js may reference by name. */
  --accent: var(--g-base);
  --red:    var(--r-text);

  /* Three type roles, same as the app: Archivo displays, Inter speaks,
     Plex Mono counts. Money and codes are ALWAYS mono + tabular-nums. */
  --font-display: 'Archivo', 'Inter', system-ui, sans-serif;
  --font-ui:      'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono:    'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* THE FOLD IS 940px and it is written out longhand in four places below
   (.door, .door-form, .brandmark, and .door-stage at the very end of this
   file). It is deliberately NOT a custom property: a media query condition
   cannot read var(), so `@media (max-width: var(--door-fold))` silently never
   matches. If you move the fold, grep this file for 940 and change all four. */

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  min-height: 100vh;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Anything that moves, stops moving on request. There is very little motion
   on these pages by design — a door should open, not perform. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* One visible focus treatment for everything. A keyboard user must never have
   to guess where they are on the page that gates the whole product. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--g-base);
  outline-offset: 2px;
  border-radius: 4px;
}


/* ── The door ─────────────────────────────────────────────────────────────
   Form on the left, a dimmed still of the product's right rail on the right.
   Below the fold the still is display:none — not just hidden, removed, so it
   costs nothing on a phone. */
.door {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 100vh;
}
@media (max-width: 940px) {
  .door { grid-template-columns: 1fr; }
  /* The rule that drops the still lives at the BOTTOM of this file, not here.
     See the note there — putting it here does nothing. */
}

.door-form {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 96px 34px 56px;
  background: var(--ink);
}
@media (max-width: 940px) {
  .door-form { padding: 84px 20px 40px; }
}

.form-col {
  width: 100%;
  max-width: 372px;
}

/* Pinned to the corner of the page, not floating above the form — the mark is
   navigation back to the site, not a headline. */
.brandmark {
  position: absolute;
  top: 34px;
  left: 38px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: .4em;
  color: var(--text);
  text-decoration: none;
}
.brandmark:hover { color: var(--g-text); }
@media (max-width: 940px) {
  .brandmark { top: 26px; left: 20px; }
}


/* ── Headings and body copy ─────────────────────────────────────────────── */
.h {
  font-family: var(--font-display);
  font-size: 29px;
  font-weight: 800;
  letter-spacing: -.035em;
  line-height: 1.12;
  margin-bottom: 7px;
}
@media (max-width: 400px) { .h { font-size: 25px; } }

.sub {
  font-size: 13.5px;
  color: var(--mute);
  margin-bottom: 26px;
  line-height: 1.6;
}


/* ── Fields ───────────────────────────────────────────────────────────────
   Every input has a real <label for>. They are visible, not screen-reader-only:
   a placeholder disappears the moment you type, which on a password field you
   are about to get wrong is exactly when you want to know what it wanted. */
.fld { margin-bottom: 14px; }

.fld label {
  display: block;
  font-size: 11.5px;
  color: var(--mute);
  margin-bottom: 6px;
}

.fld .wrap { position: relative; }

.fld input {
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 9px;
  padding: 12px 13px;
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  transition: border-color .15s, box-shadow .15s;
}
.fld input:focus {
  outline: none;
  border-color: var(--g-line);
  box-shadow: 0 0 0 3px var(--g-wash);
}
/* Keyboard focus still gets the outline treatment on top of the green ring —
   the ring alone is a 6.5% wash and is not a reliable focus indicator. */
.fld input:focus-visible {
  outline: 2px solid var(--g-base);
  outline-offset: 1px;
}
.fld input::placeholder { color: var(--dim); }

.fld .hint {
  font-size: 11.5px;
  color: var(--dim);
  margin-top: 6px;
  line-height: 1.5;
}

/* Codes are read character by character, so they get the mono face and the
   letter spacing that makes an O and a 0 tell themselves apart. */
.fld.code input,
.fld.digits input {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: .14em;
}
.fld.code input { text-transform: uppercase; }
.fld.digits input { text-align: center; font-size: 17px; letter-spacing: .3em; }

/* "Forgot it?" sits beside the Password label but comes AFTER the password
   input in the markup, so that Tab goes username → password → reveal → forgot
   rather than stopping at a link on the way to the password box. Absolute
   positioning is what lets the two orders differ; it is pinned to the same
   line as the label, which is why the font-size and line-height below match
   .fld label exactly. Both strings are short, so there is no wrap risk even
   in the 320px column at 375px wide. */
.fld-pw { position: relative; }
.forgot-link {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 11.5px;
  line-height: 1.5;
}

/* Reveal button, absolutely positioned inside the field. Kept inside .wrap so
   it never overlaps the label. */
.password-toggle {
  position: absolute;
  right: 7px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  color: var(--mute);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  padding: 5px 7px;
  border-radius: 6px;
  transition: color .15s, background .15s;
}
.password-toggle:hover { color: var(--text); background: var(--panel-2); }
/* Room for the button so a long password never runs underneath it. */
.password-wrap input { padding-right: 62px; }


/* ── The consent checkbox ─────────────────────────────────────────────────
   WHY THIS IS A CHECKBOX AND NOT .switch: the design language says never ship
   a RAW <input type="checkbox">, and this one is not raw — appearance:none
   plus a drawn box and tick. It is deliberately NOT the .switch component,
   which is a settings toggle ("notifications on/off") and carries the wrong
   meaning for an agreement you make once. It is also load-bearing: login.js
   reads #agreeTerms.checked before it will POST. Note that the DB timestamp
   (profile.terms_agreed_at) is stamped at EMAIL VERIFICATION, not here —
   see users/views.py verify_email(). This box records what the person was
   shown at signup; removing it is a legal decision, not a visual one. */
.consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 12px;
  color: var(--mute);
  line-height: 1.6;
  cursor: pointer;
  margin: 4px 0 2px;
}
.consent input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border: 1px solid var(--line-2);
  border-radius: 5px;
  background: var(--panel);
  cursor: pointer;
  display: grid;
  place-content: center;
  transition: border-color .15s, background .15s;
}
.consent input[type="checkbox"]::before {
  content: "";
  width: 9px;
  height: 9px;
  transform: scale(0);
  transition: transform .12s ease-out;
  box-shadow: inset 1em 1em var(--g-base);
  clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0, 39% 62%);
}
.consent input[type="checkbox"]:checked { border-color: var(--g-line); background: var(--g-wash); }
.consent input[type="checkbox"]:checked::before { transform: scale(1); }
.consent a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.consent a:hover { color: var(--g-text); }


/* ── Buttons and links ───────────────────────────────────────────────────── */
.btn-primary {
  display: block;
  width: 100%;
  background: var(--g-base);
  color: #04140a;
  border: none;
  border-radius: 9px;
  padding: 13px 18px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  margin-top: 6px;
  text-align: center;
  text-decoration: none;
  transition: opacity .15s;
}
.btn-primary:hover { opacity: .92; }
.btn-primary:disabled { opacity: .55; cursor: default; }

/* A link that happens to be a <button> because it changes panel rather than
   navigating. Styled as text so it never reads as a second primary action. */
.link-btn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--mute);
  cursor: pointer;
  text-decoration: none;
}
.link-btn:hover { color: var(--text); }

.alt {
  font-size: 13px;
  color: var(--mute);
  margin-top: 20px;
  text-align: center;
}
.alt .link-btn,
.alt a {
  color: var(--text);
  font-weight: 500;
  border-bottom: 1px solid var(--line-2);
}
.alt .link-btn:hover,
.alt a:hover { border-bottom-color: var(--g-base); }

.mini {
  font-size: 12px;
  color: var(--dim);
  text-align: center;
  margin-top: 14px;
  line-height: 1.6;
}
.mini .link-btn { color: var(--mute); text-decoration: underline; text-underline-offset: 2px; }

/* Sits directly under the Log In button. Sourced: SIMPLE_JWT REFRESH_TOKEN_LIFETIME
   is 30 days (backend/settings/base.py). If that setting changes, this sentence
   becomes a lie — change it here too. */
.auth-note {
  font-size: 11.5px;
  color: var(--dim);
  text-align: center;
  margin-top: 10px;
}


/* ── Status messages ──────────────────────────────────────────────────────
   Every one of these carries role="alert" in the markup so a screen reader is
   told when it changes. min-height keeps the layout from jumping when text
   appears. */
.auth-error {
  font-size: 12.5px;
  color: var(--r-text);
  line-height: 1.5;
  min-height: 17px;
  margin: 2px 0 4px;
}
/* Success tone for the same element. It is a CLASS, not an inline style: the
   old code set errorEl.style.color directly, and the panel switchers cleared
   textContent but never the colour, so the next real error rendered green. */
.auth-error.is-ok { color: var(--g-text); }
.auth-error a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.auth-error .link-btn { color: inherit; text-decoration: underline; text-underline-offset: 2px; font-size: inherit; }


/* ── "Check your email" / done / expired states ──────────────────────────── */
.sent { text-align: center; }
.sent .h { font-size: 25px; }
.sent-ic {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--g-wash);
  border: 1px solid var(--g-line);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 20px;
  font-size: 21px;
  color: var(--g-text);
}
.sent-ic.is-bad { background: var(--r-wash); border-color: var(--r-line); color: var(--r-text); }
.sent-addr {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px;
  margin: 16px 0 4px;
  overflow-wrap: anywhere;
}


/* ── Panel visibility ─────────────────────────────────────────────────────
   ⚠️ THE TRAP THIS RULE EXISTS FOR. login.js and reset.js switch panels by
   setting `style.display = "flex"` inline. Any panel id missing from this
   selector list still gets display:flex — but with the browser default
   flex-direction:row and no gap, so its fields land side by side and squash
   to nothing. That is exactly how the two-step verification screen shipped
   with a 76px-wide code input, and how reset.html's two password fields sat
   flush against each other.

   ADD EVERY NEW PANEL ID TO THIS LIST. There is no test that will catch it —
   the page still "works", it just looks broken. */
#panelLogin,
#panelRegister,
#panelForgot,
#panelSent,
#panelVerify,
#panelTOTP,
#panelReset,
#panelDone,
#panelInvalid {
  display: flex;
  flex-direction: column;
}

.auth-footer {
  margin-top: 34px;
  text-align: center;
  font-size: 11.5px;
  color: var(--dim);
}
.auth-footer a { color: var(--dim); text-decoration: none; border-bottom: 1px solid transparent; }
.auth-footer a:hover { color: var(--mute); border-bottom-color: var(--line-2); }


/* ══════════════════════════════════════════════════════════════════════════
   THE STILL — the right half of the door.

   It is a photograph of the product, not a working copy: the same right rail
   that sits beside every page once you are in, rendered from the same shapes
   shared.css uses (.rail-item / .rail-row / outline .rail-btn) so it cannot
   drift into showing something ALPHA does not actually do.

   Three things keep it honest and out of the way:
     1. aria-hidden + inert in the markup — never read out, never tabbable.
        There are no <button>s or <a>s inside it either, so even in a browser
        without `inert` there is nothing to focus.
     2. opacity .58 + saturate(.55) — the product's green button is outline
        style AND desaturated, so it cannot be mistaken for the real Log in
        button on the left.
     3. It is captioned as sample figures. The numbers are the ones in
        rail.js's own usage example; they are not anybody's account.
   ══════════════════════════════════════════════════════════════════════════ */
.door-stage {
  position: relative;
  background: #0a0a0c;
  border-left: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 46px 40px;
  overflow: hidden;
}
/* Vignette. pointer-events:none so it is scenery, not a hit target. */
.door-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(105% 78% at 50% 40%, transparent 18%, rgba(8,8,10,.82) 78%, #08080a 100%);
}

.still {
  width: 100%;
  max-width: 322px;
  opacity: .58;
  filter: saturate(.55);
  user-select: none;
}

.still-block { margin-bottom: 22px; }

.still-label {
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: 10px;
}

.still-item {
  border: 1px solid var(--a-line);
  background: var(--a-wash);
  border-radius: 10px;
  padding: 13px 14px;
}
.still-item-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 3px;
}
.still-item-detail { font-size: 11.5px; color: var(--mute); line-height: 1.55; }

/* Outline, never solid. Rule 2 in rail.js: the page owns the loudest button;
   the rail sits beside it. Doubly true here, where the loudest button on
   screen must be the real Log in. */
.still-btn {
  margin-top: 11px;
  padding: 9px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
}

.still-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--mute);
}
.still-row:last-child { border-bottom: none; }

/* Money is mono + tabular-nums everywhere in ALPHA, including in a picture
   of ALPHA. Columns of figures that do not line up read as a different,
   sloppier product. */
.still-amount,
.still-row b {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--text);
}
.still-row-hero b { font-size: 15px; }
.tone-ok   { color: var(--g-text); }
.tone-warn { color: var(--a-text); }

.still-cap {
  position: relative;
  z-index: 2;
  font-size: 11.5px;
  color: var(--dim);
  text-align: center;
  margin-top: 26px;
  line-height: 1.6;
}


/* ══════════════════════════════════════════════════════════════════════════
   NARROW SCREENS — this block MUST stay last in the file.

   ⚠️ A MEDIA QUERY DOES NOT ADD SPECIFICITY. `.door-stage` inside
   `@media (max-width:940px)` and the plain `.door-stage` rule above are both
   one class, so the tie is broken by SOURCE ORDER and the later one wins.
   This exact rule was first written up beside the .door grid switch near the
   top of the file, where the base .door-stage block came after it — the grid
   collapsed to one column but the still kept its display:flex and rendered
   UNDERNEATH the form on every phone. Measured in a 375px frame:
   getComputedStyle(stage).display was "flex", not "none".

   If you add more narrow-screen overrides, add them here, at the end.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 940px) {
  /* Removed, not just hidden: below the fold the door is only the form, and a
     picture of the rail is not worth a phone's bandwidth or scroll. */
  .door-stage { display: none; }
}
