/*
 * Sign in, sign up, reset, and the TOTP challenge.
 *
 * ASYMMETRIC SPLIT, NEVER A CENTRED CARD. A form floating in the middle of an
 * empty page is the templated answer and it is what the competitor does. The
 * form takes the paper side and the band carries the product claim, which also
 * means the first thing a customer sees is the same device the marketing site
 * closes with.
 */

.auth {
  min-block-size: 100vh;
  display: flex;
  flex-direction: column;
}

.auth__grid {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr;
}

/* The form column.
 *
 * THE CAP IS ON THE FIELDS, AND THE PADDING IS ADDED TO IT. Under the global
 * border-box reset a plain `max-inline-size: 30rem` includes the padding, so
 * at the widest breakpoint the 96px gutters were eating 192px of it and the
 * fields rendered at 288px inside a 480px column. That is the whole reason
 * this looked cramped: the number said 480 and the form was 288.
 *
 * So the two are separate values and the cap is computed. --auth-field is the
 * width a customer actually sees, which is the number worth choosing, and
 * --auth-pad steps independently as the column gets more room around it.
 *
 * 25rem is a comfortable single column of fields. Wider than this and the eye
 * starts losing the line back from a field to its label; the marketing site's
 * 44rem measure is for running text, which this is not. */
.auth__form {
  --auth-field: 25rem;                                 /* 400 */
  --auth-pad: var(--mbx-space-5);                      /* 24 */
  display: flex;
  flex-direction: column;
  gap: var(--mbx-space-5);
  inline-size: 100%;
  max-inline-size: calc(var(--auth-field) + 2 * var(--auth-pad));
  margin-inline: auto;
  padding: var(--mbx-space-8) var(--auth-pad);
  /* Centred vertically at EVERY width, not only where the band is showing.
     Below 960 the band is hidden, so without this the form sits against the
     top of the page and the empty paper all collects at the bottom, which is
     the "page that stops halfway down" fault on a page that legitimately has
     little content. Balanced space above and below reads as composed.

     `safe` is doing real work and is not decoration: plain `center` in a flex
     column CLIPS THE TOP of anything taller than its container, with no way
     to scroll to it, which is exactly what happens to this form on a short
     phone in landscape. `safe` falls back to flex-start at that point. */
  justify-content: safe center;
}

.auth__mark {
  display: flex;
  align-items: center;
  gap: var(--mbx-space-3);
  text-decoration: none;
  color: var(--mbx-ink);
  margin-block-end: var(--mbx-space-4);
}

/* The wordmark is LIVE TEXT, not a file: Bricolage 700 at the heading track,
   already loaded, so it costs nothing extra and beats an outlined SVG for
   selection and accessibility. */
.auth__wordmark {
  font-family: var(--mbx-font-display);
  font-weight: 700;
  font-size: var(--pnl-text-lg);
  letter-spacing: var(--mbx-track-heading);
}
.auth__wordmark-b { color: var(--mbx-accent); }

/* text-wrap: pretty because this is the one line of prose on the page and a
   two-line standfirst ending on a single word looks like an accident. */
.auth__standfirst {
  margin-block-start: var(--mbx-space-2);
  text-wrap: pretty;
}

.auth__fields {
  display: flex;
  flex-direction: column;
  gap: var(--mbx-space-4);
}
.auth__fields .btn { margin-block-start: var(--mbx-space-2); }

.auth__alt {
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  color: var(--mbx-ink-muted);
}

/* WCAG 2.2 2.5.8, TARGET SIZE, and the line between what needs fixing and
   what does not is worth writing down because the next pass will meet it too.
   A keyboard sweep found every link on this page at 17px tall.

   2.5.8 exempts a target that is INSIDE A SENTENCE, on the reasoning that its
   size is set by the surrounding prose rather than by the designer. So "Create
   one" in "No account yet? Create one." stays exactly as it is, and forcing a
   24px box around it would push it off the baseline of its own sentence for no
   benefit.
   
   These four are not in a sentence. "Forgotten your password?" is a standalone
   line and the three footer links are a row of their own, so nothing about
   them is constrained by anything and the exception does not apply. */
.auth__alt--lone a,
.auth__foot a {
  min-block-size: var(--mbx-space-5);          /* 24 */
  display: inline-flex;
  align-items: center;
}

/* A rule with a word in it, drawn with a border on a pseudo-element rather
   than a background gradient, so it stays a hairline at any zoom. */
.auth__or {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--mbx-space-4);
  color: var(--mbx-ink-soft);
  font-size: var(--pnl-text-sm);
}
.auth__or::before, .auth__or::after {
  content: "";
  border-block-start: 1px solid var(--mbx-border);
}

.auth__social {
  display: flex;
  flex-direction: column;
  gap: var(--mbx-space-3);
}

/* ---- The band side ------------------------------------------------------
   Hidden below 60em rather than stacked. On a phone the form is the whole job
   and a claim panel underneath it is something nobody scrolls to. */
.auth__aside { display: none; }

/* A VERY SOFT LIFT BEHIND THE CHARACTER, and the restraint is the design.
 *
 * Flat, this is up to 1280px of one unbroken navy on a wide monitor, and the
 * figure floats on it: he is lit from the front left with a soft ambient glow
 * and there is nothing behind him for that light to be coming from. The
 * gradient gives him a ground. It is not a hero wash and it must not become
 * one: a gradient loud enough to notice as a gradient turns a document into a
 * SaaS landing page, which is the one thing this brand is not.
 *
 * IT IS BOUNDED BY --mbx-band-lift AT ITS BRIGHTEST, and that bound is what
 * keeps it measurable rather than merely tasteful. tools/palette.py cannot
 * gate a gradient at all - its own notes say so, because every checker reads
 * an element's own background, finds it transparent over a gradient and then
 * cheerfully reports that all the text passes. So the CONSTRUCTION carries the
 * argument instead: the lightest pixel anywhere in here is exactly band-lift,
 * and every text token that appears on this side is gated against both ends of
 * that range in PAIRS:
 *
 *     on-band        on band  17.16 : 1     on band-lift  10.85 : 1
 *     on-band-muted  on band   8.71 : 1     on band-lift   5.51 : 1
 *     accent-300     on band   9.77 : 1     on band-lift   6.18 : 1
 *
 * The whole span is therefore measured. Introduce a stop lighter than
 * band-lift and none of that holds any more: generate a new step in
 * palette.py, gate it to exit 0, and use that.
 *
 * Interpolated in OKLAB rather than sRGB. Two navies of different lightness
 * blended in sRGB pass through a slightly dead middle; in a perceptual space
 * the ramp is even, which is the difference between a lift you feel and a
 * band you can see the edge of.
 *
 * The rail in app.css stays FLAT, deliberately. That is app chrome behind
 * working navigation, and a gradient there would be decoration in the one
 * place a customer is trying to read quickly. */
.auth__aside {
  background-color: var(--mbx-band);
  background-image:
    radial-gradient(
      105% 78% at 34% 30% in oklab,
      var(--mbx-band-lift) 0%,
      color-mix(in oklab, var(--mbx-band-lift) 62%, var(--mbx-band)) 34%,
      color-mix(in oklab, var(--mbx-band-lift) 22%, var(--mbx-band)) 62%,
      var(--mbx-band) 86%
    );
}

/* THE BLOCK IS CENTRED IN THE BAND AND IT SCALES WITH IT. Left-aligned at a
   fixed 34rem, a wide monitor left a third of the panel empty on the right and
   the whole side read as a block that had failed to fill. The band is half the
   viewport, so on a 2560 screen it is 1280px of navy, and a 544px column
   pinned to its left edge is not a composition.

   So: centred, and capped at a width that still has presence at 1280 while
   keeping the ledger's leaders long enough to read as leaders. The prose
   inside is capped separately by --mbx-measure, which is what actually
   protects the line length. */
.auth__aside-inner {
  inline-size: 100%;
  max-inline-size: 42rem;
  display: flex;
  flex-direction: column;
  gap: var(--mbx-space-5);
}

/* THE ONE STEP UP FROM THE h2 DEFAULT, and the reason is role rather than
   taste. The panel's h2 step is 22/28, ratio 1.273, which is derived for a
   SECTION HEADING: a short line labelling the block under it. This is not that.
   It is the band's display line, the one piece of persuasion on the screen,
   set two lines long in a 700-weight face with a 0.66em cap height. At 1.273
   the second line's caps come within about 9px of the first line's descenders,
   which passes every check and still reads tight, which is exactly what the
   owner spotted.

   The fix is the SIZE, not the leading. Pushing 22px to 32px leading would be
   ratio 1.455, outside the heading band for that size and reported. Stepping
   to 28/32 gives ratio 1.14, comfortably inside the band for 24px and above,
   keeps the leading an exact 4px multiple, and gives the claim the presence it
   should have had: one step below the 36px H1 beside it rather than two.

   THE LEADING WAS NEVER OFF THE GRID. 28 is 4x7 and always was. What was off
   was using a section-heading step for a display line. */
.auth__claim {
  font-size: var(--pnl-text-2xl);
  line-height: var(--pnl-lh-2xl);
  text-wrap: balance;
}

/* The only running prose on this side, so it is the only thing here that takes
   the measure. The claim and the ledger are scanned rather than read and are
   correct at the full width of the block. */
.auth__note {
  max-inline-size: var(--mbx-measure);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  padding-block-start: var(--mbx-space-4);
  border-block-start: 1px solid var(--mbx-on-band-border);
}

/* ---- The character ------------------------------------------------------
   He sits ABOVE the text, so the band reads figure, kicker, claim, evidence:
   the order an editorial page is set in. He also faces into the headline from
   up there rather than away from it.

   SIZED OFF THE VIEWPORT RATHER THAN GATED AT ONE THRESHOLD. A fixed size
   large enough to look deliberate on a 1080 monitor clips its own head on a
   1366x768 laptop, and hiding him below a threshold means a very common
   laptop never sees him at all. The vh term makes him the thing that gives
   when the column runs short, which is right: he is the decoration and the
   ledger is the argument.

   He is still hidden outright below 960, where the whole band is, and on a
   genuinely short window, because a mascot with his head cut off is worse
   than no mascot.

   align-self stops the flex column stretching him to its full width, which
   would fight the height cap; flex: none stops it shrinking his height
   independently of his width, which would squash him. */
.auth__art { display: none; }

@media (min-width: 60em) and (min-height: 40em) {
  .auth__art {
    display: block;
    align-self: flex-start;
    flex: none;
    inline-size: auto;
    max-block-size: min(34rem, 42vh);
  }
}

/* On a big monitor there is room for him to be a presence rather than a
   decoration, and the band is half of it. */
@media (min-width: 90em) and (min-height: 48em) {
  .auth__art { max-block-size: min(40rem, 46vh); }
}

.foot {
  padding: var(--mbx-space-5);
  text-align: center;
}
.foot .hint { max-inline-size: none; }

/* From 640 the fields get a little more room. Below it the form is already
   the full width of the viewport and the cap never binds. */
@media (min-width: 40em) {
  .auth__form { --auth-field: 26.5rem; }               /* 424 */
}

/* THE `$auth_wide` MODIFIER, which auth-top.php promises widens the column.
   It lived in pages/consent.css until 2026-09-04, which meant the promise was
   kept only on the pages that happened to load that stylesheet: the reviewer
   application set the flag, got the class, and laid a two-column form out
   inside a 424px column. A modifier on a shared layout belongs beside the
   layout.

   34rem is the width at which a list of mailboxes and four lines of grant stop
   reading as a very long thin page. A screen needing more than that says so for
   itself by setting --auth-field again, which the application form does. */
.auth--wide .auth__form { --auth-field: 34rem; }

@media (min-width: 60em) {
  .auth__grid { grid-template-columns: 1fr 1fr; }

  /* Only the GUTTER grows here, not the fields. The column has more room
     around it, which is what the extra padding is for; the form itself is
     already the width it should be and making it track the viewport is how a
     sign-in ends up 700px wide on a big monitor. */
  .auth__form {
    --auth-pad: var(--mbx-space-8);                    /* 64 */
  }

  /* padding-block is 48 rather than the 96 this had when the band was text
     only: the character needs the room more than the margin does. `safe`
     centring for the same reason it is on the form column - plain `center`
     clips the top of anything taller than its container, with nothing to
     scroll. */
  .auth__aside {
    display: flex;
    align-items: safe center;
    justify-content: center;
    padding: var(--mbx-space-7) var(--mbx-space-8);
  }

  /* The footer belongs under the form, not across the band. */
  .foot { text-align: start; padding-inline: var(--mbx-space-8); }
}

@media (min-width: 80em) {
  .auth__form { --auth-pad: var(--mbx-space-9); }      /* 96 */
  .foot { padding-inline: var(--mbx-space-9); }
}

/* The callout carries its own bottom margin for the shell's content column,
   where blocks stack in normal flow. The form column is a flex column with a
   24px gap, so the two add up and leave a hole. The gap wins here. */
.auth__form > .callout { margin-block-end: 0; }

/* ---- Validation ---------------------------------------------------------
   THE ERROR SITS UNDER THE FIELD IT BELONGS TO, in the danger ink, and the
   field's own border changes with it. Colour alone would fail WCAG 1.4.1, so
   the message itself is the signal and the colour is reinforcement: a screen
   reader gets it through aria-describedby and aria-invalid, and a customer who
   cannot distinguish the red border still reads the sentence. */
.field__error {
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  color: var(--mbx-danger-ink);
  max-inline-size: var(--mbx-measure);
}
.field--invalid .input { border-color: var(--mbx-danger-ink); }

/* The form-level summary. tabindex="-1" in the markup so a template can move
   focus here; the outline is suppressed because focus arrived programmatically
   rather than from the keyboard, and a ring on a block nobody tabbed to reads
   as a control that is not one. */
.form-summary:focus { outline: none; }
.form-summary__list { padding-inline-start: var(--mbx-space-5); }
.form-summary__list li { list-style: disc; }
.form-summary__list li + li { margin-block-start: var(--mbx-space-2); }

/* The backoff line. Never a warning colour: there is no amber in this system,
   and a wait that is about to end on its own is informational rather than a
   failure. */
.auth__wait { margin-block-start: var(--mbx-space-2); }

/* A disabled control still has to be READABLE. The convention of dropping a
   button to 40% opacity puts its label under 3:1 and makes "why can I not
   press this" impossible to answer. Half opacity on this fill keeps the label
   legible while the state is unmistakable. */
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* ==========================================================================
   THE SIX-DIGIT CODE FIELD

   SIX CELLS, SHOULDER TO SHOULDER, SEPARATED BY A HAIRLINE. Owner ruling,
   2026-08-29: it should look like six boxes and the cursor should move on by
   itself, with nothing to tab into or click.

   IT IS ONE `<input>`, PAINTED AS SIX. That is what makes the "no tabbing"
   part free rather than something JavaScript has to fake: within a single
   field the caret advances as you type, backspace walks back, select-all
   works, and arrow keys do what arrow keys do. Six real inputs would mean
   reimplementing every one of those, and would cost three things that are not
   worth a border:

     - `autocomplete="one-time-code"` stops working, so a phone no longer
       offers the code from the notification. That is the biggest usability win
       available on this screen and it costs one attribute.
     - paste breaks, which is what somebody using a password manager does.
     - a screen reader meets six unlabelled boxes instead of one labelled
       field.

   HOW THE CELLS ARE DRAWN. One repeating gradient, five separators, sized to
   exactly five cells so no line is drawn after the sixth. Everything is
   derived from `--cell`, so changing that one value keeps the separators, the
   letter-spacing and the width in step. Getting them out of step is the whole
   failure mode of this pattern: digits drifting away from their boxes as the
   field fills.

   THE HALF-CELL INDENT IS NOT A FUDGE. `letter-spacing` adds its space AFTER
   each glyph, so every digit sits hard against the left of its cell with the
   gap to its right. Shifting the whole line by half a letter-space centres
   each digit in its own box. Without it the code reads as consistently
   off-centre in a way people see and cannot name.
   ========================================================================== */
/* THE FIELD IS THE CONTAINER, so a cell can be one sixth of the actual column
   rather than one sixth of a guess. Scoped to the field that holds the code and
   not to `.auth__form`, so the containment this turns on cannot reach any other
   control on the page. */
.field:has(> .input--code) { container-type: inline-size; }

.input--code {
  /* ONE CELL IS ONE SIXTH OF THE COLUMN, AND THE FONT DOES NOT GET A VOTE.

     This was `4.2ch`, chosen because six of them measured 424px in Martian Mono
     at 24px, which is the form column and therefore the Sign in button. That
     tied the width of the field to the metrics of a font, and a font is not a
     layout system: the number was only ever correct at one font size, in one
     face, at one viewport. Everywhere else the two disagreed. Measured at ten
     widths after the owner said it looked wrong: 0.6px narrow at desktop, and
     45 to 59px narrow between 360 and 480, which is most phones. A fallback
     face would have moved every one of those numbers again.

     `100cqi` is the column, exactly, because the container is this field and
     the field is a flex item stretched to the column - the same 100% the button
     resolves. Minus the 2px of border, because `box-sizing` is border-box and
     the digits and the hairlines are painted inside it. Six of these now fill
     the field precisely, whatever the face, whatever the size, at every width,
     with no number in this file that has to be re-measured when any of that
     changes. */
  --cell: calc((100cqi - 2px) / 6);

  /* The hint sits directly above and the field is 56px tall, so the 8px the
     `.field` stack gives every control read as the sentence resting on the box.
     16px is the separation this one earns for being the only thing on the
     page. */
  margin-block-start: var(--mbx-space-2);

  /* TALLER THAN A NORMAL CONTROL, DELIBERATELY. This field is the only thing
     on the screen; at the standard 40px it read as a minor utility input for
     the page's single act. 56px is a multiple of 8, so it sits on the spacing
     grid, and it gives the cells enough height to read as boxes rather than as
     a divided line. */
  block-size: 3.5rem;

  /* THE SIZE NOW SETS ONLY HOW BIG A DIGIT IS, which is what it should always
     have been. It used to set the width as well, and the two jobs pulled in
     opposite directions: the clamp had to have a 15px floor - small for a
     six-digit code somebody is copying off a phone - purely because an 18px
     floor made the field wider than a 360px screen and scrolled the page
     sideways.

     Kept as a clamp because it tracks the cell nicely by accident: the column
     runs 272px to 424px and the cell with it, and 4.2vw runs 13px to 24px over
     roughly the same range, so the digit stays in proportion to the box it sits
     in. It is now free to be re-tuned on how it LOOKS, with no layout riding on
     the answer. */
  font-size: clamp(0.9375rem, 4.2vw, 1.5rem);
  line-height: 1.5rem;
  /* A mono face is what makes a cell a fixed width at all. Proportional digits
     would make the code shuffle sideways as it is typed, and no amount of
     tuning fixes that. */
  font-family: var(--mbx-font-mono);

  letter-spacing: calc(var(--cell) - 1ch);
  text-indent: calc((var(--cell) - 1ch) / 2);
  /* The same 100% the button beneath resolves, so the two cannot differ. */
  inline-size: 100%;
  padding-inline: 0;
  text-align: start;

  /* Five hairlines, one at the end of each of the first five cells. The size
     stops the repeat after five, which is why there is no line down the right
     edge doubling the border. */
  background-image: repeating-linear-gradient(
    to right,
    transparent 0,
    transparent calc(var(--cell) - 1px),
    var(--mbx-border) calc(var(--cell) - 1px),
    var(--mbx-border) var(--cell)
  );
  background-repeat: no-repeat;
  background-size: calc(var(--cell) * 5) 100%;
}

/* The caret is the thing that shows which cell is next, so it earns a little
   more presence than the browser's default hairline. */
.input--code { caret-color: var(--mbx-accent); }

/* Only the height, and only on a phone. The WIDTH needs no breakpoint at all
   now: it is the column, and the column already knows how wide it is. */
@media (max-width: 30rem) {
  .input--code { block-size: 3rem; }
}
