/*!
 * @kaleidr/auth-core/styles — shared auth visual primitives.
 *
 * SINGLE SOURCE OF TRUTH. Do not duplicate these rules in per-app
 * stylesheets; override tokens instead (see `:root` below).
 *
 * Shipping classes, grouped by the React component that consumes
 * them (AuthModal + AccountDropdown both live in
 * `@kaleidr/auth-core/react` and are rendered by map-ui, studio-ui,
 * and home-ui):
 *
 *   <AuthModal>  (Sign in / Sign up / Forgot password / Verify)
 *     .auth-modal-overlay, .auth-modal
 *     .auth-header, .auth-close-btn, .auth-title, .auth-subtitle
 *     .auth-trust-bullets
 *     .auth-body, .auth-form, .auth-field, .auth-field-label-row
 *     .auth-divider, .auth-description
 *     .oauth-btn, .auth-continue-btn
 *     .auth-error, .auth-success
 *     .auth-forgot-inline, .auth-link, .auth-toggle
 *     .auth-pw-reqs, .auth-pw-req-dot
 *     .auth-terms-inline, .auth-terms-link
 *
 *   <AccountDropdown>  (signed-in avatar menu — identity + Manage
 *                       Account + inline Billing + Sign Out)
 *     .auth-dropdown-header, .auth-dropdown-name   (identity row —
 *                                                   name only, the
 *                                                   tier chip moved
 *                                                   into the Billing
 *                                                   block, see below)
 *     .auth-dropdown-tier,
 *     .auth-tier-free / -paid / -business / -admin (tier chip — used
 *                                                   inside the Billing
 *                                                   "Current Plan" row)
 *     .auth-dropdown-email                          (secondary email
 *                                                    line under name)
 *     .auth-dropdown-divider
 *     .auth-dropdown-item, (.auth-dropdown-signout) (menu items)
 *     .auth-avatar-initials, .auth-button-spinner
 *
 *     The four "title" rows — `.auth-dropdown-name` (identifier),
 *     `.auth-dropdown-item` (Manage Account / Sign Out), and
 *     `.auth-dropdown-billing-title` (Billing heading) — share one
 *     canonical type style declared via a grouped rule near
 *     `.auth-dropdown-item`. Sign Out is the only intentional
 *     divergence and only diverges in color.
 *     .auth-dropdown-billing,
 *     .auth-dropdown-billing-title          (inline billing block
 *                                            wrapper + heading)
 *     .auth-settings-billing-row,
 *     .auth-settings-billing-label,
 *     .auth-settings-billing-summary,
 *     .auth-settings-billing-period-range,
 *     .auth-settings-usage-list,
 *     .auth-settings-past-due*              (billing-block internals,
 *                                            kept under the legacy
 *                                            `auth-settings-*` prefix
 *                                            to avoid churning every
 *                                            class consumer)
 *     .auth-usage-meter, .auth-usage-meter-header,
 *     .auth-usage-meter-pct, .auth-usage-meter-track,
 *     .auth-usage-meter-fill (.is-critical) (generic meter primitives,
 *                                            also used by map-ui's
 *                                            <MobileAccountPanel>)
 *
 *     2026-05-10: the inline Upgrade / Manage / Enterprise CTAs were
 *     centralised on home-ui's /settings page. The matching
 *     `.auth-dropdown-billing-cta`, `.auth-settings-past-due-action`,
 *     and `.auth-settings-enterprise` selectors were removed; the
 *     dropdown is now read-only.
 *
 * Home-ui consumes two extra class hooks only — its EJS templates
 * render the rest via Tailwind utilities:
 *   [data-pw-req], .pw-req-dot   (real-time password checklist)
 *
 * Responsive behaviour (see RESPONSIVE block):
 *   - Desktop / tablet (≥ 481px): every modal is a floating card,
 *     440 px wide.
 *   - Phones (≤ 480px): every modal flips to a full-viewport page.
 *     Every secondary-text block steps 16→14 px to match the
 *     account dropdown's scale.
 *
 * Tokens are namespaced --auth-* and default to a dark backdrop;
 * host apps override via --glass-bg / --color-primary etc.
 * The `:root { --auth-* }` block lives in `./tokens.css` (Apr 2026)
 * so apps can import just the tokens without the full chrome — see
 * that file's header for the import-path cheat-sheet.
 */

:root {
  /* ---------- Surface ----------------------------------------------
     Glass background + border + blur for the auth modal chrome.
     Falls through to the host app's glass tokens if set; otherwise
     a dark-glass default matching map-ui's light-glass inverse.  */
  --auth-glass-bg: var(--glass-bg, rgba(15, 17, 21, 0.92));
  --auth-glass-border: var(--glass-border, 1px solid rgba(255, 255, 255, 0.08));
  --auth-glass-blur: var(--glass-blur, blur(12px));

  /* `.auth-modal` chrome (border / radius / shadow). Falls through to
     the host app's `--modal-card-*` system when defined so every modal
     in a host (sign-in, sign-up, forgot password, verify, account
     settings, plus any host-side modals like quota / share) reads as
     one family. Defaults preserve the historical auth-core look so
     studio-ui keeps its previous appearance verbatim. The mobile
     @media block in auth.css strips these to `none` for full-page
     phone layout — that override still wins because it's an explicit
     `border: none` declaration, not token-based. */
  --auth-modal-radius: var(--modal-card-radius, 1.5rem);
  --auth-modal-border: var(--modal-card-border, 1.5px solid var(--auth-input-border));
  --auth-modal-shadow: var(
    --modal-card-shadow,
    0 8px 32px rgba(0, 0, 0, 0.3),
    0 2px 8px rgba(0, 0, 0, 0.15)
  );

  /* ---------- Modal scrim ----------------------------------------
     The translucent layer rendered BEHIND `.auth-modal` (and any
     other auth-core overlay). Tracks the host app's `--modal-scrim-*`
     when defined so every modal in a host (auth, quota walls, share,
     etc.) shares one transparency style; falls back to the historical
     dark veil for hosts that haven't opted in.

     Map-ui sets `--modal-scrim-bg` to a light wash so the modal card
     reads as one crisp surface. Studio-ui omits the host token, so the
     dark fallback below applies and the previous look is preserved. */
  --auth-modal-scrim-bg: var(--modal-scrim-bg, rgba(0, 0, 0, 0.6));
  --auth-modal-scrim-blur: var(--modal-scrim-blur, blur(4px));

  /* ---------- Modal card surface ---------------------------------
     The `.auth-modal` card background. Intentionally split from
     `--auth-glass-bg` (which still drives translucent surfaces like
     the toolbar login button and the account dropdown) because a
     translucent card on top of a translucent scrim COMPOUNDS the dim
     and makes the modal read as a darker shade than every other
     window — the more transparent the user has set their UI, the
     worse it gets.

     Map-ui sets `--modal-card-bg` to `--ui-bg-solid` (the opaque
     theme-tracking colour: white in light mode, dark navy in dark
     mode) so the modal card never bleeds the scrim through and
     always matches the user's chosen light/dark surface tone. Hosts
     that haven't opted in fall back to the historical
     `--auth-glass-bg` translucent recipe (studio-ui keeps its
     previous appearance verbatim). */
  --auth-modal-bg: var(--modal-card-bg, var(--auth-glass-bg));

  /* ---------- Typography scale (single source of truth) -----------
     Every font-size inside the shared auth modal reads from one of
     these four tokens, AND home-ui's `.field-label` / `.input-field` /
     `.btn-google` pull from the same values via @apply fallbacks.
     That's how all three apps stay pixel-identical even though
     home-ui uses Tailwind utilities and the modal uses plain CSS.

     Body = 16px matches Tailwind's `text-base` default, which is the
     size home-ui renders for labels, inputs, OAuth buttons, trust
     bullets, and the "Already have an account?" toggle. Modal CTA
     and body sit at the same 16px tier deliberately — the button
     outweighs the text through weight + background, not through
     point size. */
  --auth-font-title: 22px;   /* modal h2 */
  --auth-font-cta: 16px;     /* primary submit button (matches body) */
  --auth-font-body: 16px;    /* inputs, OAuth btn, toggle link,
                                trust bullets, inline T&C, subtitle,
                                banners, field labels, descriptions */
  --auth-font-meta: 13px;    /* password checklist, small footnotes */

  /* ---------- Form field dimensions -------------------------------
     Shared between the React `.auth-field input` / `.oauth-btn` and
     home-ui's `.input-field` / `.btn-google`. Any visual contract
     change (taller fields, rounder corners, tighter horizontal
     padding) happens HERE, once, and both apps track it. */
  --auth-field-height: 3rem;          /* 48px — same as Tailwind h-12  */
  --auth-field-radius: 0.75rem;       /* 12px — same as Tailwind rounded-xl */
  --auth-field-padding-x: 1rem;       /* 16px — same as Tailwind px-4 */

  /* ---------- Modal sizing (single source of truth) --------------
     The shared `.auth-modal` rule reads from these tokens. Host apps
     wanting a wider/narrower modal should override the token rather
     than re-declaring `.auth-modal { width: ... }` in their local
     stylesheet. */
  --auth-modal-width: 440px;
  --auth-modal-max-width: 90vw;
  --auth-modal-max-height: 90vh;

  /* ---------- Text colors ----------------------------------------
     Primary + secondary follow the host's palette (map-ui uses
     light-glass so --color-primary-white/secondary-white are
     *dark*; studio-ui and home-ui are dark-glass so they're white).
     Muted is derived from currentColor so it automatically fades
     against whatever text color the modal is inheriting — this
     keeps the password checklist and inline T&C line readable on
     both light and dark modal surfaces without each host having to
     define a separate --color-tertiary-* token. Hosts may still
     override via --color-tertiary-white if they want a specific hue. */
  --auth-text-primary: var(--color-primary-white, rgba(255, 255, 255, 0.92));
  --auth-text-secondary: var(--color-secondary-white, rgba(255, 255, 255, 0.6));
  --auth-text-muted: var(
    --color-tertiary-white,
    color-mix(in srgb, currentColor 55%, transparent)
  );

  /* ---------- Brand ---------------------------------------------- */
  --auth-highlight: var(--highlight-color, var(--color-primary, #2da84a));
  --auth-highlight-hover: color-mix(in srgb, var(--auth-highlight) 90%, white);

  /* `--auth-highlight-on` is the foreground color that pairs with
     `--auth-highlight` when the highlight is used as a BACKGROUND
     (the primary `.auth-continue-btn` is the canonical case). It exists
     because `--auth-text-primary` tracks the modal SURFACE — dark on
     map-ui's white glass, white on studio-ui's dark glass — which is
     correct for body text but wrong for accent-colored buttons:
     when a user picks an accent that happens to match the surface
     (e.g. kaleidr-black on map-ui's white modal), the button text
     collapses into the button background and disappears.

     Map-ui sets this token dynamically from luminance in
     `updateHighlightColor()` (uiCore.ts) — white for dark accents,
     near-black for light accents (kaleidr-white, kaleidr-yellow). Apps
     that don't dynamically rotate the highlight (studio-ui's static
     green, home-ui's static accent) just inherit the `#ffffff`
     fallback below, which is correct for any reasonably-dark brand
     color. Apps wanting a different default override
     `--highlight-color-on` directly. */
  --auth-highlight-on: var(--highlight-color-on, #ffffff);

  /* `--auth-highlight-text` is the "highlight as TEXT on glass"
     companion. Use it instead of `--auth-highlight` whenever the
     accent color is rendered as TEXT or icon glyph on top of the
     regular modal/dropdown surface (link color, focus-text,
     accent-tinted badges like the auth "Free" tier).

     Why a second variant? `--auth-highlight` is allowed to be the
     raw user pick — including kaleidr-white — because it's also used
     as a BACKGROUND token (primary CTA, focus border, password-meter
     fill) where a literal white *is* what the user wants. But the
     same literal white as TEXT on the white-glass modal would
     disappear. The map-ui host darkens this token for high-luminance
     accents only (see `pickHighlightTextColor` in uiCore.ts); for
     dark accents the two tokens are identical. Apps that don't
     dynamically rotate the accent (studio-ui, home-ui) inherit
     `--auth-highlight` as the fallback below — same behaviour as
     before this token existed. */
  --auth-highlight-text: var(--highlight-color-text, var(--auth-highlight));

  /* ---------- Form colors ----------------------------------------
     --auth-input-bg and --auth-input-border derive from
     --auth-text-primary so they auto-adapt to the host app's theme:
       * light theme (map-ui, studio-ui light) → text is dark,
         border renders as dark-22% (clearly visible on white glass)
       * dark theme (studio-ui dark) → text is light, border renders
         as light-22% (visible on the dark glass surface)
     Previously this was hardcoded rgba(255,255,255,0.12), which was
     effectively invisible on any light-mode background. Host apps
     needing a different contrast ratio can override the token or
     override --auth-text-primary; they must NOT re-declare
     `.auth-field input { border-color: ... }` in their local CSS
     (that's how the map-ui vs studio-ui hover divergence crept in). */
  --auth-input-bg: color-mix(in srgb, var(--auth-text-primary) 4%, transparent);
  --auth-input-border: color-mix(in srgb, var(--auth-text-primary) 22%, transparent);
  --auth-input-border-focus: var(--auth-highlight);
  --auth-error-color: #ff6b6b;
  /* Success uses the read-on-glass variant of the highlight so the
     success banner / valid-password-rule / "Verified" copy stay legible
     even when the user has rotated map-ui's accent slider to a
     near-white pick. For dark accents this resolves to exactly the
     same color as `--auth-highlight`, so no visual change in the
     common case. */
  --auth-success-color: var(--auth-highlight-text, var(--auth-highlight));

  /* ---------- Motion + z-index ----------------------------------- */
  --auth-interactive-transition: var(--interactive-transition-normal, all 0.2s ease);
  --auth-wrapper-z: 10010;
  --auth-modal-z: 1000001;
}

/* =============================================== */
/* SHARED CHROME — used by map-ui & studio-ui      */
/* =============================================== */

@keyframes auth-overlay-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes auth-modal-scale-in {
  from { opacity: 0; transform: scale(0.95); }
  to { opacity: 1; transform: scale(1); }
}

.auth-modal-overlay {
  position: fixed;
  inset: 0;
  /* Scrim tokens live in tokens.css; hosts override via --modal-scrim-*
     so every modal surface (auth, quota, share, …) shares a single
     transparency story. Default falls back to the original dark veil. */
  background: var(--auth-modal-scrim-bg);
  backdrop-filter: var(--auth-modal-scrim-blur);
  -webkit-backdrop-filter: var(--auth-modal-scrim-blur);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--auth-modal-z);
  animation: auth-overlay-fade-in 0.2s ease;
}

.auth-modal {
  position: relative;
  width: var(--auth-modal-width);
  max-width: var(--auth-modal-max-width);
  max-height: var(--auth-modal-max-height);
  /* Card chrome (bg / border / radius / shadow) all read from
     `--auth-modal-*` tokens. Hosts override those tokens to plug
     the auth modal into their own modal-system (map-ui maps them to
     `--modal-card-*`); studio-ui inherits the original auth-core
     look via the fallbacks declared in tokens.css. backdrop-filter
     is kept for the translucent fallback path; it's a no-op when
     the host swaps in an opaque bg. */
  background: var(--auth-modal-bg);
  backdrop-filter: var(--auth-glass-blur);
  -webkit-backdrop-filter: var(--auth-glass-blur);
  border: var(--auth-modal-border);
  border-radius: var(--auth-modal-radius);
  box-shadow: var(--auth-modal-shadow);
  color: var(--auth-text-primary);
  overflow-y: auto;
  overflow-x: hidden;
  animation: auth-modal-scale-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.auth-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 32px 32px 0;
  position: relative;
}

.auth-title {
  font-size: var(--auth-font-title);
  font-weight: 600;
  color: var(--auth-text-primary);
  margin: 0;
  line-height: 1.2;
  text-align: center;
}

.auth-subtitle {
  font-size: var(--auth-font-body);
  color: var(--auth-text-secondary);
  margin: 4px 0 0;
  text-align: center;
}

/*
 * Trust bullets — checkmark list rendered under the "Create Account"
 * heading. Replaces the single-paragraph `auth.signUp.subtitle`
 * phrasing with three scannable reassurance lines. Uses the shared
 * --auth-highlight variable so the checkmark always picks up the
 * host app's accent color (orange in home-ui/map-ui, studio's blue).
 */
.auth-trust-bullets {
  list-style: none;
  padding: 0;
  margin: 8px 0 4px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--auth-font-body);
  color: var(--auth-text-secondary);
  line-height: 1.35;
}
.auth-trust-bullets li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
}
/* The ::before marker is a Unicode check glyph rather than an SVG —
   keeps the auth-core bundle font-only and inherits currentColor so
   RTL locales and any future theme tweaks just work. `flex-shrink: 0`
   guarantees the glyph can't collapse on narrow mobile widths.
   Intentionally +1px over the body token so the glyph reads as
   decisive rather than weedy. */
.auth-trust-bullets li::before {
  content: '✓';
  /* The ✓ glyph sits as text on the modal surface (no bg fill of its
     own). Use the text-on-glass variant of the highlight so it stays
     visible if the user picks a near-white accent — for dark accents
     this resolves identically to --auth-highlight. */
  color: var(--auth-highlight-text);
  font-weight: 700;
  font-size: calc(var(--auth-font-body) + 1px);
  line-height: 1.2;
  flex-shrink: 0;
  width: 16px;
  text-align: center;
}

.auth-body {
  padding: 24px 32px 32px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.auth-close-btn {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.2s, background 0.2s;
  color: var(--auth-text-primary);
  font-size: 18px;
  line-height: 1;
}

.auth-close-btn:hover {
  opacity: 1;
  background: color-mix(in srgb, var(--auth-text-primary) 8%, transparent);
}

/* OAuth (Google) button. Part of the AUTH FORM FIELD design contract
   shared with home-ui — see the invariant table at the top of
   kaleidr-home-ui/src/styles/components.css. When you change
   height/radius/border/hover/focus here, change `.btn-google` there
   (and vice versa). Visually-identical to `.auth-field input` so the
   three stacked rectangles (OAuth button, email input, password
   input) read as one coherent form block. */
.oauth-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  height: 3rem;
  padding: 0 16px;
  border-radius: 0.75rem;
  border: 1px solid var(--auth-input-border);
  background: transparent;
  color: var(--auth-text-primary);
  font-size: var(--auth-font-body);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  user-select: none;
  box-sizing: border-box;
}

.oauth-btn:hover {
  border-color: var(--auth-highlight);
}
.oauth-btn:focus-visible {
  outline: none;
  border-color: var(--auth-highlight);
  box-shadow: 0 0 0 1px var(--auth-highlight);
}
.oauth-btn:active { transform: scale(0.99); }
.oauth-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.oauth-btn svg { flex-shrink: 0; }

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

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.1);
}

.auth-divider span {
  font-size: var(--auth-font-body);
  color: var(--auth-text-secondary);
}

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

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

.auth-field label {
  font-size: var(--auth-font-body);
  font-weight: 500;
  color: var(--auth-text-secondary);
}

/* Header row for a field whose label shares a line with a trailing
   action link — the canonical case is the "Password" label sitting
   next to a right-aligned "Forgot password?" button, mirroring
   home-ui's login-page pattern:
     <div class="flex items-center justify-between mb-1.5">
       <label>Password</label>
       <a>Forgot password?</a>
     </div>
   Used by the sign-in and reset flows in AuthModal.tsx. Without this
   row, the forgot-password button rendered inline inside the <label>
   and collided visually with the label text.
   `width: 100%` is explicit (not relying on flex-child cross-axis
   stretch) so the row fills the field even if a future host app
   wraps `.auth-field` in a non-column flex container. `gap: 16px`
   is the minimum padding — below that, long translations
   (e.g. German "Passwort vergessen?") visually collide with the
   label. */
.auth-field-label-row {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.auth-field-label-row label {
  margin: 0;
}

.auth-field input {
  width: 100%;
  height: 48px;
  padding: 0 16px;
  border-radius: 12px;
  border: 1px solid var(--auth-input-border);
  background: var(--auth-input-bg);
  color: var(--auth-text-primary);
  font-size: var(--auth-font-body);
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  box-sizing: border-box;
}

.auth-field input::placeholder {
  color: var(--auth-text-muted);
}

/* Hover / focus border color is deliberately the accent (primary)
   color, matching home-ui's `.input-field` Tailwind recipe
   (`hover:border-primary focus:border-primary focus:ring-1`).
   The focus state is a 1px solid accent border PLUS a 1px solid
   accent box-shadow ring sitting just outside it — combined, they
   read as a single 2px accent outline with no content-shift (which
   would happen if we bumped `border-width` from 1px to 2px). This
   matches the Tailwind `focus:ring-1 focus:ring-primary` pattern
   home-ui uses on `.input-field`. */
.auth-field input:hover {
  border-color: var(--auth-highlight);
}

.auth-field input:focus {
  border-color: var(--auth-input-border-focus);
  box-shadow: 0 0 0 1px var(--auth-highlight);
}

/* Primary submit button.
   Color strategy: text reads from --auth-highlight-on, the contrast
   color paired with --auth-highlight (= --highlight-color in the host
   app). Earlier versions tied the label to --auth-text-primary, which
   tracks the modal SURFACE (dark on white glass, white on dark glass)
   rather than the BUTTON. That broke for any accent that happened to
   match the surface — most visibly when a map-ui user picked
   kaleidr-black for the accent and the "Sign In" label vanished into
   the now-black button. --auth-highlight-on is computed from the
   accent's relative luminance (uiCore.ts → updateHighlightColor) so
   the label is white on dark accents (green / blue / black / etc.)
   and near-black on light accents (kaleidr-white, kaleidr-yellow).
   Apps that don't rotate the accent (studio-ui's static green,
   home-ui's static brand) inherit the `#ffffff` fallback baked into
   tokens.css. */
.auth-continue-btn {
  width: 100%;
  height: 48px;
  border-radius: 12px;
  border: none;
  background: var(--auth-highlight);
  color: var(--auth-highlight-on);
  font-size: var(--auth-font-cta);
  font-weight: 600;
  cursor: pointer;
  transition: var(--auth-interactive-transition);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.auth-continue-btn:hover { background: var(--auth-highlight-hover); }
.auth-continue-btn:active { transform: scale(0.99); }
.auth-continue-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--auth-highlight);
}

.auth-toggle {
  font-size: var(--auth-font-body);
  color: var(--auth-text-secondary);
  text-align: center;
  margin: 0;
}

/* Inline link / toggle-switch button (e.g. "Sign in", "Sign up",
   "Resend code"). Rendered as a <button> for a11y, but <button>
   elements do NOT inherit font-family / font-size from their parent
   by default — every browser's UA stylesheet sets a button-specific
   font, typically 13.33px Arial. Without the `font: inherit` reset
   the "Sign up" link at the bottom of the sign-in modal rendered
   noticeably smaller than the surrounding "Don't have an account?"
   text, even though .auth-toggle sets --auth-font-body (16px).
   We re-apply `font-weight: 600` after the reset because the single
   shorthand would otherwise wipe it. */
/* Color strategy: matches the "Sign In" modal title (--auth-text-primary)
   rather than the accent color. Same reason as .auth-continue-btn —
   map-ui's user-selectable accent can be near-black, which collapses
   accent-colored links into the dark modal surface. Pairing link color
   with the title guarantees the "Sign up" / "Sign in" toggle is always
   as readable as the heading above it. Font weight 600 + hover
   underline carry the "this is a link" affordance instead of color. */
.auth-link {
  font: inherit;
  font-weight: 600;
  background: none;
  border: none;
  padding: 0;
  color: var(--auth-text-primary);
  cursor: pointer;
  text-decoration: none;
}

.auth-link:hover {
  text-decoration: underline;
}

.auth-error {
  padding: 12px 14px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--auth-error-color) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--auth-error-color) 30%, transparent);
  color: var(--auth-error-color);
  font-size: var(--auth-font-body);
}

.auth-success {
  padding: 12px 14px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--auth-success-color) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--auth-success-color) 30%, transparent);
  color: var(--auth-success-color);
  font-size: var(--auth-font-body);
}

/* Celebratory success card rendered when AuthModal enters
   `mode === 'verified'` (see AuthModal.tsx for the lifecycle).
   The card body is decorative — the reassurance copy ("Email
   verified successfully!") lives in the modal header. Layout is
   intentionally compact + centered so it reads as a beat between
   "code accepted" and "host app takes over". The header X button
   stays available throughout for the manual escape hatch (WCAG
   2.2.1). Auto-dismiss after VERIFIED_AUTOCLOSE_MS is handled by
   the effect on AuthModal.tsx, not by CSS animation. */
.auth-verified {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 20px 0 12px;
}

.auth-verified__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--auth-success-color) 14%, transparent);
  color: var(--auth-success-color);
  font-size: 32px;
  line-height: 1;
  font-weight: 700;
  /* Subtle pop on enter so the user notices the state change. */
  animation: auth-verified-pop 220ms cubic-bezier(0.2, 0.7, 0.2, 1.4);
}

@keyframes auth-verified-pop {
  0%   { transform: scale(0.6); opacity: 0; }
  60%  { transform: scale(1.05); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

.auth-verified__subtitle {
  margin: 0;
  font-size: var(--auth-font-body);
  color: var(--auth-text-secondary, var(--auth-text-primary));
  text-align: center;
}

.auth-verified__spinner {
  width: 22px;
  height: 22px;
  border: 2.5px solid color-mix(in srgb, var(--auth-text-primary) 12%, transparent);
  border-top-color: var(--auth-success-color);
  border-radius: 50%;
  animation: auth-verified-spin 0.7s linear infinite;
}

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

/* Respect `prefers-reduced-motion`: drop both the pop and the
   spin so users with vestibular sensitivities don't get an
   unexpected animation. The card still renders correctly — the
   spinner just becomes a static ring. */
@media (prefers-reduced-motion: reduce) {
  .auth-verified__icon {
    animation: none;
  }
  .auth-verified__spinner {
    animation: none;
  }
}

/* Forgot-password link rendered inline in `.auth-field-label-row`.
   `font: inherit` forces the <button> to inherit the label's 16px
   size (see .auth-link comment above).

   Color: --auth-text-primary (same rationale as .auth-link — pairs
   with modal surface, not with user-picked accent — so a dark
   accent can't make it collapse into the backdrop).

   Positioning: the parent row uses `justify-content: space-between`
   which normally pushes this button to the far right. We ALSO set
   `margin-inline-start: auto` as a belt-and-suspenders fallback —
   some host apps' global button resets set `margin: 0` with higher
   specificity, which would cancel space-between's effect on the last
   flex child. `margin-inline-start: auto` uses the flex container's
   free space to right-align this button regardless. Together these
   two rules mean the button right-aligns under every cascade we've
   seen in map-ui, studio-ui, and home-ui. */
.auth-forgot-inline {
  font: inherit;
  font-weight: 600;
  background: none;
  border: none;
  padding: 0;
  margin-inline-start: auto;
  color: var(--auth-text-primary);
  cursor: pointer;
  white-space: nowrap;
}

.auth-forgot-inline:hover { text-decoration: underline; }

.auth-description {
  font-size: var(--auth-font-body);
  color: var(--auth-text-secondary);
  margin: 0;
}

/* Real-time password-requirement checklist. Each <li> toggles an
   `is-valid` modifier via JSX state as the password meets each rule,
   mirroring home-ui's `data-pw-req` / `is-valid` pattern. */
.auth-pw-reqs {
  list-style: none;
  padding: 0;
  margin: 6px 0 0;
  font-size: var(--auth-font-meta);
  line-height: 1.55;
  color: var(--auth-text-muted);
}

.auth-pw-reqs li {
  display: flex;
  align-items: center;
  gap: 8px;
  transition: color 0.15s ease;
}

.auth-pw-reqs li.is-valid {
  color: var(--auth-success-color);
}

.auth-pw-req-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.45;
  transition: opacity 0.15s ease;
  flex-shrink: 0;
}

.auth-pw-reqs li.is-valid .auth-pw-req-dot {
  opacity: 1;
}

/* T&C / privacy inline links inside .auth-terms-inline. Uses
   --auth-text-primary for the same reason as .auth-link — the
   terms line is rendered in var(--auth-text-muted), so its anchors
   must contrast against the modal backdrop, not against a
   user-picked accent that may itself be dark. Underline on hover
   carries the "this is a link" cue. */
.auth-terms-link {
  color: var(--auth-text-primary);
  text-decoration: none;
  font-weight: 600;
}
.auth-terms-link:hover { text-decoration: underline; }

/* Inline T&C line shown directly under the sign-up submit button.
   Used when the consent model is "clicking submit = acceptance" (home-ui
   pattern, now mirrored by map-ui/studio-ui). Design decision: font-size
   deliberately matches `.auth-toggle` and `.auth-trust-bullets`
   (--auth-font-body) so the three secondary-text blocks inside the
   sign-up modal read at the same weight. Muted color, not muted size
   — the hierarchy is carried by color, not by shrinking the glyphs. */
.auth-terms-inline {
  margin: 10px 0 0;
  font-size: var(--auth-font-body);
  line-height: 1.45;
  text-align: center;
  color: var(--auth-text-muted);
}

/* =============================================== */
/* RESPONSIVE                                        */
/* =============================================== */

/* Short viewports (≤ 720px tall). Sign-up is the densest form
   (email + password + 5-item checklist + inline T&C + OAuth +
   divider + toggle link). We tighten vertical rhythm so everything
   fits in one view. Constraint is height not width — font sizes
   stay at :root tokens. */
@media (max-height: 720px) {
  .auth-header { padding: 20px 32px 0; }
  .auth-body   { padding: 16px 32px 24px; gap: 12px; }
  .auth-form   { gap: 14px; }
  .auth-field input,
  .auth-continue-btn,
  .oauth-btn   { height: 44px; }
  .auth-subtitle { margin-top: 2px; }
  .auth-trust-bullets { margin-top: 4px; gap: 4px; }
}

/* Mobile portrait (≤ 480px). Every auth flow — Sign in, Sign up,
   Forgot password, Verify — becomes a full-viewport page instead
   of a floating card. A 440 px card in a 360 px viewport reads as
   a stuck dialog; native apps make auth the screen, we match.

   Key invariants:
     - Overlay transparent (no scrim, no blur) so the overlay and
       modal don't both paint `--auth-glass-bg` + backdrop-filter
       and produce a visible inset "wrapping box".
     - Modal uses `position: absolute; inset: 0` (not 100vw/100vh);
       `inset: 0` tracks the overlay's real bounds while 100vw
       includes scrollbar width and clips the right edge.
     - Content column caps at 440px, centered, so tablet-portrait
       viewports don't stretch inputs edge-to-edge.
     - Header top-padding 52px + `env(safe-area-inset-top)` clears
       iOS notches; close button at `top: 14px` reads as page chrome.
     - Every secondary-text string (subtitle, trust bullets, T&C,
       toggle, description, divider "or", error/success banners)
       drops 16→14 px to match `.auth-dropdown-email` /
       `.auth-usage-meter-header` — one scale across every
       surface the avatar reveals. Field labels + inputs stay at
       16 px for tap-target legibility; `.auth-pw-reqs` keeps its
       own `--auth-font-meta` token. The ✓ glyph drops 17→15 px so
       it still reads as decisive at the smaller bullet size. */
@media (max-width: 480px) {
  .auth-modal-overlay {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    animation: none;
  }
  .auth-modal {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    animation: none;
  }
  .auth-modal > .auth-header,
  .auth-modal > .auth-body {
    width: 100%;
    max-width: 440px;
    margin-left: auto;
    margin-right: auto;
    box-sizing: border-box;
  }
  .auth-header    { padding: calc(52px + env(safe-area-inset-top, 0px)) 20px 0; }
  .auth-body      { padding: 18px 20px 24px; gap: 14px; }
  .auth-form      { gap: 16px; }
  .auth-title     { font-size: 20px; }
  .auth-close-btn { top: calc(14px + env(safe-area-inset-top, 0px)); }

  .auth-subtitle,
  .auth-trust-bullets,
  .auth-terms-inline,
  .auth-toggle,
  .auth-description,
  .auth-divider span,
  .auth-error,
  .auth-success { font-size: 14px; }
  .auth-trust-bullets li::before { font-size: 15px; }
}

/* =============================================== */
/* ACCOUNT DROPDOWN CHROME                          */
/* =============================================== */

/* Structural rules for <AccountDropdown>'s children. The outer
 * `.auth-dropdown` shell (position, width, border-color, shadow) is
 * owned by the consumer because it depends on where the trigger
 * button sits — map-ui anchors from a vertical toolbar so the menu
 * slides leftward; studio-ui lives in a horizontal header so it
 * slides down. Everything below here is pure chrome and reads from
 * `--auth-*` tokens so consumers only wire tokens, not re-declare
 * 150 lines of nearly-identical rules.
 *
 * Hover/active backgrounds use `color-mix` against
 * `--auth-text-primary` so they auto-adapt to light vs dark surfaces
 * without a dark-mode override — the same pattern already used by
 * `--auth-input-bg` and `--auth-input-border` above. */

.auth-dropdown-header {
  padding: 14px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Identity row — just the name (or email fallback). The tier chip
   that used to sit on the right edge moved into the Billing block's
   "Current Plan" row on 2026-05-10, so the row no longer needs the
   space-between flex layout it was originally built for. The header's
   own `flex-direction: column` (above) handles the name → email
   stack; this rule only carries the ellipsis behaviour for the name
   itself. Type style (size / weight / line-height / color) is
   centralised below in the `.auth-dropdown-name, .auth-dropdown-item,
   .auth-dropdown-billing-title` group rule so the identifier line,
   the menu items, and the Billing section heading all read at one
   canonical scale. */
.auth-dropdown-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.auth-dropdown-tier {
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  padding: 3px 8px;
  border-radius: 6px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
}

/* Tier badge palette.
 *
 * Free + Pro are anchored to the host app's highlight token
 * (`--auth-highlight` — map = kaleidr-green, studio = s-primary,
 * etc.) so paid-tier signaling reads as a brand "you've upgraded"
 * status, not an off-palette blue chip. Differentiation is
 * encoded by *fill weight*, not by hue:
 *
 *   - **Free** — 15 % accent tint on glass, accent-colored text.
 *     Subtle. Says "default tier, no extra status".
 *   - **Pro / paid** — solid accent fill, paired contrast text
 *     (`--auth-highlight-on`, the same token used by primary CTAs
 *     like the Continue / Save / Manage Billing buttons). Bold.
 *     Reads as a premium status marker at a glance.
 *
 * Enterprise + Admin keep their distinct hues (purple / red) — those
 * are intentionally OFF the brand accent so the rare-but-important
 * states stand out from the common Free / Pro path. */
.auth-tier-free       { background: color-mix(in srgb, var(--auth-highlight) 15%, transparent); color: var(--auth-highlight-text); }
.auth-tier-paid,
.auth-tier-pro        { background: var(--auth-highlight);                                       color: var(--auth-highlight-on); }
.auth-tier-business,
.auth-tier-enterprise { background: rgba(156, 71, 200, 0.15);                                    color: #9c47c8; }
.auth-tier-admin      { background: rgba(255, 59, 48, 0.15);                                     color: #ff3b30; }

/* Past-due override: any tier chip flips to the warning palette when
   the subscription's status is `past_due`. Implemented as an
   additional class so the underlying tier-specific rule still applies
   for layout (padding/letter-spacing) and only color/background
   change. Mirrors the §4.4 UX decision to keep past_due signaling
   confined to the dropdown chip + Settings → Billing banner. */
.auth-dropdown-tier.is-past-due {
  background: rgba(220, 53, 69, 0.15);
  color: #dc3545;
}

.auth-dropdown-email {
  font-size: 14px;
  color: var(--auth-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Generic usage-meter primitive — pass any surface a
   `<div class="auth-usage-meter">` and the rest renders. Used by
   `<AccountDropdown>`'s inline billing block (AI Credits + Link
   Share rows) and map-ui's `<MobileAccountPanel>`. The
   `.auth-dropdown-` prefix was intentionally dropped so future
   surfaces (admin panels, embed billing widget) can adopt the
   meter without re-shipping CSS. */
.auth-usage-meter {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.auth-usage-meter-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  color: var(--auth-text-secondary);
}

.auth-usage-meter-pct { font-weight: 500; }

/* Wrapper for the right-hand side of the meter header when both a
   percentage AND an absolute count are shown (AI Credits row).
   Inline-flex so `12% (12K / 500K)` reads as a single visual unit
   rather than wrapping mid-pair on narrow modals. */
.auth-usage-meter-value {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}

/* Absolute count label — used both as the secondary glyph alongside
   the percentage on the AI Credits row (`(12K / 500K)`) and as the
   sole right-hand glyph on the Link Share row
   (`12 / 1,000`). Slightly muted relative to `.auth-usage-meter-pct`
   so the percentage stays the primary read in the token row, while
   the count remains the primary read on the share-opens row through
   the absence of a competing pct. */
.auth-usage-meter-count {
  color: var(--auth-text-secondary);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
}

.auth-usage-meter-track {
  height: 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--auth-text-primary) 8%, transparent);
  overflow: hidden;
}

.auth-usage-meter-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--auth-highlight);
  transition: width 0.4s ease;
  min-width: 2px;
}

.auth-usage-meter-fill.is-critical { background: #ff3b30; }

.auth-dropdown-divider {
  height: 1px;
  background: color-mix(in srgb, var(--auth-text-primary) 10%, transparent);
}

.auth-dropdown-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 14px;
  border: none;
  background: transparent;
  cursor: pointer;
  transition: background 0.15s ease;
  text-align: left;
  font-family: inherit;
  /* Type style (size / weight / line-height / color) is centralised
     in the shared `.auth-dropdown-name, .auth-dropdown-item,
     .auth-dropdown-billing-title` group rule below. */
}

.auth-dropdown-item:hover  { background: color-mix(in srgb, var(--auth-text-primary) 6%, transparent); }
.auth-dropdown-item:active { background: color-mix(in srgb, var(--auth-text-primary) 12%, transparent); }

/* Sign Out is the ONE intentional divergence from the canonical
   dropdown title style — it keeps the same size / weight /
   line-height as the other rows but flips to a warning red so the
   destructive action reads as "exit", not "another menu item". */
.auth-dropdown-signout { color: rgba(220, 53, 69, 0.9); }
.auth-dropdown-signout:hover { background: rgba(220, 53, 69, 0.08); }

/* ------- Canonical "title" type style for the dropdown ------------
   Applied to every top-level row inside `<AccountDropdown>` —
   identifier (display name OR email), navigation items
   ("Manage Account", "Sign Out"), and the inline-billing section
   heading ("Billing") — so the menu reads as one consistent text
   scale top-to-bottom. The four selectors deliberately share size,
   weight, line-height, and color; the only intentional divergence
   is `.auth-dropdown-signout`'s warning color (declared above).
   Keep this rule canonical: any per-row tweak (color, ellipsis,
   margin) goes on the individual rule, never inside this group. */
.auth-dropdown-name,
.auth-dropdown-item,
.auth-dropdown-billing-title {
  font-size: 15px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--auth-text-primary);
}

/* Inline billing block — current plan, period, AI Credits + Link
   Share meters, past-due banner, Stripe upgrade/manage CTA,
   Enterprise contact. Renders below "Manage Account" inside
   `<AccountDropdown>` and is mirrored by map-ui's
   `<MobileAccountPanel>` via the same `.auth-dropdown-billing*`
   chrome. Internals are split into `.auth-settings-billing-*` and
   `.auth-usage-meter*` primitives (defined further down) so any
   future surface (admin panels, embed billing widget) can render
   the same block without re-shipping CSS. */
.auth-dropdown-billing {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px 14px;
}

.auth-dropdown-billing-title {
  margin: 0;
  /* Mixed-case "Billing" — the heading uses the locale string verbatim
     (no `text-transform: uppercase`) so the all-caps tracking rule
     doesn't fight scripts that have no case (Arabic, CJK) and so
     Latin-script locales render exactly as the bundle declares them.

     Type style (size / weight / line-height / color) is centralised
     in the shared `.auth-dropdown-name, .auth-dropdown-item,
     .auth-dropdown-billing-title` group rule above so all four
     dropdown title rows read at one canonical scale. */
}

/* =============================================== */
/* INLINE BILLING BLOCK — internals                 */
/* =============================================== */

/* The dropdown's `.auth-dropdown-billing` wrapper renders these
   internals: plan + period summary row, stacked usage meters, and
   a (read-only) past-due banner. Class names keep their
   `auth-settings-*` prefix from the (now-retired)
   `<AccountSettingsModal>` so locale bundles and any external CSS
   overrides don't churn.

   The Upgrade / Manage / Enterprise affordances were moved to
   home-ui's `/settings` page on 2026-05-10 — see
   `<AccountDropdown>` for the centralisation rationale. The
   matching `.auth-dropdown-billing-cta`, `.auth-settings-past-due-
   action`, and `.auth-settings-enterprise` selectors were removed
   from this file then. */

.auth-settings-billing-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* `nowrap` on the label is load-bearing — the dropdown column is
   340 px wide, so a label like "Current Period" that's 1 px wider
   than the available column would fall to a second line and orphan
   the date range. Pinning the label shifts any squeeze onto the
   value side, which has plenty of room.

   `font-size: 14px` matches `.auth-usage-meter-header` (the AI
   Credits / Link Share row labels) so every label inside the inline
   billing block reads at one consistent size. */
.auth-settings-billing-label {
  font-size: 14px;
  color: var(--auth-text-secondary);
  white-space: nowrap;
}

/* Plan + period header above the usage meters. `gap: 6px` keeps the
   two rows visually paired so users read them as a single
   "what plan + when does it reset" block, distinct from the meters
   below. */
.auth-settings-billing-summary {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Period range. Tabular numerals so "May 1" / "Jun 11" don't jitter
   widths across renders; a tiny font-size step down so the row
   reads as secondary metadata to the plan badge (which IS the
   headline). `nowrap` keeps "May 1, 2026 – Jun 1, 2026" on one
   line. `text-align: right` matches the right-aligned value
   convention of the plan row above. */
.auth-settings-billing-period-range {
  font-size: 13px;
  color: var(--auth-text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}

/* Stacked usage meters below the plan + period summary. Wraps one
   or more `.auth-usage-meter` blocks (AI Credits, Link Share);
   rendered only when the consumer supplies the matching usage
   data so studio-ui (no share-opens) and admin / unlimited tiers
   collapse to nothing rather than showing an empty container. */
.auth-settings-usage-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Past-due banner — high-contrast red surface. §4.4 keeps past_due
   alerting scoped to the dropdown chip + this banner (no
   top-of-canvas alert), so this is the most prominent signal a
   paying customer sees about a failed payment in-app. The
   "Update payment method" affordance lives on home-ui's /settings
   page now (alongside the rest of the billing CTAs); the banner
   here is read-only. */
.auth-settings-past-due {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(220, 53, 69, 0.08);
  border: 1px solid rgba(220, 53, 69, 0.25);
  color: #b02a37;
}

.auth-settings-past-due strong {
  font-size: 14px;
  font-weight: 600;
}

.auth-settings-past-due p {
  margin: 0;
  font-size: var(--auth-font-body);
  color: #b02a37;
  line-height: 1.4;
}

/* =============================================== */
/* AVATAR INITIALS + BUTTON SPINNER                 */
/* =============================================== */

.auth-avatar-initials {
  font-size: 13px;
  font-weight: 600;
  color: var(--auth-text-primary);
  text-transform: uppercase;
  line-height: 1;
  letter-spacing: 0.5px;
}

.auth-button-spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid color-mix(in srgb, var(--auth-text-primary) 18%, transparent);
  border-top-color: var(--auth-text-primary);
  border-radius: 50%;
  animation: auth-spin 0.6s linear infinite;
}

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

/* =============================================== */
/* HOME-UI HOOKS — password-requirement checklist   */
/* =============================================== */

/* home-ui's signup.html and settings.html (Change Password section)
 * render the checklist with Tailwind utilities plus these two
 * structural hooks. Each <li data-pw-req="rule"> has its `is-valid`
 * class toggled by `initPwReqsChecklist` in
 * `kaleidr-home-ui/src/scripts/auth.js` as the typed password
 * satisfies each rule, mirroring AuthModal's
 * `.auth-pw-reqs li.is-valid` state. Centralized here so every
 * password-entry surface across the apps shares one valid-state
 * color token (--auth-success-color). */
[data-pw-req] .pw-req-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: currentColor;
  opacity: 0.5;
  flex-shrink: 0;
}

[data-pw-req].is-valid {
  color: var(--auth-success-color);
}

[data-pw-req].is-valid .pw-req-dot {
  opacity: 1;
  background: var(--auth-success-color);
}

/* ============================================================================
   Stripe billing landing pages (BillingCancel + BillingSuccess)
   ============================================================================
   Standalone, full-viewport layout (NOT nested under any app shell)
   because Stripe redirects sometimes happen mid-session and we don't
   want the host app's chrome flashing in/out around an "are you
   upgraded yet?" poll loop. Both apps' landing pages render through
   the shared <BillingCancel /> + <BillingSuccess /> components and
   pull these classes from this stylesheet — no per-app billing.css.
   ============================================================================ */

.billing-landing {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--auth-surface, #fff);
  color: var(--auth-text-primary, #111);
}

.billing-landing__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  max-width: 480px;
  text-align: center;
  padding: 40px 32px;
  border-radius: 16px;
  background: var(--auth-surface-elevated, #fff);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
}

.billing-landing__card h1 {
  font-size: 22px;
  font-weight: 600;
  margin: 0;
  line-height: 1.3;
}

.billing-landing__card p {
  font-size: 15px;
  line-height: 1.5;
  color: var(--auth-text-secondary, #666);
  margin: 0;
}

.billing-landing__card .billing-continue-btn {
  margin-top: 8px;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-width: 200px;
  height: 44px;
  padding: 0 20px;
  border-radius: 10px;
  background: var(--highlight-color, var(--auth-highlight, #2e85fa));
  color: #fff;
  font-weight: 500;
  border: none;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.billing-landing__card .billing-continue-btn:hover {
  opacity: 0.9;
}

.billing-landing__spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--auth-input-border, #e5e5e5);
  border-top-color: var(--auth-highlight, #2e85fa);
  border-radius: 50%;
  animation: billing-spinner 0.7s linear infinite;
  margin-top: 8px;
}

@keyframes billing-spinner {
  to {
    transform: rotate(360deg);
  }
}
