/* ==========================================================================
   auth-mobile.css — premium full-bleed mobile auth (Welcome / Sign In / Create)
   Shown ONLY on phones (≤768px) or when running as an installed PWA.
   Desktop browsers keep the existing #loginOverlay form untouched.
   All tokens + selectors are scoped under .m-auth so nothing here can leak
   into or collide with styles.css.
   ========================================================================== */

/* ── DESKTOP login box: staggered entrance ───────────────────────────────
   Everything in the box fades and rises in top-to-bottom so the whole panel
   arrives as one composed motion, rather than the social row being the only
   thing that animated.

   The order comes from an explicit --d index set on each block in the HTML,
   NOT from :nth-child. Sign-in and sign-up hide different rows (confirm
   password, password rules, remember me, ToS consent), and nth-child still
   counts the hidden ones — which left visible holes in the timing. Sharing an
   index between rows that never appear together keeps the cascade contiguous
   in both modes.

   animation-fill-mode: both, and the delay is the ONLY thing --d controls, so
   a missing index just means that block animates first — never that it stays
   invisible. */
@keyframes ft-auth-rise {
  from { opacity:0; transform:translateY(8px); }
  to   { opacity:1; transform:none; }
}
.login-inner > [style*="--d"],
#loginForm > [style*="--d"],
.dsk-social > *:not(.dsk-provider-row),
.dsk-provider-row > * {
  animation:ft-auth-rise .44s cubic-bezier(.22,.61,.36,1) both;
  animation-delay:calc(var(--d, 0) * 45ms);
}
/* Once risen, frozen. A password-manager extension (Proton Pass, in the
   user's recording) rewrites the email row's inline style and/or re-inserts
   the row to anchor its icon on focus. Either restarts this animation from
   its delay (measured: running@58ms, opacity 0), so the email row vanished
   for 180ms and rose back into place on every click. The password row never
   moved: its input sits inside .login-pass-wrap, not directly in the animated
   row. auth.js adds .ft-risen on animationend; a frozen row cannot replay. */
.login-inner > .ft-risen,
#loginForm > .ft-risen,
.dsk-social > .ft-risen,
.dsk-provider-row > .ft-risen {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}

/* The social block continues the cascade instead of arriving as one slab —
   divider, then each tile in turn, then the caption. Animating .dsk-social as
   a single unit made everything above it flow and then this land hard.
   .dsk-provider-row is excluded above because its CHILDREN animate; letting
   the row animate too would fade them twice. Indices are set here rather than
   inline because all three tiles are always present, so nth-child is safe —
   unlike the form rows, where hidden mode-specific fields make it lie. */
.dsk-divider{ --d:9; }
.dsk-provider-row > *:nth-child(1){ --d:10; }
.dsk-provider-row > *:nth-child(2){ --d:11; }
.dsk-provider-row > *:nth-child(3){ --d:12; }
.dsk-soon-tag{ --d:13; }

@media (prefers-reduced-motion:reduce){
  .login-inner > [style*="--d"],
  #loginForm > [style*="--d"],
  .dsk-social > *:not(.dsk-provider-row),
  .dsk-provider-row > *{ animation:none; }
}

/* ── DESKTOP social sign-in (inside the existing #loginOverlay form) ──────
   Not scoped to .m-auth — this is the desktop/tablet form, which keeps its
   original look; we're only appending a provider button below the submit. */
.dsk-divider{ display:flex; align-items:center; gap:12px; margin-bottom:14px;
  color:var(--text-faint); font-size:10px; letter-spacing:.14em; text-transform:uppercase; }
.dsk-divider::before, .dsk-divider::after{ content:""; height:1px; flex:1; background:var(--border-primary); }
/* Desktop tile for Google's OFFICIAL icon button — same treatment as the mobile
   tile so a future Microsoft tile can sit beside it. We style the tile only. */
/* Desktop provider row — same compact treatment, centred, ready for a second
   provider beside it. No tile around Google's button. */
.dsk-social{ margin-top:18px; }
/* Ring colour for the Apple / Microsoft tiles.
   Google draws its OWN ring (theme: outline_dark) at #8e918f and we cannot
   restyle its button, so ours match that value and the three read as one set.
   Deliberately NOT --border-primary / --m-line2: both composite to about
   rgb(46,48,52) — roughly a third as bright — which made Google's tile visibly
   pop out from its neighbours. If Google ever changes its ring, re-measure and
   update here rather than reaching for a site token. */
/* All four values measured directly from Google's injected stylesheet, dark
   variant (.i5vt6e-to915-Ia7Qfc), so our tiles behave identically to the
   button we cannot restyle. --soc-ring-hover is pale blue because Google's
   generic :hover rule sets border-color:#d2e3fc and sits LATER in their sheet
   at equal specificity (0,2,0) than the dark-variant border — so it wins.
   Re-measure from the CSSOM if Google ever changes it. */
:root{ --soc-ring:#8e918f; --soc-ring-hover:#d2e3fc;
       --soc-state-hover:rgba(255,255,255,.08);
       --soc-state-active:rgba(255,255,255,.12); }

/* provider row sits centred BELOW the "or continue with" divider */
.dsk-provider-row{ display:flex; justify-content:center; align-items:center; gap:14px; }
.dsk-soc{ width:40px; height:40px; flex:none; border-radius:50%;
  background:var(--bg-elevated); border:1px solid var(--soc-ring);
  display:flex; align-items:center; justify-content:center;
  transition:background-color .18s ease, background-image .18s ease, border-color .18s ease; }
.dsk-soc{ cursor:pointer; }
/* Google washes a white state layer over its button face on hover/active and
   turns its ring pale blue. Reproduced with a background-image overlay so it
   composites over our own background-color exactly the way their inner state
   layer composites over theirs — no glow, no border-width change. Gated on
   (hover:hover) so a tap on touch doesn't leave the state stuck on. */
@media (hover:hover){
  .dsk-soc:not(.soon):hover{ border-color:var(--soc-ring-hover);
    background-image:linear-gradient(var(--soc-state-hover),var(--soc-state-hover)); }
}
.dsk-soc:not(.soon):active{
  background-image:linear-gradient(var(--soc-state-active),var(--soc-state-active)); }
.dsk-soc:focus-visible{ outline:2px solid var(--accent-primary); outline-offset:2px; }
/* .soon = provider not configured. social-signin.js adds/removes this at
   runtime from /api/auth config, so a tile must never LOOK clickable unless
   it actually is. Hover is neutralised, not just the cursor. */
.dsk-soc.soon{ opacity:.45; cursor:default; }
.dsk-soc svg{ width:18px; height:18px; }
/* Ships EMPTY and is filled by social-signin.js once /api/auth reports which
   providers are actually configured. Hardcoding "Apple & Microsoft coming
   soon" meant the page stated something the server then contradicted a moment
   later, visibly rewriting itself to "Apple coming soon". min-height reserves
   the line so filling it in shifts nothing. */
.dsk-soon-tag{ text-align:center; font-size:10px; color:var(--text-faint);
  margin-top:10px; letter-spacing:.06em; min-height:1.2em; }
/* Reserve the button's footprint up front. Google renders asynchronously, so
   with fit-content this slot is 0x0 until its script lands and then snaps to
   40x40 — shoving Apple and Microsoft sideways mid-load (a real layout shift,
   observed at ~550ms with both tiles as the sources). 40x40 is Google's own
   icon-button size, so the slot is exactly filled once it arrives. */
.dsk-gis{ width:40px; height:40px; flex:none; padding:0;
  display:grid; place-items:center;
  /* Dressed as a tile rather than left transparent. Reserving the 40x40 alone
     stopped the row shifting, but the slot still painted as a HOLE between
     Apple and Microsoft until Google's button arrived — the remaining "flash".
     These match .dsk-soc exactly, and Google's own button is 40x40 with the
     same ring colour, so it lands precisely on top and the swap is invisible. */
  border:0; border-radius:50%; background:var(--bg-elevated);
  /* inset shadow, NOT a border: a 1px border shrinks the content box to 38px,
     and Google's 40px button then cannot centre in it — it landed 1px down and
     right, showing a double edge. An inset shadow paints the same ring without
     taking layout space. */
  box-shadow:inset 0 0 0 1px var(--soc-ring); }
/* If GIS can't load we replace the contents with a message — don't frame that
   in a button-shaped ring, it would read as a dead control. */
.dsk-gis[data-gis-failed]{ box-shadow:none; background:none; width:auto; height:auto; }

/* ── settling the button in ───────────────────────────────────────────────
   The tile is painted from the first frame, but Google injects its logo
   whenever its script happens to finish, and that last step still landed as a
   hard pop. Fading the injected contents in turns it into the icon settling
   into a tile that was already there.

   Scoped to the slot's CHILDREN so the tile itself never animates — the ring
   must be solid immediately or we are back to a hole appearing.

   Deliberately an ANIMATION, not a transition driven by a JS class. A class
   toggle means any missed call — an error path, an early return, a Google
   change — leaves the button stuck at opacity 0 and invisible. An animation
   fires the moment Google injects the node and always finishes at 1, so the
   worst case is no fade rather than no button. It also needs no JS at all. */
@keyframes ft-gis-in { from { opacity:0 } to { opacity:1 } }
.dsk-gis > *, .m-gis > *{ animation:ft-gis-in .32s cubic-bezier(.22,.61,.36,1) both; }
@media (prefers-reduced-motion:reduce){
  .dsk-gis > *, .m-gis > *{ animation:none; }
}
.dsk-gis > div{ margin:0 !important; }
/* Do NOT zero the iframe's margin. On the production origin Google renders
   the icon button as a 64x44 iframe carrying its own `margin:-2px -12px`, so
   its margin box is exactly 40x40 and the circle centres in this tile. With
   the margin forced to 0 the frame anchored at the tile's top-left and its
   circle landed 12px right and 2px down — a second ring beside ours (seen
   live 2026-09-02). localhost gets Google's inline-DOM variant instead, which
   is why this never showed in local testing. */
.dsk-gis iframe{ display:block; }

/* ── visibility gating ───────────────────────────────────────────────── */
.m-auth{ display:none; }
@media (max-width:768px), (display-mode:standalone){
  html.login-visible #loginOverlay,
  #loginOverlay{ display:none !important; }   /* hide the desktop form */
  .m-auth{ display:block; }                    /* show the mobile design */
}

/* ── scope + tokens ──────────────────────────────────────────────────── */
.m-auth{
  --m-bg:#0a0a0a; --m-card:#111214; --m-elevated:#141518; --m-input:#121316;
  --m-line:#232427; --m-line2:#2e3034;
  --m-text:#e8eaed; --m-text2:#aab0b8; --m-muted:#8a909a; --m-faint:#6c727c; --m-heading:#fff;
  --m-acc:#ff6a00; --m-acc2:#ff8a2a;
  --m-acc-grad:linear-gradient(180deg,#ff7d1e,#ff5f04);
  --m-ok:#35d391; --m-warn:#ffb020; --m-bad:#ff5a5a;
  --m-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --m-disp:'Inter Tight','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --m-ease:cubic-bezier(.22,.61,.36,1);
  --m-spring:cubic-bezier(.34,1.56,.64,1);

  /* Scrollable, so you can still reach fields/buttons while the keyboard is open.
     The browser's own scroll-into-view (which used to fight the JS and cause the
     overshoot-then-settle) is suppressed in auth-mobile.js by taking over focus
     with preventScroll, so the JS scroll below stays the only motion. */
  position:fixed; inset:0; z-index:2000;
  overflow-y:auto; overflow-x:hidden; -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
  scroll-padding-block:20px;   /* breathing room for any scroll-into-view */
  background:radial-gradient(120% 78% at 50% -10%, #121011 0%, #0a0a0a 46%, #070707 100%);
  color:var(--m-text); font-family:var(--m-sans);
  -webkit-font-smoothing:antialiased;
}
.m-auth::-webkit-scrollbar{ display:none; }
.m-auth *{ box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
/* the real CTAs KEEP the global button press-feedback; only the icon buttons
   (back arrow, password eye) opt out — preserve their centering, no press-scale */
.m-auth .m-iconbtn:active,
.m-auth .m-in .eye:active{ transform:translateY(-50%) !important; }

/* ── ambient background ──────────────────────────────────────────────
   Alphas kept low on purpose. At .5/.34/.30 these blobs stopped reading as
   ambient light and became an orange fog across the top third — the single
   biggest thing making the screen feel heavy rather than clean. They should
   suggest warmth behind the content, not tint it. The drift and the grain
   stay: they are what keep a flat dark screen from looking dead. */
.m-aura{ position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none; }
.m-aura span{ position:absolute; border-radius:50%; filter:blur(48px); will-change:transform; }
.m-aura .beam{ border-radius:0; filter:none; top:0; left:50%; transform:translateX(-50%); width:132%; height:46%;
  /* A defined pool of light behind the orb, not a tint across the whole top.
     Flattening the alpha alone left the screen bland — the fix is a tighter,
     slightly stronger falloff, so there is a light SOURCE rather than haze. */
  background:radial-gradient(38% 62% at 50% 2%, rgba(255,138,48,.26), rgba(255,116,30,.07) 46%, rgba(255,116,30,0) 72%); }
.m-aura .a1{ width:78%; height:40%; top:-8%; right:-16%;
  background:radial-gradient(circle, rgba(255,120,32,.19), rgba(255,120,32,0) 68%);
  animation:m-drift1 16s ease-in-out infinite alternate; }
.m-aura .a2{ width:92%; height:46%; top:2%; left:-26%;
  background:radial-gradient(circle, rgba(255,74,10,.13), rgba(255,74,10,0) 70%);
  animation:m-drift2 21s ease-in-out infinite alternate; }
.m-aura .a3{ width:66%; height:32%; top:26%; right:-14%;
  background:radial-gradient(circle, rgba(255,168,66,.08), rgba(255,168,66,0) 70%);
  animation:m-drift3 19s ease-in-out infinite alternate; }
.m-aura .grain{ position:absolute; inset:0; opacity:.22; mix-blend-mode:overlay;
  background-image:radial-gradient(rgba(255,255,255,.10) .5px, transparent .6px);
  background-size:4px 4px; filter:none; }
@keyframes m-drift1{ to{ transform:translate(-9%,7%) scale(1.14); } }
@keyframes m-drift2{ to{ transform:translate(11%,-5%) scale(1.12); } }
@keyframes m-drift3{ to{ transform:translate(-7%,-9%) scale(1.16); } }

/* ── views: flow inside the scrollable container (one shown at a time) ── */
.m-views{ position:relative; z-index:5; min-height:100%; display:flex; flex-direction:column;
  transition:transform .28s var(--m-ease); will-change:transform; }
.m-view{ display:none; }
.m-view.active{ display:flex; flex-direction:column; flex:1 0 auto; min-height:100%;
  padding:calc(54px + env(safe-area-inset-top)) 24px calc(30px + env(safe-area-inset-bottom)); }
.m-view.form{ background:linear-gradient(180deg,rgba(10,10,10,.82),rgba(9,9,9,.97)); }
/* When the on-screen keyboard opens, Android shrinks the layout viewport. The flex
   spacers then collapse and the content nearly fits, leaving almost no scroll range —
   so the browser physically can't lift a low field above the keyboard. Give it room
   (and let content sit top-aligned) whenever the viewport is short. Reverts
   automatically when the keyboard closes, so there's no dead space to scroll into. */
@media (max-height:620px){
  /* Top-align when the viewport is short. The scroll room that used to live
     here as a flat `padding-bottom:200px` is now set by auth-mobile.js from
     the measured keyboard height: 200px was both the wrong size for most
     keyboards and applied to every short viewport, keyboard or not. */
  .m-view.active{ justify-content:flex-start; }
  .m-feats{ margin-bottom:0; }
  .m-foot-line, .m-legal{ margin-top:18px; }
}

/* ── app bar ─────────────────────────────────────────────────────────── */
.m-appbar{ display:flex; align-items:center; justify-content:center; position:relative;
  height:30px; margin-bottom:6px; flex:none; }
.m-appbar .t{ font-family:var(--m-disp); font-weight:600; font-size:17px; color:var(--m-heading); }
.m-iconbtn{ position:absolute; left:0; top:50%; transform:translateY(-50%);
  width:38px;height:38px;border-radius:12px; cursor:pointer;
  background:rgba(255,255,255,.05); border:1px solid var(--m-line);
  display:flex;align-items:center;justify-content:center; color:var(--m-text); transition:.18s var(--m-ease); }
.m-iconbtn:active{ background:rgba(255,255,255,.10); }
.m-skip{ position:absolute; top:calc(56px + env(safe-area-inset-top)); right:22px; z-index:6;
  background:none; border:none; color:var(--m-muted); font-size:13.5px; font-weight:600; cursor:pointer; padding:6px 4px; }

/* ── logo badge (glossy orb) ─────────────────────────────────────────── */
.m-badge{ border-radius:50%; flex:none; display:flex;align-items:center;justify-content:center; position:relative;
  background:radial-gradient(circle at 50% 34%, #1d1714 0%, #0b0b0d 74%);
  /* Glow deliberately restrained: the old halo (40px at .42) bled across the
     whole upper third of the screen and read as haze rather than light. The
     rim, the inner crescent and a much tighter ambient are enough to give the
     orb depth without washing the background. */
  border:1.5px solid rgba(255,128,40,.34);
  box-shadow:0 6px 20px rgba(255,106,0,.15), inset 0 -14px 26px rgba(255,106,0,.14), inset 0 3px 12px rgba(255,255,255,.05);
  /* Slow breathe on the outer halo only — the inner crescent and rim stay put,
     so the orb keeps its shape and just seems lit from behind. 4.6s is
     deliberately out of step with the 2.8s summit-dot pulse: matched periods
     read as a mechanical blink, offset ones read as something alive. */
  animation:m-orbbreathe 4.6s ease-in-out infinite; }
@keyframes m-orbbreathe{
  0%,100%{ box-shadow:0 6px 18px rgba(255,106,0,.11), inset 0 -14px 26px rgba(255,106,0,.14), inset 0 3px 12px rgba(255,255,255,.05); }
  50%    { box-shadow:0 6px 34px rgba(255,106,0,.28), inset 0 -14px 26px rgba(255,106,0,.17), inset 0 3px 12px rgba(255,255,255,.05); }
}
@media (prefers-reduced-motion:reduce){ .m-badge{ animation:none; } }
.m-badge svg{ filter:drop-shadow(0 0 4px rgba(255,140,50,.30)); overflow:visible; position:relative; z-index:2; }
.m-line{ stroke-dasharray:100; stroke-dashoffset:100; animation:m-draw 1.25s var(--m-ease) .3s forwards; }
@keyframes m-draw{ to{ stroke-dashoffset:0; } }
.m-ldot{ transform-box:fill-box; transform-origin:center; opacity:0;
  animation:m-dotpop .5s var(--m-spring) 1.4s forwards, m-dotpulse 2.8s ease-in-out 2s infinite; }
@keyframes m-dotpop{ 0%{opacity:0;transform:scale(0)} 62%{opacity:1;transform:scale(1.3)} 100%{opacity:1;transform:scale(1)} }
@keyframes m-dotpulse{ 0%,100%{filter:drop-shadow(0 0 1px rgba(255,154,42,.5))} 50%{filter:drop-shadow(0 0 6px rgba(255,154,42,1))} }

/* ── WELCOME (carousel) ──────────────────────────────────────────────── */
#mv-welcome{ padding-top:calc(56px + env(safe-area-inset-top)); }
.m-lock{ display:flex; flex-direction:column; align-items:center; text-align:center; margin-top:2px; }
.m-lock .m-badge{ width:94px;height:94px;margin:0 0 18px; }
.m-lock .m-badge::before{ content:""; position:absolute; inset:-44px; border-radius:50%; pointer-events:none; z-index:0;
  background:repeating-radial-gradient(circle, transparent 0 15px, rgba(255,140,60,.10) 15px 16px);
  -webkit-mask:radial-gradient(circle, transparent 47px, #000 52px, transparent 104px);
          mask:radial-gradient(circle, transparent 47px, #000 52px, transparent 104px); }
.m-lock .m-badge::after{ content:""; position:absolute; left:50%; bottom:-6px; transform:translateX(-50%);
  width:74%; height:15px; border-radius:50%; z-index:1;
  background:radial-gradient(ellipse, rgba(255,150,52,.32), rgba(255,150,52,0) 70%); filter:blur(6px); }
.m-word{ font-family:var(--m-disp); font-weight:800; line-height:1.0; letter-spacing:.08em; font-size:23px; color:#f4f5f6; position:relative; white-space:nowrap; }
.m-word .fire{ background:var(--gradient-fire, linear-gradient(90deg,#ff4500,#ff8c00,#ff5f10)); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
.m-word .t2{ display:inline; color:#f4f5f6; margin-top:0; text-shadow:0 0 20px rgba(255,255,255,.12); }
.m-word::after{ content:""; display:block; width:132px; height:1px; margin:18px auto 0;
  background:linear-gradient(90deg, transparent, rgba(255,150,55,.5), transparent); box-shadow:0 0 6px rgba(255,140,50,.22); }

/* welcome: tagline + feature CARDS + two CTAs + secure footer */
.m-tag{ margin:16px auto 26px; font-size:14.5px; line-height:1.55; color:var(--m-text2); max-width:280px; text-align:center; }
/* auto on BOTH sides so spare vertical space splits above and below the cards
   instead of pooling into one dead band above the CTAs. The 26px minimum gap
   under the tagline now comes from .m-tag's own margin, so a short screen
   (where the autos collapse to 0) still breathes. */
.m-feats{ display:flex; flex-direction:column; gap:10px; margin:auto 0; }
.m-feat{ display:flex; align-items:center;
  background:linear-gradient(160deg, rgba(255,255,255,.035), rgba(255,255,255,.012));
  border:1px solid var(--m-line); border-radius:16px; padding:11px 15px; }
.m-feat .fic{ width:44px;height:44px;border-radius:12px; flex:none; margin-right:14px; position:relative;
  display:flex;align-items:center;justify-content:center; color:#ff9a3a;
  background:radial-gradient(circle at 50% 32%, rgba(255,106,0,.22), rgba(255,106,0,.05) 78%);
  border:1px solid rgba(255,124,36,.26);
  box-shadow:inset 0 -6px 12px rgba(255,106,0,.10); }
.m-feat .fic svg{ width:21px; height:21px; filter:drop-shadow(0 0 3px rgba(255,140,50,.22)); }
.m-feat .ftx{ padding-left:14px; border-left:1px solid var(--m-line2); }
.m-feat .ft{ font-weight:700; font-size:15px; color:var(--m-heading); margin:0 0 2px; }
.m-feat .fs{ font-size:12px; line-height:1.4; color:var(--m-muted); }
.m-foot{ margin-top:24px; }   /* guaranteed gap so CTAs never touch the last card */
.m-secure{ display:flex; align-items:center; justify-content:center; gap:7px; margin:16px 0 0; font-size:12px; color:var(--m-faint); }
.m-secure svg{ opacity:.8; }

.m-carousel{ margin-top:auto; overflow:hidden; touch-action:pan-y; cursor:grab; }
.m-carousel:active{ cursor:grabbing; }
.m-track{ display:flex; transition:transform .42s var(--m-ease); }
.m-slide{ flex:0 0 100%; display:flex; flex-direction:column; align-items:center; text-align:center; padding:0 8px; }
.m-slide .ic{ width:100px;height:100px;border-radius:28px; flex:none; margin-bottom:34px; position:relative;
  display:flex;align-items:center;justify-content:center; color:#ff9a3a;
  background:radial-gradient(circle at 50% 30%, #1d1714 0%, #0b0b0d 76%);
  border:1.5px solid rgba(255,124,36,.52);
  box-shadow:0 10px 40px rgba(255,106,0,.34), inset 0 -16px 28px rgba(255,106,0,.24), inset 0 3px 12px rgba(255,255,255,.06); }
.m-slide .ic svg{ filter:drop-shadow(0 0 7px rgba(255,140,50,.7)); position:relative; z-index:2; }
.m-slide .ic::after{ content:""; position:absolute; left:50%; bottom:-16px; transform:translateX(-50%);
  width:74%; height:18px; border-radius:50%; background:radial-gradient(ellipse, rgba(255,132,46,.5), rgba(255,132,46,0) 70%); filter:blur(7px); }
.m-slide .tt{ font-family:var(--m-disp); font-weight:700; font-size:29px; color:var(--m-heading); margin:0 0 11px; letter-spacing:-.01em; text-shadow:0 0 26px rgba(255,255,255,.12); }
.m-slide .sub{ font-size:14.5px; line-height:1.6; color:var(--m-text2); max-width:274px; margin:0; }

.m-dots{ display:flex; gap:7px; justify-content:center; margin:30px 0 auto; }
.m-dots i{ width:7px;height:7px;border-radius:50%;background:var(--m-line2);transition:.3s var(--m-ease); cursor:pointer; }
.m-dots i.on{ width:22px;border-radius:4px;background:var(--m-acc); }
.m-foot{ margin-top:24px; }

/* ── buttons ─────────────────────────────────────────────────────────── */
.m-btn{ position:relative; font-family:var(--m-sans); font-weight:700; font-size:16px;
  border:none; border-radius:16px; padding:17px 22px; cursor:pointer; width:100%;
  display:flex; align-items:center; justify-content:center; color:#fff;
  transition:transform .16s var(--m-ease), box-shadow .24s var(--m-ease), filter .2s; }
.m-btn:active{ filter:brightness(.97); }
.m-btn[disabled]{ opacity:.5; pointer-events:none; }
.m-btn-primary{ background:var(--m-acc-grad);
  /* the 46px ambient made the button look lit from within and fogged the
     area around it; the lift shadow alone reads as a solid, pressable control */
  box-shadow:0 10px 26px -10px rgba(255,106,0,.5), 0 1px 0 rgba(255,255,255,.28) inset; }
.m-btn-primary:active{ filter:brightness(.96); }
.m-btn-ghost{ background:rgba(255,255,255,.05); color:var(--m-text); border:1px solid var(--m-line2); box-shadow:none; }
.m-btn-ghost:active{ background:rgba(255,255,255,.09); }
.m-foot .m-btn + .m-btn{ margin-top:11px; }
.m-btn .arw{ position:absolute; right:20px; transition:transform .3s var(--m-spring); }
.m-signin-line{ text-align:center; font-size:13.5px; color:var(--m-muted); margin-top:16px; }
.m-link{ color:var(--m-acc2); font-weight:650; cursor:pointer; text-decoration:none; }
.m-link:active{ opacity:.7; }

/* ── form screens ────────────────────────────────────────────────────── */
.m-badge.sm{ width:78px;height:78px;margin:8px auto 0; }
.m-cap{ text-align:center; margin:14px 0 22px; }
.m-head{ font-family:var(--m-disp); font-weight:700; letter-spacing:-.02em; font-size:24px; color:var(--m-heading); margin:0 0 6px; }
.m-sub{ font-size:13.5px; line-height:1.5; color:var(--m-muted); margin:0; }
.m-form{ display:flex; flex-direction:column; gap:15px; }
.m-fl{ display:block; font-size:12.5px; font-weight:650; color:var(--m-text2); margin-bottom:7px; }
.m-in{ position:relative; }
.m-in .lic{ position:absolute; left:15px; top:50%; transform:translateY(-50%); color:var(--m-acc); display:flex; }
.m-in input{ width:100%; font-family:var(--m-sans); font-size:16px; color:var(--m-text);
  background:var(--m-input); border:1.5px solid var(--m-line); border-radius:14px; padding:15px 46px 15px 44px; outline:none;
  transition:border-color .2s var(--m-ease), box-shadow .2s, background .2s;
  /* gap the browser leaves when it scrolls this field into view above the keyboard */
  scroll-margin-bottom:24px; scroll-margin-top:22px; }
.m-in input::placeholder{ color:var(--m-faint); }
.m-in input:focus{ border-color:rgba(255,106,0,.7); background:#161211; box-shadow:0 0 0 4px rgba(255,106,0,.10); }
.m-in .eye{ position:absolute; right:8px; top:50%; transform:translateY(-50%); background:transparent; border:none; color:var(--m-muted); cursor:pointer; padding:9px; border-radius:9px; display:flex; }
.m-rowline{ display:flex; align-items:center; justify-content:space-between; margin-top:-2px; }
.m-check{ display:flex; align-items:center; gap:9px; cursor:pointer; user-select:none; }
.m-box{ width:21px;height:21px;border-radius:6px;border:1.5px solid var(--m-line2);flex:none; display:flex;align-items:center;justify-content:center;transition:.16s; background:var(--m-input); }
.m-box.on{ background:var(--m-acc); border-color:transparent; }
.m-box svg{ opacity:0;transition:.16s;transform:scale(.5); }
.m-box.on svg{ opacity:1;transform:none; }
.m-check span{ font-size:13.5px;color:var(--m-text2); }

.m-pwm{ display:flex; gap:5px; margin-top:9px; }
.m-pwm i{ flex:1;height:4px;border-radius:2px;background:var(--m-line2);transition:.25s; }
.m-pwl{ font-size:12px; color:var(--m-muted); margin-top:6px; }
.m-pwl b{ font-weight:700; }

.m-divider{ display:flex; align-items:center; gap:12px; margin:20px 0 16px; color:var(--m-faint); font-size:12px; }
.m-divider::before,.m-divider::after{ content:""; height:1px; flex:1; background:var(--m-line); }
/* Shown in place of the Google button when GIS can't run (insecure origin,
   missing client ID, blocked script…). The area stays visible on purpose —
   never silently hidden — and the console carries the specific reason. */
.gis-fallback{ width:100%; display:flex; align-items:center; justify-content:center;
  min-height:44px; padding:11px 14px; border-radius:12px;
  border:1px dashed rgba(255,255,255,.16); background:rgba(255,255,255,.03);
  color:#8a909a; font-size:12.5px; line-height:1.35; text-align:center; }

/* ── compact provider row ────────────────────────────────────────────────
   Google's rendered button is the ONLY Google boundary — no tile, no card
   around it. Other providers are sized to match it so the row reads as one
   consistent set of controls. */
.m-social{ display:flex; justify-content:center; align-items:center; gap:14px; }

/* Wrapper for Google's official button: literally just a centring box.
   No border, background, shadow or padding — and nothing that would stretch,
   crop, recolour or intercept Google's control. */
/* see the .dsk-gis note — same reserved footprint */
.m-gis{ width:40px; height:40px; flex:none; border:0; background:none;
  box-shadow:none; padding:0; display:grid; place-items:center; }
.m-gis > div{ margin:0 !important; }
/* same rule as .dsk-gis iframe — see the note there */
.m-gis iframe{ display:block; }

/* COMPAT FIX — do not remove.
   Google renders its button as real DOM in our page (not a sealed iframe), so
   our global reset applies to it and breaks two of Google's own assumptions:

   1. box-sizing. Google's CSS is written for the browser default (content-box):
      the logo wrapper is 20x20 + 10px padding = the 40px button. Our
      `* { box-sizing: border-box }` made that 20x20 INCLUSIVE of padding, so the
      content box became 0x0 and the logo sat 9px below centre.
   2. intrinsic size. The logo <svg> ships with only viewBox="0 0 48 48" and no
      width/height, relying on the host page — our reset left it 0x0, so the
      button rendered as an empty circle.

   Restoring both gives Google's button exactly the geometry it expects. Nothing
   is recoloured, cropped, scaled, overlaid or intercepted. */
.m-gis *, .dsk-gis *{ box-sizing:content-box; }
.m-gis svg, .dsk-gis svg{ width:18px; height:18px; }

/* Apple / Microsoft — same 40px circular footprint as Google's icon button */
.m-soc{ width:40px; height:40px; flex:none; border-radius:50%;
  background:var(--m-elevated); border:1px solid var(--soc-ring);
  display:flex; align-items:center; justify-content:center;
  transition:background-color .18s var(--m-ease), background-image .18s var(--m-ease),
             border-color .18s var(--m-ease); }
.m-soc{ cursor:pointer; }
/* see the .dsk-soc note — on phones :active is the equivalent moment, and it
   gets the state layer only: Google's pale-blue ring comes from its :hover
   rule, which never fires on touch. */
@media (hover:hover){
  .m-soc:not(.soon):hover{ border-color:var(--soc-ring-hover);
    background-image:linear-gradient(var(--soc-state-hover),var(--soc-state-hover)); }
}
.m-soc:not(.soon):active{
  background-image:linear-gradient(var(--soc-state-active),var(--soc-state-active)); }
.m-soc:focus-visible{ outline:2px solid var(--m-acc); outline-offset:2px; }
/* see the .dsk-soc.soon note — same runtime toggle, same rule */
.m-soc.soon{ opacity:.45; cursor:default; }
.m-soc svg{ width:18px; height:18px; }
/* see the .dsk-soon-tag note — ships empty, filled after config, height reserved */
.m-soon-tag{ text-align:center; font-size:11px; color:var(--m-faint); margin-top:9px; letter-spacing:.02em; min-height:1.2em; }
.m-bio-row{ display:flex; align-items:center; justify-content:center; gap:8px; margin-top:16px; font-size:12.5px; color:var(--m-muted); cursor:pointer; }
.m-bio-row svg{ color:var(--m-acc2); }
.m-foot-line{ text-align:center; font-size:13.5px; color:var(--m-muted); margin-top:auto; padding-top:20px; }
.m-legal{ text-align:center; font-size:12px; line-height:1.5; color:var(--m-faint); margin:14px 4px 0; }
.m-legal .m-link{ font-weight:600; }
/* big glossy hero icon (forgot / check-email / MFA) */
.m-ic-lg{ width:92px;height:92px;border-radius:26px; position:relative; margin:6px auto 26px; flex:none;
  display:flex;align-items:center;justify-content:center; color:#ff9a3a;
  background:radial-gradient(circle at 50% 30%, #1d1714 0%, #0b0b0d 76%);
  border:1.5px solid rgba(255,124,36,.30);
  box-shadow:0 6px 20px rgba(255,106,0,.13), inset 0 -14px 24px rgba(255,106,0,.12), inset 0 3px 12px rgba(255,255,255,.05); }
.m-ic-lg svg{ filter:drop-shadow(0 0 4px rgba(255,140,50,.28)); position:relative; z-index:2; }
.m-ic-lg::after{ content:""; position:absolute; left:50%; bottom:-14px; transform:translateX(-50%);
  width:72%; height:16px; border-radius:50%;
  background:radial-gradient(ellipse, rgba(255,132,46,.24), rgba(255,132,46,0) 70%); filter:blur(7px); }

/* 6-digit code boxes */
.m-otp{ display:flex; gap:9px; justify-content:center; margin:4px 0 2px; }
.m-otp input{ width:46px;height:56px;text-align:center;font-size:23px;font-weight:700;
  font-family:var(--m-disp); color:var(--m-heading); background:var(--m-input);
  border:1.5px solid var(--m-line); border-radius:13px; outline:none; caret-color:var(--m-acc);
  transition:border-color .18s, box-shadow .18s, background .18s; }
.m-otp input:focus{ border-color:rgba(255,106,0,.7); box-shadow:0 0 0 4px rgba(255,106,0,.10); }
.m-otp input.filled{ border-color:rgba(255,106,0,.45); background:#161211; }
.m-resend{ text-align:center; font-size:13px; color:var(--m-muted); margin-top:16px; }

.m-err{ font-size:13px; line-height:1.4; text-align:center; margin-top:6px; min-height:1px; }
.m-err.err{ color:var(--m-bad); }
.m-err.ok{ color:var(--m-ok); }

.m-stag{ opacity:0; transform:translateY(13px); }
.m-view.active .m-stag{ animation:m-rise2 .55s var(--m-ease) both; }
@keyframes m-rise2{ from{ opacity:0; transform:translateY(13px); } to{ opacity:1; transform:none; } }
/* The social row carries .s5, so its three tiles arrived together as one slab
   while everything above them cascaded — the same thing that made the desktop
   row land hard. Each tile now continues the sequence from .s5 (.32s) at the
   same 70ms step the s-scale uses. The row itself stops animating, or the
   tiles would fade twice. */
.m-view.active .m-social{ animation:none; opacity:1; transform:none; }
.m-view.active .m-social > *{ animation:m-rise2 .55s var(--m-ease) both; }
.m-view.active .m-social > *:nth-child(1){ animation-delay:.32s }
.m-view.active .m-social > *:nth-child(2){ animation-delay:.39s }
.m-view.active .m-social > *:nth-child(3){ animation-delay:.46s }

.m-view.active .s1{animation-delay:.04s}.m-view.active .s2{animation-delay:.11s}
.m-view.active .s3{animation-delay:.18s}.m-view.active .s4{animation-delay:.25s}
.m-view.active .s5{animation-delay:.32s}.m-view.active .s6{animation-delay:.39s}
/* after the s-scale on purpose: .m-soon-tag also carries .s5, and equal
   specificity means whichever is written last wins. */
.m-view.active .m-soon-tag{ animation-delay:.53s }

/* The orb also carries .m-stag, and that rule sets the `animation` shorthand —
   which REPLACED the breathe outright, so it silently never ran. Both are
   listed here, after the s-scale, so the entrance and the ambient pulse
   coexist. They animate different properties (m-rise2: opacity/transform,
   m-orbbreathe: box-shadow), so there is nothing to fight over. */
.m-view.active .m-badge{
  animation:m-rise2 .55s var(--m-ease) both, m-orbbreathe 4.6s ease-in-out infinite; }
@media (prefers-reduced-motion:reduce){
  .m-view.active .m-badge{ animation:none; }
}

@media (prefers-reduced-motion:reduce){
  .m-auth *{ animation-duration:.01ms !important; transition-duration:.06s !important; }
}
