/* Auth pages (sign-up, sign-in, verify) — Task 93, polish pass Task 95,
   two-column layout pass (the form beside a poster gallery, mirroring the
   homepage's .sheet-hero: content column left, catalog visual right).
   Adds components; theoria.css owns every token and every shared control,
   so nothing here restyles .field, .sheet-head, or button{} — see the auth
   design notes for the one exception (input[type="email"], added to
   theoria.css's own shared input rule, not here).

   Lime still appears nowhere but the submit button (and its own loading
   spinner), and every other value is a theoria.css token. Depth
   (--shadow-card), an entrance for the card, and a code field that reads
   as six boxes while staying the one real <input> the
   autocomplete="one-time-code"/paste behaviour depends on. No Theoria
   wordmark on these pages -- the card's own "Sign up"/"Sign in"/"Enter your
   code" heading is identity enough, and one less thing between a reader and
   the form. */

/* Two columns at the width home.css's .sheet-hero already uses this
   container at (1240px, minus padding): the card on a plain background,
   a poster gallery filling the other side. Grid's own default align-items
   (stretch) is left alone deliberately -- both columns fill the full row
   height, so the card reads as a real panel the size of the gallery beside
   it, not a small box floating in a tall row. Below the breakpoint the
   gallery becomes a short band under the card, the same move .sheet-hero
   makes for .contact-sheet. */
.auth-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--space-9);
  min-height: min(80vh, 680px);
  margin-bottom: var(--space-9);
}

.auth-panel {
  display: flex;
}

/* Square, like every other sheet on the site (--radius: 2px, "sheets and
   plates, not pills" -- theoria.css). An earlier pass gave this card its
   own 20px rounded corner as a deliberate exception; tried edgy instead
   (matching the sitewide sheet convention explicitly, not just leaving
   border-radius unset -- unset would mean a literal 0, a different, new
   value nothing else on the site uses either). */
.auth-card {
  display: flex;
  flex-direction: column;
  /* Stretched to fill the panel (its own height, from .auth-panel's flex
     stretch); centering the form vertically inside that is what actually
     reads as "a real panel," not just a wider box. */
  justify-content: center;
  width: 100%;
  max-width: 36rem;
  padding: var(--space-9);
  background: var(--sheet);
  border: var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  animation: auth-card-in var(--dur-slow) var(--ease) both;
}

@keyframes auth-card-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* .sheet-head is the shared page-title header (movie/person/studio pages,
   the account page too), left-aligned there by default; centered here
   because on this card, sitting above a centered form with nothing beside
   it to align against, a left-aligned heading reads as adrift rather than
   anchored. Scoped to .auth-card so the shared class's own default is
   untouched everywhere else it's used. */
.auth-card .sheet-head {
  text-align: center;
}

.auth-card .sheet-sub {
  margin-inline: auto;
  text-wrap: balance;
}

.auth-card .nowrap {
  white-space: nowrap;
}

/* The gallery: home.css's .contact-sheet recipe (gap, stagger, desaturate/
   hover treatment) at a taller tile floor -- 116px, the same value
   theoria.css's own .poster-grid.is-tight already uses, not a new number
   invented for this file. .contact-sheet's own 84px is tuned for a sheet
   that runs the width of the whole homepage and is meant to read as "the
   entire catalog, dense"; in this narrower column at 84px the posters were
   too small to actually look at. Two more differences: plain tiles, not
   links (this one is decorative in a way a stray click shouldn't be able
   to leave the sign-up/sign-in flow over), so the hover cue below is
   motion only, never lime -- lime marks something actionable everywhere
   else on the site (see .auth-resend's own note), and a tile here isn't. */
.auth-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: 4px;
  align-content: start;
  height: 100%;
  max-height: min(80vh, 680px);
  overflow: hidden;
}

/* overflow: clip, not hidden: a hidden tile becomes a scroll container, whose
   automatic minimum height is 0, and the height-capped grid then squeezed every
   row into overlapping strips. clip still contains the hover zoom. */
.auth-tile {
  position: relative;
  aspect-ratio: 2 / 3;
  background: var(--sheet-2);
  overflow: clip;
  opacity: 0;
  animation: auth-tile-in var(--dur-slow) var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 9ms);
}

.auth-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.82) contrast(0.96);
  transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.auth-tile:hover img {
  filter: none;
  transform: scale(1.06);
}

@media (prefers-color-scheme: dark) {
  .auth-tile img {
    filter: saturate(0.95) brightness(0.86);
  }

  .auth-tile:hover img {
    filter: none;
  }
}

@keyframes auth-tile-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .auth-tile {
    opacity: 1;
    animation: none;
  }
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin-top: var(--space-7);
  /* Carried as mask sources -- docs/icons/ (Material Symbols PNG exports the
     user supplied), copied into static/img/icons/ under short names since
     Django only serves what's under static/. Masked rather than plain
     <img>s so each stays tinted per theme/state (--ink-faint below) like
     every other icon on the site: the source PNGs are baked a flat light
     grey meant for a dark surface, and would all but disappear on this
     card in light mode used as-is. Scoped to this file/class rather than
     theoria.css's shared :root: only the auth pages use them. */
  --icon-at: url("../img/icons/alternate-email.b79b527cc117.png");
  --icon-mail: url("../img/icons/mail.9652c3bc0c28.png");
}

/* The small uppercase label above a field -- Theoria's own type language
   (the same var(--font-label) tracked-caps treatment button{} already
   uses), not the reference screenshot's plain sentence-case one. Only
   username/email use it now -- the code field has no visible label (an
   aria-label carries its accessible name instead), since "Enter your
   code" above it and six digit boxes already say what the field is. */
.field-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-muted);
}

.auth-form .field {
  width: 100%;
}

/* Username and email get a leading icon inside the pill, the same
   mask-on-::before technique theoria.css's own search field already uses
   (.field:has(input[type="search"])::before) -- scoped to .auth-form so it
   never touches an email field anywhere else on the site. The code field
   below deliberately does NOT get one this way: it's sized to fit-content
   and centred as a unit (see its own rule below), and adding a fourth
   element to that flex row would throw its careful centring off --  its
   icon lives in its label instead. */
.auth-form .field:has(input[name="username"])::before {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  margin-left: var(--space-4);
  background-color: var(--ink-faint);
  -webkit-mask-image: var(--icon-at);
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-image: var(--icon-at);
  mask-mode: alpha;
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
}

.auth-form .field:has(input[type="email"])::before {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  margin-left: var(--space-4);
  background-color: var(--ink-faint);
  -webkit-mask-image: var(--icon-mail);
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-image: var(--icon-mail);
  mask-mode: alpha;
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
}

/* The icon already supplies the left inset, same as search's own rule. */
.auth-form .field:has(input[name="username"]) input[name="username"],
.auth-form .field:has(input[type="email"]) input[type="email"] {
  padding-left: var(--space-3);
}

/* Matches .field:focus-within's own lime border-color transition (already
   defined in theoria.css) with a matching, equally subtle lift -- the two
   read as one motion instead of the border alone snapping over. */
.auth-form .field {
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.auth-form .field:focus-within {
  transform: translateY(-1px);
}

/* A field the browser (or the server, after submit) considers invalid gets
   the same red as .form-error below, so the field itself
   -- not just a line of text under it -- shows something is wrong.
   :user-invalid, not :invalid, so a required-but-empty field (the code field
   has no placeholder to test against) doesn't read as an error before the
   reader has touched it. */
.auth-form .field:has(input:user-invalid),
.auth-form div:has(> .form-error) > .field {
  border-color: var(--danger);
}

/* The code field has no border of its own to redden (its .code-cell spans
   carry the border instead -- see below), so the same two conditions are
   repeated against them directly. */
.auth-form .field:has(input:user-invalid) .code-cell,
.auth-form div:has(> .form-error) > .field .code-cell {
  border-color: var(--danger);
}

/* The one-time-code field: a single real <input> -- see the auth design
   notes for why (autocomplete="one-time-code" and paste both depend on it
   being one field, and it renders the digits themselves -- see its own
   rule below). The six rounded boxes are a `.code-cells` overlay of plain,
   empty <span>s sitting behind it purely for the border/background (see
   verify.html); theoria.js's initCodeInput() only adds .is-active to
   whichever one sits under the caret, so the DOM still has exactly one
   real <input> while the reader sees six independent, individually-
   highlighted cells -- a variant of the shadcn/ui "input-otp" technique. */
.auth-form .field:has(.code-input) {
  /* A long pill reads oddly at this width -- unlike the username/email
     pills this isn't a field skimmed as a whole, it's read digit by digit,
     so it drops the shared pill shell entirely and lets the six cells
     supply their own borders instead.
     width:fit-content, not auto: .field here is a normal block box (its
     parent is a plain wrapping <div>, not a flex container -- .auth-form's
     own flex row never reaches it), and block-level auto width means "fill
     the containing block," not "shrink to content," so plain `auto` would
     silently stay full-width with nothing for margin-inline:auto to centre
     within. Only a sized box (fit-content, max-content, an explicit length)
     leaves that spare space for the margin to distribute. */
  /* Declared here, not on .code-cell: .code-input needs the same value (its
     letter-spacing math below) and the two aren't nested inside each other,
     only both descendants of this .field -- a custom property set on their
     common ancestor is what lets both read one number. */
  --cell: 2.75rem;
  position: relative;
  border: none;
  border-radius: 0;
  background: none;
  width: fit-content;
  margin-inline: auto;
}

.code-cells {
  display: flex;
  gap: var(--space-3);
  pointer-events: none;
}

.code-cell {
  width: var(--cell);
  height: 3.25rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.625rem;
  background: var(--paper);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

/* The cell under the caret -- the same border+wash pairing selected chips
   and menu options already use sitewide (theoria.css), not a one-off glow
   invented for this field. */
.code-cell.is-active {
  border-color: var(--lime-mark);
  background: var(--lime-wash);
}

/* `.auth-form .code-input`, not bare `.code-input`: theoria.css's shared
   `input[type="text"], input[type="search"] { background: none; ... }`
   (a type+attribute selector) outranks a single class on specificity alone
   -- see the CSS specificity note this cost real debugging time to work out
   (for_learning.md, Task 95 UI pass). Two classes beats one regardless of
   load order or the other selector's own type component. */
.auth-form .code-input {
  /* Sits on top of .code-cells (behind it in the DOM but absolutely
     positioned, which stacks above the cells' plain in-flow boxes) and
     renders the actual digits itself, in view -- there is no second copy
     of the text anywhere, so there's nothing for JS mirroring to fail to
     keep in sync. initCodeInput() only ever adds the .is-active box behind
     whichever digit sits under the caret; it was never responsible for
     making typed digits visible, and now can't be. */
  /* One pitch wider than the six cells: letter-spacing is also added after
     the sixth digit, so with the caret past it the text outgrows a 100%-wide
     input and the browser scrolls it sideways to keep the caret in view --
     every digit slides left out of its box the moment the last one lands.
     The spare pitch leaves room for that trailing spacing; clip-path hides
     it (and the caret parked in it) so nothing draws outside the cells. */
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(100% + var(--cell) + var(--space-3));
  height: 100%;
  clip-path: inset(0 calc(var(--cell) + var(--space-3)) 0 0);
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  text-align: left;
  /* Lines up each character (and therefore each click, which the browser
     resolves against this same spacing) with the centre of its visual box:
     the pitch between characters is a full cell *plus* the gap between
     boxes, not just the cell -- .code-cells has no trailing gap after the
     last box, so the total spread still matches its rendered width. */
  letter-spacing: calc(var(--cell) + var(--space-3) - 1ch);
  /* Centres digit 1 in cell 1: (cell - glyph) / 2. The gap is not halved in
     here -- it sits after a cell, not around it. */
  text-indent: calc((var(--cell) - 1ch) / 2);
}

/* Red text inside the card, right where the reader is looking -- field
   errors, form-wide errors, and queued messages (base.html's sitewide notice
   list is suppressed on these pages) all share it. */
.form-error {
  margin-top: var(--space-2);
  color: var(--danger);
  font-size: var(--text-sm);
  font-weight: 500;
}

.auth-form > .form-error {
  margin-top: 0;
  text-align: center;
}

/* The code field is centred as a unit, so its error is too. */
.auth-form div:has(.code-input) > .form-error {
  text-align: center;
}

/* A server-rendered error (a full page load) can afford to just be there;
   one added by initInlineValidation() (theoria.js) appears while the
   reader is still on the page, so it gets a small entrance instead of
   popping in -- the same "something changed, here's where" cue the rest
   of this file gives a field on focus. */
.form-error.is-live {
  animation: auth-error-in var(--dur-fast) var(--ease) both;
}

@keyframes auth-error-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.btn-block {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  width: 100%;
  text-align: center;
}

/* The lime spinner shown in place of the label text while a submit is in
   flight (theoria.js's initAuthFormSubmitState()) -- --ink-on-lime, the
   same fixed colour the button's own label already uses, so it reads as
   the button working rather than as a new element. Built, not hidden, so
   with JS off (no class is ever added) it never exists in the DOM. */
.btn-spinner {
  width: 1em;
  height: 1em;
  border: 2px solid var(--ink-on-lime);
  border-right-color: transparent;
  border-radius: 50%;
  animation: auth-spin 0.6s linear infinite;
}

@keyframes auth-spin {
  to {
    transform: rotate(360deg);
  }
}

.auth-alt {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: var(--hairline);
  color: var(--ink-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* Was a bare text link; now the sitewide secondary-action pill --
   background var(--sheet-2), no border, bold, mass instead of a hairline --
   the same family as .collection-action/.chip/.pagination a. Still not
   lime at rest: the code-entry button above stays the one lime element on
   the page (lime marks a measurement, not a form), so this only *fills* to
   the lime wash on hover, same as those. */
.auth-resend {
  margin-top: var(--space-5);
  text-align: center;
}

.auth-resend button {
  min-height: 2rem;
  padding: var(--space-2) var(--space-4);
  background: var(--sheet-2);
  border: none;
  border-radius: var(--radius-pill);
  color: var(--ink);
  font-family: var(--font-label);
  font-weight: 700;
  font-stretch: 78%;
  font-size: var(--text-3xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}

.auth-resend button:hover:not(:disabled) {
  background: var(--lime-wash);
  color: var(--lime-deep);
  text-decoration: none;
}

.auth-resend button:disabled {
  background: var(--sheet-2);
  color: var(--ink-faint);
  cursor: default;
}

@media (max-width: 900px) {
  .auth-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-6);
    min-height: 0;
  }

  .auth-panel {
    justify-content: center;
  }

  /* Stacked, the gallery is a band of exactly two full rows: a fixed five
     columns and ten tiles, so no width ever shows a sliver of a third row.
     height:auto, not the desktop 100% -- with no grid row to fill, a
     percentage height squeezed each 2:3 tile into overlapping strips. */
  .auth-gallery {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    align-content: start;
    height: auto;
    max-height: none;
  }

  .auth-tile:nth-child(n + 11) {
    display: none;
  }
}

@media (max-width: 720px) {
  .auth-card {
    max-width: none;
    padding: var(--space-6);
  }

  .auth-card .sheet-sub {
    font-size: var(--text-base);
  }

  /* Six 2.75rem cells (16.5rem) plus the card's own padding no longer fits
     a narrow phone screen at the desktop cell size, so the whole field
     scales down as one unit -- .code-cell's width and .code-input's
     letter-spacing/text-indent all read off the one --cell custom property
     set on their shared .field ancestor, so shrinking it once keeps every
     digit centred in its (now smaller) box instead of drifting out of
     alignment the way editing individual values here would risk.
     `.auth-form .code-input`, not bare `.code-input`, for the same
     specificity reason as the desktop rule above -- font-size here would
     otherwise lose to the shared input rule's own font-size. */
  .auth-form .field:has(.code-input) {
    --cell: 2.35rem;
  }

  .code-cell {
    height: 2.9rem;
    font-size: var(--text-lg);
  }

  .auth-form .code-input {
    font-size: var(--text-lg);
  }
}

@media (max-width: 360px) {
  .auth-form .field:has(.code-input) {
    --cell: 2.05rem;
  }
}

/* --- Touch ------------------------------------------------------------------
   Under 16px iOS Safari zooms the page when a field takes focus, and it stays
   zoomed after the keyboard goes away. The shared input rule sits below that
   on a phone, so the floor is set here, on the sign-in forms only. The resend
   button and the "Sign up / Sign in" links under the card are small text
   targets, so they get a 44px hit area without changing how they look. */
@media (pointer: coarse) {
  .auth-form input:not(.code-input) {
    font-size: max(16px, 1em);
  }

  .auth-resend button,
  .auth-card .sheet-foot a,
  .auth-card p a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* The on-screen keyboard takes half the viewport; a hero sized to 80vh
     would push the card behind it, so the card is only as tall as it is. */
  .auth-hero {
    min-height: 0;
  }
}

/* A phone on its side has ~390px of height and a keyboard that fills most of
   it. Drop the poster band entirely: the form is the only thing that matters. */
@media (max-height: 480px) and (orientation: landscape) {
  .auth-gallery {
    display: none;
  }

  .auth-card {
    padding: var(--space-5) var(--space-6);
  }

  /* The title is sized for a tall card; on 390px of height it would be a
     third of the screen. */
  .auth-card .sheet-title {
    font-size: var(--text-xl);
  }
}

/* --- TV ----------------------------------------------------------------------
   Same layout as desktop, sized for a sofa. Under the shell's zoom, 80vh is
   multiplied by the zoom, so the hero would be taller than the screen; --vh is
   the pre-zoom unit. The card and the poster band are capped to the same
   number so they stay a pair. */
html.is-tv .auth-hero {
  min-height: min(calc(80 * var(--vh, 1vh)), 680px);
}

html.is-tv .auth-gallery {
  max-height: min(calc(80 * var(--vh, 1vh)), 680px);
}

html.is-tv .auth-card {
  max-width: 40rem;
}

html.is-tv .auth-form .field,
html.is-tv .auth-card .btn-block,
html.is-tv .auth-card button[type="submit"] {
  min-height: 3.5rem;
}

/* Fields already turn their frame lime on :focus-within; on a TV that
   hairline is not visible from across the room, so it also gets a halo. */
html.is-tv .auth-form .field:focus-within {
  box-shadow: 0 0 0 3px var(--lime-mark);
}

html.is-tv .code-cell {
  height: 3.75rem;
}

html.is-tv .auth-resend button,
html.is-tv .auth-card p a {
  display: inline-flex;
  align-items: center;
  min-height: 3rem;
  padding-inline: var(--space-3);
}

/* The poster band is decoration and not focusable, so on a small TV
   (hamburger layout, stacked) it would only add scroll for a remote user. */
@media (max-width: 900px) {
  html.is-tv .auth-gallery {
    display: none;
  }
}
