/* =====================================================================
   /start/ v2 — sign-in / sign-up
   ---------------------------------------------------------------------
   Mirrors /home/'s .home-v2 visual language (dark, neon-cyan, Russo One
   headlines) but scoped to .start-v2 so
   it can't bleed into the rest of the site's chrome.

   Inputs need to live happily on a near-black surface. The site's base
   .form-control / .input-group styles assume white forms, so this file
   doesn't lean on them — every input/button gets a fresh sv2- variant.

   Form ids/names are intentionally unchanged from the legacy markup;
   the OTP / SMS / register JS in /start/index.php still wires up by id.
   ===================================================================== */

/* JetBrains Mono is named first in every monospace stack below (terminal
   strip, OTP digits, captcha, alt-links) but was never actually loaded —
   the strip silently fell back to Consolas on Windows and to whatever
   generic monospace a phone happens to ship. Loaded here so the page gets
   the face its own CSS asks for. */
@import url('https://fonts.googleapis.com/css2?family=Russo+One&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap');

.start-v2 {
    /* surface */
    --sv2-bg:        #0B1220;
    --sv2-surface:   #111A2E;
    --sv2-surface-2: #18243F;
    --sv2-line:      rgba(125, 211, 252, 0.18);

    /* accents */
    --sv2-cyan:      #7DD3FC;
    --sv2-green:     #86EFAC;
    --sv2-red:       #F87171;

    /* The human-check runs GREEN, not the page's cyan. It is the one
       block that is not part of signing in — it is a gate in front of it
       — and a different hue says that faster than any wording. Kept as a
       hue token PLUS its raw rgb triple, because rgba() cannot take a hex
       custom property: every tint, ring and keyframe below is mixed from
       --sv2-captcha-rgb, so re-hueing the whole block is a two-line edit
       and the two themes can never drift apart. */
    --sv2-captcha-accent: #86EFAC;
    --sv2-captcha-rgb:    134, 239, 172;
    /* rgb triple for --sv2-red, same reason: rgba() cannot take a hex
       custom property, and the wrong-answer state needs tints of it. */
    --sv2-red-rgb:        248, 113, 113;

    /* text */
    --sv2-fg:        #F1F5F9;
    --sv2-fg-muted:  #B8C2D9;
    /* Placeholders get their OWN token, well below body text. Sharing
       --sv2-fg-dim put hint text at ~5:1 on the field — the same weight
       as a real typed value, which is exactly why people read the fields
       as pre-filled and did not type. ~3.4:1 still reads clearly but
       lands as a hint. (WCAG sets no minimum for placeholder text; the
       floor here is legibility, not compliance.) */
    --sv2-placeholder: #5F6A83;

    /* Was #6E7A95, which measures 4.35:1 on the page and only 4.03:1 on
       the lighter card — both just under AA, and it carries the terminal
       strip and the "didn't get it? / resend" row. Nudged to the same
       value the sitewide footer landed on for this exact problem
       (includes/structure/footer.php): still clearly the muted step down
       from --sv2-fg-muted, but 5.41:1 / 5.01:1. The 600px block below
       lifts it further again for glare. */
    --sv2-fg-dim:    #7E8AA6;
}

.start-v2 {
    background: var(--sv2-bg);
    color: var(--sv2-fg);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    /* /start/ skips structure/header.php (the sign-in page doesn't want
       the full site nav), so the island owns its top padding entirely.
       Smaller top inset than /home/ since there's no transparent chrome
       to clear — the logo lockup sits inside .sv2-inner. */
    padding: clamp(48px, 7vw, 80px) clamp(16px, 4vw, 48px) 96px;
    min-height: 100vh;
    position: relative;
    overflow: hidden;
}

/* The site theme paints .page-content with bg-white in some places, and
   .page-wraper is hard-coded #fff. Force both wrappers around our island
   to the island's own surface so the seam doesn't show as a white sliver
   (and neither does an overscroll bounce on iOS).

   The colour is a LITERAL, not var(--sv2-bg): the sv2-* tokens are
   declared on .start-v2, so a custom property referenced from an
   ANCESTOR resolves to nothing and the whole declaration is dropped.
   That is exactly what used to happen here — this rule was dead. */
.page-content:has(> .start-v2),
.page-wraper:has(.start-v2) { background: #0B1220; }

/* Subtle radial wash behind the headline — same recipe as /home/ */
.start-v2::before {
    content: '';
    position: absolute;
    top: -10vh;
    left: 50%;
    width: 90vw;
    height: 60vh;
    transform: translateX(-50%);
    background: radial-gradient(ellipse at center, rgba(125,211,252,0.10) 0%, transparent 65%);
    pointer-events: none;
    z-index: 0;
}

.start-v2 ::selection {
    background: var(--sv2-cyan);
    color: var(--sv2-bg);
}

/* ========== base-stylesheet bleed guard ==========
   /css/style.css sets `b, strong, .strong { color: #000 }` at element
   specificity, which lands BLACK text on the near-black card. It bit the
   one place the page uses <strong>: the "We sent 6 digits to <email>"
   line, where the address — the single piece of text the user is there to
   verify — rendered at 1.21:1 and read as a censored bar.
   Anything bold inside the island inherits its surroundings instead. */
.start-v2 b,
.start-v2 strong {
    color: inherit;
    font-weight: 700;
}
/* ...and the address/number itself gets the accent, so it reads as the
   value being confirmed rather than just heavier body copy. */
.start-v2 #otp-code-email-label {
    color: var(--sv2-cyan);
    word-break: break-word;   /* long addresses must not widen the card */
}

.start-v2 .sv2-inner {
    position: relative;
    z-index: 1;
    max-width: 560px;
    margin: 0 auto;
    /* One declaration centres the whole island; the handful of flex rows
       below (actions, trust, picker) need their own justify-content
       because text-align does not reach a flex container's children. */
    text-align: center;
}

/* ========== logo lockup ==========
   /start/ has no site-nav header; the logo IS the chrome. Generous size
   so it reads as the page brand, not a corner mark. Click target sends
   them to the public root (logged-out exit hatch). Subtle drop-glow on
   hover hooks into the cyan accent palette. */
.start-v2 .sv2-logo {
    display: block;
    margin: 0 auto 40px;
    width: clamp(160px, 22vw, 240px);
    line-height: 0;
    /* Keep the link inert-feeling (no underline, no border-bottom)
       since the logo is the affordance, not the text under it. */
    text-decoration: none;
    border: none;
    transition: filter 0.2s ease, transform 0.2s ease;
}
.start-v2 .sv2-logo img {
    width: 100%;
    height: auto;
    display: block;
    /* Soft cyan halo so the logo feels lit-from-behind on the dark
       surface without recoloring the SVG itself. */
    filter: drop-shadow(0 0 18px rgba(125, 211, 252, 0.18));
}
.start-v2 .sv2-logo:hover {
    transform: translateY(-1px);
}
.start-v2 .sv2-logo:hover img {
    filter: drop-shadow(0 0 22px rgba(125, 211, 252, 0.35));
}
@media (max-width: 480px) {
    .start-v2 .sv2-logo { margin-bottom: 28px; }
}
/* The shimini wordmark is navy ink — invisible on the dark surface — so
   the markup carries both variants and the theme class picks one, same
   CSS-only toggle as the sitewide footer (.dmf-logo-night/-day). */
.start-v2 .sv2-logo .sv2-logo-day { display: none; }
html.dm-light .start-v2 .sv2-logo .sv2-logo-night { display: none; }
html.dm-light .start-v2 .sv2-logo .sv2-logo-day { display: block; }

/* The terminal strip, greeting and gate-line headline were removed with
   their markup (see page_content/sign-in-content.php). sv2-blink went
   with the headline's blinking cursor; sv2-fade-up stays — the card, the
   sign-up prompt and the invite footnote all still ride it. */
@keyframes sv2-fade-up {
    to { opacity: 1; transform: translateY(0); }
}

/* ========== the "first time here?" section ==========
   Signing up used to be a bullet inside the sign-in card's intro, which
   put the one action a brand-new visitor CAN take inside a form they
   cannot use. It is its own destination now. Dashed border rather than
   the card's solid one: it reads as a related aside, not a second form
   competing with the first. */
.start-v2 .sv2-newacct {
    margin: 20px 0 0;
    padding: 18px 20px 20px;
    border: 1px dashed var(--sv2-line-field);
    border-radius: 14px;
    opacity: 0;
    transform: translateY(8px);
    animation: sv2-fade-up 0.5s cubic-bezier(0.2,0.8,0.2,1) 0.3s forwards;
}
.start-v2 .sv2-newacct-head {
    margin: 0 0 13px;
    font-size: 15px;
    font-weight: 600;
    color: var(--sv2-fg-muted);
}

/* Outline sibling of the primary button. The page must not show two
   filled cyan buttons at once — the eye reads them as equals and the
   actual sign-in loses its priority. */
.start-v2 .sv2-btn--ghost {
    background: transparent;
    color: var(--sv2-cyan);
    border-color: var(--sv2-cyan);
}
.start-v2 .sv2-btn--ghost:hover {
    transform: translateY(-2px);
    background: var(--sv2-cyan);
    color: var(--sv2-bg);
    text-decoration: none;
}

/* ========== the invite footnote ==========
   People who arrive holding a game invite try to use it on this page
   instead of signing in. This is their recovery path, so it lives BELOW
   the form: a footnote, not a precondition. Quieter than the card above
   it (no fill, just an accent rule) so it never competes with the thing
   the page actually wants them to do. */
.start-v2 .sv2-gate {
    margin: 22px 0 0;
    opacity: 0;
    transform: translateY(8px);
    animation: sv2-fade-up 0.5s cubic-bezier(0.2,0.8,0.2,1) 0.35s forwards;
}

/* The heading throws a small party, then sits down.
   ---------------------------------------------------------------------
   Only this LINE animates — not the block around it. A whole panel
   breathing behind body copy makes the copy harder to read, which
   defeats the point; a single short line catches the eye and then gets
   out of the way of the two bullets under it.

   It runs SIX times and stops (`6 both`), landing on the 100% keyframe,
   which is the same plain state it would have had with no animation at
   all — so it settles honestly instead of freezing mid-glow. About 15
   seconds of attract, then a normal page.

   Festive means the party-light hue walk — cyan, violet, gold — not
   speed. 2.4s per full pass is ~0.4Hz, nowhere near the ~3Hz flicker
   threshold, and the text never drops below full opacity, so it stays
   readable the entire time rather than blinking in and out. */
.start-v2 .sv2-gate-head {
    /* inline-block so the float below rotates the TEXT, not a
       full-width block whose empty right half swings a visible
       distance while the words barely move. */
    display: inline-block;
    margin: 0 0 8px;
    font-size: 17px;
    font-weight: 700;
    color: var(--sv2-fg);
    /* Two animations on the same element, same 2.4s period, same 1s
       delay, same 6-run stop — so the colour walk and the float start
       and finish together instead of drifting into an unrelated wobble.
       They touch disjoint properties (color/text-shadow vs transform). */
    animation: sv2-gate-party 2.4s ease-in-out 1s 6 both,
               sv2-gate-float 2.4s ease-in-out 1s 6 both;
}

/* The float: a lazy bob with a degree of tilt at each end. Amplitude is
   deliberately tiny — 2px and ~1.2deg. Anything more and it reads as a
   broken layout rather than a nudge, and text that actually moves is
   text you have to chase to read. */
@keyframes sv2-gate-float {
    0%   { transform: translateY(0)      rotate(0deg); }
    25%  { transform: translateY(-2.5px) rotate(-1.2deg); }
    50%  { transform: translateY(0)      rotate(0deg); }
    75%  { transform: translateY(-2.5px) rotate(1.2deg); }
    100% { transform: translateY(0)      rotate(0deg); }
}

@keyframes sv2-gate-party {
    0%   { color: var(--sv2-fg); text-shadow: none; }
    22%  { color: #7DD3FC; text-shadow: 0 0 14px rgba(125, 211, 252, 0.75); }
    48%  { color: #C4A6FF; text-shadow: 0 0 16px rgba(196, 166, 255, 0.72); }
    74%  { color: #FBBF24; text-shadow: 0 0 16px rgba(251, 191, 36, 0.70); }
    100% { color: var(--sv2-fg); text-shadow: none; }
}

/* Motion is the attention hook, but for some readers it is the thing
   that makes a page unusable — so honour the OS switch. The emphasis
   survives without movement: the heading simply sits in the accent
   colour, which is still the only coloured line in the footnote.
   .sv2-gate needs opacity/transform restated because killing its
   animation also kills the entrance that `forwards` was holding. */
@media (prefers-reduced-motion: reduce) {
    .start-v2 .sv2-gate {
        animation: none;
        opacity: 1;
        transform: none;
    }
    .start-v2 .sv2-gate-head {
        animation: none;
        transform: none;
        color: var(--sv2-cyan);
    }
}
.start-v2 .sv2-gate-step {
    margin: 0 0 6px;
    font-size: 13px;
    line-height: 1.55;
    color: var(--sv2-fg-muted);
}
.start-v2 .sv2-gate-step:last-child { margin-bottom: 0; }

/* "One Time Sign On Password" inside the bullets. Named, in the accent,
   in the mono face, so the thing we email is unmistakably a different
   object from the invite link they are holding. */
.start-v2 .sv2-term-em {
    font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
    color: var(--sv2-cyan);
    font-weight: 700;
    letter-spacing: 0.01em;
}

/* ========== card ========== */
.start-v2 .sv2-card {
    background: var(--sv2-surface);
    border: 1px solid var(--sv2-line);
    border-radius: 14px;
    padding: 32px 32px 28px;
    box-shadow: 0 16px 40px -12px rgba(0,0,0,0.5);
    /* Mild fade-in so the card lands a beat after the headline. */
    opacity: 0;
    transform: translateY(8px);
    animation: sv2-fade-up 0.5s cubic-bezier(0.2,0.8,0.2,1) 0.25s forwards;
}
@media (max-width: 480px) {
    .start-v2 .sv2-card { padding: 24px 20px 22px; }
}

/* Nothing follows the heading inside .sv2-card-head any more — the
   descriptive sub-lines came out — so it only has to clear the form. */
.start-v2 .sv2-card-head { margin: 0 0 18px; }
.start-v2 .sv2-card-head h2 {
    font-family: 'Russo One', Impact, sans-serif;
    font-size: clamp(20px, 2.6vw, 26px);
    margin: 0 0 8px;
    color: var(--sv2-fg);
    letter-spacing: 0.5px;
    line-height: 1.15;
}
/* The address/number the code was sent to, riding in the alt row now
   that the card sub-lines are gone. Its own line above the resend links
   so it reads as a fact, not another link. */
.start-v2 .sv2-alt-target {
    display: block;
    margin-bottom: 4px;
    color: var(--sv2-fg-muted);
}

/* ========== links ========== */
.start-v2 .sv2-link {
    color: var(--sv2-cyan);
    text-decoration: none;
    border-bottom: 1px dashed rgba(125, 211, 252, 0.4);
    transition: border-color 0.15s ease, color 0.15s ease;
    cursor: pointer;
}
.start-v2 .sv2-link:hover {
    color: var(--sv2-fg);
    border-bottom-color: var(--sv2-fg);
    text-decoration: none;
}

/* ========== form ========== */
.start-v2 .sv2-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* The field's border is NOT the decorative --sv2-line hairline.
   A field's fill (--sv2-bg) sits only 1.08:1 away from the card it is on,
   in BOTH themes — so the border alone has to say "box you type in", and
   at --sv2-line's alpha it measured 1.5:1 and did not. --sv2-line-field is
   the same cyan pushed to ~3.1:1, which is what WCAG 1.4.11 asks of a
   control boundary; the light theme sets its own ink value. Card edges and
   dividers keep the soft hairline — they are separators, not controls. */
.start-v2 {
    --sv2-line-field: rgba(125, 211, 252, 0.45);
}

.start-v2 .sv2-field {
    display: flex;
    align-items: stretch;
    background: var(--sv2-bg);
    border: 1px solid var(--sv2-line-field);
    border-radius: 10px;
    overflow: hidden;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    margin: 0;
}
.start-v2 .sv2-field:focus-within {
    border-color: var(--sv2-cyan);
    box-shadow: 0 0 0 3px rgba(125,211,252,0.15);
}
.start-v2 .sv2-field-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    color: var(--sv2-fg-dim);
    font-size: 18px;
    border-right: 1px solid var(--sv2-line-field);
    background: rgba(125,211,252,0.04);
    flex-shrink: 0;
}
.start-v2 .sv2-field input {
    flex: 1;
    background: transparent;
    border: none;
    outline: none;
    color: var(--sv2-fg);
    font-family: 'Inter', sans-serif;
    font-size: 15px;
    padding: 14px 16px;
    width: 100%;
    /* kill the white shadow Bootstrap puts on .form-control */
    box-shadow: none;
}
/* The one thing that does NOT centre. Each field carries a 44px icon
   on its left, so the input's own box starts 44px in — centred text
   inside it sits 22px right of the field's true middle, which on an
   otherwise centred page reads as a mistake rather than a choice. Long
   addresses also track better from a fixed left edge. `:not()` keeps the
   OTP field's deliberate centred digits, which have no such offset
   problem because the value is six characters wide. */
.start-v2 .sv2-field input:not(.sv2-input--otp) { text-align: left; }

.start-v2 .sv2-field input::placeholder {
    color: var(--sv2-placeholder);
    /* Firefox drops placeholders to 54% by default; pin it so the three
       browsers agree on how faint "faint" is. */
    opacity: 1;
}

/* Defeat browser-specific autofill yellow → keep the dark vibe. */
.start-v2 .sv2-field input:-webkit-autofill,
.start-v2 .sv2-field input:-webkit-autofill:hover,
.start-v2 .sv2-field input:-webkit-autofill:focus,
.start-v2 .sv2-field input:-webkit-autofill:active {
    -webkit-text-fill-color: var(--sv2-fg);
    -webkit-box-shadow: 0 0 0 1000px var(--sv2-bg) inset;
    transition: background-color 5000s ease-in-out 0s;
    caret-color: var(--sv2-fg);
}

/* OTP input — fat spaced digits */
.start-v2 input.sv2-input--otp {
    font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
    font-size: 1.6rem;
    letter-spacing: 0.45em;
    text-align: center;
    padding: 16px 10px;
}

/* ========== trust-this-device row ========== */
.start-v2 .sv2-trust {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    color: var(--sv2-fg-muted);
    font-size: 13px;
    cursor: pointer;
    user-select: none;
    margin: 0;
    font-weight: 400;
}
.start-v2 .sv2-trust input {
    accent-color: var(--sv2-cyan);
    width: 16px;
    height: 16px;
    margin: 0;
}

/* ========== captcha (human check) ==========
   Deliberately loud: solid green border + halo + a badge so it reads as a
   required gate, not an optional extra. The "Text me my code" button is
   kept hidden by JS until this is answered correctly; send-sms-code.php
   still re-validates the answer against $_SESSION, so this is a UX gate
   (and a cheap bot speed-bump), not the real check. */
.start-v2 .sv2-captcha {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    background: rgba(var(--sv2-captcha-rgb), 0.06);
    border: 1.5px solid var(--sv2-captcha-accent);
    border-radius: 12px;
    padding: 16px 18px;
    box-shadow: 0 0 0 3px rgba(var(--sv2-captcha-rgb), 0.10);
}
.start-v2 .sv2-captcha-badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-family: 'Russo One', Impact, sans-serif;
    font-size: 12px;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    color: var(--sv2-bg);
    background: var(--sv2-captcha-accent);
    padding: 5px 12px;
    border-radius: 999px;
    margin-bottom: 12px;
}
/* Question on its own row, centered above the answer box. */
.start-v2 .sv2-captcha-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
/* The actual question — loudest thing in the box so nobody misses that
   they have to solve it. Green chip, bigger type. */
.start-v2 .sv2-captcha-q {
    margin: 0;
    color: var(--sv2-captcha-accent);
    background: rgba(var(--sv2-captcha-rgb), 0.12);
    border: 1px solid rgba(var(--sv2-captcha-rgb), 0.45);
    border-radius: 8px;
    padding: 10px 16px;
    font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: 0.02em;
    /* Four pulses, then still. No delay, unlike the invite footnote's
       attract loop: this element starts life `display:none` inside
       #smsCaptchaBox and is only revealed once a full 10-digit number is
       typed, so the animation cannot begin until then — a hidden element
       does not run one. That timing is the point. The chip appears
       mid-flow, right where someone has just finished typing and is
       reaching for the (still hidden) submit button, and the pulse is
       what says "this is the thing standing between you and the code".
       Ends on the 100% keyframe, which is the plain state. */
    animation: sv2-captcha-pulse 1.15s ease-in-out 4 both;
}

/* Scale from the chip's own centre plus a widening ring — no colour walk
   here. The question is already the only green chip in the box; cycling
   hues on a maths prompt would read as an error state on a field the
   person has not answered yet.
   ONE keyframe set serves both themes: the ring mixes from
   --sv2-captcha-rgb, which each theme redefines, so the light variant
   that used to exist purely to swap the halo colour is gone. */
@keyframes sv2-captcha-pulse {
    0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(var(--sv2-captcha-rgb), 0.45); }
    45%  { transform: scale(1.06); box-shadow: 0 0 0 8px rgba(var(--sv2-captcha-rgb), 0); }
    100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(var(--sv2-captcha-rgb), 0); }
}

/* Same rule as everywhere else on this page: motion is the hook, but for
   some readers it is what makes a page unusable. The chip is already the
   loudest thing in the box without it. */
@media (prefers-reduced-motion: reduce) {
    .start-v2 .sv2-captcha-q { animation: none; transform: none; }
}
.start-v2 .sv2-captcha-a {
    width: 92px;
    background: var(--sv2-bg);
    border: 1px solid var(--sv2-line-field);
    border-radius: 6px;
    color: var(--sv2-fg);
    padding: 10px 12px;
    text-align: center;
    font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
    font-size: 18px;
    outline: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.start-v2 .sv2-captcha-a:focus {
    border-color: var(--sv2-captcha-accent);
    box-shadow: 0 0 0 3px rgba(var(--sv2-captcha-rgb), 0.15);
}
/* Confirmed state, once the answer matches (class set by JS). It used to
   be "green vs the cyan focus ring" — but the whole block is green now,
   so hue alone no longer distinguishes the two. The difference is a FILL:
   focus outlines the box, correct fills it. */
.start-v2 .sv2-captcha-a.is-correct {
    border-color: var(--sv2-captcha-accent);
    background: rgba(var(--sv2-captcha-rgb), 0.18);
    box-shadow: 0 0 0 3px rgba(var(--sv2-captcha-rgb), 0.22);
}

/* Wrong answer: red, and a head-shaking "no". Must sit AFTER the :focus
   rule above — both score (0,3,1), so source order is the tie-break, and
   the field is focused the whole time someone is typing into it. */
.start-v2 .sv2-captcha-a.is-wrong {
    border-color: var(--sv2-red);
    color: var(--sv2-red);
    background: rgba(var(--sv2-red-rgb), 0.12);
    box-shadow: 0 0 0 3px rgba(var(--sv2-red-rgb), 0.22);
}
/* Horizontal only, and it settles back to centre — a shake that drifts
   or scales reads as breakage rather than disagreement. Short and
   damped: two firm swings, then smaller ones, like a head saying no. */
.start-v2 .sv2-captcha-a.sv2-shake {
    animation: sv2-shake 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes sv2-shake {
    10%, 90%          { transform: translateX(-2px); }
    20%, 80%          { transform: translateX(3px); }
    30%, 50%, 70%     { transform: translateX(-5px); }
    40%, 60%          { transform: translateX(5px); }
    0%, 100%          { transform: translateX(0); }
}
/* The colour carries the message on its own for anyone who cannot take
   the movement. */
@media (prefers-reduced-motion: reduce) {
    .start-v2 .sv2-captcha-a.sv2-shake { animation: none; }
}
.start-v2 .sv2-captcha-hint {
    margin: 12px 0 0;
    font-size: 13px;
    color: var(--sv2-fg-muted);
    font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
}

/* ========== buttons ========== */
.start-v2 .sv2-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 4px;
}
.start-v2 .sv2-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 12px 22px;
    font-family: 'Russo One', Impact, sans-serif;
    font-size: 15px;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    border: 1px solid var(--sv2-cyan);
    border-radius: 999px;
    cursor: pointer;
    transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
    text-decoration: none;
}
.start-v2 .sv2-btn--primary {
    background: var(--sv2-cyan);
    color: var(--sv2-bg);
}
.start-v2 .sv2-btn--primary:hover {
    transform: translateY(-2px);
    background: #B6E7FE;
    color: var(--sv2-bg);
    box-shadow: 0 14px 30px -12px rgba(125,211,252,0.6);
    text-decoration: none;
}
.start-v2 .sv2-btn[disabled],
.start-v2 .sv2-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}
.start-v2 .sv2-btn-arrow { font-size: 14px; }

.start-v2 .sv2-spinner {
    height: 32px;
    width: auto;
    /* The default processing.gif is dark on transparent — invert so it
       reads on the dark surface without re-shipping the asset. */
    filter: invert(1) brightness(1.3);
}

/* ========== alt-link row + inline message ========== */
.start-v2 .sv2-alt {
    margin-top: 6px;
    font-size: 13px;
    color: var(--sv2-fg-dim);
    font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
}

.start-v2 .sv2-msg {
    margin-top: 4px;
    font-size: 13px;
    min-height: 1.2em;
    line-height: 1.4;
}
.start-v2 .sv2-msg:empty { display: none; }

/* Status colours are CLASSES, not inline styles. index.php used to set
   `.css({color: '#F87171'})` directly on these divs; an inline style wins
   over every stylesheet rule, so those two hard-coded neons would have
   survived the day/night flip and sat unreadable on paper (#86EFAC on
   white is ~1.4:1). The JS now toggles these classes instead. */
.start-v2 .sv2-msg--error { color: var(--sv2-red); }
.start-v2 .sv2-msg--ok    { color: var(--sv2-green); }

/* ========== identity picker ========== */
/* JS injects raw <button class="btn btn-primary"> elements into
   #smsIdentityChoices when one phone matches multiple accounts. We
   restyle them under this scope so they match the rest of the page
   without touching the JS. */
.start-v2 .sv2-picker {
    margin-top: 22px;
    padding: 18px;
    border: 1px dashed var(--sv2-line);
    border-radius: 10px;
    background: rgba(125, 211, 252, 0.03);
}
.start-v2 .sv2-picker-q {
    margin: 0 0 12px;
    color: var(--sv2-fg);
    font-family: 'Russo One', Impact, sans-serif;
    font-size: 16px;
    letter-spacing: 0.4px;
}
.start-v2 .sv2-picker-choices {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
    margin: 0;
}
.start-v2 .sv2-picker-choices .btn,
.start-v2 .sv2-picker-choices button {
    background: var(--sv2-surface-2);
    color: var(--sv2-fg);
    border: 1px solid var(--sv2-line);
    border-radius: 999px;
    padding: 10px 18px;
    font-family: 'Inter', sans-serif;
    font-weight: 600;
    font-size: 14px;
    margin: 0 !important;     /* JS sets inline margin: 0.25rem */
    transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.start-v2 .sv2-picker-choices .btn:hover,
.start-v2 .sv2-picker-choices button:hover {
    background: var(--sv2-bg);
    border-color: var(--sv2-cyan);
    transform: translateY(-1px);
}
.start-v2 .sv2-picker-choices .btn[disabled],
.start-v2 .sv2-picker-choices button[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

/* ========== mobile legibility ==========
   In daylight on a phone the muted greys wash out and the 13-15px
   terminal/body text is a squint. Below 600px: brighten the muted/dim
   text ramp (higher contrast against the near-black surface) and bump
   every interactive/label font size. Inputs go to >=17px so iOS Safari
   stops auto-zooming the viewport when a field is focused. */
@media (max-width: 600px) {
    .start-v2 {
        --sv2-fg-muted: #D7E0F0;   /* was #B8C2D9 */
        --sv2-fg-dim:   #9AA7C2;   /* was #6E7A95 */
    }

    .start-v2 .sv2-field input { font-size: 17px; padding: 15px 16px; }
    /* "EMAIL MY SIGN ON PASSWORD" is a long label in an uppercase
       display face; at 320px it would otherwise overflow the card.
       Slightly tighter type + a permitted wrap keeps it inside. */
    .start-v2 .sv2-btn         { font-size: 15px; padding: 14px 20px; text-align: center; max-width: 100%; }
    .start-v2 .sv2-gate-step   { font-size: 15px; }
    .start-v2 .sv2-newacct-head { font-size: 16px; }
    .start-v2 .sv2-gate-head   { font-size: 19px; }
    .start-v2 .sv2-trust       { font-size: 15px; }
    .start-v2 .sv2-alt,
    .start-v2 .sv2-msg         { font-size: 15px; }
    .start-v2 .sv2-captcha-q   { font-size: 21px; }
    .start-v2 .sv2-captcha-a   { font-size: 19px; }
    .start-v2 .sv2-captcha-hint { font-size: 14px; }
}

/* =====================================================================
   DAY / NIGHT
   ---------------------------------------------------------------------
   /start/ is the one page on the site every visitor sees before they are
   anybody, and it is routinely opened one-handed outdoors — a phone in
   sunlight is exactly where an ink-dark page stops being a mood and
   starts being a locked door. So it ships both themes, same as every
   module (see the day/night rule in CLAUDE.md).

   The preference is SITEWIDE: one `dmTheme` key in localStorage and an
   explicit `dm-light` / `dm-dark` class stamped on <html> pre-paint by
   renderThemeBoot() in the head. Someone who flips to light here is still
   in light when they land in /vault/ thirty seconds later. Because /start/
   has no module nav to hang the control on, the button is its own fixed
   pill in the top-right rather than a bar item.
   ===================================================================== */

.sv2-theme {
    position: fixed;
    top: clamp(12px, 2.5vw, 22px);
    right: clamp(12px, 2.5vw, 22px);
    z-index: 20;

    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;      /* 44px, not 42 — the minimum comfortable tap target,
                         and this is a one-handed-outdoors control */
    height: 44px;
    padding: 0;

    background: rgba(17, 26, 46, 0.72);
    /* Same 3:1 control-boundary target as the form fields — this pill is
       the one control on the page a squinting user is hunting for. */
    border: 1px solid var(--sv2-theme-line, rgba(125, 211, 252, 0.45));
    border-radius: 999px;
    color: var(--sv2-theme-fg, #7DD3FC);
    cursor: pointer;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    transition: background 0.15s ease, border-color 0.15s ease,
                color 0.15s ease, transform 0.15s ease;
}
.sv2-theme:hover,
.sv2-theme:focus-visible {
    transform: translateY(-1px);
    border-color: var(--sv2-theme-fg, #7DD3FC);
    outline: none;
}
.sv2-theme svg {
    width: 20px;
    height: 20px;
    display: block;
}

/* Icon shows the theme you would GET, not the one you are in: a sun while
   dark, a moon while light. A control picturing the CURRENT state reads
   as a status light and people stop pressing it.

   Both rules carry `.sv2-theme` so they score (0,2,0) and outrank the
   (0,1,1) `.sv2-theme svg { display: block }` above. Drop that class and
   the generic rule wins and BOTH icons render — the exact bug the module
   nav shipped with. */
.sv2-theme .sv2-theme-moon { display: none; }
.sv2-theme .sv2-theme-sun  { display: block; }
html.dm-light .sv2-theme .sv2-theme-moon { display: block; }
html.dm-light .sv2-theme .sv2-theme-sun  { display: none; }

/* =====================================================================
   LIGHT THEME
   ---------------------------------------------------------------------
   A light theme is a DESIGN, not an inversion. The cyan that glows on ink
   (#7DD3FC) measures ~1.35:1 on white — as a link colour it is not dim,
   it is invisible. So the accent gets a separate paper value (#0369A1,
   5.9:1 on the card, and still 5.5:1 as white-on-fill for the button),
   which re-themes every link, prompt, border and badge in one line
   because they all read --sv2-cyan.

   What could NOT be retokenised is listed after: a handful of literal
   rgba(125,211,252,.0x) washes that are near-invisible on white, plus the
   pale hover fill and the inverted spinner, which only make sense on ink.
   ===================================================================== */
html.dm-light .start-v2 {
    /* surface */
    --sv2-bg:        #F4F6FB;   /* paper */
    --sv2-surface:   #FFFFFF;   /* the card */
    --sv2-surface-2: #EDF1F9;
    --sv2-line:      rgba(23, 27, 51, 0.14);
    /* Paper edition of the field boundary — see --sv2-line-field above for
       why the controls do not share the decorative hairline. 3.3:1 on white. */
    --sv2-line-field: rgba(23, 27, 51, 0.50);

    /* accents — ink-on-paper values, not the neon ones */
    --sv2-cyan:      #0369A1;   /* 5.93:1 on #FFF, 5.49:1 on #F4F6FB */
    --sv2-green:     #15803D;   /* 5.02:1 on #FFF */
    --sv2-red:       #B91C1C;   /* 6.47:1 on #FFF */

    /* text */
    --sv2-fg:        #0F1729;   /* 17.9:1 */
    --sv2-fg-muted:  #4A5270;   /* 7.69:1 */
    --sv2-fg-dim:    #5A6280;   /* 6.01:1 */
    /* Paper edition of the placeholder token — 3.5:1 on the field fill.
       The dark theme's value is a mid-navy that all but vanishes on
       paper, so this is picked for white rather than inverted. */
    --sv2-placeholder: #7B8298;

    /* toggle chrome — .48 puts the pill's own outline at 3.1:1, so the
       control is findable in sun even before you read the icon inside it */
    --sv2-theme-fg:   #0369A1;
    --sv2-theme-line: rgba(23, 27, 51, 0.48);
}

html.dm-light .page-content:has(> .start-v2),
html.dm-light .page-wraper:has(.start-v2) { background: #F4F6FB; }

html.dm-light .sv2-theme { background: rgba(255, 255, 255, 0.82); }

/* The wash behind the headline: a cyan bloom on paper looks like a dirty
   screen. Same gesture, ink-tinted and fainter. */
html.dm-light .start-v2::before {
    background: radial-gradient(ellipse at center, rgba(3, 105, 161, 0.09) 0%, transparent 65%);
}

/* The card no longer floats in a void, so it stops casting a void's
   shadow — a hairline and a soft lift instead of a 50%-black drop. */
html.dm-light .start-v2 .sv2-card {
    box-shadow: 0 10px 28px -14px rgba(15, 23, 41, 0.25);
}

/* The theme class swaps the wordmark itself (navy ink on paper, white
   on dark — see the logo lockup section); here only the backlight
   changes, which on paper reads as a printing misregistration. */
/* The party, paper edition. Every hue in the dark walk fails on white:
   the neons drop to 1.5-2:1, and a wide text-shadow halo on paper reads
   as a printing fault, not a glow. So the light theme walks the same
   three FAMILIES in ink weights — all >=4.9:1 on #FFF — with a tighter,
   fainter halo. Overriding animation-NAME alone leaves the 2.4s timing,
   the 1s delay, the easing and the 6-run stop exactly as declared above,
   so the two themes can never drift out of step. */
html.dm-light .start-v2 .sv2-gate-head {
    /* Both names, not just the recoloured one: animation-name replaces
       the ENTIRE list, so naming the hue walk alone would silently drop
       the float in light mode. The float itself is theme-agnostic. */
    animation-name: sv2-gate-party-light, sv2-gate-float;
}
@keyframes sv2-gate-party-light {
    0%   { color: var(--sv2-fg); text-shadow: none; }
    22%  { color: #0369A1; text-shadow: 0 0 9px rgba(3, 105, 161, 0.35); }
    48%  { color: #6D28D9; text-shadow: 0 0 10px rgba(109, 40, 217, 0.32); }
    74%  { color: #B45309; text-shadow: 0 0 10px rgba(180, 83, 9, 0.32); }
    100% { color: var(--sv2-fg); text-shadow: none; }
}

html.dm-light .start-v2 .sv2-logo img { filter: none; }
html.dm-light .start-v2 .sv2-logo:hover img {
    filter: drop-shadow(0 4px 10px rgba(96, 57, 224, 0.28));
}

/* Field chrome. The .04 cyan tint on the icon gutter is invisible on
   white; the focus halo needs the paper accent to be seen at all. */
html.dm-light .start-v2 .sv2-field-icon { background: rgba(3, 105, 161, 0.05); }
html.dm-light .start-v2 .sv2-field:focus-within {
    box-shadow: 0 0 0 3px rgba(3, 105, 161, 0.16);
}
/* Captcha box on paper. Every tint, border, ring and keyframe in the
   block reads --sv2-captcha-*, so re-hueing the whole thing for the light
   theme is these two lines — no per-rule overrides, and no duplicate
   keyframes to keep in sync. #86EFAC mint is ~1.3:1 on white and would be
   a smear; this forest green is 5.0:1, and stays legible as the chip's
   text colour, not just as a border. */
html.dm-light .start-v2 {
    --sv2-captcha-accent: #15803D;
    --sv2-captcha-rgb:    21, 128, 61;
    --sv2-red-rgb:        185, 28, 28;
}

/* Link underline picks up the paper accent (the literal cyan rgba would
   be a ghost) — and stays visible after the :hover colour change. */
html.dm-light .start-v2 .sv2-link { border-bottom-color: rgba(3, 105, 161, 0.45); }

/* The primary button's hover fill is a PALE cyan, which on paper would
   turn white-ish text on a white-ish pill. It has to go the other way in
   light: darker on hover, not lighter. */
html.dm-light .start-v2 .sv2-btn--primary:hover {
    background: #075985;                                   /* 7.56:1 vs white */
    box-shadow: 0 14px 30px -12px rgba(3, 105, 161, 0.45);
}

/* processing.gif is dark artwork on transparent — it was inverted to be
   seen on ink. On paper the original is correct. */
html.dm-light .start-v2 .sv2-spinner { filter: none; }

html.dm-light .start-v2 .sv2-picker { background: rgba(3, 105, 161, 0.035); }

/* Mobile legibility, paper edition. The dark theme's 600px block
   BRIGHTENS the muted ramp to fight glare; on paper the same problem
   needs the opposite move, so the light values go darker rather than
   inheriting a fix aimed at the other background. These carry
   html.dm-light and so score (0,2,0) — they beat that media block
   regardless of source order. */
@media (max-width: 600px) {
    html.dm-light .start-v2 {
        --sv2-fg-muted: #3E4664;
        --sv2-fg-dim:   #4A5270;
    }
}
