/*
 * The login page. Self-contained: this file and ui-kit.css are the whole
 * stylesheet, glass.css is gone.
 *
 * Two columns. In RTL the first grid track is the rightmost, so listing the
 * form first puts it on the right without a single directional property - the
 * same trick the shell uses for its sidebar. Below 1024px the art column is
 * dropped rather than squeezed: an operator locked out on a phone still has to
 * be able to get in, and a decorative panel is the first thing that can go.
 *
 * Two places below use PHYSICAL left/right instead of the logical property, and
 * both say why at the rule. They are not oversights.
 */

.auth-page {
    height: 100%;
    background: var(--canvas);
}

.auth {
    display: grid;
    grid-template-columns: 46% minmax(0, 1fr);
    height: 100%;
    overflow: hidden;
}

/* ==========================================================================
   form column
   ========================================================================== */

/*
 * margin:auto on the child rather than justify-content:center on the parent.
 * Both centre; only margin:auto degrades correctly when the form is taller
 * than the viewport - centring with justify-content pushes the top of an
 * overflowing child out of reach of the scrollbar.
 */
.auth__pane {
    display: flex;
    min-height: 0;
    padding: 40px 34px;
    overflow-y: auto;
    background: var(--canvas);
}

.auth__inner {
    width: 100%;
    max-width: 440px;
    margin: auto;
}

.auth__head { margin-bottom: 8px; }

.auth__mark {
    display: grid;
    place-items: center;
    width: 38px; height: 38px;
    margin-bottom: 16px;
    border-radius: 11px;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: var(--fs-lg);
    font-weight: 800;
    user-select: none;
}

.auth__title {
    margin: 0 0 3px;
    font-size: var(--fs-2xl);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.4;
}
.auth__sub { margin: 0; font-size: var(--fs-sm); color: var(--ink-3); }

.auth__hint {
    margin: 0 0 14px;
    font-size: var(--fs-sm);
    color: var(--ink-2);
    line-height: 1.9;
}

/*
 * The feedback slot.
 *
 * It always holds one alert's worth of height, empty or not, so a rejected
 * password does not move the field the operator is about to retype into - the
 * single most common interaction on this page happens to be the retry. The
 * cost is a fixed gap under the heading, which is roughly the whitespace the
 * layout wanted there anyway.
 */
.auth__slot { min-height: 48px; }
.auth__slot .alert { margin: 0; padding: 8px var(--sp-3); }

/* ---- fields ---------------------------------------------------------------
   44px is a deliberate floor: this form is the one screen a phone user cannot
   route around, and ui-kit's compact 34px control is sized for dense tables. */

.auth__form .field { margin-bottom: 14px; }
.auth .input {
    min-height: 44px;
    padding: 10px 13px;
    border-radius: var(--radius-md);
}

.auth .btn--block {
    width: 100%;
    min-height: 44px;
    margin-top: 6px;
    font-size: var(--fs-md);
    border-radius: var(--radius-md);
}

/* Reuses ui-kit's spinner keyframe rather than declaring a second one, so the
   project's cap of two looping animations in total is unchanged. */
.spinner--btn {
    width: 14px; height: 14px;
    border-width: 2px;
    border-color: rgba(255, 255, 255, 0.35);
    border-top-color: var(--accent-ink);
}

/* ---- password field -------------------------------------------------------
   The wrapper inherits the page's RTL, but the input carries dir="ltr" so its
   own text runs the other way. A logical property on the button resolves
   against the wrapper and the same logical property on the input resolves
   against the input, which puts them on OPPOSITE edges - this page shipped
   with exactly that bug once. Both are pinned with physical left, which is the
   inline-end (far) edge in RTL and the conventional place for the toggle, and
   the input's padding-left keeps its left-to-right text clear of it. */

.pw { position: relative; display: block; }
.pw .input { padding-left: 46px; }

.pw__toggle {
    position: absolute;
    left: 5px;
    top: 50%;
    transform: translateY(-50%);
    display: grid;
    place-items: center;
    width: 34px; height: 34px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--ink-3);
    transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.pw__toggle:hover { background: var(--hover); color: var(--ink); }
.pw__icon--hide { display: none; }
.pw__toggle[aria-pressed="true"] .pw__icon--show { display: none; }
.pw__toggle[aria-pressed="true"] .pw__icon--hide { display: block; }

/* Empty until login.js writes into it, and holding its line of height either
   way, so the submit button does not jump as Caps Lock is toggled. min-height
   and not the [hidden] attribute - base.css would collapse that to nothing -
   and not visibility either, because the span is a live region and its text has
   to actually arrive for the warning to be announced. */
.auth__caps {
    display: block;
    min-height: 18px;
    font-size: var(--fs-xs);
    font-weight: 700;
    line-height: 1.6;
    color: var(--amber-ink);
}

/* ---- one-time code --------------------------------------------------------
   One box per configured digit, never six: otp.length is an installation
   setting. The boxes flex so eight of them still fit the 440px column. */

.otp {
    display: flex;
    direction: ltr;
    gap: 8px;
    justify-content: center;
    margin: 4px 0 18px;
}
.otp__box {
    flex: 0 1 52px;
    min-width: 0;
    height: 56px;
    padding: 0;
    border: 1px solid var(--line-2);
    border-radius: var(--radius-md);
    background: var(--field);
    text-align: center;
    font-family: var(--font-ui);
    font-size: var(--fs-xl);
    font-variant-numeric: tabular-nums;
    font-weight: 800;
    line-height: 1;
    color: var(--ink);
    caret-color: var(--accent);
    -moz-appearance: textfield;
    transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.otp__box::-webkit-outer-spin-button,
.otp__box::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.otp__box:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.otp__box.is-filled { border-color: var(--accent); background: var(--accent-soft); }

/* The masked destination number, always rendered left-to-right. */
.auth__mobile {
    direction: ltr;
    unicode-bidi: isolate;
    display: inline-block;
    font-weight: 800;
    color: var(--ink);
}

/* ---- secondary actions ----------------------------------------------------
   Quiet on purpose: resending a code and starting over are both recoveries,
   not the thing the page is asking for. */

.auth__actions { display: flex; gap: var(--sp-2); justify-content: center; margin-top: 10px; }
.auth__actions--split { justify-content: space-between; }
.auth__resend { font-variant-numeric: tabular-nums; }
.auth__resend:disabled { color: var(--ink-3); opacity: 1; }

/* ---- second-factor chooser ----------------------------------------------- */

.factor-choice { display: grid; gap: var(--sp-2); margin: 0 0 12px; }
.factor-choice__item {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr);
    align-items: center;
    gap: 11px;
    padding: 11px 12px;
    border: 1px solid var(--line-2);
    border-radius: var(--radius-md);
    background: var(--surface);
    text-align: right;
    transition: border-color var(--dur-fast) ease, background var(--dur-fast) ease;
}
.factor-choice__item:hover { background: var(--hover-soft); border-color: var(--accent); }
.factor-choice__item:disabled { opacity: 0.5; }
.factor-choice__icon {
    display: grid; place-items: center;
    width: 32px; height: 32px;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
}
.factor-choice__label { font-weight: 800; font-size: var(--fs-sm); color: var(--ink); }
.factor-choice__hint { display: block; margin-top: 1px; font-size: var(--fs-xs); color: var(--ink-3); }

/* ---- failure shake --------------------------------------------------------
   Finite, three swings, and it moves the whole form block so the eye lands on
   the message that just appeared above it. */

.auth__inner.is-shaking { animation: auth-shake 380ms var(--ease-out); }
@keyframes auth-shake {
    0%, 100% { transform: translateX(0); }
    22% { transform: translateX(-8px); }
    55% { transform: translateX(7px); }
    80% { transform: translateX(-4px); }
}
@media (prefers-reduced-motion: reduce) {
    .auth__inner.is-shaking { animation: none; }
}

/* ==========================================================================
   art column
   ==========================================================================

   One <aside>, two pseudo-elements, two nodes. Everything visual is a
   BACKGROUND LAYER and never a positioned child: a stack of background layers
   on one box is a single paint, while a dozen absolutely positioned divs are a
   dozen boxes to lay out, clip and blend. No blur, no backdrop filter, no
   transform on the panel, no animation at all - the column costs one
   rasterisation at load and nothing afterwards.

   The art is two registers of the same shape:

     GROUND    .auth__art::before - three percentage-positioned pools over a
               base wash, plus the medallion. Percentages rescale with the box,
               so a 21:9 window stretches the light instead of tiling anything.
               The light is placed, not scattered: the ochre pool sits where
               the medallion sits so the drawing has something to be legible
               against, and the deep pool sits under the text.

     STRUCTURE .auth__art::after - the lattice tile, and it sits ABOVE the
               scrim. That is what stops the bottom of the panel from turning
               into a plain dark bar: the pattern runs straight through the
               plinth the text sits on, so the darkening reads as part of the
               printed sheet rather than a rectangle dropped over it.

   THE GEOMETRY. Both SVGs are the same octagon at two scales.

     The tile is the 4.8.8 tessellation - regular octagons with square
     interstices - reduced to its edges. Period 128, so the octagon side is
     128 / (1 + sqrt2) = 53.02 and the four half-edge stubs are 26.51 each; the
     neighbouring tiles complete them, which is why the repeat has no seam to
     find. It is the substrate under a great deal of Persian pattern work
     without being any particular one of them: a nod at hairline weight, not a
     motif dropped in. Both stroke groups are drawn at ONE weight on purpose -
     every octagon boundary is four stubs plus four diamond sides, and at two
     different alphas you read a field of squares instead of octagons.

     The medallion is that octagon as four concentric frames at circumradius
     290 / 246 / 150 / 104, the third rotated 22.5deg so the nesting alternates
     flat-top and vertex-top. The eight ticks run from radius 252 to 284 on all
     eight axes, crossing the outer frame's flat edges (inradius 267.9) - a
     dial, not a sun. Cropped by two edges, it reads as a detail of something
     larger rather than a logo the page forgot to centre.

   RTL. The medallion is off the top-LEFT corner, which in this RTL page is the
   OUTER corner, diagonally opposite the text block on the inner bottom; that
   diagonal runs the same way the page reads. background-position is physical
   in every direction, so an LTR install would need the x offset measured from
   the right and the two side pools swapped. The page is dir="rtl" only and an
   untested mirror would be dead code, so there is none.

   COLOUR. Every value is a locked token or a stated warm neighbour of one.
   ========================================================================== */

.auth__art {
    /* The panel's own palette, declared once. These are NOT tokens and must not
       move into tokens.css: nothing else in the project renders a dark ground,
       and a token nobody else can use is not a token.

       All are neighbours of --accent (#1b3b6f) on the same navy axis, none
       invented at the point of use: #12294a deep navy, #1b3b6f the accent
       itself, #16325c between them, #0b1a30 the near-black the wash falls to,
       plus a steel and a paler sky for the two light pools. The greys of the
       app are strictly neutral; this panel is not grey, it is the brand colour
       at full strength, which is the one place chroma is the point.

       The text is plain white at three alphas rather than a tinted paper. On a
       navy ground a tinted white reads as a colour cast, and white leaves the
       most contrast headroom for the footnote, which is the line with none to
       spare. */
    --art-base:  #12294a;
    --art-navy:  #1b3b6f;
    --art-mid:   #16325c;
    --art-deep:  #0b1a30;
    --art-steel: 108, 148, 196;
    --art-sky:   146, 182, 222;
    --art-pool:  6, 14, 26;
    --art-scrim: 5, 12, 22;
    --art-paper: #ffffff;
    --art-rule:  #93b3d8;

    /* The column's width, named rather than inlined, because the medallion is
       sized against the VIEWPORT and that is only exact while this track really
       is 54vw. Change the grid at the top of the file and change this with it. */
    --art-col: 54vw;
    /*  - the vw term leads at every ordinary aspect ratio, 1024px included;
        - the vh term only bites past roughly 2.3:1, and it is over 100 on
          purpose: a letterbox window should crop the medallion HARDER rather
          than shrink it into a corner sticker;
        - the clamp holds both ends: legible at 600px tall, not a mural at 1600. */
    --art-medallion: clamp(440px, min(120vh, calc(var(--art-col) - 2vw)), 1100px);

    position: relative;
    display: grid;
    align-content: end;
    overflow: hidden;
    padding: 48px;
    /* On the element itself, so the panel is never a white flash while the two
       data URIs decode and the gradients always have a floor under them. */
    background-color: var(--art-base);
    /* A stacking context. Without it the z-indexed pseudo-elements are free to
       sort themselves against the page and the scrim can end up under the
       panel instead of over it. */
    isolation: isolate;
}

/* ---- ground + medallion ---------------------------------------------------
   Five layers, first listed on top. None of the three pools is centred and
   none is wholly inside the frame: a light source you can see all of stops
   looking like light and starts looking like a blob.

   The medallion is pushed off the corner by a fixed fraction of its OWN size,
   so the crop is the same composition at every viewport rather than a fixed
   pixel bite that swallows the drawing on a small screen. */
.auth__art::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='600' viewBox='0 0 600 600'%3E%3Cg fill='none' stroke='%23ffffff'%3E%3Cpath stroke-width='1.5' stroke-opacity='.26' d='M567.9 189v222L411 567.9H189L32.1 411V189L189 32.1h222Z'/%3E%3Cpath stroke-width='1' stroke-opacity='.17' d='M527.3 205.9v188.2L394.1 527.3H205.9L72.7 394.1V205.9L205.9 72.7h188.2Z'/%3E%3Cpath stroke-width='1' stroke-opacity='.23' d='M450 300 406.1 406.1 300 450 193.9 406.1 150 300 193.9 193.9 300 150 406.1 193.9Z'/%3E%3Cpath stroke-width='1' stroke-opacity='.18' d='M396.1 260.2v79.6L339.8 396.1h-79.6L203.9 339.8v-79.6L260.2 203.9h79.6Z'/%3E%3Cpath stroke-width='1.2' stroke-opacity='.17' d='M552 300h32M478.2 478.2 500.8 500.8M300 552v32M121.8 478.2 99.2 500.8M48 300H16M121.8 121.8 99.2 99.2M300 48V16M478.2 121.8 500.8 99.2'/%3E%3C/g%3E%3Ccircle cx='300' cy='300' r='3' fill='%23ffffff' fill-opacity='.26'/%3E%3C/svg%3E"),
        /* sky lift, top outer - what the medallion is drawn against */
        radial-gradient(58% 50% at 19% 11%, rgba(var(--art-sky), 0.30), rgba(var(--art-sky), 0) 64%),
        /* steel, cropped by the inner edge, so the seam with the form column is
           the lit side and the far screen edge is the deep side */
        radial-gradient(48% 44% at 96% 40%, rgba(var(--art-steel), 0.34), rgba(var(--art-steel), 0) 66%),
        /* the pool the text sits in, pushed below the bottom edge */
        radial-gradient(86% 62% at 40% 112%, rgba(var(--art-pool), 0.85), rgba(var(--art-pool), 0) 68%),
        /* base wash, tipped off vertical so it does not read as a stock hero */
        linear-gradient(196deg, var(--art-navy) 0%, var(--art-mid) 44%, var(--art-deep) 100%);
    background-repeat: no-repeat;
    background-position:
        calc(var(--art-medallion) * -0.26) calc(var(--art-medallion) * -0.32),
        0 0, 0 0, 0 0, 0 0;
    background-size: var(--art-medallion) auto, auto, auto, auto, auto;
}

/*
 * The scrim. Always painted, drawn art or uploaded photograph: it is what
 * guarantees the overlay keeps its contrast over an image nobody has seen yet,
 * and over the drawn art it deepens the bottom edge where the text sits. The
 * alphas are not taste - see the measured numbers on .auth__over.
 */
.auth__scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(to top,
        rgba(var(--art-scrim), 0.86) 0%,
        rgba(var(--art-scrim), 0.50) 30%,
        rgba(var(--art-scrim), 0.10) 64%,
        rgba(var(--art-scrim), 0.16) 100%);
}

/* ---- lattice --------------------------------------------------------------
   Painted 1:1 at 128 CSS px, so its 1-unit strokes are 1 device pixel at DPR 1
   and its density is IDENTICAL at 1024px and at 2560px. A pattern that scales
   with the panel is exactly what reads as stretched on a wide screen. 128 is
   chosen, not rounded to: 128 x 1.25, x 1.5 and x 2 are all whole numbers, so
   on fractional-DPR displays every tile lands on the same subpixel phase and
   the repeat cannot beat into moire.

   It is drawn above the scrim, and at roughly half the alpha it could carry
   below one. That is the trade: running the pattern through the plinth is what
   keeps the bottom of the panel alive, and it costs contrast on the quietest
   line, so the numbers on .auth__over are measured with it in place. */
.auth__art::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128' viewBox='0 0 128 128'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1' stroke-opacity='.075'%3E%3Cpath d='M64 0v26.51M64 101.49V128M0 64h26.51M101.49 64H128'/%3E%3Cpath d='M64 26.51 101.49 64 64 101.49 26.51 64Z'/%3E%3C/g%3E%3Cg fill='%23ffffff' fill-opacity='.10'%3E%3Ccircle cx='0' cy='0' r='1.8'/%3E%3Ccircle cx='128' cy='0' r='1.8'/%3E%3Ccircle cx='0' cy='128' r='1.8'/%3E%3Ccircle cx='128' cy='128' r='1.8'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: repeat;
    background-position: left top;
    background-size: 128px 128px;
}

/* An uploaded photograph replaces the whole drawing - ground, medallion and
   lattice. Only the scrim stays, because that is what the text needs. */
.auth__photo {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.auth__art--photo::before,
.auth__art--photo::after { content: none; }

/* ---- overlay text ---------------------------------------------------------
   Three lines and one short rule. The art has to carry the panel; anything
   longer than a single clause here turns the column into a second page.

   CONTRAST is measured off rendered pixels, not modelled: the panel is shot
   with the text hidden and the LIGHTEST pixel inside the text's own box taken
   as the ground - the worst case, not an average. Re-measured for the navy
   panel at four sizes; the floor is still the short panel, where the scrim has
   the least room to deepen and the lightest ground is rgb(43,60,84):

     brand  #ffffff                  11.19:1   (13.69 at 1920x1080)
     line   rgba(255,255,255,0.90)    9.41:1   (11.38)
     foot   rgba(255,255,255,0.72)    6.68:1   ( 7.86)

   Every line has more room than the warm panel it replaces (9.13 / 7.74 / 5.63
   at the same floor), because white on navy beats tinted paper on terracotta.
   The footer is still the thinnest line on the page. Do NOT quieten it below
   0.72: that is where it stops having room against the lightest ground. */
.auth__over {
    position: relative;
    z-index: 3;
    max-width: 32ch;
    color: var(--art-paper);
}

/* The one non-typographic mark in the text block: a short sand rule tying the
   overlay to the drawing above it. A pseudo-element, so it costs no node. */
.auth__over::before {
    content: '';
    display: block;
    width: 30px;
    height: 2px;
    margin-bottom: 20px;
    background: var(--art-rule);
    opacity: 0.55;
}

.auth__brand {
    margin: 0 0 10px;
    font-size: clamp(var(--fs-2xl), 2.1vw, 34px);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.4;
}
.auth__line {
    margin: 0 0 22px;
    font-size: var(--fs-lg);
    font-weight: 500;
    line-height: 1.9;
    /* A hair off the brand's white so the two are not one slab of colour. */
    color: rgba(255, 255, 255, 0.90);
}
.auth__foot {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: 1.7;
    color: rgba(255, 255, 255, 0.72);
}

/* ==========================================================================
   environment
   ========================================================================== */

/*
 * 1024px is the shell's own breakpoint. BELOW it - 1024 itself still gets the
 * two columns, hence the .98 - the art column is removed from the grid
 * entirely rather than hidden on top of an empty track, so the form gets the
 * whole width and centres in it.
 */
@media (max-width: 1023.98px) {
    .auth { grid-template-columns: minmax(0, 1fr); }
    .auth__art { display: none; }
}

@media (max-width: 640px) {
    .auth__pane { padding: 28px 20px; }
    .auth__title { font-size: var(--fs-xl); }
    .auth__mark { width: 34px; height: 34px; margin-bottom: 12px; }
    .otp { gap: 6px; }
    .otp__box { flex: 1 1 0; height: 52px; font-size: var(--fs-lg); }
    .auth__actions--split { flex-wrap: wrap; }
}

/* A short window - a laptop in landscape, or a phone with the keyboard up -
   loses the vertical air first; nothing is hidden, it just tightens. The
   medallion is not touched here: it is width-led at every ordinary aspect
   ratio, so its own clamp has already handled the short window. */
@media (max-height: 620px) {
    .auth__pane { padding: 22px 34px; }
    .auth__mark { margin-bottom: 10px; }
    .auth__slot { min-height: 44px; }
    .auth__art { padding: 30px; }
    .auth__over::before { margin-bottom: 14px; }
    /* A long brand wraps, and the block grows upward toward the medallion.
       Capping the size here is what keeps the two apart on a short panel. */
    .auth__brand { font-size: var(--fs-xl); margin-bottom: 6px; }
    .auth__line { margin-bottom: 14px; }
}

/*
 * Windows high-contrast and friends. The whole drawing is background images,
 * and forced-colors keeps those - so without this the panel half-survives as
 * an unreadable smear instead of stepping aside cleanly. Note the pseudos need
 * their own rule: setting `background` on .auth__art clears only that element.
 */
@media (forced-colors: active) {
    .auth__art {
        background: Canvas;
        border-inline-start: 1px solid CanvasText;
    }
    .auth__art::before,
    .auth__art::after,
    .auth__scrim,
    /* the photograph is a real <img>, so forced-colors keeps it while removing
       every background layer around it - including the scrim that made the
       overlay legible over it */
    .auth__photo { display: none; }
    .auth__over { color: CanvasText; }
    .auth__line, .auth__foot { color: CanvasText; }
}

/* No @media print rule here on purpose: ui-kit.css already hides every body
   child that is not a .print-host, so this page prints blank whatever this file
   says, and a rule that can never apply is worse than no rule. */
