/* Dolvero Edge - the signed-out pages: sign in, and sign up.
   ==========================================================================
   Lifted out of v2/index.php, where it was 23KB of inline CSS re-sent on every
   load of the page every visitor sees first.

   It is SHARED on purpose. The sign-up page is the same shell as the sign-in
   page - same aurora ground, same panel, same field, same button - and the two
   drifting apart would be visible the moment somebody clicked "Create one".
   The light-theme background fix from 2026-08-25 lives here, so both pages get
   it or neither does.

   Cached for 30 days by .htaccess. Bump the ?v= stamp in BOTH pages on any edit.
   ========================================================================== */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}

/* Two complete palettes. Every colour is defined on :root first, so a token can
   never exist only inside the light block - that is how half-themed pages happen. */
:root{
  --ink:#080d16;
  --blue:#4f7df0;
  --blue-deep:#2554d4;
  --blue-soft:#93b8ff;
  --text:#fff;
  --muted:rgba(226,234,250,.62);
  --faint:rgba(226,234,250,.4);
  --line:rgba(147,184,255,.16);
  --danger:#ff8d95;
  --ok:#7ee2b8;
  --glass:rgba(255,255,255,.06);
  --glass-line:rgba(147,184,255,.22);
  --shadow:0 10px 30px -10px rgba(37,84,212,.85);
}

:root[data-theme="light"]{
  --ink:#f6f8fd;
  --text:#0a1020;
  /* These are deliberately dark. The first pass used the dark theme's opacities
     inverted, which left the legal line and the disabled buttons failing
     contrast against a near-white ground. */
  --muted:rgba(10,16,32,.74);
  --faint:rgba(10,16,32,.58);
  --line:rgba(20,45,110,.14);
  --danger:#b3232f;
  --ok:#0b6b47;
  --glass:rgba(255,255,255,.72);
  --glass-line:rgba(20,45,110,.14);
  --shadow:0 12px 34px -14px rgba(37,84,212,.42);
}

/* The slow crossfade between schemes. Kept on colour properties only - animating
   layout would jank on a 1 vCPU box. */
/* One shared easing and duration so nothing arrives out of step with anything
   else. Colour properties only - animating layout would jank on a 1 vCPU box. */
:root{--theme-ease:cubic-bezier(.32,.72,0,1);--theme-dur:900ms}
body,.bg,.bg__scrim,.bg__vignette,.bg__grid,.field input,.btn,.notice,.legal,
.foot,.mark,.themer,.divider,.pill,.peek,.field label,.hint,.field-error{
  transition:background-color var(--theme-dur) var(--theme-ease),
             /* background-image is NOT transitioned. It is a DISCRETE property -
                no engine can interpolate one gradient into another - so the
                browser swaps it at the halfway point of the duration instead.
                For the first 450ms of every theme change the element therefore
                had NO background image at all, which is why the primary sign-in
                button rendered as white-on-white (its blue glow painted, its
                gradient did not) and why glass panels washed out mid-flip.
                Colours still cross-fade through background-color. */
             color var(--theme-dur) var(--theme-ease),
             border-color var(--theme-dur) var(--theme-ease),
             box-shadow var(--theme-dur) var(--theme-ease),
             opacity var(--theme-dur) var(--theme-ease),
             filter var(--theme-dur) var(--theme-ease)}
@media (prefers-reduced-motion: reduce){
  body,.field input,.btn,.notice,.legal,.foot,.mark,.themer{transition:none}
}

/* Light mode needs the logo dark, and the mark is a white PNG. */
:root[data-theme="light"] .mark{filter:invert(1) brightness(.22) saturate(1.4)}
:root[data-theme="light"] .field input{background:rgba(255,255,255,.66)}
:root[data-theme="light"] .field input::placeholder{color:rgba(18,32,58,.38)}
:root[data-theme="light"] .btn--ghost{background:rgba(255,255,255,.5)}
:root[data-theme="light"] .btn--soon{background:rgba(255,255,255,.4);color:rgba(18,32,58,.5)}
:root[data-theme="light"] .peek:hover{background:rgba(13,22,38,.06)}


/* Light mode is not the dark layout with different colours. The scrim that
   keeps text legible must LIFT the centre to near-white instead of darkening
   it, the vignette must not muddy the edges, and the dot grid has to become
   dark dots on light rather than white dots that vanish. */
/* Mainly white, but with real blue in it - a flat white ground looked unfinished.
   The blue lives at the edges and in the ribbons; the centre stays near-white so
   type keeps its contrast. */
/* The light ground lives in .bg__day above, so it can cross-fade. Re-declaring
   it here would snap and undo that. */
/* The light scrim and vignette are composited into .bg__day too, for the same
   reason: they are gradients, so re-declaring them here would snap while the day
   layer fades. The grid and the aurora below are NOT moved - the grid changes
   colour only, and the aurora only its opacity and blur, and both of those
   properties interpolate perfectly well. */
:root[data-theme="light"] .bg__grid{
  background-image:radial-gradient(circle, rgba(20,45,110,.10) 1px, transparent 1px)}
/* Light needs MORE of the aurora than dark does, not less. In dark the ribbons
   sit on a near-black ground and read at almost any strength; in light they are
   seen through the day layer above them, so whatever survives that is what the
   page has. Raising the opacity and tightening the blur puts recognisable
   shapes back into the corners instead of an even haze.

   Kept out of the middle by the day layer's own centre scrim, which is still
   .88 white plus a .93 scrim on top of it - the sign-in form has no card behind
   it, so the centre stays deliberately calm and only the edges carry colour. */
:root[data-theme="light"] .bg__aurora{opacity:1;filter:blur(64px) saturate(1.45)}
/* multiply was tried here on 26 Aug and reverted the same minute. The theory was
   sound - screen only lightens, so it is a no-op on a light ground, and ink-in-
   water is the light-mode analogue of glowing smoke. In practice darkening the
   whole ground turned "light theme" into a flat grey-blue murk: the smoke did
   not read as smoke, it read as dirt on the screen. Do not re-try it without
   also solving the ground staying light, which is the part that failed. */
/* When the shader compiles it replaces the blobs entirely (body.gl-on hides
   them), and in light it is the only thing moving, so it is allowed to be
   stronger than the dark default rather than a ghost under the white. */
:root[data-theme="light"] .bg__canvas{opacity:0}
:root[data-theme="light"] .bg__canvas.is-live{opacity:1;filter:saturate(1.6) contrast(1.08)}

/* Controls need real edges on white - a hairline that reads as a border. */
:root[data-theme="light"] .field input{background:#fff;border-color:rgba(20,45,110,.18)}
:root[data-theme="light"] .field input:focus{box-shadow:0 0 0 3px rgba(79,125,240,.20)}
:root[data-theme="light"] .btn--ghost{background:#fff;border-color:rgba(20,45,110,.16);color:var(--text)}
:root[data-theme="light"] .btn--soon{background:rgba(255,255,255,.86);
  border-color:rgba(20,45,110,.14);color:rgba(10,16,32,.62)}
:root[data-theme="light"] .btn--soon svg{opacity:.75}
:root[data-theme="light"] .pill{background:rgba(37,84,212,.10);color:#2554d4;
  border-color:rgba(37,84,212,.22)}
:root[data-theme="light"] .legal{color:rgba(10,16,32,.62)}
:root[data-theme="light"] .legal a{color:rgba(10,16,32,.82)}
:root[data-theme="light"] .foot{color:var(--muted)}
:root[data-theme="light"] .divider{color:rgba(10,16,32,.5)}
:root[data-theme="light"] .themer{box-shadow:0 8px 26px -14px rgba(20,45,110,.45),
  inset 0 1px 0 rgba(255,255,255,.9)}
:root[data-theme="light"] .btn--primary{box-shadow:0 10px 26px -12px rgba(37,84,212,.6)}

/* ---- glassmorphic theme toggle, top right ---- */
.themer{position:fixed;top:18px;right:18px;z-index:5;
  display:flex;align-items:center;gap:8px;height:42px;padding:0 6px 0 14px;
  border-radius:999px;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:600;
  color:var(--muted);background:var(--glass);border:1px solid var(--glass-line);
  -webkit-backdrop-filter:blur(14px) saturate(1.4);backdrop-filter:blur(14px) saturate(1.4);
  box-shadow:0 8px 30px -12px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.18)}
.themer:hover{color:var(--text)}
.themer:focus-visible{outline:2px solid var(--blue-soft);outline-offset:3px}
.themer__ico{width:30px;height:30px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,var(--blue),var(--blue-deep));color:#fff}
.themer__ico svg{display:block}
.themer__lbl{white-space:nowrap}
@media (max-width:420px){.themer__lbl{display:none}.themer{padding:0 6px}}

html,body{height:100%}
body{
  background:var(--ink);color:var(--text);
  font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  -webkit-font-smoothing:antialiased;
  min-height:100svh;display:flex;align-items:center;justify-content:center;
  padding:32px 20px;overflow-x:hidden;
}

/* ---------- moving background ---------- */
.bg{position:fixed;inset:0;z-index:0;overflow:hidden;
  /* Pushed toward blue. The old stops were nearly neutral (#101c31 to #0b1221),
     which read as grey once the scrim sat on top of them and wasted the blue the
     ribbons are painting underneath. */
  background:radial-gradient(125% 95% at 50% 4%, #16294a 0%, #0e1b35 40%, #0a1222 74%, var(--ink) 100%)}
.bg__canvas{position:absolute;inset:0;width:100%;height:100%;display:block;
  opacity:0;transition:opacity 1.1s ease}
.bg__canvas.is-live{opacity:1}
/* the CSS blobs below are the fallback; the shader hides them once it compiles */
body.gl-on .bg__aurora{display:none}
/* Guarantees text contrast no matter where the ribbon drifts. A moving
   background must never be trusted to stay dark behind the content. */
/* The scrim still guarantees contrast, but in deep BLUE rather than near-black.
   rgba(3,6,12) is effectively a grey filter: it removed the colour from the very
   ribbons it was sitting on. Same luminance, same protection, blue kept. */
.bg__scrim{position:absolute;inset:0;background:
  radial-gradient(58% 46% at 50% 46%, rgba(7,17,42,.78) 0%, rgba(7,17,42,.40) 48%, transparent 76%)}
.bg__aurora{position:absolute;inset:-25%;filter:blur(90px);opacity:.92}

/* ---------- the day layer ----------
   The whole light ground - base, scrim and vignette - composited into ONE
   element that cross-fades on OPACITY.

   Why not just re-declare the gradients under [data-theme="light"]: background-image
   is a DISCRETE property. No engine interpolates one gradient into another, so a
   themed re-declaration either snaps instantly or swaps at the halfway point.
   Meanwhile the WebGL ribbons underneath ease across on their own 900ms
   smoothstep. That mismatch is what produced a fully-light white scrim sitting on
   a still-dark canvas: a white ellipse in the middle of a dark page.

   Opacity IS interpolatable, so this layer fades in on exactly the curve and
   duration the shader uses and the two arrive together. */
/* THE DARK PROTECTION LAYERS DO NOT BELONG IN LIGHT MODE.

   .bg__scrim and .bg__vignette exist to keep white text readable over a moving
   ground. In light mode the text is DARK and the ground is white, so both of
   them are solving a problem that is not there - and neither had a light
   override, so light mode was painting a 78% navy scrim over the middle of the
   screen and a 74% near-black vignette around the edge, and then burying both
   under the white day layer to recover legibility. That compounding is the
   whole reason light looked dead: measured at the centre of the frame, the
   ribbons kept 26/255 of their amplitude in dark and 0.2/255 in light. Not less
   motion - the same motion under three opaque films.

   Turning them off in light is what lets the day layer be a ground rather than
   a rescue, which is what makes the numbers below possible. */
:root[data-theme="light"] .bg__scrim,
:root[data-theme="light"] .bg__vignette{background:none}

.bg__day{position:absolute;inset:0;opacity:0;
  transition:opacity var(--theme-dur) var(--theme-ease);
  background:
    /* EDGE BLUE. Reaches much further in than it used to (22% rather than 44%)
       and lands at .66 rather than .40. This is the layer that makes the light
       theme read as blue atmosphere instead of white paper with a faint tint. */
    radial-gradient(125% 92% at 50% 40%, transparent 22%, rgba(96,142,222,.66) 100%),
    /* THE CALM CENTRE. The sign-in form has no card behind it, so the middle
       still has to stay bright enough to read black text on. Trimmed slightly
       (.40 from .45) so the smoke shows through behind the form rather than
       stopping dead at its edge. */
    radial-gradient(54% 42% at 50% 44%,
      rgba(255,255,255,.40) 0%, rgba(255,255,255,.20) 50%, rgba(255,255,255,0) 80%),
    /* THE BASE, now blue rather than white.
       This is the change that does the work. Every stop used to be white or
       near-white (255,255,255 / 240,245,255), so the ground was paper and the
       ribbons were the only colour - and the ribbons are 26% of their amplitude
       under this layer, which is why it read as washed out. Tinting the base
       itself means the blue does not depend on the animation showing through. */
    radial-gradient(120% 95% at 50% -10%,
      rgba(233,242,255,.44) 0%,
      rgba(203,224,252,.42) 38%,
      rgba(172,203,248,.38) 72%,
      rgba(146,185,243,.34) 100%);
}
:root[data-theme="light"] .bg__day{opacity:1}
@media (prefers-reduced-motion: reduce){.bg__day{transition:none}}
.blob{position:absolute;border-radius:50%;mix-blend-mode:screen;
  will-change:transform;transform:translateZ(0);backface-visibility:hidden}
.blob--a{width:68vmax;height:68vmax;left:-14%;top:-16%;
  background:radial-gradient(circle at 34% 34%, rgba(37,84,212,.92) 0%, rgba(37,84,212,.34) 42%, transparent 68%);
  animation:driftA 52s ease-in-out infinite}
.blob--b{width:58vmax;height:58vmax;right:-12%;top:6%;
  background:radial-gradient(circle at 60% 40%, rgba(79,125,240,.78) 0%, rgba(79,125,240,.24) 44%, transparent 70%);
  animation:driftB 64s ease-in-out infinite}
.blob--c{width:52vmax;height:52vmax;left:16%;bottom:-24%;
  background:radial-gradient(circle at 50% 50%, rgba(147,184,255,.44) 0%, rgba(74,111,165,.18) 46%, transparent 72%);
  animation:driftC 46s ease-in-out infinite}
.blob--d{width:38vmax;height:38vmax;left:44%;top:26%;
  background:radial-gradient(circle at 50% 50%, rgba(28,47,79,.9) 0%, rgba(28,47,79,.3) 50%, transparent 74%);
  animation:driftD 58s ease-in-out infinite}

@keyframes driftA{0%,100%{transform:translate3d(0,0,0) scale(1)}33%{transform:translate3d(7vw,5vh,0) scale(1.12)}66%{transform:translate3d(-4vw,9vh,0) scale(.94)}}
@keyframes driftB{0%,100%{transform:translate3d(0,0,0) scale(1.04)}40%{transform:translate3d(-9vw,7vh,0) scale(.9)}70%{transform:translate3d(-3vw,-6vh,0) scale(1.16)}}
@keyframes driftC{0%,100%{transform:translate3d(0,0,0) scale(.98)}50%{transform:translate3d(9vw,-8vh,0) scale(1.18)}}
@keyframes driftD{0%,100%{transform:translate3d(0,0,0) scale(1)}45%{transform:translate3d(-8vw,4vh,0) scale(1.2)}80%{transform:translate3d(5vw,-5vh,0) scale(.92)}}

.bg__grid{position:absolute;inset:0;
  background-image:radial-gradient(circle, rgba(255,255,255,.05) 1px, transparent 1px);
  background-size:22px 22px;
  -webkit-mask-image:radial-gradient(115% 85% at 50% 45%, #000 35%, transparent 78%);
  mask-image:radial-gradient(115% 85% at 50% 45%, #000 35%, transparent 78%)}
.bg__vignette{position:absolute;inset:0;
  background:radial-gradient(105% 75% at 50% 45%, transparent 38%, rgba(5,12,30,.74) 100%)}

/* Small box, long-lived tab: stop compositing what nobody is looking at. */
body.is-paused .blob{animation-play-state:paused}
@media (prefers-reduced-motion: reduce){.blob{animation:none}}

/* ---------- content ---------- */
.shell{position:relative;z-index:1;width:100%;max-width:412px;text-align:center}
.mark{width:52px;height:52px;margin:0 auto 28px;
  background:url('/fulllogo.png') left center / auto 100% no-repeat}
h1{font-size:clamp(28px,5.6vw,38px);line-height:1.1;letter-spacing:-.032em;
  font-weight:800;margin-bottom:14px;text-wrap:balance}
/* Shown only when an admin was bounced here from /v2/admin. Amber because it is
   an instruction about a permission, not an error the person caused. Both
   schemes get an explicit colour; the light one is darkened for contrast. */
.adminhint{margin:10px 0 0;padding:9px 12px;border-radius:9px;max-width:52ch;
  font-size:13px;line-height:1.5;color:#f0b429;
  background:rgba(245,158,11,.10);border:1px solid rgba(245,158,11,.28)}
:root[data-theme="light"] .adminhint{color:#8a5a00;background:rgba(245,158,11,.13)}
.sub{font-size:15.5px;line-height:1.6;color:var(--muted);margin-bottom:30px;text-wrap:balance}

.panel{display:none}
.panel.is-active{display:block}

/* ---------- arrival and step motion ----------

   The page used to arrive all at once and each step swapped with a single 9px
   rise, which read as a form appearing rather than as somewhere you had gone.
   Two changes, both cheap:

   ARRIVAL is staged in reading order - mark, headline, subline, then the panel -
   about 70ms apart. The eye follows the same path it was going to take anyway,
   so the stagger is felt as pace rather than as waiting. It runs once: the
   is-boot class is on <html> from the server and removed after the sequence, so
   later step changes are immediate rather than replaying an intro.

   STEPS carry DIRECTION. Going deeper (choose a method, then forget password)
   enters from the right; coming back enters from the left. That is the whole
   difference between "the content changed" and "I moved", and it costs one class.

   Everything animates transform and opacity only, so it composites on the GPU
   and stays smooth on a phone. Nothing animates layout, nothing animates while
   the theme is cross-fading, and all of it is off under reduced motion. */

@keyframes v2Rise {from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:none}}
@keyframes v2Mark {from{opacity:0;transform:translateY(9px) scale(.9)} to{opacity:1;transform:none}}
@keyframes v2Fwd  {from{opacity:0;transform:translateX(18px)} to{opacity:1;transform:none}}
@keyframes v2Back {from{opacity:0;transform:translateX(-18px)} to{opacity:1;transform:none}}
@keyframes v2Text {from{opacity:0;transform:translateY(5px)} to{opacity:1;transform:none}}

/* One curve for the whole page. Decelerating, no overshoot: a sign-in is not the
   place for a bounce. */
:root{--v2-ease:cubic-bezier(.16,1,.3,1)}

html.is-boot .mark        {animation:v2Mark .60s var(--v2-ease) both}
html.is-boot h1           {animation:v2Rise .54s var(--v2-ease) .07s both}
html.is-boot .sub         {animation:v2Rise .54s var(--v2-ease) .14s both}
html.is-boot .adminhint   {animation:v2Rise .54s var(--v2-ease) .17s both}
html.is-boot .panel.is-active{animation:v2Rise .54s var(--v2-ease) .21s both}
html.is-boot .foot        {animation:v2Rise .54s var(--v2-ease) .30s both}

/* After the intro: the active panel arrives from the side it came from. */
html:not(.is-boot) .panel.is-active{animation:v2Fwd .30s var(--v2-ease)}
html:not(.is-boot) .shell.is-back .panel.is-active{animation-name:v2Back}

/* The headline and subline are rewritten on every step, so they get their own
   short fade - text that changes underneath a moving panel without moving reads
   as a glitch. */
html:not(.is-boot) .shell.is-swap #title,
html:not(.is-boot) .shell.is-swap #subtitle{animation:v2Text .28s var(--v2-ease)}

@media (prefers-reduced-motion: reduce){
  html.is-boot .mark, html.is-boot h1, html.is-boot .sub,
  html.is-boot .adminhint, html.is-boot .panel.is-active, html.is-boot .foot,
  html:not(.is-boot) .panel.is-active,
  html:not(.is-boot) .shell.is-swap #title,
  html:not(.is-boot) .shell.is-swap #subtitle{animation:none}
}

.notice{min-height:0;font-size:13.5px;line-height:1.55;text-align:left;
  border-radius:11px;padding:0 14px;max-height:0;overflow:hidden;
  transition:max-height .25s ease, padding .25s ease, margin .25s ease;margin-bottom:0}
.notice.is-shown{max-height:140px;padding:12px 14px;margin-bottom:16px}
.notice.is-error{background:rgba(255,141,149,.1);border:1px solid rgba(255,141,149,.3);color:var(--danger)}
.notice.is-ok{background:rgba(126,226,184,.1);border:1px solid rgba(126,226,184,.3);color:var(--ok)}

.field{text-align:left}
.field + .field{margin-top:14px}
.field label{display:block;font-size:13px;font-weight:600;color:rgba(226,234,250,.82);margin-bottom:7px}
.input-wrap{position:relative}
.field input{width:100%;height:52px;padding:0 15px;border-radius:12px;
  background:rgba(255,255,255,.05);border:1px solid var(--line);color:var(--text);
  font-family:inherit;font-size:15.5px;
  transition:border-color .16s ease, background .16s ease, box-shadow .16s ease}
.field input::placeholder{color:rgba(226,234,250,.32)}
.field input:focus{outline:none;border-color:var(--blue);background:rgba(255,255,255,.075);
  box-shadow:0 0 0 3px rgba(79,125,240,.16)}
.field input[aria-invalid="true"]{border-color:var(--danger)}
#password{padding-right:52px}

.peek{position:absolute;right:6px;top:50%;transform:translateY(-50%);
  width:40px;height:40px;display:flex;align-items:center;justify-content:center;
  background:none;border:none;cursor:pointer;color:var(--faint);border-radius:9px}
.peek:hover{color:var(--text);background:rgba(255,255,255,.06)}
.peek:focus-visible{outline:2px solid var(--blue-soft);outline-offset:2px}

.field-error{display:block;font-size:12.5px;color:var(--danger);
  max-height:0;overflow:hidden;transition:max-height .2s ease, margin .2s ease}
.field-error.is-shown{max-height:40px;margin-top:6px}

.hint{display:flex;align-items:center;gap:6px;font-size:12.5px;color:#ffd27d;
  max-height:0;overflow:hidden;transition:max-height .2s ease, margin .2s ease}
.hint.is-shown{max-height:40px;margin-top:6px}

.row{display:flex;justify-content:flex-end;margin:14px 0 18px}
.linkish{background:none;border:none;padding:0;cursor:pointer;font-family:inherit;
  font-size:13.5px;font-weight:600;color:var(--blue-soft);text-decoration:none}
.linkish:hover{text-decoration:underline}

.btn{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;
  height:54px;border-radius:13px;border:1px solid transparent;cursor:pointer;
  font-family:inherit;font-size:15.5px;font-weight:600;letter-spacing:-.01em;
  transition:transform .16s ease, box-shadow .16s ease, background .16s ease, opacity .16s ease}
.btn:active{transform:translateY(1px)}
.btn:focus-visible{outline:2px solid var(--blue-soft);outline-offset:3px}
.btn[disabled]{opacity:.62;cursor:default;transform:none}
.btn--primary{background:linear-gradient(135deg,var(--blue) 0%,var(--blue-deep) 100%);
  color:#fff;box-shadow:0 10px 30px -10px rgba(37,84,212,.85)}
.btn--primary:not([disabled]):hover{box-shadow:0 14px 38px -10px rgba(79,125,240,.95)}
.btn__mark{width:18px;height:18px;flex:none;
  background:url('/fulllogo.png') left center / auto 100% no-repeat}
.btn--ghost{background:rgba(255,255,255,.045);color:var(--text);border-color:var(--line);margin-top:12px}
.btn--ghost:hover{background:rgba(255,255,255,.085)}

.spinner{width:17px;height:17px;border:2px solid rgba(255,255,255,.32);
  border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

.divider{display:flex;align-items:center;gap:14px;margin:22px 0 14px;
  color:rgba(226,234,250,.36);font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}
.divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--line)}
.providers{display:flex;flex-direction:column;gap:10px}
.btn--soon{background:rgba(255,255,255,.03);color:rgba(226,234,250,.46);
  border-color:var(--line);cursor:not-allowed}
.btn--soon:active{transform:none}
.btn--soon svg{opacity:.45}
.pill{font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  padding:3px 8px;border-radius:999px;background:rgba(147,184,255,.14);
  color:var(--blue-soft);border:1px solid rgba(147,184,255,.22)}
.foot{margin-top:24px;font-size:13.5px;color:var(--muted)}
.foot a{color:var(--blue-soft);font-weight:600;text-decoration:none}
.foot a:hover{text-decoration:underline}
.legal{margin-top:22px;font-size:11.5px;line-height:1.6;color:rgba(226,234,250,.34)}
.legal a{color:rgba(226,234,250,.52);text-decoration:underline;text-underline-offset:2px}

@media (max-width:420px){.mark{width:44px;height:44px;margin-bottom:22px}.sub{margin-bottom:26px}}

/* ---------- LIGHT-THEME CONTRAST FIXES ----------
   Both of these are near-white with no light override, so on a near-white
   ground they were invisible. Measured before and after against the light
   ground (#f2f5fc):

     .field label   rgb(228,235,250)  1.10:1   ->  --muted  7.98:1
     .divider       same family, same problem

   This is NOT a sign-up bug. Both rules are the sign-in page's, so "Email" and
   "Password" have been unreadable in light mode on the page every visitor sees
   first - it only became obvious when a second page used the same stylesheet
   and somebody looked at it in daylight. Fixed once, for both. */
:root[data-theme="light"] .field label{color:var(--muted)}
:root[data-theme="light"] .divider{color:var(--faint)}

/* ==========================================================================
   SIGN-UP - the pieces the sign-in page does not have.
   Everything else on /v2/register is the shell above: same ground, same panel,
   same field, same button. Only the multi-step furniture is new.
   ========================================================================== */

/* Four marks, not a percentage bar. A bar on a four-step form implies more
   precision than exists and reads as slower; four marks say "this is short" at
   a glance, which is the only thing a person wants to know before starting. */
/* Sits between the subtitle and the first field, with room either side. .sub
   already carries 30px beneath it, so this only needs its own breathing space
   below - an earlier version had no top margin at all and the marks landed on
   the subtitle's descenders. */
.steps{display:flex;gap:6px;justify-content:center;list-style:none;
  margin:0 0 22px;padding:0}
.steps li{width:26px;height:3px;border-radius:2px;background:var(--line,rgba(255,255,255,.14));
  transition:background .25s ease,width .25s ease}
.steps li.is-on{background:var(--accent,#4f7df0);width:34px}
.steps li.is-done{background:var(--accent,#4f7df0);opacity:.5;width:26px}

/* A select has to be the same object as an input here or the identity step
   looks like a different form from the two either side of it. */
.field select{width:100%;height:46px;padding:0 12px;border-radius:11px;
  font:inherit;font-size:15px;color:inherit;
  background:var(--w1,rgba(255,255,255,.04));
  border:1px solid var(--line,rgba(255,255,255,.14));
  -webkit-appearance:none;appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                   linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 18px) 21px,calc(100% - 13px) 21px;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.field select:focus{outline:none;border-color:var(--accent,#4f7df0);
  box-shadow:0 0 0 3px rgba(79,125,240,.22)}
:root[data-theme="light"] .field select{background-color:#fff;border-color:rgba(20,45,110,.18)}

/* A date input keeps the browser's own picker - a custom one is three controls
   and a locale bug for something every platform already does well. */
.field input[type="date"]{min-height:46px}

/* NOT .hint. That class is the sign-in page's caps-lock warning and ships
   max-height:0 with overflow:hidden - it is invisible until JS adds .is-shown.
   Reusing the name made the age note and the "or" divider vanish, which looked
   like a missing element rather than a collapsed one. */
.note{margin:6px 0 0;font-size:12px;line-height:1.5;text-align:left;color:var(--faint)}

/* The marketing opt-in. Unticked, and left of its label, because a checkbox
   that follows its text reads as already agreed to. */
.check{display:flex;align-items:flex-start;gap:10px;margin:2px 0 16px;
  font-size:13px;line-height:1.5;text-align:left;cursor:pointer}
.check input{flex:none;width:17px;height:17px;margin:1px 0 0;accent-color:var(--accent,#4f7df0)}

/* A refusal has to look different from a status line, or "one moment" and
   "that email is taken" read the same. */
.notice.is-bad{color:#ef4444}
:root[data-theme="light"] .notice.is-bad{color:#c02626}

/* =========================================================================
   SIGN-UP: spacing and the step-4 controls
   Added 26 Aug from Will's screenshots.
   ========================================================================= */

/* The submit button sat flush against the field above it.

   The cause was not a missing margin so much as a misplaced assumption: the
   only thing between the last field and the button is a .field-error, which is
   collapsed to max-height:0 until it has something to say. The layout was
   effectively relying on an error message for its spacing, so a valid form had
   none. The gap belongs on the button, unconditionally. */
form .btn--primary{margin-top:20px}
/* The checkbox already carries a bottom margin, so a second gap on top of it
   would double up on the last step. */
.check + .btn--primary{margin-top:0}

/* The <select> was a browser default sitting in a column of custom inputs -
   different height, different radius, different arrow. It matches .field input
   now, with the native arrow replaced by one drawn in the same stroke as the
   rest of the page.

   The chevron is an inline SVG data URI rather than a pseudo-element, because
   a <select> cannot host ::after in every engine. Two are declared, one per
   scheme, since a stroke colour cannot be a CSS variable inside a data URI. */
.field select{
  width:100%;height:52px;padding:0 44px 0 15px;border-radius:12px;
  background:rgba(255,255,255,.05);border:1px solid var(--line);color:var(--text);
  font-family:inherit;font-size:15.5px;cursor:pointer;
  -webkit-appearance:none;-moz-appearance:none;appearance:none;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23e2eafa' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 15px center;
  transition:border-color .16s ease, background-color .16s ease, box-shadow .16s ease}
.field select:focus{outline:none;border-color:var(--blue);
  background-color:rgba(255,255,255,.075);box-shadow:0 0 0 3px rgba(79,125,240,.16)}
/* The options list is drawn by the OS, so it needs a real ground of its own -
   left transparent it renders white-on-white in a dark scheme. */
.field select option{background:#0d1424;color:#eaf0fb}

:root[data-theme="light"] .field select{
  background-color:rgba(255,255,255,.9);
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%230a1020' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")}
:root[data-theme="light"] .field select option{background:#ffffff;color:#0a1020}

/* The marketing opt-in was a raw browser checkbox. accent-color alone leaves
   the box itself at the OS default size and radius, which reads as an
   unfinished control beside 52px rounded inputs. */
.check{margin:14px 0 4px;font-size:14px;line-height:1.5;cursor:pointer}
.check input{
  -webkit-appearance:none;-moz-appearance:none;appearance:none;
  width:20px;height:20px;margin:0;flex:none;border-radius:6px;
  background:rgba(255,255,255,.05);border:1px solid var(--line);
  cursor:pointer;position:relative;
  transition:background .14s ease, border-color .14s ease}
.check input:hover{border-color:var(--blue-soft,#93b8ff)}
.check input:checked{background:var(--blue);border-color:var(--blue)}
/* The tick is drawn, not a glyph: a text checkmark inherits the page font and
   sits differently in every one of them. */
.check input:checked::after{
  content:'';position:absolute;left:6.5px;top:3px;width:5px;height:10px;
  border:solid #fff;border-width:0 2px 2px 0;transform:rotate(43deg)}
.check input:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(79,125,240,.28)}
