/*!
 * @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
 *
 *   Dialog footers  (home-ui popups, editor-ui's confirm dialog)
 *     .auth-action, .auth-action--primary / --secondary / --destructive
 *
 *   <AccountDropdown>  (signed-in avatar menu — identity +
 *                       personalization + Private Usage + Settings +
 *                       Billing + Sign Out)
 *     .auth-dropdown-header, .auth-dropdown-name   (identity row —
 *                                                   name only, the
 *                                                   tier chip moved
 *                                                   into the Billing
 *                                                   block, see below)
 *     .auth-perso*, .auth-accent-dot,
 *     .auth-lang-option                            (Background / Color /
 *                                                   Language controls —
 *                                                   see the
 *                                                   PERSONALIZATION block)
 *     .auth-dropdown-usage*                         (Private Usage block —
 *                                                    the AI-credit pool and
 *                                                    its per-product share)
 *     .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
 *
 *   Satellite account chrome  (studio / analytics / template store)
 *     .auth-button-wrapper, .auth-nav-user-btn, .auth-nav-user-icon,
 *     and the .auth-dropdown shell under the wrapper
 *
 *     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.  */
  /* Glass default is deliberately translucent (0.86) so the blur(12px)
     reads as real glass rather than a near-opaque slab — hosts that
     want opaque cards opt in via --modal-card-bg (map-ui does).
     This is NOT the card: the card is --auth-modal-bg below. The only
     thing this paints inside auth-core is the selected accent-dot's
     ring (auth.css), drawn in the surface colour so the swatch reads
     as a gap; a white default would make that the hard outline the
     rule there exists to avoid. It is also part of the published
     token surface, so consumers may paint their own chrome with it. */
  --auth-glass-bg: var(--glass-bg, rgba(15, 17, 21, 0.86));
  --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. Default border is a 1px hairline derived from the text
     color so it adapts to light and dark surfaces alike. 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, 1px 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` 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. That is what this
     token exists to stop, so its default is fully OPAQUE.

     The default is the opaque form of the historical dark-glass
     recipe: same hue, alpha dropped to 1. It must stay dark, because
     `--auth-text-primary` below defaults to white — a light default
     here would be white-on-white for any host that sets neither
     token. That is the whole reason, and it is sufficient: the card's
     border reads `--auth-modal-border` -> `--modal-card-border` ->
     `--auth-input-border`, which is a `color-mix` over
     `--auth-text-primary`, so it tracks the text and inverts with the
     surface rather than being a fixed white that could disappear.

     Do NOT reach for `--ui-bg-solid` here. It is a HOST-private name
     (map-ui, studio-ui), undefined in this package and in analytics-
     ui and template-ui, so naming it directly bottoms the chain out
     at its literal fallback on exactly the hosts it was meant to
     help. Theme-tracking is the host's opt-in: set `--modal-card-bg`
     to whatever opaque, theme-aware surface that host owns (map-ui
     maps it to `--ui-bg-solid`, home-ui to `--color-dark`). */
  --auth-modal-bg: var(--modal-card-bg, rgb(15 17 21));

  /* ---------- 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 */
  --auth-font-caption: 12px; /* usage breakdown: product labels, their
                                meters, the period line */

  /* ---------- 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);
  /* Hover strengthens the NEUTRAL border instead of jumping to the
     accent — the accent is reserved for :focus-visible so a form full
     of idle fields never lights up brand-colored on mere pointer
     travel. */
  --auth-input-border-hover: color-mix(in srgb, var(--auth-text-primary) 36%, transparent);
  --auth-input-border-focus: var(--auth-highlight);
  --auth-error-color: #ff3b30; /* kaleidr red — matches every UI's error convention */
  /* Destructive action (`.auth-action--destructive`): Tailwind red-500. */
  --auth-destructive: oklch(63.7% 0.237 25.331);
  /* 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-*`); a host that opts into none gets the opaque
     dark default declared in tokens.css. backdrop-filter is kept
     because `--auth-glass-blur` is a host-overridable token and the
     card is only opaque by DEFAULT — a host that points
     `--modal-card-bg` at a translucent surface still gets glass. */
  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;
}

/* Hover stays NEUTRAL (stronger hairline); the accent is reserved for
   :focus-visible — matches the `.auth-field input` treatment below so
   the whole form block shares one idle/hover/focus story. */
.oauth-btn:hover {
  border-color: var(--auth-input-border-hover);
}
.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);
}

/* Idle + hover borders are NEUTRAL (text-derived hairline; hover just
   strengthens it). The accent appears ONLY on :focus-visible: a 1px
   solid accent border PLUS a 1px accent box-shadow ring just outside
   it — combined they read as a single 2px accent outline with no
   content-shift (which bumping `border-width` to 2px would cause).
   Text inputs match :focus-visible on every focus (pointer or
   keyboard), so this is the same coverage `:focus` gave, minus the
   accent leaking onto hover. */
.auth-field input:hover {
  border-color: var(--auth-input-border-hover);
}

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

/* Browser autofill paints its own white/yellow background + text over the
   input. Repaint with the field's own surface + text colour so an
   autofilled email/password still reads as a normal field on the glass
   modal (home-ui does the same on `.input-field`). The long transition
   defeats Chrome's autofill background fade-in. */
.auth-field input:-webkit-autofill,
.auth-field input:-webkit-autofill:hover,
.auth-field input:-webkit-autofill:focus,
.auth-field input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px var(--auth-input-bg) inset;
  -webkit-text-fill-color: var(--auth-text-primary);
  caret-color: var(--auth-text-primary);
  transition: background-color 9999s ease-in-out 0s;
}

/* 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);
  /* Explicit near-white fallback at the USAGE site, not only in
     tokens.css: a consumer that loads this chrome with a stale or
     remapped token set (template-ui's vendored copy once bled an
     amber host token into the label) still renders a white label on
     the accent fill. Hosts that rotate the accent to a light color
     override --highlight-color-on from luminance (map-ui). */
  color: var(--auth-highlight-on, #ffffff);
  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);
}

/* Dialog footer buttons — Cancel and the action it guards, side by side.
   The one family for every confirm on every app: home-ui's popups (Delete
   Account, the API key confirms) and editor-ui's confirm dialog (Move to
   Trash, Delete, unsaved changes) wear these classes instead of styling
   buttons of their own. Metrics are the auth field's, so a footer lines up
   with the Sign In submit; only the color role differs. The base sets no
   fill or ink (its border is transparent), so a host's own role class
   composes with it in any order. */
.auth-action {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  height: var(--auth-field-height);
  border: 1px solid transparent;
  padding: 0;
  border-radius: var(--auth-field-radius);
  font: inherit;
  font-size: var(--auth-font-cta);
  font-weight: 600;
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
/* A transparent outline, not none: forced-colors mode drops box-shadow
   and paints the outline instead. */
.auth-action:focus-visible {
  outline: 2px solid transparent;
  box-shadow:
    0 0 0 2px var(--auth-modal-bg),
    0 0 0 4px var(--auth-highlight);
}
.auth-action:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.auth-action--primary {
  background: var(--auth-highlight);
  color: var(--auth-highlight-on);
}
.auth-action--primary:hover:not(:disabled) {
  background: var(--auth-highlight-hover);
}

/* Neutral outline. A host with its own outline system feeds the border
   through `--control-border` / `--control-edge` on the element (home-ui's
   conic ring fades that edge while its sweep runs, and the sweep is its
   hover, so the hover rule resolves to the same edge there); elsewhere it
   is the auth field's hairline. */
.auth-action--secondary {
  color: var(--auth-text-primary);
  background: transparent;
  border: var(--control-border, 1px) solid var(--control-edge, var(--auth-input-border));
}
.auth-action--secondary:hover:not(:disabled) {
  border-color: var(--control-edge, var(--auth-input-border-hover));
}

/* The label pulls a fifth of the way toward the surface's ink: on dark
   chrome that is Tailwind's red-400, on light chrome a red dark enough to
   read on its own tint, which red-400 is not (2.2:1 on a white card). */
.auth-action--destructive {
  color: color-mix(in oklab, var(--auth-destructive) 80%, var(--auth-text-primary));
  background: color-mix(in oklab, var(--auth-destructive) 20%, transparent);
  border-color: color-mix(in oklab, var(--auth-destructive) 30%, transparent);
}
.auth-action--destructive:hover:not(:disabled) {
  background: color-mix(in oklab, var(--auth-destructive) 30%, transparent);
}

.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;
}

/* Error convention (platform-wide): kaleidr-red BORDER only — transparent
   body, message in the surrounding surface's own ink. The border is the only
   channel carrying severity, and `--auth-modal-bg` is host-overridable with
   `backdrop-filter` in play, so 1.5px buys perceptual weight on translucent
   glass over map tiles. Thickness does NOT enter the contrast ratio — light
   and dark hosts both clear 3:1 on colour alone; a mid-luminance glass
   backdrop is the case that would need a different red, not more pixels. */
.auth-error {
  padding: 12px 14px;
  border-radius: 10px;
  background: transparent;
  border: 1.5px solid var(--auth-error-color);
  color: inherit;
  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 {
  position: relative;
  padding: 14px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Phone-only close button. Hosts open this menu as a full-width panel on
   phones, where click-outside — the desktop dismissal — has almost no
   outside left to tap. Desktop keeps the click-outside-only header. */
.auth-dropdown-close {
  display: none;
  position: absolute;
  top: 6px;
  right: 6px;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 10px;
  background: none;
  color: var(--auth-text-secondary);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.auth-dropdown-close:hover,
.auth-dropdown-close:focus-visible {
  background: var(--auth-input-bg);
  color: var(--auth-text-primary);
}
@media (max-width: 768px) {
  .auth-dropdown-close {
    display: flex;
  }
  /* Keep the name clear of the button. */
  .auth-dropdown-header {
    padding-right: 52px;
  }
}

/* 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;
}

/* The tier chip ("PRO") in the Billing block. */
.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;
}

/* Tier is a status token, so it shouts. */
.auth-dropdown-tier { 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;
}

/* =============================================== */
/* PERSONALIZATION — Background / Color / Language  */
/* =============================================== */

/* The three appearance controls at the bottom of the identity block.
   Ported from home-ui's `personalization-menu.html` (2026-07-28) so
   every satellite offers the same picks; the underlying preference
   (`setAppearance`) was already shared, only the UI was home-ui-only.
   Colors read from `--auth-*` tokens rather than home-ui's Tailwind
   theme vars, so the row adapts to each host's surface the way the
   rest of the dropdown does. */

.auth-perso {
  margin-top: 6px;
}

.auth-perso-quickrow {
  display: flex;
  align-items: flex-start;
  gap: 4px;
}

/* One icon-over-label tile serves both rows: the Background / Color /
   Language quick-row buttons AND the Background preset options inside
   the palette (they were pixel-identical, so they share the class
   rather than duplicating the chrome). `.is-active` marks the current
   Background preset. */
.auth-perso-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 4px;
  border: none;
  border-radius: 12px;
  background: transparent;
  color: var(--auth-text-secondary);
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.auth-perso-btn:hover,
.auth-perso-btn:focus-visible {
  background: color-mix(in srgb, var(--auth-text-primary) 6%, transparent);
  color: var(--auth-text-primary);
}

.auth-perso-btn.is-active { color: var(--auth-highlight-text); }
.auth-perso-btn.is-active .auth-perso-icon { border-color: var(--auth-highlight); }
.auth-perso-btn.is-active .auth-perso-text { font-weight: 600; }

.auth-perso-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--auth-input-border);
  background: var(--auth-input-bg);
  /* The glyph carries the brand accent while the label stays neutral —
     the same emphasis split home-ui uses, and it makes the active
     accent visible before the Color palette is even opened. */
  color: var(--auth-highlight-text);
}

.auth-perso-icon svg {
  width: 18px;
  height: 18px;
}

/* Language has no glyph — it shows the active locale's 1–2 character
   badge (EN / 한 / 简), so the "icon" slot renders type instead. */
.auth-perso-langcode {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.5px;
}

.auth-perso-text {
  font-size: 12px;
  line-height: 1.2;
}

/* Each palette spans the full menu width beneath the quick row —
   no border or distinct fill, so it reads as an extension of the
   menu rather than a nested popover. */
.auth-perso-palette {
  margin-top: 4px;
}

.auth-perso-palette--bg {
  display: flex;
  align-items: flex-start;
  gap: 4px;
}

/* All seven swatches on ONE row. The 340px dropdown leaves ~312px of
   content width, so 7 × 30px + gaps fits without wrapping. */
.auth-perso-palette--swatches {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  padding: 6px 2px;
}

/* ~4 languages tall, then scroll — a 10-entry list would otherwise
   push Settings / Billing / Sign Out below the fold. */
.auth-perso-palette--list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 9.5rem;
  overflow-y: auto;
}

.auth-accent-dot {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.auth-accent-dot:hover,
.auth-accent-dot:focus-visible {
  transform: scale(1.15);
}
.auth-accent-dot:focus-visible {
  box-shadow: 0 0 0 2px var(--auth-glass-bg), 0 0 0 4px var(--auth-text-secondary);
}

/* The selected ring is drawn in the SURFACE colour, not a hard
   outline — the swatch reads as a clean gap plus shadow on both the
   light-glass (map) and dark-glass (studio) menus. */
.auth-accent-dot.is-active {
  border-color: var(--auth-glass-bg);
  box-shadow: 0 0 0 2px var(--auth-highlight);
}
.auth-accent-dot.is-active:focus-visible {
  box-shadow: 0 0 0 2px var(--auth-highlight), 0 0 0 4px var(--auth-text-secondary);
}

.auth-lang-option {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--auth-text-primary);
  font-family: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}

.auth-lang-option:hover,
.auth-lang-option:focus-visible {
  background: color-mix(in srgb, var(--auth-text-primary) 6%, transparent);
}

.auth-lang-option.is-active {
  color: var(--auth-highlight-text);
  font-weight: 600;
}

/* 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:is(:hover, :focus-visible) { 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); }

/* Leading glyph on Settings / Billing / Sign Out. Fixed-width so the
   three labels align on one text column regardless of glyph width,
   and muted relative to the label so the icon reads as a marker, not
   a second focal point. `currentColor` lets Sign Out's warning red
   carry through to its icon without a second rule. */
.auth-dropdown-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 16px;
  color: var(--auth-text-secondary);
}

.auth-dropdown-item-icon svg { width: 14px; height: 14px; }

.auth-dropdown-signout .auth-dropdown-item-icon { color: inherit; }

/* 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:is(:hover, :focus-visible) { 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,
.auth-dropdown-usage-title {
  font-size: 15px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--auth-text-primary);
}

/* Private Usage — the account's AI-credit pool (`renderUsage`), the same
   block on every surface: the pool total, its period, then each product's
   share. Narrower than home-ui's `/billing` card (no plan chip), so it stays
   a status glance rather than a second billing surface. Rows render through
   the shared `.auth-usage-meter*` primitives declared above. */
.auth-dropdown-usage {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px 14px;
}

/* Capped to the window so Sign Out is always reachable: the usage block
   scrolls and the rows below it stay in view. `--auth-dropdown-top` is the
   room the host's shell leaves above the menu. */
.auth-dropdown {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - var(--auth-dropdown-top, 76px));
  max-height: calc(100dvh - var(--auth-dropdown-top, 76px));
}

/* Keyboard focus inside the menu reads as each control's hover state (rules
   above), never the browser's black ring. A transparent outline, not none:
   forced-colors mode drops the tints and paints the outline instead. */
.auth-dropdown :focus-visible {
  outline: 2px solid transparent;
  outline-offset: -2px;
}
/* The usage block scrolls, so a browser may make it a tab stop. */
.auth-dropdown-usage:focus-visible {
  background: color-mix(in srgb, var(--auth-text-primary) 4%, transparent);
}

/* ---- Satellite account chrome (studio / analytics / template store) ----
   The round avatar trigger and the menu shell below it, styled once here
   instead of in each host. Hosts place `.auth-button-wrapper` with the menu as
   its direct child; map-ui and
   home-ui keep their own toolbar / header chrome and do not use these
   classes. Surface and shadows read the satellite shell's `--s-soft-*` /
   `--s-elev-*` tokens (frosted over a brand photo) and fall back to an opaque
   card with a plain drop shadow. */
.auth-button-wrapper {
  position: relative;
  display: inline-flex;
}

/* A ring in the UI highlight color on the UI background. */
.auth-nav-user-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  /* The satellite shell's control size, so it matches the rail toggle. */
  width: var(--s-control-size, 34px);
  height: var(--s-control-size, 34px);
  padding: 0;
  border: 3px solid var(--auth-highlight);
  border-radius: 50%;
  background: var(--auth-modal-bg);
  color: var(--auth-highlight-text);
  box-shadow: var(--s-elev-raised-sm, 0 2px 8px rgba(0, 0, 0, 0.12));
  cursor: pointer;
  transition: box-shadow 0.15s ease;
}
.auth-nav-user-btn[aria-expanded="true"] {
  box-shadow: var(--s-elev-pressed, inset 0 2px 6px rgba(0, 0, 0, 0.2));
}
.auth-nav-user-icon {
  display: block;
  width: 20px;
  height: 20px;
}

.auth-button-wrapper > .auth-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: var(--auth-modal-z);
  width: 340px;
  overflow: hidden;
  border-radius: 20px;
  background: var(--s-soft-bg, var(--auth-modal-bg));
  -webkit-backdrop-filter: var(--s-soft-blur, none);
  backdrop-filter: var(--s-soft-blur, none);
  box-shadow: var(--s-elev-raised, 0 8px 32px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.08));
  user-select: none;
  animation: auth-modal-scale-in 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (prefers-reduced-motion: reduce) {
  .auth-button-wrapper > .auth-dropdown {
    animation: auth-overlay-fade-in 0.15s ease;
  }
}

.auth-dropdown .auth-dropdown-usage {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.auth-dropdown-usage-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.auth-dropdown-usage-title { margin: 0; }

/* The React menu's mount point for the shared `renderUsage` nodes: it takes
   no box, so the divider and block sit in the menu as their own children. */
.auth-dropdown-usage-slot { display: contents; }

.auth-dropdown-usage-period {
  margin: 0;
  font-size: var(--auth-font-caption);
  color: var(--auth-text-muted);
}

/* Per-product breakdown beneath the pool total. The rule
   is separated + indented so it reads as detail BELONGING TO the
   meters above rather than as more allowance alongside them — the
   plan grants one AI-credits pool that every product draws from, and
   `\billing` spends a paragraph saying so. This dropdown has no room
   for that paragraph, so the indent and the muted label carry the
   same message structurally. Don't flatten it back to a single list. */
.auth-dropdown-usage-breakdown {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 10px;
  padding-left: 10px;
  border-top: 1px solid color-mix(in srgb, var(--auth-text-primary) 10%, transparent);
}

.auth-dropdown-usage-product-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* A product's meters sit one step in from its label and read at the label's
   size: the group is one unit, below the pool total's scale. */
.auth-dropdown-usage-product-group .auth-usage-meter {
  padding-left: 10px;
}

.auth-dropdown-usage-product-group .auth-usage-meter-header,
.auth-dropdown-usage-product-group .auth-usage-meter-count {
  font-size: var(--auth-font-caption);
}

.auth-dropdown-usage-product-label {
  margin: 0;
  font-size: var(--auth-font-caption);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.3px;
  color: var(--auth-text-muted);
}

/* 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;
}

/* Spinner variant inside the accent-filled primary CTA: track from
   currentColor (the CTA label token) instead of the surface text
   token, so it stays visible on the accent fill in both themes. */
.auth-continue-btn .auth-button-spinner {
  border-color: color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
}

@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);
  }
}
