/* ==========================================================================
   CIBL Obsidian — the sign-in stage (login, two-factor setup / verify)

   Loaded AFTER obsidian.css + obsidian-motion.css and consumes only their
   tokens and keyframes. One layout, every auth page (accounts/auth_base.html):

   * .login-body.login-split  — cinematic split. LEFT ~58% is a layered
     stage: an ember aurora that always runs, a masked hairline lattice, the
     Remotion film screen-blended over both, film grain, then the seam, and
     the lockup with its rotating bank facts. RIGHT ~42% is a near-black
     panel holding the glass card: sign-in, second factor, authenticator
     enrolment, password change — so login → second factor → portal is one
     continuous take.

   The film is an ENRICHMENT, never the floor. Browsers stand muted video
   down on their own (battery savers, data savers, backgrounded tabs), and a
   film that is the only thing on the left leaves half a bank's sign-in page
   an empty black rectangle when that happens. So the aurora, the lattice
   and the grain are CSS, they always run, and the panel is composed with or
   without a single frame of video. The film is screen-blended so its own
   blacks fall into the ground and only its embers and plates carry.

   Restraint is still the brief: one entrance per page load, nothing in the
   card loops. Every motion rule lives under prefers-reduced-motion:
   no-preference; reduced motion gets a fully settled, still composition.
   ========================================================================== */

/* ---- ground ---------------------------------------------------------------- */

.login-body {
  overflow-x: hidden;
}
.login-body.login-split {
  display: block;
  /* obsidian.css centres the plain login body with place-items; on a block
     body that still sets justify-items, and current engines honour
     justify-self on block-level children — the stage would shrink to
     fit-content and float centred. Reset it so the stage fills the viewport. */
  place-items: normal;
  padding: 0;
  background: var(--ground);
}

/* ---- stage: film | panel --------------------------------------------------- */

.login-stage {
  display: grid;
  width: 100%;
  justify-self: stretch;
  grid-template-columns: minmax(0, 58fr) minmax(0, 42fr);
  min-height: 100vh;
  min-height: 100dvh;
}

/* film (left) ---------------------------------------------------------------- */

.login-film {
  position: relative;
  overflow: hidden;
  background: var(--ground);
  isolation: isolate;
}

/* layer 0 — the aurora. Two counter-drifting ember fields; the floor of the
   composition and the reason the panel is never empty. */
.login-aurora {
  position: absolute; inset: -30%; z-index: 0; pointer-events: none;
  background:
    radial-gradient(52% 46% at 70% 20%, rgba(255, 107, 0, 0.22), transparent 68%),
    radial-gradient(46% 54% at 22% 76%, rgba(255, 163, 51, 0.14), transparent 66%);
}
.login-aurora::before {
  content: ""; position: absolute; inset: -18%;
  background:
    radial-gradient(36% 32% at 46% 54%, rgba(255, 138, 26, 0.12), transparent 70%),
    radial-gradient(30% 40% at 86% 84%, rgba(255, 107, 0, 0.10), transparent 72%);
}

/* layer 1 — the lattice. A hairline instrument grid, masked to a soft ellipse
   so it never reaches an edge: precision underneath the warmth. */
.login-lattice {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background-image:
    linear-gradient(rgba(var(--overlay-rgb), 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--overlay-rgb), 0.05) 1px, transparent 1px);
  background-size: 76px 76px;
  -webkit-mask-image: radial-gradient(74% 70% at 48% 44%, #000 8%, transparent 80%);
  mask-image: radial-gradient(74% 70% at 48% 44%, #000 8%, transparent 80%);
}

/* layer 2 — the film */
.login-film .login-video,
.login-film .login-poster {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  display: block;
}
/* the theme composites the film, it never removes it: screened onto obsidian,
   luminance-inverted (hue kept) and multiplied into the paper — light used to
   drop the hero and leave 58% of the stage empty. */
.login-film .login-video {
  opacity: 1;
  mix-blend-mode: var(--film-blend);
  filter: var(--film-filter);
}
/* the still is only ever shown to reduced-motion users */
.login-film .login-poster {
  display: none;
  mix-blend-mode: var(--film-blend);
  filter: var(--film-filter);
}

/* layer 3 — grain. Four percent of fractal noise across the panel: large
   dark gradients band badly on 8-bit displays and this is what stops it. */
.login-grain {
  position: absolute; inset: -50px; z-index: 3; pointer-events: none;
  opacity: 0.05; mix-blend-mode: overlay;
  background-size: 180px 180px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* layer 4 — the seam. Lighter than a vignette: the film has to be seen, not
   hidden. Heaviest at the right edge so the stage dissolves into the panel
   rather than meeting it at a hard border. */
.login-seam {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background:
    linear-gradient(90deg,
      rgba(var(--ground-rgb), 0.30) 0%, transparent 22%,
      transparent 60%, rgba(var(--ground-rgb), 0.34) 84%, var(--ground) 100%),
    linear-gradient(180deg,
      rgba(var(--ground-rgb), 0.34) 0%, transparent 24%,
      transparent 56%, rgba(var(--ground-rgb), 0.60) 100%);
}

/* layer 5 — the lockup in the bottom-left corner */
.login-mark {
  /* Centred, not dumped in the corner.  The lockup is the bank introducing
     itself, so it sits on the optical centre of the panel with the film
     working around it, rather than crouching at the bottom under two thirds
     of empty frame. */
  position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  z-index: 5;
  padding-inline: clamp(40px, 5vw, 72px);
  max-width: min(600px, 76%);
}
/* the film draws real figures and they can land anywhere; the lockup carries
   the regulator and the licence number, so it gets its own soft scrim rather
   than relying on a frame being dark where the text happens to be. */
.login-mark::before {
  /* A cinematic edge, not a blob.  The previous radial scrim protected the text
     by muting the middle of the film, which is the part that moves — so the
     motion looked switched off.  This is the lower-third treatment instead: the
     left edge carries enough ground for the type to sit on, and it is gone by
     two thirds across, leaving the film alive where it is doing its work. */
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  inset: -22% -34% -22% -40%;
  background:
    linear-gradient(90deg,
      rgba(var(--ground-rgb), 0.92) 0%,
      rgba(var(--ground-rgb), 0.78) 34%,
      rgba(var(--ground-rgb), 0.38) 62%,
      transparent 82%),
    radial-gradient(64% 54% at 24% 50%, rgba(var(--ground-rgb), 0.42), transparent 76%);
}
.login-mark-eyebrow {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 14px;
  font-family: var(--font-body); font-size: 10px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; white-space: nowrap;
  color: var(--ember-hi);
}
.login-mark-eyebrow::before {
  content: ""; flex: 0 0 22px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--ember-hi));
}
.login-mark-line {
  margin: 0 0 14px;
  font-family: var(--font-body);
  font-size: clamp(20px, 2.1vw, 30px); font-weight: 500; line-height: 1.2;
  letter-spacing: -0.018em;
  color: var(--ink);
  text-shadow: 0 2px 22px rgba(var(--ground-rgb), 0.85);
}
/* the legal foot: who the bank is, who licenses it, and the licence number
   itself — the number is a reference, so it alone is mono. Two lines, DM
   Sans; the regulator is the most bank-like sentence on the page and it is
   printed, not implied. */
.login-caption {
  display: block; margin: 0;
  font-family: var(--font-body);
  text-shadow: 0 1px 12px rgba(var(--ground-rgb), 0.8);
}
.login-caption-name {
  display: block;
  font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-faint);
}
.login-caption-licence {
  display: block; margin-top: 5px;
  font-size: 11px; font-weight: 400; line-height: 1.45;
  letter-spacing: 0.01em;
  color: var(--ink-dim);
}
.login-caption-licence .ref { font-size: 11px; color: var(--ink-dim); }

/* the proposition: the one line of display type on the stage. It arrives
   once with the lockup and then never moves again. */
.login-proposition {
  margin: 0 0 18px;
  font-family: var(--font-body);
  font-size: clamp(30px, 3.2vw, 44px); font-weight: 600; line-height: 1.14;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-shadow: 0 2px 26px rgba(var(--ground-rgb), 0.9);
}

/* the three standing guarantees. Not the rotating facts — these are the
   same three on every auth page, present all at once, so two seconds of
   attention still buys three concrete promises. They enter in a stagger
   and then stand still for good. */
.guarantee-list {
  list-style: none; margin: 0 0 20px; padding: 0;
  display: grid; gap: 9px;
  --d: 640ms; --step: 70ms;
}
.guarantee {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-body); font-size: 13px; line-height: 1.35;
  color: var(--ink-dim);
  text-shadow: 0 1px 12px rgba(var(--ground-rgb), 0.85);
}
.guarantee-chip {
  flex: 0 0 28px; width: 28px; height: 28px; border-radius: 8px;
  display: grid; place-items: center;
  background: rgba(255, 138, 26, 0.12);
  border: 1px solid rgba(255, 138, 26, 0.22);
  color: var(--ember-hi);
}
/* panel (right) --------------------------------------------------------------- */

.login-panel {
  position: relative;
  display: grid; place-items: center;
  padding: 48px 56px;
  background: var(--ground);
  border-left: 1px solid var(--hairline);
}
/* a single quiet ember bloom high on the panel — the only warmth on the
   right, so the eye keeps returning to the film. */
.login-panel::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(640px 420px at 100% -10%, rgba(255, 107, 0, 0.08), transparent 62%),
    radial-gradient(520px 520px at -20% 115%, rgba(255, 163, 51, 0.04), transparent 60%);
}

/* ---- the glass card ---------------------------------------------------------- */

.login-card {
  position: relative; z-index: 1;
  width: 100%; max-width: 420px;
  background: var(--glass);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 40px 36px 30px;
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow:
    0 40px 100px rgba(var(--shadow-rgb), calc(0.55 * var(--shadow-k))),
    inset 0 1px 0 rgba(var(--overlay-rgb), 0.05);
  overflow: hidden;
}
/* an ember filament along the card's top edge; it is also the track the
   entrance sweep runs down (see the motion block). */
.login-card::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent, rgba(255, 163, 51, 0.55) 30%, rgba(255, 107, 0, 0.7) 50%,
    rgba(255, 163, 51, 0.55) 70%, transparent);
  opacity: 0.5;
}
/* wordmark stacked over its eyebrow — one line each, never wrapping */
.login-card .brand {
  flex-direction: column; align-items: flex-start; gap: 10px;
  padding: 0; margin-bottom: 30px;
}
/* the vector wordmark, lit rather than recoloured — the mark keeps its
   own brand orange and the ground supplies the warmth around it. */
.login-card .brand-mark.brand-logo {
  height: 36px;
  filter: drop-shadow(0 0 20px rgba(255, 107, 0, 0.30));
}
.login-card .brand-sub { display: block; letter-spacing: 0.14em; white-space: nowrap; }

.login-title {
  font-family: var(--font-body);
  font-size: 32px; font-weight: 700; line-height: 1.08;
  letter-spacing: -0.022em;
  margin: 0 0 6px;
}
.login-sub { color: var(--ink-dim); font-size: 14px; margin: 0 0 26px; }

/* fields: ember focus glow blooms in, settles, never pulses */
.login-card .field { margin-bottom: 18px; }
.login-card .field label { color: var(--ink-faint); margin-bottom: 8px; }
.login-card .field input {
  padding: 12px 14px;
  font-size: 15px;
  background: var(--field-grad);
  border-color: var(--field-border);
  transition: border-color 200ms ease, box-shadow 260ms ease, background-color 200ms ease;
}
.login-card .field input:hover { border-color: rgba(var(--overlay-rgb), 0.18); }
.login-card .field input:focus {
  outline: none;
  background: var(--field-grad-hi);
  border-color: var(--ember-hi);
  box-shadow:
    0 0 0 4px rgba(255, 138, 26, 0.14),
    0 0 28px rgba(255, 107, 0, 0.14);
}
.login-card .field input::placeholder { color: var(--ink-faint); }

/* error state: the message and the fields agree */
.login-card .form-error {
  display: flex; align-items: flex-start; gap: 10px;
  border-left: 2px solid var(--bad);
  margin-bottom: 20px;
}
.login-card .form-error::before {
  content: ""; flex: 0 0 6px; width: 6px; height: 6px; margin-top: 6px;
  border-radius: 50%; background: var(--bad);
  box-shadow: 0 0 8px rgba(255, 107, 107, 0.6);
}
.login-card .has-error .field input { border-color: var(--bad); }
.login-card .has-error .field input:focus {
  border-color: var(--ember-hi);
}

/* fire button: bloom on hover, sink when pressed */
.login-card .btn-ember {
  position: relative; overflow: hidden;
  margin-top: 8px; padding: 13px 16px;
  font-size: 15px; letter-spacing: 0.01em;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset;
  transition: box-shadow 260ms ease, filter 200ms ease, transform 90ms ease;
}
/* a specular sheen that crosses the button once on hover */
.login-card .btn-ember::before {
  content: ""; position: absolute; top: 0; bottom: 0; width: 42%;
  pointer-events: none; transform: translateX(-180%);
  background: linear-gradient(100deg, transparent,
              rgba(var(--overlay-rgb), 0.32) 50%, transparent);
}
.login-card .btn-ember::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 140% at 50% 120%, rgba(var(--overlay-rgb), 0.22), transparent 60%);
  opacity: 0; transition: opacity 260ms ease;
}
.login-card .btn-ember:hover {
  filter: brightness(1.04);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.14) inset,
    0 10px 34px rgba(255, 107, 0, 0.38),
    0 0 0 1px rgba(255, 163, 51, 0.35);
}
.login-card .btn-ember:hover::after { opacity: 1; }
.login-card .btn-ember:active {
  transform: scale(0.97);
  filter: brightness(0.96);
  box-shadow:
    0 1px 0 rgba(var(--overlay-rgb), 0.06) inset,
    0 3px 12px rgba(255, 107, 0, 0.28);
}
.login-card .btn-ember:focus-visible {
  outline: 2px solid var(--ember-hi);
  outline-offset: 3px;
}

/* footnote: mono, hairline above, a quiet mint point for "secured" */
.login-foot {
  display: flex; align-items: center; gap: 9px;
  margin: 26px 0 0; padding-top: 16px;
  border-top: 1px solid var(--hairline);
  font-family: var(--font-body); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-faint);
}
/* the card carries the regulator too, because on a phone the stage is gone
   and this is the only place a client could read it. The licence number is a
   reference, so it alone is mono. */
.login-licence {
  margin: 9px 0 0;
  font-family: var(--font-body); font-size: 11px; line-height: 1.45;
  color: var(--ink-faint);
}
.login-licence .ref { font-size: 11px; color: var(--ink-faint); }
.login-foot::before {
  content: ""; flex: 0 0 5px; width: 5px; height: 5px; border-radius: 50%;
  background: var(--mint);
  box-shadow: 0 0 8px rgba(62, 207, 142, 0.55);
}

/* ---- motion (only when the visitor allows it) -------------------------------

   The entrance is choreographed, not decorated. Three beats:

     0ms   the stage is already lit — the aurora and the lattice never start
           from nothing, so there is no flash of empty black;
     90ms  the card arrives: it rises a little, settles out of a short blur,
           and a single filament sweeps its top edge;
     190ms its contents follow one at a time, 70ms apart, in reading order:
           wordmark, title, then the FORM'S OWN CHILDREN one by one (the
           fields rise, the button pops on a spring), then the passkey path
           and the footnote. The steps overlap the card's own arrival so an
           empty glass rectangle is never on screen long enough to read as
           a loading skeleton; the whole entrance is over inside a second.

   The aurora keeps breathing, and the facts under the lockup crossfade
   every five seconds. Nothing else in the card ever loops — a control
   that moves while you are trying to type in it is a distraction, not a
   flourish. Two one-shots answer real events: a wrong password shakes
   the card once as the message drops in, and a submit turns the button
   into a sweeping pill for exactly as long as the request takes.
   -------------------------------------------------------------------------- */

@keyframes login-rise {
  from { opacity: 0; transform: translate3d(0, 24px, 0) scale(0.985); filter: blur(9px); }
  55%  { filter: blur(0); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
@keyframes login-item {
  from { opacity: 0; transform: translate3d(0, 11px, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes login-pop {
  from { opacity: 0; transform: translateY(10px) scale(0.92); }
  to   { opacity: 1; transform: none; }
}
@keyframes login-qr-scan {
  from { transform: translateY(-48px); }
  to   { transform: translateY(240px); }
}
@keyframes login-sweep {
  from { transform: translateX(-100%); opacity: 0; }
  18%  { opacity: 1; }
  to   { transform: translateX(100%); opacity: 0; }
}
@keyframes login-film-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes login-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* the two aurora fields drift against each other so the light never repeats
   on an obvious period */
@keyframes login-aurora-a {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1); }
  50%  { transform: translate3d(3%, 2.5%, 0) scale(1.07); }
  100% { transform: translate3d(1%, -2%, 0) scale(1.02); }
}
@keyframes login-aurora-b {
  0%   { transform: translate3d(2%, 1%, 0) scale(1.04); }
  50%  { transform: translate3d(-2.5%, -2%, 0) scale(1); }
  100% { transform: translate3d(-1%, 2%, 0) scale(1.05); }
}
@keyframes btn-sheen {
  from { transform: translateX(-180%); }
  to   { transform: translateX(320%); }
}

@media (prefers-reduced-motion: no-preference) {
  .login-aurora { animation: login-aurora-a 34s ease-in-out infinite alternate; }
  .login-aurora::before { animation: login-aurora-b 46s ease-in-out infinite alternate; }
  .login-lattice { animation: login-fade 2400ms ease-out both; }

  .login-card {
    animation: login-rise 560ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 90ms;
  }
  /* the filament sweep rides the card's top edge, once */
  .login-card::after {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
    pointer-events: none; z-index: 2;
    background: linear-gradient(90deg, transparent, var(--ember-hi), transparent);
    animation: login-sweep 950ms cubic-bezier(0.4, 0, 0.2, 1) both;
    animation-delay: 300ms;
  }

  /* contents in reading order, 70ms apart. --t is each slot's start; the
     form hands its slot to its own children (fields rise, button pops) and
     everything after the form waits for them. */
  .login-card > * { animation: login-item 460ms cubic-bezier(0.16, 1, 0.3, 1) both; animation-delay: var(--t, 750ms); }
  .login-card > *:nth-child(1) { --t: 190ms; }
  .login-card > *:nth-child(2) { --t: 260ms; }
  .login-card > *:nth-child(3) { --t: 330ms; }
  .login-card > *:nth-child(4) { --t: 400ms; }
  .login-card > *:nth-child(5) { --t: 470ms; }
  .login-card > *:nth-child(6) { --t: 540ms; }
  .login-card > *:nth-child(7) { --t: 610ms; }
  .login-card > *:nth-child(8) { --t: 680ms; }
  .login-card > *:nth-child(n+9) { --t: 750ms; }
  .login-card > .login-form { animation: none; }
  .login-form > .field, .login-form > .btn-ember, .login-form > .otp-qr-frame {
    animation: login-item 460ms cubic-bezier(0.16, 1, 0.3, 1) both; animation-delay: var(--t);
  }
  .login-form > .field:nth-of-type(2) { animation-delay: calc(var(--t) + 70ms); }
  .login-form > .field:nth-of-type(3) { animation-delay: calc(var(--t) + 140ms); }
  .login-form > .btn-ember {
    animation-name: login-pop; animation-duration: 520ms;
    animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); animation-delay: calc(var(--t) + 220ms);
  }
  .login-card > .login-form ~ * { animation-delay: calc(var(--t) + 200ms); }
  /* the error message drops in from above (errIn), and the whole card
     answers it with one shake once it has arrived */
  .login-card > .form-error { animation-name: obs-err-in; }
  .login-card:has(.form-error) {
    animation: login-rise 560ms cubic-bezier(0.16, 1, 0.3, 1) 90ms both, obs-shake 450ms ease 720ms;
  }
  .login-card .field-error { animation: obs-err-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }

  /* the one line under the proposition: one entrance, with the lockup */
  .login-mark-line { animation: login-item 750ms cubic-bezier(0.16, 1, 0.3, 1) both; animation-delay: 700ms; }

  /* the proposition and the three guarantees: one entrance each, with the
     lockup, and then they are furniture */
  .login-proposition {
    animation: login-item 500ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 560ms;
  }
  .guarantee-list { --d: 1000ms; }

  /* the film arrives slowly, after the card has begun to settle */
  .login-film .login-video { animation: login-film-in 2000ms ease-out both; animation-delay: 180ms; }
  .login-mark { animation: login-fade 1000ms ease-out both; animation-delay: 560ms; }
  .login-panel::before { animation: login-fade 1400ms ease-out both; }

  .login-card .btn-ember:hover::before { animation: btn-sheen 900ms ease-out; }
  /* the submit pill: obsidian-motion.js adds .is-submitting on the real
     post; the sheen pseudo becomes a full-width sweep until the response.
     Restated here because the card's own ::before rule outranks the shared
     one. */
  .login-card .btn-ember.is-submitting::before {
    inset: 0; width: auto; transform: none;
    background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, 0.35) 50%, transparent 70%);
    animation: obs-sweep 1.1s ease-in-out infinite;
  }
  .login-card .btn-ember.is-submitting::after { opacity: 0; }

  /* authenticator enrolment: a soft band crosses the QR frame slowly — the
     cue that this is a live code to point a camera at */
  .otp-qr-frame::after { animation: login-qr-scan 5.2s linear infinite; }
}

@media (prefers-reduced-motion: reduce) {
  /* no film and no drift: the poster still if it was rendered, else the
     aurora exactly as composed, standing still. */
  .login-film .login-video { display: none; }
  .login-film .login-poster { display: block; }
  .login-aurora, .login-aurora::before, .login-lattice,
  .login-card, .login-card > *, .login-form > *, .login-mark, .login-panel::before,
  .login-mark-line, .otp-qr-frame::after,
  .login-proposition, .guarantee-list > * { animation: none; }
  .login-card::after { display: none; }
  .login-card .field input,
  .login-card .btn-ember, .login-card .btn-ember::after { transition: none; }
  .login-card .btn-ember::before { display: none; }
  .login-card .btn-ember:active { transform: none; }
  .otp-qr-frame::after { display: none; }
}

/* ---- responsive ------------------------------------------------------------------
   <=900  the stage becomes the whole background under a darker veil; the card
          centres over it.
   <=480  the card runs edge to edge; every control is at least 44px tall. */

@media (max-width: 1240px) {
  .login-mark { left: 40px; right: 40px; bottom: 36px; }
}

/* the lockup has to clear the film, not crowd it: the guarantees go first,
   then the proposition, leaving the eyebrow, the rotating fact and the legal
   foot. On a phone the card is the only thing that matters. */
@media (max-width: 1100px) {
  .guarantee-list { display: none; }
}

@media (max-width: 1080px) {
  .login-panel { padding: 40px 36px; }
  .login-mark { left: 32px; right: 32px; bottom: 28px; max-width: min(380px, 70%); }
  .login-mark-eyebrow { margin-bottom: 10px; }
}

/* a short viewport is the film's problem, not the lockup's: the guarantees
   are the first thing to go so the stage never crowds the frame. */
@media (max-height: 720px) {
  .guarantee-list { display: none; }
}

@media (max-width: 900px) {
  .login-stage { grid-template-columns: 1fr; }
  .login-film {
    position: fixed; inset: 0; z-index: 0;
  }
  .login-seam {
    background:
      radial-gradient(900px 640px at 50% 34%, rgba(var(--ground-rgb), 0.42), rgba(var(--ground-rgb), 0.92));
  }
  /* the lockup would sit under the card on one column; the caption alone
     stays, centred at the foot. */
  .login-mark {
    left: 16px; right: 16px; bottom: 16px; max-width: none; text-align: center;
  }
  .login-mark-eyebrow, .login-mark-line,
  .login-proposition, .guarantee-list { display: none; }
  .login-caption-name { font-size: 9.5px; letter-spacing: 0.14em; }
  .login-caption-licence { font-size: 10px; }
  .login-panel {
    position: relative; z-index: 1;
    min-height: 100vh; min-height: 100dvh;
    padding: 56px 20px 72px;
    background: transparent; border-left: none;
  }
  .login-panel::before { display: none; }
  .login-card { max-width: 440px; }
}

/* CLI-08 / ADM-14 — the promise on the card.
   The stage is a fixed film behind the card below 900px and its lockup is
   hidden, so the proposition and the three guarantees are rendered a second
   time inside the card (accounts/_promise.html, one markup, two positions)
   and only this copy is shown there.  Sized for a phone: the proposition
   drops to the card's own title scale and the chips stack. */
.login-card-promise { display: none; }

/* CLI-08, the band the first pass missed.  The STAGE drops the three
   guarantees in two places above 900px — at 1100px wide, so the lockup
   clears the film, and at a viewport 720px tall, for the same reason — but
   the card copy only appeared below 900.  Between 901 and 1100, and on any
   short laptop window (a 1366x768 screen with a browser chrome is under
   720 tall), the bank's three promises were on NEITHER half of its own
   front door.  Show the card's chips wherever the stage has dropped them.
   Only the chips: above 900 the stage still carries the proposition and
   the mark line, and two of those on one screen is worse than none. */
@media (max-width: 1100px), (max-height: 720px) {
  .login-card-promise { display: block; margin: -6px 0 22px; }
  .login-card-promise .login-proposition,
  .login-card-promise .login-mark-line { display: none; }
  .login-card-promise .guarantee-list {
    display: grid; gap: 8px; margin: 0; padding: 0; list-style: none;
  }
  .login-card-promise .guarantee { font-size: 12.5px; }
}

@media (max-width: 900px) {
  .login-card-promise { display: block; margin: -6px 0 22px; }
  .login-card-promise .login-proposition {
    display: block; font-size: 21px; line-height: 1.2; margin: 0 0 8px;
  }
  .login-card-promise .login-mark-line {
    display: block; font-size: 13px; line-height: 1.5; margin: 0 0 14px;
  }
  .login-card-promise .guarantee-list {
    display: grid; gap: 8px; margin: 0; padding: 0; list-style: none;
  }
  .login-card-promise .guarantee { font-size: 12.5px; }
}

@media (max-width: 480px) {
  .login-panel { padding: 0; align-items: center; }
  .login-card {
    max-width: none;
    border-radius: 0; border-left: none; border-right: none;
    padding: 36px 22px 28px;
  }
  .login-card .brand { margin-bottom: 26px; }
  .login-card .brand-mark.brand-logo { height: 32px; }
  .login-title { font-size: 28px; }
  .login-card .field input { min-height: 44px; padding: 12px 14px; }
  .login-card .btn-ember { min-height: 48px; padding: 14px 16px; }
  .login-mark { display: none; }
}

/* ---- second factor / enrolment / password (the same auth shell) -----------------
   The authenticator code is one field — big, mono, spaced like the six
   digits on the phone — never six inputs, so the form contract (one
   `token`) and autofill of one-time codes both keep working. */

.otp-code input {
  font-family: var(--font-mono); font-size: 24px; font-weight: 500;
  letter-spacing: 0.42em; text-align: center; text-indent: 0.42em;
}
.otp-code input::placeholder { letter-spacing: 0.42em; color: rgba(var(--overlay-rgb), 0.18); }
.otp-steps { color: var(--ink-dim); font-size: 13px; margin: 0 0 18px; padding-left: 18px; }
.otp-steps li + li { margin-top: 4px; }
/* the QR in a glass frame: the code itself stays on white so every
   authenticator reads it; the frame carries the glow and the scan band */
.otp-qr-frame {
  position: relative; width: 218px; margin: 0 auto 14px; padding: 12px;
  border-radius: 16px; overflow: hidden;
  background: var(--glass-grad);
  border: 1px solid var(--hairline-strong);
  box-shadow: inset 0 1px 0 rgba(var(--overlay-rgb), 0.08), 0 14px 34px rgba(var(--shadow-rgb), calc(0.35 * var(--shadow-k))), 0 0 0 1px rgba(255, 163, 51, 0.08);
}
/* the band runs BEHIND the white code (z-order), lighting the glass margin
   only — a glow across the modules would cost some phones the scan */
.otp-qr-frame::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 48px; pointer-events: none; z-index: 0;
  background: linear-gradient(180deg, transparent, rgba(255, 163, 51, 0.28) 50%, transparent);
  transform: translateY(240px);
}
.otp-qr { position: relative; z-index: 1; padding: 10px; background: #fff; border-radius: 10px; }
.otp-qr svg { display: block; width: 100%; height: auto; }
.otp-secret {
  font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.08em;
  word-break: break-all; text-align: center; color: var(--ink);
  background: var(--glass); border: 1px solid var(--hairline-strong);
  border-radius: 10px; padding: 10px 12px; margin: 0 0 20px;
}

/* ---- sign-in notices: inactivity + lockout ---------------------------------------
   Same family as .form-error (left rule + dot) — the idle notice is neutral
   ember, the lockout is the red signal with the controls disabled beneath. */

.login-notice {
  position: relative;
  padding: 11px 14px 11px 26px; margin-bottom: 20px;
  font-size: 13px; line-height: 1.5; color: var(--ink-dim);
  background: var(--glass); border: 1px solid var(--hairline-strong);
  border-left: 2px solid var(--ember-hi); border-radius: var(--radius-sm);
}
.login-notice strong { color: var(--ink); font-weight: 600; }
.login-notice::before {
  content: ""; position: absolute; left: 12px; top: 17px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ember-hi); box-shadow: 0 0 8px rgba(255, 163, 51, 0.6);
}
.login-notice-locked {
  color: var(--ink-dim);
  background: var(--bad-soft); border-color: var(--bad-ring);
  border-left-color: var(--bad);
}
.login-notice-locked strong { color: var(--bad); }
.login-notice-locked::before { background: var(--bad); box-shadow: 0 0 8px rgba(255, 107, 107, 0.6); }
.login-form[aria-disabled="true"] .field input,
.login-form[aria-disabled="true"] .btn-ember { opacity: 0.45; cursor: not-allowed; }
.login-form[aria-disabled="true"] .btn-ember:hover { filter: none; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset; }
.btn-passkey:disabled { opacity: 0.45; cursor: not-allowed; }

/* the identifier label carries its own quiet hint ("(or username)") — inline,
   right of the label text, never a stray caption on its own line. obsidian.css
   makes .field-hint a block for the wizard's help text; on the auth card it is
   part of the label. */
.login-card .field-hint {
  display: inline; margin: 0 0 0 6px;
  text-transform: none; letter-spacing: 0; font-family: var(--font-body);
  font-size: 11px; line-height: inherit; color: var(--ink-faint);
}
/* ---- the password reveal (accounts/_field_reveal.html) -------------------------
   A control inside the field, not an animation: the two inline glyphs sit in
   one grid cell and crossfade in 120 ms on the real aria-pressed state. It
   only exists under html.obs-js — the motion layer's own marker — so nobody
   without JS is offered a toggle that cannot toggle. */
.input-wrap { position: relative; display: block; }
.login-card .input-wrap input { width: 100%; padding-right: 46px; }
.field-reveal { display: none; }
html.obs-js .field-reveal {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  display: grid; place-items: center;
  width: 34px; height: 34px; padding: 0;
  border: 0; border-radius: 8px; background: transparent;
  color: var(--ink-faint); cursor: pointer;
}
.field-reveal:hover { color: var(--ink); background: rgba(var(--overlay-rgb), 0.07); }
.field-reveal:focus-visible { outline: 2px solid var(--ember-hi); outline-offset: 2px; }
/* a locked-out card disables its fields; a toggle on a field nobody can type
   in is furniture, so it goes with them. */
.input-wrap input:disabled ~ .field-reveal { display: none; }
.field-reveal .reveal-glyph { grid-area: 1 / 1; }
.field-reveal .reveal-hide { opacity: 0; }
.field-reveal[aria-pressed="true"] .reveal-show { opacity: 0; }
.field-reveal[aria-pressed="true"] .reveal-hide { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .field-reveal .reveal-glyph { transition: opacity 120ms ease; }
  .field-reveal { transition: color 160ms ease, background-color 160ms ease; }
}

.field-invalid input { border-color: var(--bad); }
.field-error {
  margin: 6px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--bad);
}

/* ---- "or" seam between the password form and the passkey button --------------- */

.login-or {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 0 14px;
  font-family: var(--font-body); font-size: 10.5px; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint);
}
.login-or::before, .login-or::after {
  content: ""; flex: 1; height: 1px; background: var(--hairline-strong);
}

/* ---- passkey button: glass, the fingerprint glyph, a title and a sub-line ------- */

.btn-passkey {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 11px 14px; text-align: left;
  background: var(--glass);
  border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm);
  color: var(--ink); font: inherit; cursor: pointer;
  transition: border-color 200ms ease, background-color 200ms ease, box-shadow 260ms ease;
}
.btn-passkey:hover { border-color: var(--ember-ring); background: var(--glass-strong); }
.btn-passkey:focus-visible { outline: 2px solid var(--ember-hi); outline-offset: 3px; }
.btn-passkey[aria-busy="true"] { border-color: var(--ember-ring); box-shadow: 0 0 0 4px rgba(255, 138, 26, 0.12); }
.passkey-glyph { flex: 0 0 22px; color: var(--ember-hi); }
.btn-passkey-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.btn-passkey-title { font-size: 14.5px; font-weight: 600; letter-spacing: 0.005em; }
.btn-passkey-sub {
  font-family: var(--font-body); font-size: 10px; font-weight: 500; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-faint);
}
/* on the second-factor page the passkey IS the primary action */
.btn-passkey-primary {
  background: var(--ember-soft); border-color: rgba(255, 138, 26, 0.35);
  padding: 13px 16px; margin-bottom: 8px;
}
.btn-passkey-primary:hover { background: rgba(255, 138, 26, 0.2); box-shadow: 0 8px 28px rgba(255, 107, 0, 0.22); }
.btn-passkey-primary .btn-passkey-title { font-size: 15.5px; }
@media (prefers-reduced-motion: no-preference) {
  .btn-passkey[aria-busy="true"] .passkey-glyph { animation: passkey-breathe 1.6s ease-in-out infinite; }
}
@keyframes passkey-breathe {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(255, 163, 51, 0)); }
  50% { filter: drop-shadow(0 0 8px rgba(255, 163, 51, 0.7)); }
}

/* status line under the button; the unsupported note swaps in for the button
   once passkeys.js has decided (html[data-passkeys]). */
.passkey-status {
  margin: 10px 0 0; font-size: 13px; color: var(--ink-dim);
}
.passkey-status[data-kind="error"] { color: var(--bad); }
.passkey-status[data-kind="ok"] { color: var(--ok); }
.passkey-unsupported {
  display: none; margin: 8px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-faint);
}
html[data-passkeys="unsupported"] .btn-passkey { display: none; }
html[data-passkeys="unsupported"] .passkey-unsupported { display: block; }

/* the authenticator-code fallback folded under the passkey button */
.otp-fallback { margin-top: 14px; border-top: 1px solid var(--hairline); padding-top: 12px; }
.otp-fallback summary {
  cursor: pointer; list-style: none;
  font-size: 13.5px; font-weight: 500; color: var(--ember-hi);
}
.otp-fallback summary::-webkit-details-marker { display: none; }
.otp-fallback summary::before { content: "→ "; color: var(--ink-faint); }
.otp-fallback[open] summary::before { content: "↓ "; }
.otp-fallback summary:hover { text-decoration: underline; }
.otp-fallback[open] summary { margin-bottom: 14px; }
html[data-passkeys="unsupported"] .otp-fallback { border-top: none; padding-top: 0; margin-top: 4px; }
html[data-passkeys="unsupported"] .otp-fallback summary { display: none; }

/* quiet links under a card's form (password change, done) */
.login-links { margin: 16px 0 0; font-size: 13px; text-align: center; color: var(--ink-dim); }
.btn-link { display: block; text-align: center; text-decoration: none; }
.btn-link:hover { text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
  .btn-passkey, .btn-passkey[aria-busy="true"] .passkey-glyph { transition: none; animation: none; }
}
@media (max-width: 480px) {
  .btn-passkey { min-height: 52px; }
}
