
/* Material 3 design tokens (light + dark), matching the Pramaan mobile app:
   tonal surfaces, an indigo primary, M3 shape (rounded cards, pill buttons,
   outlined fields) and the M3 type/elevation scale. Fonts are SELF-HOSTED at
   /static/*.woff2 (same-origin, so the strict default-src 'self' CSP allows
   them — no Google Fonts / external fetch): Inter for UI, Space Grotesk for
   display, mirroring the mobile app. */
@font-face{font-family:'Inter';font-style:normal;font-weight:100 900;font-display:swap;
  src:url(/static/inter-var.woff2) format('woff2')}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:swap;
  src:url(/static/space-grotesk-var.woff2) format('woff2')}
:root{
  color-scheme:light dark;
  --font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
  --font-display:'Space Grotesk',var(--font-sans);
  --bg:#ffffff;--surface:#ffffff;--surface-2:#f1edf9;--surface-3:#eae6f6;
  --ink:#1b1b21;--ink-2:#46464f;--ink-3:#74737d;
  --outline:#c6c5d0;--outline-2:#e4e1ec;
  --brand:#4f46e5;--on-brand:#ffffff;--brand-cont:#e3e0ff;--on-brand-cont:#15136b;
  --ok:#146c2e;--err:#ba1a1a;
  --r-sm:8px;--r-md:12px;--r-lg:16px;--r-xl:28px;--r-pill:999px;
  --s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:20px;--s6:24px;--s7:32px;--s8:40px;
  --fs-eyebrow:.72rem;--fs-sm:.82rem;--fs-base:.97rem;
  --fs-title:clamp(1.45rem,1.22rem + 1.1vw,1.8rem);
  --fs-num:clamp(3.1rem,2.4rem + 3.2vw,4rem);
  --d1:120ms;--d2:200ms;--d3:300ms;--d4:480ms;
  --ease:cubic-bezier(.2,0,0,1);--ease-out:cubic-bezier(.05,.7,.1,1);
  --elev1:0 1px 2px rgba(0,0,0,.10),0 1px 3px 1px rgba(0,0,0,.06);
  --elev2:0 1px 2px rgba(0,0,0,.10),0 2px 6px 2px rgba(0,0,0,.08);
  --ring:0 0 0 3px rgba(79,70,229,.5);
  /* bg.ibelick 'Gradient 2' family — a soft violet (#7877c6) ellipse glow rising
     from above the fold. Same pattern on the authentik login page so broker <->
     authentik read as one product; light & dark correspond (white vs near-black
     base). */
  --page:radial-gradient(ellipse 80% 80% at 50% -20%,rgba(120,119,198,.28),rgba(255,255,255,0));
}
@media (prefers-color-scheme:dark){:root{
  --bg:#0a0a0a;--surface:#1e1f25;--surface-2:#272832;--surface-3:#2f3039;
  /* --ink-3 lightened from #928f9a so muted/helper text clears WCAG AA on the
     dark surface (was ~3.8:1, now ~5.4:1). */
  --ink:#e4e1e9;--ink-2:#c7c5d0;--ink-3:#a8a5b0;
  --outline:#48474f;--outline-2:#33333b;
  --brand:#c0c1ff;--on-brand:#23228f;--brand-cont:#3b3aa6;--on-brand-cont:#e3e0ff;
  --ok:#7bda8f;--err:#ffb4ab;
  --elev1:0 1px 2px rgba(0,0,0,.5),0 1px 3px 1px rgba(0,0,0,.3);
  --elev2:0 1px 2px rgba(0,0,0,.5),0 2px 6px 2px rgba(0,0,0,.35);
  --ring:0 0 0 3px rgba(192,193,255,.6);
  /* Dark = bg.ibelick 'Dark Gradient 2': a soft violet ellipse glow rising from
     above the fold over near-black (--bg #0a0a0a). Premium & minimal. */
  --page:radial-gradient(ellipse 80% 80% at 50% -20%,rgba(120,119,198,.30),rgba(255,255,255,0));
}}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
/* Smooth cross-document (MPA) navigation on supporting browsers — a gentle
   fade between the method picker, code entry and waiting pages. Progressive
   enhancement (ignored where unsupported) and fully disabled under
   reduced-motion. */
@view-transition{navigation:auto}
@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}
}
body{margin:0;min-height:100svh;display:grid;place-items:safe center;padding:var(--s6);
  /* grid-template-columns is load-bearing, not decoration: with no explicit
     template, a single-item auto grid track sizes itself by content BEFORE
     the item's own `width:100%` resolves against it (a well-known CSS Grid
     circularity), so the card computed ~18px wider than the actual padded
     content box at narrow viewports (measured: 306px against 288px
     available at a 320px-wide screen) — an invisible-in-Chrome-but-real
     overflow that Safari does not suppress as reliably via overflow-x:hidden
     on body alone. minmax(0,1fr) gives the track a definite, fully-stretched
     size so the percentage resolves correctly instead of by content.
     Always fit the viewport width — never a horizontal scrollbar (the card is
     centered + max-width capped, so nothing legitimately needs to scroll sideways). */
  grid-template-columns:minmax(0,1fr);
  overflow-x:hidden;
  font-family:var(--font-sans);
  font-size:var(--fs-base);line-height:1.55;color:var(--ink);
  background-color:var(--bg);
  background-image:var(--page);
  background-attachment:fixed;background-repeat:no-repeat;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
/* On a card taller than the viewport (short/landscape phones), `safe center`
   keeps the top reachable and lets the page scroll instead of clipping it.
   480px, not 400px: most real phones in portrait (Pixel-class ~412-414px,
   iPhone Pro Max 428px) are WIDER than 400px, so a 400px cutoff excluded
   the phones this block exists for — confirmed live, 2026-08-21, on a
   Pixel 7 profile (412px) where none of these rules were taking effect.
   The `body ` prefix on every selector below is load-bearing, not style:
   `.card`/`.logo`/`.qr`/`.flow>*+*`/`.foot` each have an UNCONDITIONAL
   same-specificity twin later in this file, and CSS resolves equal
   specificity by source order — so a plain `.card{...}` in here loses to
   the plain `.card{...}` below it regardless of whether this media query
   matches. Confirmed live: before this prefix, only `div.trust{display:
   none}` (higher specificity via the element+class selector) was actually
   taking effect; every other rule in this block was silently a no-op. */
@media (max-width:480px){
  body{padding:var(--s4)}
  body .card{padding:var(--s6) var(--s5) var(--s5)}
  body .logo{width:48px;height:48px}
  body .qr{width:190px}
  body .flow>*+*{margin-top:var(--flow,var(--s3))}
  /* The badge row (div.trust — end-to-end-verified / no-password / approve-
     on-phone) is reassurance copy, not needed to sign in; the footer's own
     single-line trust caption (span.trust) stays. */
  div.trust{display:none}
  body .foot{margin-top:var(--s5);padding-top:var(--s4)}
}
/* A second, height-keyed tier for genuinely short phones (iPhone SE-class,
   ~568-630px tall) — the 480px width tier alone still leaves real overflow
   there (measured: iPhone SE 1137px of content against a 568px viewport,
   569px short even after every rule above). The footer's trust caption is
   the least-essential trailing content (same reasoning as the badge row
   above), so it goes entirely on short screens rather than just tightening
   further — the biggest remaining chunk is the functional username+method-
   picker form, which stays untouched here. */
@media (max-height:750px){
  body .qr{width:160px}
  body .flow>*+*{margin-top:var(--flow,var(--s2))}
  body .foot{display:none}
}
/* Premium 'glass' login card, tuned per theme so it sits in the violet glow
   (mirrors the authentik login card). Light: frosted-white with a violet-tinted
   hairline + soft halo. Dark: translucent near-black so the glow reads through. */
.card{width:100%;max-width:412px;text-align:center;padding:var(--s8) var(--s7) var(--s7);
  background:rgba(255,255,255,.80);border-radius:var(--r-xl);
  -webkit-backdrop-filter:blur(18px) saturate(130%);
  backdrop-filter:blur(18px) saturate(130%);
  border:1px solid rgba(120,119,198,.18);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.65),
    0 24px 60px -28px rgba(31,27,74,.35),
    0 0 80px -50px rgba(120,119,198,.60);
  animation:card-in var(--d4) var(--ease-out) both}
@keyframes card-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
@media (prefers-color-scheme:dark){
  .card{
    background:rgba(18,18,23,.72);
    border:1px solid rgba(255,255,255,.08);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.06),
      0 24px 70px -24px rgba(0,0,0,.85),
      0 0 90px -45px rgba(120,119,198,.55)}
}
.flow>*{margin-block:0}.flow>*+*{margin-top:var(--flow,var(--s5))}
.reveal>*{animation:rise var(--d3) var(--ease-out) both}
.reveal>*:nth-child(2){animation-delay:50ms}.reveal>*:nth-child(3){animation-delay:.1s}
.reveal>*:nth-child(4){animation-delay:.15s}.reveal>*:nth-child(5){animation-delay:.2s}
.reveal>*:nth-child(6){animation-delay:.25s}.reveal>*:nth-child(7){animation-delay:.3s}
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* The mark only — no tile/border/fill — with a premium 'sheen': a soft light
   glides diagonally across the logo every few seconds, masked to the mark's own
   silhouette (the logo PNG) so the shine follows the logo shape. Pure CSS, so it
   animates reliably everywhere. */
.logo{position:relative;width:72px;height:72px;margin:0 auto;display:grid;place-items:center}
.logo img{width:60px;height:60px;display:block;object-fit:contain;
  filter:drop-shadow(0 0 12px rgba(120,119,198,.40))}
.logo::after{content:'';position:absolute;inset:0;pointer-events:none;opacity:0;
  -webkit-mask:url(/static/pramaan-logo.png) center/60px 60px no-repeat;
  mask:url(/static/pramaan-logo.png) center/60px 60px no-repeat;
  background:linear-gradient(100deg,transparent 34%,rgba(255,255,255,.9) 50%,transparent 66%) no-repeat;
  background-size:320% 100%;background-position:175% 0;mix-blend-mode:screen}
@media (prefers-reduced-motion:no-preference){
  .logo::after{opacity:1;animation:logo-sheen 5s ease-in-out infinite}
}
/* Slow, lingering glide (sweep ~4.3s) then a long pause; wide soft band. */
@keyframes logo-sheen{0%{background-position:175% 0}62%,100%{background-position:-75% 0}}
.eyebrow{font-size:var(--fs-eyebrow);font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--brand)}
.title{font-size:var(--fs-title);font-weight:600;line-height:1.2;font-family:var(--font-display);letter-spacing:-.01em}
.subtitle{color:var(--ink-2);max-width:34ch;margin-inline:auto}
.fineprint{color:var(--ink-2);font-size:var(--fs-sm);max-width:34ch;margin:var(--s3) auto 0}
.fineprint a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.context{display:inline-flex;align-items:center;gap:var(--s2);max-width:100%;
  padding:var(--s2) var(--s3);border:1px solid var(--outline);border-radius:var(--r-sm);
  background:var(--surface-2);color:var(--ink-2);font-size:var(--fs-sm);overflow:hidden}
.context svg{width:14px;height:14px;flex:none;color:var(--ink-3)}
.context b{color:var(--ink);font-weight:600}
.context span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.context .dot{width:3px;height:3px;border-radius:50%;background:var(--ink-3);flex:none}
.qr{width:240px;max-width:78%;margin-inline:auto;padding:var(--s4);background:#fff;border-radius:var(--r-lg);
  border:1px solid var(--outline-2);box-shadow:var(--elev1)}
.qr svg{display:block;width:100%;height:auto;aspect-ratio:1}
.field>*+*{margin-top:var(--s3)}
.input{width:100%;height:52px;padding:0 var(--s4);border-radius:var(--r-md);border:1px solid var(--outline);
  background:var(--surface);color:var(--ink);font-size:1rem;font-family:inherit;outline:none;
  transition:border-color var(--d1) var(--ease),box-shadow var(--d1) var(--ease)}
.input::placeholder{color:var(--ink-3)}
.input:focus-visible{border-color:var(--brand);box-shadow:var(--ring)}
/* method picker: Material choice chips (radios styled as pills, no JS) */
.pills{display:flex;flex-wrap:wrap;gap:var(--s2);justify-content:center}
.pill-in{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.pill{display:inline-flex;align-items:center;gap:7px;min-height:44px;padding:0 var(--s4);
  border:1px solid var(--outline);border-radius:var(--r-pill);font-size:var(--fs-sm);font-weight:600;
  color:var(--ink-2);cursor:pointer;user-select:none;
  transition:background var(--d1),color var(--d1),border-color var(--d1)}
.pill svg{width:16px;height:16px;flex:none}
.pill:hover{background:var(--surface-2)}
.pill-in:checked + .pill{background:var(--brand);color:var(--on-brand);border-color:var(--brand)}
.pill-in:focus-visible + .pill{outline:none;box-shadow:var(--ring)}
/* Recommended badge on the primary (push) method */
.badge{font-size:.6rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  padding:2px 6px;border-radius:var(--r-pill);background:var(--brand-cont);color:var(--on-brand-cont)}
.pill-in:checked + .pill .badge{background:rgba(255,255,255,.22);color:#fff}
/* Self-explanatory idle motion per method: the push phone buzzes, SMS pops, the
   WhatsApp bubble pulses, the Telegram plane darts, the envelope bobs. Gentle &
   staggered so the row stays calm; reduce-motion disables it entirely. */
.pill svg{transform-origin:center}
@media (prefers-reduced-motion:no-preference){
  .pill[data-m=push] svg{animation:m-buzz 2s ease-in-out infinite}
  .pill[data-m=sms] svg{animation:m-pop 2.1s ease-in-out .15s infinite}
  .pill[data-m=whatsapp] svg{animation:m-pulse 2s ease-in-out .3s infinite}
  .pill[data-m=telegram] svg{animation:m-fly 2.2s ease-in-out .45s infinite}
  .pill[data-m=email] svg{animation:m-bob 2s ease-in-out .6s infinite}
}
@keyframes m-buzz{0%,80%,100%{transform:rotate(0)}83%{transform:rotate(-9deg)}86%{transform:rotate(8deg)}89%{transform:rotate(-6deg)}92%{transform:rotate(5deg)}95%{transform:rotate(-2deg)}}
@keyframes m-pop{0%,72%,100%{transform:scale(1)}84%{transform:scale(1.16)}}
@keyframes m-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.12)}}
@keyframes m-fly{0%,72%,100%{transform:translate(0,0) rotate(0)}84%{transform:translate(3px,-2px) rotate(10deg)}}
@keyframes m-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-2px)}}
/* brand tagline under the eyebrow + the trust row */
.brand-tag{font-size:var(--fs-sm);color:var(--ink-3);margin-top:-2px;letter-spacing:.01em}
.trust{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--s2) var(--s5);
  margin-top:var(--s1);color:var(--ink-3);font-size:var(--fs-sm)}
.trust span{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
.trust svg{width:14px;height:14px;color:var(--brand);flex:none}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);width:100%;min-height:48px;
  padding:0 var(--s5);border:1px solid transparent;border-radius:var(--r-pill);font:inherit;font-weight:600;
  cursor:pointer;text-decoration:none;
  transition:box-shadow var(--d1),background var(--d1),border-color var(--d1),filter var(--d1)}
.btn:focus-visible{outline:none;box-shadow:var(--ring)}
.btn-primary{color:var(--on-brand);background:var(--brand)}
.btn-primary:hover{box-shadow:var(--elev2);filter:brightness(1.08)}
.btn-ghost{color:var(--brand);background:transparent;border-color:var(--outline)}
.btn-ghost:hover{background:var(--surface-2)}
.btn-link{min-height:auto;width:auto;margin-inline:auto;padding:var(--s2) var(--s3);color:var(--brand);
  background:none;border:none;font-weight:600;font-size:var(--fs-sm)}
.btn-link:hover{text-decoration:underline}
.divider{display:flex;align-items:center;gap:var(--s3);color:var(--ink-3);font-size:var(--fs-sm)}
.divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--outline-2)}
/* Manual TOTP-secret fallback: a QR code alone is useless when you're
   setting up FROM the device you'd scan with (the common mobile case).
   Native <details> — no JS needed for the disclosure itself. */
#totp-manual{margin-top:var(--s3);text-align:left}
#totp-manual summary{list-style:none;cursor:pointer;display:inline-block;color:var(--brand);font-weight:600;font-size:var(--fs-sm)}
#totp-manual summary::-webkit-details-marker{display:none}
#totp-manual summary:hover{text-decoration:underline}
.totp-secret{display:block;margin:var(--s2) 0;padding:var(--s3);border-radius:var(--r-md);
  background:var(--surface-2);border:1px solid var(--outline-2);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9rem;
  letter-spacing:.05em;word-break:break-all;user-select:all;color:var(--ink)}
.match{margin-inline:auto;padding:var(--s5) var(--s8);border-radius:var(--r-lg);
  background:var(--brand-cont);color:var(--on-brand-cont)}
.num{font-size:var(--fs-num);font-weight:600;line-height:1;letter-spacing:.06em;font-variant-numeric:tabular-nums;
  color:var(--on-brand-cont);animation:num-in var(--d3) var(--ease-out) both}
@keyframes num-in{from{opacity:0;transform:scale(.92)}to{opacity:1;transform:none}}
.status{display:inline-flex;align-items:center;gap:var(--s2);color:var(--ink-2);font-size:var(--fs-sm)}
.status.is-ok{color:var(--ok);font-weight:600}
.status.is-err{color:var(--err);font-weight:600}
.spinner{width:15px;height:15px;border-radius:50%;flex:none;
  border:2px solid var(--outline);border-top-color:var(--brand);animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* Shimmer skeleton — theme-aware loading placeholder, matching the app. The
   global prefers-reduced-motion rule below freezes the sweep automatically. */
.skeleton{position:relative;overflow:hidden;border-radius:var(--r-md);
  background:var(--surface-2)}
.skeleton::after{content:'';position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--surface-3),transparent);
  animation:shimmer 1.2s ease-in-out infinite}
@keyframes shimmer{100%{transform:translateX(100%)}}
.sk-bar{height:12px;width:70%;margin:var(--s3) auto 0}
.offline{width:100%;text-align:left;border:1px solid var(--outline-2);border-radius:var(--r-md);
  background:var(--surface-2);padding:0 var(--s4)}
.offline>summary{cursor:pointer;list-style:none;padding:var(--s3) 0;color:var(--ink-2);
  font-size:var(--fs-sm);font-weight:600;text-align:center}
.offline>summary::-webkit-details-marker{display:none}
.offline[open]{padding-bottom:var(--s4)}
.offline .muted{color:var(--ink-3);font-size:var(--fs-sm);margin:var(--s2) 0}
.offline .input{margin-bottom:var(--s3);font-variant-numeric:tabular-nums;letter-spacing:.12em}
.foot{margin-top:var(--s7);padding-top:var(--s6);border-top:1px solid var(--outline-2);
  color:var(--ink-3);font-size:var(--fs-sm);line-height:1.5}
.trust{display:inline-flex;align-items:center;gap:var(--s2);justify-content:center}
.trust svg{width:13px;height:13px;flex:none}
/* Match-Glyphs: the user confirms this emoji row matches the one on their phone,
   a tripwire that the origin shown on the phone is the one driving this login. */
.glyph-check{margin-top:var(--s5);text-align:center}
.glyph-check-label{font-size:var(--fs-sm);color:var(--ink-3);margin:0 0 var(--s2);font-weight:600}
.glyphs{font-size:1.9rem;line-height:1.1;display:flex;justify-content:center;gap:.16em;
  font-variant-emoji:emoji}
/* --- Onboarding: the step rail -------------------------------------------
   The ceremony genuinely IS a sequence -- accept, choose a method, done -- so
   numbered steps encode something true rather than decorating the page. The
   rail is driven by `#root[data-step]`, set by invite.js, so the markup carries
   no state and stays byte-identical for every caller on a host (spec 6.2). */
.steps{display:flex;align-items:flex-start;gap:0;margin:0 0 var(--s5);list-style:none;padding:0}
.steps li{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative;
  font-size:var(--fs-sm);color:var(--ink-3);text-align:center;min-width:0}
/* The connector sits BEHIND the dots and stops short of each, so a long label
   never collides with the line. */
.steps li::before{content:'';position:absolute;top:11px;left:-50%;width:100%;height:2px;
  background:var(--outline);z-index:0}
.steps li:first-child::before{display:none}
.steps .dot{position:relative;z-index:1;width:22px;height:22px;border-radius:50%;
  display:grid;place-items:center;font-size:11px;font-weight:700;
  background:var(--surface);color:var(--ink-3);border:2px solid var(--outline);
  transition:background var(--d2) var(--ease),border-color var(--d2) var(--ease),
    color var(--d2) var(--ease)}
.steps .lbl{max-width:11ch;line-height:1.25}
/* Current step: filled, and gently breathing so the eye lands on it. */
#root[data-step='1'] .steps li:nth-child(1) .dot,
#root[data-step='2'] .steps li:nth-child(2) .dot,
#root[data-step='3'] .steps li:nth-child(3) .dot{
  background:var(--brand);border-color:var(--brand);color:var(--on-brand)}
#root[data-step='1'] .steps li:nth-child(1),
#root[data-step='2'] .steps li:nth-child(2),
#root[data-step='3'] .steps li:nth-child(3){color:var(--ink-1);font-weight:600}
@media (prefers-reduced-motion:no-preference){
  #root[data-step='1'] .steps li:nth-child(1) .dot,
  #root[data-step='2'] .steps li:nth-child(2) .dot,
  #root[data-step='3'] .steps li:nth-child(3) .dot{animation:step-pulse 2.4s ease-in-out infinite}
}
@keyframes step-pulse{0%,100%{box-shadow:0 0 0 0 rgba(0,0,0,0)}
  50%{box-shadow:0 0 0 5px color-mix(in srgb,var(--brand) 18%,transparent)}}
/* Completed steps: filled, ticked, and no longer animating. */
.steps li.is-done .dot{background:var(--brand-cont);border-color:var(--brand-cont);
  color:var(--on-brand-cont);animation:none}
.steps li.is-done .dot::after{content:'\2713';font-size:12px;line-height:1}
.steps li.is-done .dot span{display:none}

/* --- Onboarding: animated method pictograms ------------------------------
   Drawn inline as SVG rather than fetched: the CSP is `default-src 'self'` and
   these must render before any network round-trip, on the one page a brand-new
   user was told by email to trust. Each animation depicts the ACTION the button
   performs, which is the point -- a fingerprint being read, a code counting
   down, a phone being scanned. */
.mi{width:34px;height:34px;display:block;margin:0 auto var(--s2);color:var(--brand);flex:none}
/* SVG children transform about the viewBox, not their own bbox: `style=` is
   blocked by the CSP, so every origin is declared here. */
.mi *{transform-box:view-box;transform-origin:center}
.mi-app .frame{transform-origin:12px 11px}
.mi-wrap{display:flex;align-items:center;gap:var(--s3);justify-content:center}
@media (prefers-reduced-motion:no-preference){
  /* Passkey: the scan line sweeps down the fingerprint, then rests. */
  .mi-passkey .scan{animation:mi-scan 2.6s ease-in-out infinite}
  /* Authenticator: the ring depletes like a 30-second TOTP window. */
  .mi-totp .ring{animation:mi-tick 3s linear infinite}
  /* Password: the dots fill left to right. */
  .mi-pw .d1{animation:mi-fill 2.4s ease-in-out infinite}
  .mi-pw .d2{animation:mi-fill 2.4s ease-in-out .18s infinite}
  .mi-pw .d3{animation:mi-fill 2.4s ease-in-out .36s infinite}
  /* App: the scan frame closes in on the phone. */
  .mi-app .frame{animation:mi-focus 2.8s ease-in-out infinite}
}
@keyframes mi-scan{0%,100%{transform:translateY(-7px);opacity:0}
  15%{opacity:1}50%{transform:translateY(7px);opacity:1}70%{opacity:0}}
@keyframes mi-tick{from{stroke-dashoffset:0}to{stroke-dashoffset:63}}
@keyframes mi-fill{0%,100%{opacity:.25}45%{opacity:1}}
@keyframes mi-focus{0%,100%{transform:scale(1);opacity:.55}50%{transform:scale(.86);opacity:1}}
/* The browser reports no BUILT-IN authenticator. Dimmed and explained, never
   hidden and never disabled.
   `isUserVerifyingPlatformAuthenticatorAvailable()` answers only about a
   PLATFORM authenticator. It is false on a desktop with a security key, and
   false for phone-as-passkey over cross-device hybrid -- both of which work.
   So this is a nudge toward the likelier option, not a verdict: blocking the
   click here would deny a working method to exactly the users who brought
   their own authenticator. */
.is-no-builtin{opacity:.62}
@media (prefers-contrast:more){:root{--outline:#000;--ink-2:#000;--ink-3:#3a3a42}}
@media (prefers-reduced-motion:reduce){*{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}
