/*
 * Design Upgrade
 * Aesthetic direction: Refined SaaS warmth — labor.quest's existing marketing-site
 *   language (soft purple hero gradient, bold Outfit type, hand-drawn sticky-note
 *   accents) extended onto the previously unstyled auth screens.
 * Font pairing: Outfit (UI/body) + Poppins 800 (wordmark) + Permanent Marker /
 *   Patrick Hand (decorative sticky-note accent)
 * Rationale: these auth pages are the first "real" screen after the marketing
 *   site's CTA — reusing its exact palette, type, card/button language, and
 *   sticky-note motif makes the handoff feel like one continuous product
 *   instead of a bare utilitarian form bolted onto the side.
 */

:root {
  /* ---- brand palette, copied verbatim from the landing site ---- */
  --color-purple: #534AB7;
  --color-purple-tint: #EEEDFE;
  --color-purple-deep: #3f38a0;
  --color-blue: #00A8FF;
  --color-blue-tint: #E0F2FE;
  --color-green: #10b981;
  --color-green-tint: #D1FAE5;
  --color-yellow: #CA8A04;
  --color-yellow-tint: #FEF9C3;

  /* Untitled-UI-style gray scale, same shades the landing site uses */
  --color-gray-50: #f9fafb;
  --color-gray-100: #f2f4f7;
  --color-gray-200: #e4e7ec;
  --color-gray-300: #d0d5dd;
  --color-gray-400: #98a2b3;
  --color-gray-500: #667085;
  --color-gray-600: #475467;
  --color-gray-700: #344054;
  --color-ink: #101828;

  /* Not part of the marketing palette — the landing page never needed an
     error color. Chosen to sit quietly alongside purple/blue/green/yellow
     (same Tailwind-600/50 lightness pairing as the rest of the scale). */
  --color-error: #DC2626;
  --color-error-tint: #FEF2F2;

  --font-sans: "Outfit", ui-sans-serif, system-ui, sans-serif;
  --font-wordmark: "Poppins", -apple-system, sans-serif;
  --font-marker: "Permanent Marker", "Bradley Hand", "Segoe Print", cursive;
  --font-hand: "Patrick Hand", "Bradley Hand", "Segoe Print", cursive;

  --radius-lg: 20px;
  --radius-md: 12px;
  --radius-sm: 8px;

  --shadow-card: 0 24px 64px rgba(83, 74, 183, 0.15);
  --shadow-soft: 0 2px 12px rgba(0, 0, 0, 0.08);
  --shadow-button: 0 8px 24px rgba(83, 74, 183, 0.28);

  --ease-brand: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* ---- S-54: the role tokens the dark theme redefines ----------------------
     These pages are pre-login, so there is no settings row to read and no
     `data-theme` attribute to honour — the dark theme here is a
     `prefers-color-scheme` media query at the end of this file and nothing
     else. That's the right answer rather than a limitation: the only signal
     available before a user has an account is the one the OS already carries,
     and the SPA's own default ("system") agrees with it, so a dark-mode user
     goes login -> dashboard without the page ever flashing white.

     Everything below states its colour through a token. The values here are
     the literals they replaced, byte for byte, except the six 12-13px text
     sites that were on --color-gray-400 (#98a2b3, 2.55:1 on white — below AA)
     and now sit on --color-gray-500 (5.24:1). Same fix, same reasoning, as
     frontend/src/index.css's --lq-muted; the two files are separate design
     systems by design (see CLAUDE.md) but they agree about contrast. */
  --surface: #ffffff;
  --surface-header: rgba(255, 255, 255, 0.95);
  --card-border: rgba(83, 74, 183, 0.08);
  --page-bg: linear-gradient(to bottom, #EEEDFE 0%, #EEEDFE 30%, #F3F2FB 55%, #F9FAFB 100%);
  --wordmark: #000000;
  --focus-ring: rgba(83, 74, 183, 0.12);
  --input-bg: var(--color-gray-50);
  --input-bg-focus: #ffffff;
  --overlay-bg: rgba(249, 250, 251, 0.92);

  /* The purple in its solid-fill role. One value in light, two in dark — a
     lavender CTA with dark type reads as a different product, so the fill
     stays saturated while the text/icon purple lightens. Same split, and the
     same reason, as --lq-purple / --lq-purple-fill in the SPA. */
  --color-purple-fill: #534AB7;

  /* The sticky note, which is one thing rather than a palette entry: a bright
     yellow paper square is the single largest light source on these pages, and
     at full brightness on a dark screen it would be the only thing anyone
     sees. In dark it becomes dim ochre paper — still paper, still yellow, no
     longer a lamp — with its handwriting held at 6.4:1 on it. */
  --note-bg: #FEF9C3;
  --note-ink: #6b5504;
  --note-shadow: 0 10px 22px rgba(202, 138, 4, 0.22), 0 2px 6px rgba(0, 0, 0, 0.08);

  /* A badge's or alert's own label over its own wash. */
  --alert-error-ink: #991b1b;
  --alert-success-ink: #065f46;
  --badge-green-ink: #047857;
  --badge-blue-ink: #0369a1;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  /* Native scrollbars, the internals of form controls and autofill styling are
     the UA's to draw, and this is the only way to tell it which way round.
     Both keywords, because these pages genuinely follow the OS either way. */
  color-scheme: light dark;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  color: var(--color-ink);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  background: var(--page-bg);
  min-height: 100vh;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: inherit;
}

/* ---------------------------------------------------------------- */
/* Page shell                                                        */
/* ---------------------------------------------------------------- */

.shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Same treatment as the marketing site's fixed navbar (bg-white/95
   backdrop-blur-sm border-b border-gray-100, h-[64px] sm:h-[80px]) —
   not fixed here since these are single-card pages, not a scrolling one,
   but the bar itself should look identical. */
.site-header {
  background: var(--surface-header);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--color-gray-100);
  height: 64px;
  display: flex;
  align-items: center;
  padding: 0 16px;
}

@media (min-width: 640px) {
  .site-header {
    height: 80px;
    padding: 0 24px;
  }
}

.logo-link {
  display: inline-flex;
  transition: opacity 200ms var(--ease-brand);
}

.logo-link:hover {
  opacity: 0.8;
}

.logo-link:focus-visible {
  outline: 2px solid var(--color-purple);
  outline-offset: 4px;
  border-radius: 4px;
}

/* Wordmark — same custom glyphs as the marketing site's <LaborQuestLogo />,
   same responsive sizing too (text-2xl -> min-[440px]:text-3xl ->
   lg:text-5xl on the navbar's size="lg" instance). */
.logo {
  font-family: var(--font-wordmark);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--wordmark);
  font-size: 1.5rem;
  /* Deliberately NOT flex: the source component is plain inline text flow,
     and each glyph SVG is positioned purely via `vertical-align` on itself.
     `vertical-align` has no effect on flex items — an inline-flex wrapper
     here silently breaks every glyph's hand-tuned offset (that's why the
     magnifying-glass sat noticeably higher than the rest of the word). */
  display: inline-block;
}

@media (min-width: 440px) {
  .logo {
    font-size: 1.875rem;
  }
}

@media (min-width: 1024px) {
  .logo {
    font-size: 3rem;
  }
}

.logo svg {
  display: inline-block;
}

/* S-54: the three coloured shapes inside the wordmark's glyphs state their
   colour as SVG presentation attributes in base.html (fill="#534AB7",
   stroke="#00A8FF", fill="#10b981"). A presentation attribute loses to any
   author rule, so re-stating the same three colours here as tokens costs no
   markup change and lets the dark theme's lighter purple/blue/green reach the
   logo. Matched structurally, not by attribute value, so the rules survive
   the next hand-sync with frontend/src/components/Logo.tsx: in both glyph
   SVGs the circles are always ring (currentColor), inner ring (blue), pupil
   (green), in that document order, and the only <rect> in the wordmark is the
   purple dot of the "r." Putting var() in the attribute itself was the
   alternative and isn't worth it — presentation attributes are parsed as CSS
   but var() support there has never been dependable across browsers. */
.logo svg rect {
  fill: var(--color-purple);
}

.logo svg circle:nth-of-type(2) {
  stroke: var(--color-blue);
}

.logo svg circle:nth-of-type(3) {
  fill: var(--color-green);
}

.auth-main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 20px 56px;
}

.site-footer {
  padding: 20px 24px 32px;
  text-align: center;
}

.site-footer p {
  margin: 0;
  font-size: 12px;
  font-weight: 300;
  color: var(--color-gray-500);
}

/* ---------------------------------------------------------------- */
/* Auth card                                                         */
/* ---------------------------------------------------------------- */

/* ---------------------------------------------------------------- */
/* Desktop promo panel (/login, /signup)                             */
/* ---------------------------------------------------------------- */

/* Below its page's breakpoint this is a plain centering flex wrapper around
   .auth-wrap only — .auth-promo is display:none (next rule) and doesn't
   participate, so both pages render identically to every other auth page. */
.auth-shell {
  width: 100%;
  display: flex;
  justify-content: center;
}

.auth-promo {
  display: none;
}

@media (min-width: 1024px) {
  .auth-shell {
    /* align-items default (stretch) still makes .auth-promo match
       .auth-wrap/.auth-card's real, natural content height exactly — no
       hardcoded number. A real gap now (not 0): each panel is a complete,
       independent rounded card (see .auth-promo below), not two shapes
       fused into one, so they need visible separation to read that way. */
    justify-content: center;
    gap: 24px;
  }

  /* Everything the two panels share. What differs per page is only the art
     and the box that art needs, so each modifier below carries its own
     width, its own image, and its own `display:block` at the viewport width
     that box actually fits in — /signup's art is taller than /login's,
     because /signup's form is. */
  .auth-promo {
    flex-shrink: 0;
    /* A complete card in its own right — rounded on all four corners,
       same shadow language as .auth-card. */
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
    /* Fills any sliver of letterbox space from the panel's height not
       matching the image's exactly (an error alert makes the form card
       taller, and the panel stretches with it) — background-size:contain
       (not cover) means the full image is always visible, never cropped,
       at the cost of that small gap on one axis, which this tint covers
       tastefully rather than leaving a hole. */
    background-color: var(--color-purple-tint);
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
  }

  /* Both widths below are fixed numbers, and that isn't laziness. Tried
     sizing the panel via aspect-ratio + width:auto so it would derive its
     width purely from the stretched height with zero cropping — but that
     hits a genuine flex circular-dependency case Chrome doesn't resolve
     (main-axis auto size depending on a cross-axis stretch that itself
     depends on the row's content, which depends on this item's main-axis
     size...), confirmed empirically: computed width collapsed to 0px. A
     fixed width sidesteps that entirely.
     Each one is its art's native ratio × the panel's real stretched height
     on that page, i.e. that page's .auth-card content height, measured
     rather than guessed. Swapping either asset for one with different
     native proportions means recomputing that product, not reusing the
     number below, and regenerating the variants off the new master.

     Every variant is pre-scaled to the box its panel actually renders (1x)
     and its retina double (2x) — neither is ever displayed larger than its
     own width, so shipping a ~1200px master would be megabytes to paint a
     few hundred CSS px. Regenerate all of them from the master with Pillow
     (LANCZOS, webp quality 82) if the art changes.
     Per panel: fallback first (browsers that don't parse image-set), then a
     -webkit-prefixed copy for older Safari, then the unprefixed form —
     later valid declarations win, so order is deliberate. The plain url()
     fallback is the 1x PNG, not @2x: photographic content this size costs
     over a megabyte as a 2x PNG, and the only browsers that reach this
     declaration at all support neither form of image-set, where a soft 1x
     image is a better trade than that download. */
  .auth-promo--login {
    /* 1122:1402 ≈ 0.800 against a ~557-560px card: 560 × 0.800 ≈ 448px. */
    display: block;
    width: 448px;
    background-image: url("/login/assets/images/auth/login-promo.png");
    background-image: -webkit-image-set(
      url("/login/assets/images/auth/login-promo.webp") 1x,
      url("/login/assets/images/auth/login-promo@2x.webp") 2x
    );
    background-image: image-set(
      url("/login/assets/images/auth/login-promo.webp") 1x,
      url("/login/assets/images/auth/login-promo@2x.webp") 2x
    );
  }

  .auth-promo--signup {
    /* 1172:1342 ≈ 0.873 against a 667px card (the signup form carries a
       name field and the password rules, so it runs ~110px taller than
       login's): 667 × 0.873 ≈ 582px. Deliberately not display:block here —
       408 + 24 + 582 plus .auth-main's padding needs ~1054px, so this
       panel turns on in its own wider media query below instead. */
    width: 582px;
    /* The one thing /login's panel doesn't need. After a successful signup
       the form and the Google button are replaced by a single "check your
       email" alert, which collapses the card to roughly a third of this
       height — and a stretch-only panel would collapse with it, squeezing
       the art into a narrow strip inside a tall band of tint. This floors
       the panel at the art's own height (582 × 1342/1172 ≈ 666) so the
       image keeps its shape and the short success card simply sits at the
       top of the row. */
    min-height: 666px;
    background-image: url("/login/assets/images/auth/signup-promo.png");
    background-image: -webkit-image-set(
      url("/login/assets/images/auth/signup-promo.webp") 1x,
      url("/login/assets/images/auth/signup-promo@2x.webp") 2x
    );
    background-image: image-set(
      url("/login/assets/images/auth/signup-promo.webp") 1x,
      url("/login/assets/images/auth/signup-promo@2x.webp") 2x
    );
  }
}

/* /signup's panel is 134px wider than /login's, so it needs a wider window
   than 1024px before it fits beside a full-width form card. At exactly
   1024px (iPad landscape, a real and common width) showing it anyway would
   force .auth-wrap to shrink from 408px to ~378px, narrowing the form to
   buy the decoration — so between 1024px and 1100px /signup stays the plain
   centered card it is on mobile, and the panel appears once there's room
   for both at full size. */
@media (min-width: 1100px) {
  .auth-promo--signup {
    display: block;
  }
}

.auth-wrap {
  width: 100%;
  max-width: 408px;
  position: relative;
  /* Only meaningful once .auth-wrap is a grid item (the promo pages'
     .auth-shell, ≥1024px) — justify-self has no effect on a flex item, so
     this is a no-op on every other auth page's plain flex-centered layout. */
  justify-self: center;
}

.auth-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--card-border);
  box-shadow: var(--shadow-card);
  padding: 36px 32px 32px;
  position: relative;
  z-index: 1;
  animation: card-rise 700ms var(--ease-brand) both;
}

@media (max-width: 400px) {
  .auth-card {
    padding: 28px 22px 24px;
    border-radius: 16px;
  }
}

@keyframes card-rise {
  from {
    opacity: 0;
    transform: translateY(18px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .auth-card {
    animation: none;
  }
}

.eyebrow {
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-purple);
}

.auth-card h1 {
  margin: 0 0 8px;
  font-size: 26px;
  font-weight: 900;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--color-ink);
}

.lede {
  margin: 0 0 26px;
  font-size: 14px;
  font-weight: 300;
  line-height: 1.6;
  color: var(--color-gray-500);
}

/* The hand-tilted sticky-note accent that used to hang off .auth-wrap's
   top-right corner on /signup ("Free to start, no card needed") is gone as
   of 2026-09-12: the page now carries the promo panel instead, and the note
   sat exactly where that panel's headline does the same job with real words.
   Its rules are deleted rather than left dead, but --note-bg / --note-ink /
   --note-shadow / --font-hand stay in the token table and base.html keeps
   loading Patrick Hand — the note motif is part of the shared design system
   (see the hero's note board on the landing site), and the next page that
   wants one should re-style it from those tokens. */

/* ---------------------------------------------------------------- */
/* Alerts                                                            */
/* ---------------------------------------------------------------- */

.alert {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1.5;
  font-weight: 400;
  margin: 0 0 20px;
}

.alert-error {
  background: var(--color-error-tint);
  color: var(--alert-error-ink);
}

.alert-success {
  background: var(--color-green-tint);
  color: var(--alert-success-ink);
}

.alert-icon {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  margin-top: 1px;
}

/* ---------------------------------------------------------------- */
/* Form fields                                                       */
/* ---------------------------------------------------------------- */

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field span {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-gray-700);
}

.field input {
  font-family: var(--font-sans);
  font-size: 15px;
  color: var(--color-ink);
  background: var(--input-bg);
  border: 1.5px solid var(--color-gray-200);
  border-radius: var(--radius-sm);
  padding: 11px 14px;
  transition: border-color 180ms var(--ease-brand), background-color 180ms var(--ease-brand), box-shadow 180ms var(--ease-brand);
}

.field input::placeholder {
  color: var(--color-gray-500);
}

.field input:hover {
  border-color: var(--color-gray-300);
}

.field input:focus {
  outline: none;
  background: var(--input-bg-focus);
  border-color: var(--color-purple);
  box-shadow: 0 0 0 4px var(--focus-ring);
}

.field-hint {
  margin: 0;
  font-size: 12px;
  font-weight: 300;
  line-height: 1.5;
  color: var(--color-gray-500);
}

/* ---------------------------------------------------------------- */
/* Buttons                                                           */
/* ---------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  border: none;
  border-radius: var(--radius-sm);
  padding: 12px 20px;
  cursor: pointer;
  transition: transform 180ms var(--ease-brand), opacity 180ms var(--ease-brand), box-shadow 180ms var(--ease-brand), background-color 180ms var(--ease-brand);
}

.btn:focus-visible {
  outline: 2px solid var(--color-purple);
  outline-offset: 2px;
}

.btn-block {
  width: 100%;
}

.btn-primary {
  background: var(--color-purple-fill);
  color: #ffffff;
  box-shadow: var(--shadow-button);
}

.btn-primary:hover {
  opacity: 0.95;
  transform: translateY(-1px);
}

.btn-primary:active {
  transform: translateY(0);
}

.btn-outline {
  background: var(--surface);
  color: var(--color-gray-700);
  border: 1.5px solid var(--color-gray-200);
}

.btn-outline:hover {
  border-color: var(--color-gray-300);
  background: var(--color-gray-50);
}

/* ---------------------------------------------------------------- */
/* Divider + Google button row                                       */
/* ---------------------------------------------------------------- */

.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0;
}

.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-gray-200);
}

.divider span {
  font-size: 12px;
  font-weight: 400;
  color: var(--color-gray-500);
  white-space: nowrap;
}

.google-btn-row {
  display: flex;
  justify-content: center;
}

/* S-55: GIS renders inside its own iframe (color-scheme: light by default).
   In dark mode, an iframe with color-scheme: light still renders the button
   pill with a white background (that's Google's design), but set it explicitly
   and add a hairline rim so it reads as a distinct card on the dark page. */
@media (prefers-color-scheme: dark) {
  .google-btn-row iframe {
    color-scheme: light;
    outline: 1px solid var(--color-gray-200);
    border-radius: 20px;
  }
}

/* ---------------------------------------------------------------- */
/* Links under the card                                              */
/* ---------------------------------------------------------------- */

.auth-links {
  margin-top: 16px;
  text-align: center;
}

.auth-links a {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-purple);
  transition: opacity 150ms var(--ease-brand);
}

.auth-links a:hover {
  opacity: 0.75;
  text-decoration: underline;
}

.auth-switch {
  margin: 22px 0 0;
  text-align: center;
  font-size: 13px;
  font-weight: 300;
  color: var(--color-gray-500);
}

.auth-switch a {
  font-weight: 600;
  color: var(--color-purple);
}

.auth-switch a:hover {
  text-decoration: underline;
}

.auth-links-row {
  margin-top: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-gray-500);
}

.auth-links-row a {
  font-weight: 500;
  color: var(--color-purple);
}

.auth-links-row a:hover {
  text-decoration: underline;
}

.auth-links-row .sep {
  color: var(--color-gray-200);
}

/* ---------------------------------------------------------------- */
/* Profile / "me" page                                               */
/* ---------------------------------------------------------------- */

.profile-wrap {
  width: 100%;
  max-width: 460px;
}

.profile-header {
  text-align: center;
  margin-bottom: 24px;
}

.profile-avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin: 0 auto 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-purple-tint);
  color: var(--color-purple);
  font-family: var(--font-wordmark);
  font-weight: 800;
  font-size: 24px;
}

.profile-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--card-border);
  box-shadow: var(--shadow-soft);
  overflow: hidden;
  animation: card-rise 700ms var(--ease-brand) both;
}

@media (prefers-reduced-motion: reduce) {
  .profile-card {
    animation: none;
  }
}

.profile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--color-gray-100);
}

.profile-row:last-child {
  border-bottom: none;
}

.profile-row dt {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-gray-500);
  margin: 0;
}

.profile-row dd {
  margin: 0;
  font-size: 14px;
  font-weight: 500;
  color: var(--color-ink);
  text-align: right;
  word-break: break-word;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 3px 10px;
  border-radius: 999px;
}

.badge-green {
  background: var(--color-green-tint);
  color: var(--badge-green-ink);
}

.badge-purple {
  background: var(--color-purple-tint);
  color: var(--color-purple);
}

.badge-blue {
  background: var(--color-blue-tint);
  color: var(--badge-blue-ink);
}

.badge-gray {
  background: var(--color-gray-100);
  color: var(--color-gray-500);
}

.profile-actions {
  margin-top: 24px;
  display: flex;
  justify-content: center;
}

/* ---- Google sign-in loading overlay ---- */
.google-auth-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: var(--overlay-bg);
  backdrop-filter: blur(4px);
}

.google-auth-overlay[hidden] {
  display: none;
}

.google-auth-overlay p {
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  color: var(--color-gray-600);
}

.google-auth-spinner {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 4px solid var(--color-purple-tint);
  border-top-color: var(--color-purple);
  animation: google-auth-spin 0.8s linear infinite;
}

@keyframes google-auth-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---------------------------------------------------------------- */
/* S-54: dark theme                                                  */
/* ---------------------------------------------------------------- */

/* One media query, redefining the tokens declared at the top of this file and
   nothing else — no rule below `:root` needed a dark variant, because every
   rule below `:root` already states its colour through a token.
   `prefers-color-scheme` is the whole of the mechanism here: these pages are
   pre-login, so there's no stored preference to read yet, and the SPA's own
   default is "system" anyway, so a dark-mode user goes login -> dashboard
   without a white flash in between.

   Three things the values are built around, all measured, all the same rules
   the SPA's index.css follows (that file's header has the long version):
   1. No pure black. The darkest surface here is #191820, the bottom of the
      page gradient, 1.19:1 against #000 — enough separation for a shadow to
      exist and for the eye to stop reading the screen as a hole.
   2. Desaturate, don't just darken. The brand purple #534AB7 has chroma .166
      in OkLCH; on a dark background at that chroma it glows and smears. The
      dark purple #ABAEFF is the same hue (281.9 deg, held to the degree) at
      L .780 / C .120 — recognisably the brand, no longer a light source. Blue,
      green, yellow and red get the same treatment at the same L and C, which
      is also what keeps them looking like one family in dark rather than four
      unrelated stickers.
   3. Elevation inverts. In light the card is white and the page is tinted; in
      dark the page is the darkest thing and the card (#222229) is lighter than
      it. Shadows stay, but they stop doing the work of separating a card from
      its page and go back to only suggesting height. */
@media (prefers-color-scheme: dark) {
  :root {
    /* ---- brand, desaturated (OkLCH L .780 / C .120, hue unchanged) ---- */
    --color-purple: #ABAEFF;
    --color-purple-tint: #38384B;
    --color-purple-deep: #6D6BCB;
    --color-blue: #6EC0FE;
    --color-blue-tint: #2E3B4B;
    --color-green: #66CFA1;
    --color-green-tint: #2D3E3C;
    --color-yellow: #E6AB5A;
    --color-yellow-tint: #413831;

    /* The fill role keeps its saturation while the text purple lightens: a
       lavender primary button reads as a different product, and white on
       #5E59BC is 5.78:1, so the CTA can stay purple and stay legible. */
    --color-purple-fill: #5E59BC;

    /* The gray ramp, inverted rather than remapped — 50 is still the quietest
       fill and 700 is still the loudest text, so every rule that reads it
       keeps meaning what it meant. The three darkest steps are hairlines and
       fills; 400 up are text, and all of them clear 5:1 on the card. */
    --color-gray-50: #2C2C34;
    --color-gray-100: #383841;
    --color-gray-200: #4C4C55;
    --color-gray-300: #5A5A64;
    --color-gray-400: #B0B0B7;
    --color-gray-500: #B0B0B7;
    --color-gray-600: #CECFD4;
    --color-gray-700: #CECFD4;
    --color-ink: #ECECEF;

    --color-error: #FB9890;
    --color-error-tint: #453539;

    /* Shadows go black rather than purple-tinted. A coloured shadow on a dark
       surface reads as a glow, which is the opposite of what a shadow is for,
       and they're deeper here because the card is only ~1.1:1 away from the
       page it sits on. */
    --shadow-card: 0 24px 64px rgba(0, 0, 0, 0.55);
    --shadow-soft: 0 2px 12px rgba(0, 0, 0, 0.4);
    --shadow-button: 0 8px 24px rgba(0, 0, 0, 0.45);

    /* ---- role tokens ---- */
    --surface: #222229;
    --surface-header: rgba(34, 34, 41, 0.95);
    /* A faint purple rim instead of light's near-invisible one: it's what
       tells the eye the card is nearer than the page now that the card is the
       lighter of the two, which is the job the drop shadow did in light. */
    --card-border: rgba(171, 174, 255, 0.1);
    /* The same three-stop gradient as light, at the same hue — the page still
       opens purple at the top and cools to neutral at the bottom, it just does
       it between 1.28:1 and 1.19:1 against black instead of near white. */
    --page-bg: linear-gradient(to bottom, #1F1D32 0%, #1F1D32 30%, #1D1C2C 55%, #191820 100%);
    /* Not #FFFFFF: at 21:1 a heavy 800-weight wordmark this size vibrates
       against the dark page. #F5F5F8 is 16.2:1 and sits still. */
    --wordmark: #F5F5F8;
    --focus-ring: rgba(171, 174, 255, 0.22);
    /* An input is recessed in dark, not raised — darker than the card it's on,
       going lighter on focus, which is the reverse of light's gray-to-white. */
    --input-bg: #2A2A32;
    --input-bg-focus: #33333C;
    --overlay-bg: rgba(25, 24, 32, 0.92);

    /* Dim ochre paper, not a lamp. The note is the largest single block of
       colour on these pages; left at #FEF9C3 it would be the only thing
       anyone saw on a dark screen. Its handwriting is 6.4:1 on it. */
    --note-bg: #4E4828;
    --note-ink: #E3D99C;
    --note-shadow: 0 10px 22px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.3);

    /* Each label on its own wash: 6.2:1, 6.8:1, 6.8:1, 6.6:1. */
    --alert-error-ink: #FFA8A0;
    --alert-success-ink: #7DDBAF;
    --badge-green-ink: #7DDBAF;
    --badge-blue-ink: #82CDFF;
  }
}
