/* ===========================================================================
   Sign in — the front door
   ---------------------------------------------------------------------------
   Structure is a DOORWAY, not a card. A solid wall (--slate, the near surface)
   carries the whole form; past its single hairline the ground drops to --abyss
   and opens into a field. In dark mode raised surfaces are lighter than the
   page, so wall-lighter-than-field is the token system's own elevation rule
   read literally: the form is near you, the field is far away.

   Nothing is centred in a rounded box, nothing has a soft grey shadow, and the
   only gradient is a 7% spill anchored to the wall's EDGE — light through a
   door left ajar, not a blob behind a card.

   THE FIELD is a SIGNAL LATTICE: a sparse matrix of small squares with two
   waves of brightness and scale running through it on crossing diagonals. It
   is drawn into a canvas by login.js; everything in this file is the layer it
   paints into, the light that spills past the wall, and the seam.

   The regularity is deliberate — a matrix is what hardware looks like — and
   it is NOT graph paper. A fixed seeded jitter of a couple of pixels, a
   per-cell amplitude, cells dropped at random, and the same density falloff
   toward the wall the previous field used. Two waves rather than one: a
   single front at a fixed interval reads as a metronome, and the second,
   slower and shallower pass on a different diagonal makes the timing
   irregular without making it busy.

   Cost, measured rather than assumed: 60.0 fps with 0.57 ms of main thread
   per frame, 3.4% of a 16.7 ms budget, for roughly 500 cells. That number is
   only trustworthy because the first attempt at measuring it was wrong — 2D
   canvas calls are deferred, so timing them inside the rAF callback reported
   0.03 ms for work that had not happened yet. The figure above forces
   rasterisation before it stops the clock. The cost is low because alpha is
   quantised to 14 buckets and each bucket is one Path2D and one fill, so 500
   squares are 13 fill calls, not 500.

   This one DOES have a frame loop, which the CSS field it replaces did not,
   and that is the one thing about this screen that can leak. login.js owns
   it: the loop checks that its canvas is still in the document and shuts
   itself and both observers down when the router replaces the view. rAF does
   not fire on a hidden tab, so there is still no visibilitychange case.

   It holds no data: no axis, no series, no trend, no labels, nothing
   measured. A lattice is the one shape here that could be mistaken for a
   readout, so it carries none of the things that would make it one.
   ========================================================================= */

.gate {
  --pad: clamp(24px, 4.6vw, 56px);
  --spill: 7%;

  /* The field's own ink is NOT a custom property any more. The lattice picks
     its rest and peak alphas in login.js from the resolved theme, because the
     two appearances are not one multiplier apart: on paper the floor has to
     come UP (a 0.08 square is invisible on white) while the ceiling comes
     DOWN, which is a compressed range rather than a scale factor. Inverting a
     single --mk-ink is what the previous field did and it would have made
     light mode either a grey wash or a page of soot. */

  position: relative;
  min-height: 100dvh;
  display: grid;
  grid-template-columns: clamp(360px, 33vw, 560px) minmax(0, 1fr);
  background: var(--abyss);
  overflow: hidden;
}

:root[data-theme="light"] .gate {
  --spill: 5%;
}

/* ---- the wall --------------------------------------------------------- */

.gate__wall {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--pad);
  background: var(--slate);
  border-right: 1px solid var(--hair);
}

/* The note is pinned to the foot, so it is out of this flow — otherwise
   margin-top:auto fights justify-content:center and wins. The centred group
   only yields room for it when there is one to yield room for. */
.gate__wall:has(.gate__note) { padding-bottom: calc(var(--pad) * 2 + 2.6em); }

/* The one-shot entrance: a signal hairline draws down the seam where the wall
   meets the field, then leaves. The --hair border underneath is what stays.
   One gesture, one direction, and it is the direction the field moves in.
   It lives inside the field because the seam is the field's own edge; the
   z-index puts it over the spill, which is strongest right there. */
.gate__edge {
  position: absolute;
  z-index: 3;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: linear-gradient(to bottom,
    transparent, var(--signal) 14%, var(--signal) 86%, transparent);
  transform-origin: top;
  opacity: 0;
  animation: gate-edge 2.2s var(--e-standard) 0.2s backwards;
  pointer-events: none;
}

@keyframes gate-edge {
  0%   { transform: scaleY(0); opacity: 0.8; }
  56%  { transform: scaleY(1); opacity: 0.8; }
  100% { transform: scaleY(1); opacity: 0; }
}

/* The wall's contents arrive on a slower ramp than the product's shared
   entrance: this is a front door, not a table of rows. */
.gate__wall > .gate__word,
.gate__wall > .gate__form,
.gate__wall > .gate__note {
  animation: rise var(--d-slow) var(--e-standard) backwards;
}
.gate__wall > .gate__word { animation-delay: 0.14s; }
.gate__wall > .gate__form { animation-delay: 0.30s; }
.gate__wall > .gate__note { animation-delay: 0.46s; }

/* The wordmark, and the larger of the display face's two jobs.

   Every number in the clamp is the serif's, multiplied by 0.9488, and that
   factor is measured rather than chosen: each candidate face's x-height was
   read off a canvas at 100px, and 0.9488 is what brings Space Grotesk 700's
   0.496 em x-height to 32.0px where the serif's clamp used to top out at
   68px and 34.7px.

   It comes DOWN, which is the previous note read backwards. The serif was set
   BIGGER than the sans because its lowercase advance was 0.392 em; this face
   runs 0.616, so "Teletoken" is half again as wide at the same size and 68px
   no longer fits the wall. At 64.5px it fills 80.7% of the wall's inner
   width, which leaves room for a longer name and for a narrower wall.

   Tracking tightens from -0.012em to -0.03em. A geometric grotesque at
   display size has open counters and generous sidebearings, and the serif's
   near-zero tracking left this face looking spaced rather than set. */
.gate__word {
  font-family: var(--f-display);
  font-size: clamp(38px, 5.31vw, 64.5px);
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.03em;
  color: var(--ice);
}

.gate__form {
  margin-top: clamp(var(--s6), 7vh, var(--s8));
  max-width: 360px;
  display: grid;
  gap: var(--s3);
}

.gate__label {
  display: block;
  font-size: var(--t-foot);
  font-weight: var(--w-medium);
  color: var(--mist);
  margin-bottom: var(--s2);
}

/* The button matches the field's height exactly, so the two stack as one
   object rather than as two controls that happen to be adjacent. */
.gate__form .btn--block { min-height: 44px; }

.gate__note {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: var(--pad);
  font-size: var(--t-cap);
  line-height: 1.4;
  color: var(--dim);
}

/* ---- the field -------------------------------------------------------- */

.gate__field {
  position: relative;
  overflow: hidden;
  /* No `color` here any more. The previous field painted every mark with
     currentColor; the lattice paints with a resolved rgba() it builds in
     login.js, because a canvas cannot inherit a CSS colour. --signal is read
     off :root there instead. */
}

/* Light spilling past the wall's edge. Linear and edge-anchored on purpose:
   a radial glow behind the form is the default look and this is not it.

   z-index 2 puts it OVER the lattice, which is the ordering the composition
   was chosen at: the spill is atmosphere between the viewer and the matrix,
   so the cells nearest the wall are washed by it rather than drawn on top of
   it. Under the seam, which is 3. */
.gate__field::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(97deg,
    color-mix(in oklab, var(--signal) var(--spill), transparent) 0%,
    transparent 54%);
  pointer-events: none;
}

/* The layer the lattice paints into.

   The mask is the field's depth falloff, and it is here rather than in the
   drawing code for a reason the previous field did not have to face. That one
   thinned its marks near the wall when it PLACED them, which works when
   nothing moves. A wave travels, so a cell that was placed in the quiet
   region still brightens there. A mask holds wherever the motion goes, and it
   costs one compositor operation rather than a per-cell term in the loop.

   The canvas is sized in device pixels by login.js and stretched by these
   100% rules, so a 2x screen gets a 2x backing store and the squares stay
   crisp — which is the entire point of this treatment and the reason every
   coordinate in the draw is rounded. */
.gate__fx {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  -webkit-mask-image: linear-gradient(to right,
    transparent 0%, rgba(0, 0, 0, 0.26) 10%, rgba(0, 0, 0, 0.86) 38%, #000 62%);
  mask-image: linear-gradient(to right,
    transparent 0%, rgba(0, 0, 0, 0.26) 10%, rgba(0, 0, 0, 0.86) 38%, #000 62%);
  pointer-events: none;
}

/* ---- phone: the field becomes a window above the wall ------------------ */

@media (max-width: 900px) {
  .gate {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: clamp(190px, 34dvh, 320px) minmax(0, 1fr);
  }
  /* The field moves above the form so the pass still descends TOWARD it. */
  .gate__field { grid-row: 1; border-bottom: 1px solid var(--hair); }

  /* Not the desktop composition rotated. Centring the group left a flat hole
     under the seam — barely there in dark, a visible gap in light — so the
     name sits up against the seam and the form drops into the thumb's reach,
     which is where a phone wants its one action anyway. */
  .gate__wall {
    grid-row: 2;
    justify-content: flex-start;
    padding-top: clamp(var(--s6), 6dvh, var(--s7));
    border-right: 0;
  }
  .gate__form {
    margin-top: auto;
    margin-bottom: clamp(var(--s6), 11dvh, 110px);
    max-width: none;
  }

  /* The depth mask turns with the structure: the seam is now the bottom edge,
     so the falloff runs down rather than left. Without this the field faded
     toward the wrong side and the lattice piled up against the form.

     The cell spacing is handled in login.js, not here: below 900px it drops
     from 33px to 26px and the density falloff is switched off, because
     squeezing the desktop spacing into a third of the width packs the matrix
     into a texture. That is a decision about what is drawn, so it lives with
     the drawing. */
  .gate__fx {
    -webkit-mask-image: linear-gradient(to bottom,
      #000 0%, #000 46%, rgba(0, 0, 0, 0.42) 82%, transparent 100%);
    mask-image: linear-gradient(to bottom,
      #000 0%, #000 46%, rgba(0, 0, 0, 0.42) 82%, transparent 100%);
  }
  .gate__field::after {
    background: linear-gradient(185deg,
      transparent 42%,
      color-mix(in oklab, var(--signal) var(--spill), transparent) 100%);
  }
  /* The edge draw turns with the structure: left to right, along the seam. */
  .gate__edge {
    top: auto;
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    height: 1px;
    background: linear-gradient(to right,
      var(--signal), var(--signal) 70%, transparent);
    transform-origin: left;
    animation-name: gate-edge-h;
  }
}

@keyframes gate-edge-h {
  0%   { transform: scaleX(0); opacity: 0.8; }
  56%  { transform: scaleX(1); opacity: 0.8; }
  100% { transform: scaleX(1); opacity: 0; }
}

/* A very short viewport (landscape phone) gets the wall alone. A 120px strip
   of field is not a field, it is a decorative band. */
@media (max-height: 460px) and (max-width: 900px) {
  .gate { grid-template-rows: minmax(0, 1fr); }
  .gate__field { display: none; }
  .gate__wall { grid-row: 1; border-right: 0; }
  .gate__edge { display: none; }
}

/* ===========================================================================
   Reduced motion
   ---------------------------------------------------------------------------
   Not a frozen frame at an arbitrary instant, and not an empty one either.
   There is almost nothing to do here now, because the field is a canvas: when
   the preference is set login.js never starts the loop and draws ONE frame
   instead, at a phase it SOLVES for so the leading band always crosses the
   same place in the composition — about three fifths across and a little
   above centre — whatever size the field happens to be. Same picture, same
   rule, stopped at a chosen moment.

   Which leaves only the seam. motion.css clamps every duration to 1ms, so the
   one-shot draw would fire as a flash; removing the animation and holding it
   at its end state is what that rule does.
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  .gate__edge { animation: none; opacity: 0; }
}
