/* Auth UI overlay — independent of game styles, scoped via .ages-auth-* */

.ages-auth-root,
.ages-auth-root * {
  box-sizing: border-box;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
}

.ages-auth-root {
  --ages-bg: rgba(20, 20, 28, 0.92);
  --ages-fg: #f4f4f8;
  --ages-muted: #9aa0b4;
  --ages-accent: #d4a25a;
  --ages-error: #ff7373;
  --ages-input-bg: #2a2c3b;
  --ages-input-border: #3b3e52;
  --ages-btn-bg: #3a3d52;
  --ages-btn-hover: #4a4e69;
  --ages-radius: 6px;
  color: var(--ages-fg);
}

.ages-auth-hidden {
  display: none !important;
}

#ages-auth-header {
  position: fixed;
  /* S62 (Trello #50): the panel is now collapsed into a right-edge trigger on
     EVERY platform (was mobile-only in S26/S27). Its position + the slide/fade
     collapse are owned entirely by the `#ages-auth-header` collapse rule
     further down (formerly gated behind the L-004 touch media query, now
     unconditional). The S48 desktop `top:15vh` anchor is therefore retired: a
     collapsed panel no longer parks over the top-right gear (QA #47), and when
     expanded it opens from the trigger at vertical centre (top:50%) — far
     below the gear's ~12.5vh band. Only the shared visual props (background,
     radius, base gap/padding) live here; the collapse rule overrides
     top/right/transform/opacity/pointer-events on all platforms. */
  z-index: 9998;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 10px;
  background: var(--ages-bg);
  border-radius: var(--ages-radius);
  font-size: 13px;
  pointer-events: auto;
}

/* S26/S62: the inline × close button inside the expanded auth panel. Hidden
   by default; the collapse rule below reveals it (display:inline-block) on
   ALL platforms now that the panel collapses on desktop too (S62). */
.ages-auth-header-close {
  display: none;
  background: transparent;
  border: 0;
  color: var(--ages-muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  margin-right: -2px;
}
.ages-auth-header-close:hover {
  color: var(--ages-fg);
}

/* S26/S62: collapsible trigger icon. Hidden by default; the collapse rule
   below reveals it AND hides the full panel — now on EVERY platform (S62:
   Trello #50 extends the S26 mobile collapse to desktop). Pinned to the
   right-edge centre, 44×44 tap area (WCAG ≥44px, WI-028) with the visual
   half-disc inside via padding. */
#ages-auth-trigger {
  display: none;
}

.ages-auth-locales {
  display: flex;
  gap: 4px;
}

.ages-auth-locale-btn {
  background: transparent;
  border: 0;
  color: var(--ages-muted);
  padding: 2px 6px;
  cursor: pointer;
  font-size: 13px;
  letter-spacing: 0.05em;
  font-weight: 600;
}

.ages-auth-locale-btn:hover {
  color: var(--ages-fg);
}

.ages-auth-locale-btn.ages-auth-active {
  color: var(--ages-accent);
  text-decoration: underline;
}

.ages-auth-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ages-auth-user-email {
  color: var(--ages-fg);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ages-auth-primary-btn {
  background: var(--ages-btn-bg);
  color: var(--ages-fg);
  border: 0;
  border-radius: var(--ages-radius);
  padding: 5px 12px;
  cursor: pointer;
  font-size: 13px;
}

.ages-auth-primary-btn:hover {
  background: var(--ages-btn-hover);
}

/* ---------- Modal ---------- */

#ages-auth-modal-root {
  position: fixed;
  inset: 0;
  z-index: 9999;
}

.ages-auth-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  pointer-events: auto;
}

.ages-auth-modal {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--ages-bg);
  border-radius: var(--ages-radius);
  padding: 22px 22px 18px;
  width: min(360px, calc(100vw - 24px));
  /* S21: cap height to the *visual* viewport (dvh) so the modal does not get
   * clipped by the mobile Chrome URL bar in landscape (where layout height
   * 100vh > visible height). vh kept as fallback for older engines that
   * ignore dvh; the later rule wins where supported. overflow-y:auto adds
   * internal scrolling when any of the 4 views (sign-in/sign-up/verify/
   * reset) plus the social block overflow the cap on short viewports. */
  max-height: calc(100vh - 24px);
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
  overflow-x: hidden;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
  outline: 0;
}

/* S21: mobile-landscape — tighten vertical padding so the modal fits the
 * shallow viewport (~360-540px) before scrolling kicks in. Keeps the
 * "Sign in with Google" button visible without forcing a scroll on the
 * common sign-in / sign-up flows. */
@media (orientation: landscape) and (max-height: 600px) {
  .ages-auth-modal {
    padding: 12px 18px 10px;
  }
  .ages-auth-title {
    margin-bottom: 8px;
    font-size: 16px;
  }
  .ages-auth-tabs {
    margin-bottom: 8px;
  }
  .ages-auth-form {
    gap: 6px;
  }
  .ages-auth-social {
    margin-top: 8px;
  }
}

.ages-auth-close {
  position: absolute;
  top: 6px;
  right: 8px;
  background: transparent;
  border: 0;
  color: var(--ages-muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}

.ages-auth-close:hover {
  color: var(--ages-fg);
}

.ages-auth-title {
  margin: 0 0 14px;
  font-size: 18px;
  font-weight: 600;
  color: var(--ages-fg);
}

.ages-auth-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--ages-input-border);
}

.ages-auth-tab {
  background: transparent;
  border: 0;
  color: var(--ages-muted);
  padding: 6px 10px 8px;
  cursor: pointer;
  font-size: 13px;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.ages-auth-tab:hover {
  color: var(--ages-fg);
}

.ages-auth-tab.ages-auth-active {
  color: var(--ages-fg);
  border-bottom-color: var(--ages-accent);
}

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

.ages-auth-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
}

.ages-auth-label {
  color: var(--ages-muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.ages-auth-field input {
  background: var(--ages-input-bg);
  color: var(--ages-fg);
  border: 1px solid var(--ages-input-border);
  border-radius: var(--ages-radius);
  padding: 8px 10px;
  font-size: 14px;
  outline: 0;
}

.ages-auth-field input:focus {
  border-color: var(--ages-accent);
}

/* S52 / Trello #32: password field with an inline show/hide eye toggle. The
   wrapper is relative so the button pins to the input's right edge; the input
   gets right padding so its text never slides under the icon. */
.ages-auth-input-wrap {
  position: relative;
  display: block;
}

.ages-auth-input-wrap input {
  width: 100%;
  padding-right: 40px;
}

.ages-auth-eye {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--ages-muted);
}

.ages-auth-eye:hover {
  color: var(--ages-fg);
}

.ages-auth-eye svg {
  width: 18px;
  height: 18px;
  display: block;
}

.ages-auth-error {
  color: var(--ages-error);
  font-size: 13px;
  min-height: 1em;
}

.ages-auth-submit {
  background: var(--ages-accent);
  color: #1a1a22;
  border: 0;
  border-radius: var(--ages-radius);
  padding: 9px 12px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  margin-top: 4px;
}

.ages-auth-submit:hover {
  filter: brightness(1.05);
}

.ages-auth-submit:disabled {
  cursor: progress;
  opacity: 0.7;
}

/* S61 / Trello #38: empty-required-fields disabled state. Distinct muted grey
   (not the progress cursor of a mid-flight submit). Two classes match the
   :disabled rule's specificity, and this rule comes later in source order, so
   it wins where both apply (the button carries both `disabled` and this class
   while a required field is blank). */
.ages-auth-submit.ages-auth-submit-disabled {
  background: #55555f;
  color: var(--ages-muted);
  cursor: default;
  opacity: 1;
}

.ages-auth-submit.ages-auth-submit-disabled:hover {
  filter: none;
}

.ages-auth-switch {
  background: transparent;
  border: 0;
  color: var(--ages-muted);
  cursor: pointer;
  font-size: 12px;
  padding: 4px 0;
  text-decoration: underline;
  align-self: center;
}

.ages-auth-switch:hover {
  color: var(--ages-fg);
}

/* ---------- Social OAuth (S9 / ADR-017) ---------- */

.ages-auth-social {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

.ages-auth-divider {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--ages-muted);
  font-size: 12px;
  margin: 2px 0 6px;
}

.ages-auth-divider::before,
.ages-auth-divider::after {
  content: "";
  flex: 1;
  border-bottom: 1px solid var(--ages-border, rgba(255, 255, 255, 0.12));
}

.ages-auth-divider-label {
  padding: 0 10px;
}

.ages-auth-social-btn {
  background: transparent;
  color: var(--ages-fg);
  border: 1px solid var(--ages-border, rgba(255, 255, 255, 0.2));
  border-radius: var(--ages-radius);
  padding: 9px 12px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
}

.ages-auth-social-btn:hover {
  filter: brightness(1.1);
}

.ages-auth-social-btn:disabled {
  cursor: progress;
  opacity: 0.7;
}

/* ---------- Email verification (S10 / ADR-018) ---------- */

.ages-auth-verify {
  margin-top: 4px;
}

.ages-auth-verify-sent {
  margin: 0 0 12px;
  color: var(--ages-muted);
  font-size: 13px;
  line-height: 1.4;
}

.ages-auth-verify input[name="otp"] {
  letter-spacing: 0.3em;
  text-align: center;
  font-size: 18px;
}

.ages-auth-verify-resend:disabled {
  color: var(--ages-muted);
  cursor: default;
  text-decoration: none;
}

.ages-auth-verify-skip {
  color: var(--ages-muted);
}

/* Allow-with-nag banner in the fixed header. */
.ages-auth-nag {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 8px;
  border-left: 1px solid var(--ages-input-border);
}

.ages-auth-nag-text {
  color: var(--ages-accent);
  font-size: 12px;
  max-width: 220px;
}

.ages-auth-nag-btn {
  font-size: 12px;
  padding: 4px 10px;
}

@media (max-width: 480px) {
  .ages-auth-nag-text {
    max-width: 120px;
  }
}

/* ---------- Password reset (S10b / ADR-019) ---------- */

.ages-auth-reset {
  margin-top: 4px;
}

.ages-auth-reset-sent {
  margin: 0 0 12px;
  color: var(--ages-muted);
  font-size: 13px;
  line-height: 1.4;
}

.ages-auth-reset input[name="reset-otp"] {
  letter-spacing: 0.3em;
  text-align: center;
  font-size: 18px;
}

.ages-auth-reset-resend:disabled {
  color: var(--ages-muted);
  cursor: default;
  text-decoration: none;
}

.ages-auth-reset-back {
  color: var(--ages-muted);
}

/* "Forgot password?" link on the sign-in view. */
.ages-auth-forgot-link {
  color: var(--ages-muted);
}

/* Neutral post-reset success banner on the sign-in view. */
.ages-auth-reset-success {
  color: var(--ages-accent);
  font-size: 13px;
  line-height: 1.4;
  margin: 0 0 12px;
}

/* ---------- Save conflict dialog ---------- */

.ages-conflict-modal {
  width: min(560px, calc(100vw - 24px));
}

.ages-conflict-subtitle {
  margin: 0 0 14px;
  color: var(--ages-muted);
  font-size: 13px;
  line-height: 1.4;
}

.ages-conflict-diff-summary {
  margin: 0 0 14px;
  color: var(--ages-accent);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 600;
}

.ages-conflict-cards {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
}

.ages-conflict-card {
  flex: 1;
  background: var(--ages-input-bg);
  border: 1px solid var(--ages-input-border);
  border-radius: var(--ages-radius);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.ages-conflict-card-label {
  color: var(--ages-accent);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  margin-bottom: 4px;
}

.ages-conflict-card-row {
  font-size: 13px;
  color: var(--ages-fg);
}

.ages-conflict-date {
  color: var(--ages-muted);
}

/* A row whose value differs between the local and server save — drawn
   attention so the user can see WHAT is in conflict (S8a-x2 Step 3). */
.ages-conflict-row-diff {
  color: var(--ages-accent);
  font-weight: 700;
}

.ages-conflict-buttons {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.ages-conflict-btn-local {
  flex: 0 1 auto;
}

.ages-conflict-btn-server {
  flex: 0 1 auto;
  margin-top: 0;
}

@media (max-width: 480px) {
  .ages-conflict-cards {
    flex-direction: column;
  }
  .ages-conflict-buttons {
    flex-direction: column-reverse;
  }
  .ages-conflict-buttons button {
    width: 100%;
  }
}

/* ---------- Save bootstrap loader ---------- */

.ages-save-loader {
  position: fixed;
  inset: 0;
  z-index: 9997;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
}

.ages-save-loader-inner {
  background: var(--ages-bg);
  color: var(--ages-fg);
  padding: 18px 22px;
  border-radius: var(--ages-radius);
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.ages-save-loader-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--ages-input-border);
  border-top-color: var(--ages-accent);
  border-radius: 50%;
  animation: ages-spin 0.8s linear infinite;
}

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

/* ---------- OAuth redirect overlay (S9 — spinner while navigating to provider) ---------- */

.ages-auth-redirect-overlay {
  position: fixed;
  inset: 0;
  z-index: 10001;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  pointer-events: auto;
}

/* Literal colours (NOT var(--ages-*)): this overlay is appended to
   document.body, OUTSIDE the .ages-auth-root scope that defines those
   custom properties — so var() would resolve to nothing and the spinner
   border / text would be invisible. Values mirror --ages-accent / --ages-fg. */
.ages-auth-redirect-spinner {
  width: 42px;
  height: 42px;
  border: 3px solid rgba(244, 244, 248, 0.25);
  border-top-color: #d4a25a;
  border-radius: 50%;
  animation: ages-spin 0.8s linear infinite;
}

.ages-auth-redirect-text {
  color: #f4f4f8;
  font-size: 14px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
}

/* ---------- Sign-out confirm dialog ---------- */
/* Overlay carries .ages-auth-root (added in JS) so var(--ages-*) resolve and
   children inherit box-sizing/font, even though it is appended to body. */

.ages-auth-confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  pointer-events: auto;
}

.ages-auth-confirm-box {
  background: var(--ages-bg);
  color: var(--ages-fg);
  padding: 22px 24px;
  border-radius: var(--ages-radius);
  max-width: 360px;
  width: 100%;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.ages-auth-confirm-title {
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 8px;
}

.ages-auth-confirm-message {
  font-size: 14px;
  color: var(--ages-muted);
  line-height: 1.4;
  margin: 0 0 18px;
}

.ages-auth-confirm-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.ages-auth-confirm-btn {
  padding: 8px 18px;
  border: none;
  border-radius: var(--ages-radius);
  font-size: 14px;
  cursor: pointer;
}

.ages-auth-confirm-cancel {
  background: var(--ages-btn-bg);
  color: var(--ages-fg);
}
.ages-auth-confirm-cancel:hover {
  background: var(--ages-btn-hover);
}

.ages-auth-confirm-yes {
  background: var(--ages-accent);
  color: #1a1a1a;
  font-weight: 600;
}
.ages-auth-confirm-yes:hover {
  filter: brightness(1.08);
}

/* ---------- Multi-tab freeze overlay (S55 / ADR-036) ---------- */
/* Shown in a tab that was superseded by another (live) tab. Blocking,
   full-page, topmost z-index (above loader 9997 / conflict+orientation
   10000-10001 / redirect 10001) so it covers everything; pointer-events:auto
   blocks the game canvas underneath. Carries .ages-auth-root (added in JS) so
   var(--ages-*) resolve even though it is appended to document.body. */
.ages-multitab-frozen {
  position: fixed;
  inset: 0;
  z-index: 10002;
  background: rgba(10, 10, 16, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  pointer-events: auto;
}

.ages-multitab-frozen-inner {
  background: var(--ages-bg);
  color: var(--ages-fg);
  padding: 26px 28px;
  border-radius: var(--ages-radius);
  max-width: 380px;
  width: 100%;
  text-align: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.ages-multitab-frozen-title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
  margin: 0 0 18px;
}

.ages-multitab-frozen-button {
  padding: 10px 22px;
  border: none;
  border-radius: var(--ages-radius);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  background: var(--ages-accent);
  color: #1a1a1a;
}
.ages-multitab-frozen-button:hover {
  filter: brightness(1.08);
}

@media (max-width: 480px) {
  /* S26/S62: the panel now collapses into the trigger on every platform
     (see the unconditional collapse rules below) — the email cap is applied
     there too, so this narrow-window duplicate is harmless. Kept for the
     modal-padding tweak, which is genuinely width-specific (narrow desktop
     AND mobile). */
  .ages-auth-user-email {
    max-width: 140px;
  }
  .ages-auth-modal {
    padding: 18px 16px 14px;
  }
}

/* ---------- Collapsible auth panel (S26/S27 mobile → S62 universal) ----------
 *
 * S26/S27 introduced this collapse on mobile only — the whole block was gated
 * behind the L-004 touch combo `(hover: none) and (pointer: coarse)`. S62
 * (Trello #50) extends it to DESKTOP per customer request ("пусть на компе
 * также под маленькую кнопку с аккаунтом прячется, как и на телефонах"): the
 * gate is removed so the collapse is UNCONDITIONAL — one code path for both
 * platforms.
 *
 * The panel (#ages-auth-header) is collapsed off-screen by default (opacity:0
 * + translateX(110%)); tapping/clicking the trigger toggles the `is-expanded`
 * class which slides+fades the full panel in from the right edge. Outside-tap
 * / Escape / × button / second trigger tap collapse it. The JS state machine
 * (auth-ui.js: toggleHeader / handleOutsideTap / handleTriggerKey) was ALREADY
 * platform-agnostic (listeners short-circuit when collapsed), so S62 needed no
 * behavioural JS change — only the CSS gate came off.
 *
 * L-004 still governs detection where it is genuinely mobile-specific: the JS
 * positionTriggerForViewport() keeps the `(hover:none) and (pointer:coarse)`
 * matchMedia gate — its iOS layout-viewport fix (visualViewport right-edge
 * pinning, WI-029) is touch-only. On desktop that function early-returns and
 * clears any inline left/right, so the trigger uses the plain CSS `right:0`
 * below (no iOS viewport quirk on a mouse device). Mobile computed styles are
 * byte-for-byte unchanged — removing a media wrapper adds no specificity and
 * keeps source order, so the cascade resolves identically on touch devices. */

/* Collapsed off-screen by default; only `.is-expanded` reveals the panel. */
#ages-auth-header {
  /* anchor: right edge centre, transformed back by 110% on the X axis so the
     panel slides IN from off-screen when .is-expanded sets translateX(0).
     Y centred via top:50% + translateY(-50%) — well below the top-right gear
     (QA #47, ~12.5vh), so neither the collapsed trigger nor the expanded
     panel parks over it (retires the S48 15vh anchor). */
  top: 50%;
  right: 4px;
  transform: translate(110%, -50%);
  opacity: 0;
  pointer-events: none;
  transition: transform 180ms ease-out, opacity 180ms ease-out;
  /* compact the panel a touch; padding-right is smaller because the inline
     close (×) sits at the right edge. */
  gap: 8px;
  padding: 6px 6px 6px 10px;
  font-size: 13px;
}
#ages-auth-header.is-expanded {
  transform: translate(0, -50%);
  opacity: 1;
  pointer-events: auto;
}
/* Reveal the inline close (×) whenever the panel is expanded. */
#ages-auth-header .ages-auth-header-close {
  display: inline-block;
}
/* S27 (Trello #14 cosmetic + #12 bug fix): half-circle trigger pinned
   flush to the right edge — only the LEFT half of the circle is
   visible, the right half is clipped by the viewport edge. Tap area
   stays ≥44px high (WCAG, WI-028); width is 44px so the visible half-disc
   reads as a clear glyph. Light-yellow background matches the active
   RU/EN locale chip (--ages-accent). S62: same geometry on desktop —
   a mouse can trivially hit a 44px target, and a single code path avoids
   forking a desktop-only trigger (consistency + WI-028 never worsened).
 *
 * Why right:0 (was right:4px in S26) — diagnostic for Trello #12: on
 * iOS Safari/Chrome in landscape the user reported the S26 trigger
 * was completely invisible. The S26 source CSS, sibling combinator,
 * and DOM order are all correct on inspection. The remaining
 * plausible root is a viewport-quirk: the layout viewport on iOS
 * (especially iOS Chrome after the S22→S25 viewport-fit revert and
 * the index.html `height=device-width` typo) can be wider than the
 * physical visible area, pushing a `right:4px` fixed element a few
 * pixels off-screen. Anchoring at `right:0` with a half-disc that
 * intentionally bleeds off the edge guarantees that whatever the
 * viewport-vs-visible discrepancy is, half of the disc remains on
 * screen. On desktop right:0 simply pins the half-disc flush to the
 * window's right edge (positionTriggerForViewport leaves it to CSS). */
#ages-auth-trigger {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 9998;
  height: 44px;
  /* S31 followup #3: square box 44×44 (no env-padding). After S31
     `width=device-width` enabled, iOS Safari landscape on devices
     with right-side notch / Dynamic Island actually surfaces a
     non-zero env(safe-area-inset-right) — BS-diag on iPhone 15 Pro
     Safari landscape measured `width=103, height=44` → horizontal
     pill, not a disc. Removing the env-addition. Dynamic Island in
     landscape occupies the top/bottom corner only; trigger is at
     vertical centre (top:50%) so they don't overlap visually.
     Square 44×44 + 22px left border-radius = perfect semi-circle. */
  width: 44px;
  padding: 6px;
  /* S29 (Trello #14 followup): match the rest of the overlay family —
     dark translucent panel background, light-yellow glyph. Same colours
     as #ages-auth-header (`var(--ages-bg)` background + accent glyph),
     which is the visual language the user asked for. SVG `stroke` uses
     currentColor → setting `color: var(--ages-accent)` colours the
     door / silhouette icon in the light-yellow accent (like the active
     RU/EN locale chip). */
  background: var(--ages-bg);
  color: var(--ages-accent);
  border: 0;
  /* Half-circle: only the LEFT corners rounded — the right side sits
     flush against the viewport edge. Use a PIXEL radius (= half the
     44px height) so the curve is circular regardless of the box width
     — `50% 0 0 50%` gave an elliptical curve (18×22 on a 36×44 box),
     which read as a vertically-stretched egg on iPhone Pro Max
     landscape where the icon is more prominent. 22px on a 44px-tall
     box meets at the vertical centre line → proper half-disc. */
  border-radius: 22px 0 0 22px;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  transition: opacity 180ms ease-out;
  -webkit-tap-highlight-color: transparent;
}
#ages-auth-trigger svg {
  width: 24px;
  height: 24px;
  display: block;
}
/* When the panel is expanded, hide the trigger to avoid double-icon
   overlap. The panel itself carries the close (×). */
#ages-auth-header.is-expanded ~ #ages-auth-trigger {
  opacity: 0;
  pointer-events: none;
}
/* The user-email line wraps too much in a narrow panel — cap width. */
.ages-auth-user-email {
  max-width: 140px;
}

/* ---------- CTA toast (stage 7c) ---------- */

/* The CTA root sits below the auth modal (z-index 9999) so a toast that
   somehow lingers gets covered by the modal it spawned. The inner card is
   the only thing with pointer-events: auto — game canvas underneath stays
   reactive. */
#ages-cta-root {
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: none;
}

.ages-cta-toast {
  position: absolute;
  right: 16px;
  bottom: 16px;
  width: min(340px, calc(100vw - 32px));
  background: var(--ages-bg);
  color: var(--ages-fg);
  border-radius: var(--ages-radius);
  padding: 14px 32px 14px 16px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  pointer-events: auto;
  transform: translateX(120%);
  opacity: 0;
  transition: transform 240ms ease-out, opacity 240ms ease-out;
}

.ages-cta-toast.ages-cta-visible {
  transform: translateX(0);
  opacity: 1;
}

.ages-cta-close {
  position: absolute;
  top: 4px;
  right: 6px;
  background: transparent;
  border: 0;
  color: var(--ages-muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}

.ages-cta-close:hover {
  color: var(--ages-fg);
}

.ages-cta-title {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
}

.ages-cta-body {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--ages-fg);
  line-height: 1.4;
}

.ages-cta-buttons {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}

.ages-cta-btn-signup {
  background: var(--ages-accent);
  color: #1a1a22;
  border: 0;
  border-radius: var(--ages-radius);
  padding: 7px 12px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  flex: 1 0 auto;
}

.ages-cta-btn-signup:hover {
  filter: brightness(1.05);
}

.ages-cta-btn-later {
  background: var(--ages-btn-bg);
  color: var(--ages-fg);
  border: 0;
  border-radius: var(--ages-radius);
  padding: 7px 12px;
  cursor: pointer;
  font-size: 13px;
  flex: 0 1 auto;
}

.ages-cta-btn-later:hover {
  background: var(--ages-btn-hover);
}

.ages-cta-checkbox {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ages-muted);
  cursor: pointer;
  user-select: none;
}

.ages-cta-checkbox input {
  margin: 0;
  cursor: pointer;
}

@media (max-width: 480px) {
  .ages-cta-toast {
    right: 8px;
    left: 8px;
    bottom: 8px;
    width: auto;
  }
}

/* ---------- Purchase toast (S11b / ADR-032) ---------- */

/* Lightweight auto-dismissing status toast for the Stripe purchase flow
   (success/cancel/processing/applied, sign-in prompt, errors). Top-center
   so it does not collide with the bottom-right CTA toast. pointer-events:
   none — purely informational, the game canvas underneath stays reactive. */
.ages-purchase-toast {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translate(-50%, -16px);
  max-width: min(420px, calc(100vw - 32px));
  z-index: 9998;
  background: var(--ages-bg);
  color: var(--ages-fg);
  border-radius: var(--ages-radius);
  padding: 12px 18px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  font-size: 14px;
  line-height: 1.4;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: transform 220ms ease-out, opacity 220ms ease-out;
}

.ages-purchase-toast.ages-purchase-toast-visible {
  transform: translate(-50%, 0);
  opacity: 1;
}

/* ---------- Network status toast (S63 / QA #35) ---------- */

/* Mid-session connectivity signal (offline / restored). Modeled on the S11b
   purchase toast, but pinned BOTTOM-CENTER — the only screen edge the two
   existing toasts left free (purchase = top-center, guest CTA = bottom-right)
   — so it never collides with either, including the offline-during-purchase
   case. pointer-events: none — purely informational, the game canvas
   underneath stays fully reactive (it NEVER blocks input). z-index 9998 keeps
   it in the toast layer: BELOW the auth modal (9999) and every critical
   overlay (redirect / sign-out confirm 10000-10002, and the S54 boot overlay
   at 2147483000). Offline persists until reconnect; "restored" auto-hides (JS
   timer). */
.ages-net-toast {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translate(-50%, 16px);
  max-width: min(420px, calc(100vw - 32px));
  z-index: 9998;
  background: var(--ages-bg);
  color: var(--ages-fg);
  border-radius: var(--ages-radius);
  padding: 10px 18px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  font-size: 14px;
  line-height: 1.4;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: transform 220ms ease-out, opacity 220ms ease-out;
}

.ages-net-toast.ages-net-toast-visible {
  transform: translate(-50%, 0);
  opacity: 1;
}

/* A left accent bar tints the toast. Since S66 the toast is only ever the
   "restored" (success green) message — offline is now the blocking overlay
   below, not a toast — but the amber offline variant is kept harmless in case
   a future non-blocking connectivity signal wants it. */
.ages-net-toast-offline {
  border-left: 3px solid #d9812f;
}

.ages-net-toast-online {
  border-left: 3px solid #3f9e56;
}

/* ---------- Network offline blocker (S66 / #53) ---------- */

/* Shown mid-session when the network drops. The game is NOT genuinely playable
   offline — battle assets (Spine effect skeletons, level backgrounds, PvP
   textures) lazy-load from the server and a cache-miss offline yields grey/
   missing textures — so we PAUSE the battle (ADR-034 overlay_pause bridge) and
   BLOCK the canvas until the network returns. Full-page, pointer-events:auto
   (blocks the frozen battle underneath). NO buttons: we simply wait for
   reconnect; the S54 boot overlay (#ages-neterr, z 2147483000) owns the
   separate "cannot load the section at all → Retry" case.

   z-index 9998 — the connectivity layer (it supersedes the S63 offline toast,
   also 9998). Deliberately BELOW the auth modal (9999) and the multi-tab
   freeze / "Play here" (10002) so an open modal or the takeover overlay stays
   interactive ON TOP of the blocker (offline must not lock an open modal), and
   below the S54 boot overlay (2147483000). Above the game canvas and the save
   loader (9997). Carries .ages-auth-root (added in JS) so var(--ages-*) resolve
   on document.body. Modelled on the S55 multi-tab freeze overlay. */
.ages-net-blocker {
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: rgba(10, 10, 16, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  pointer-events: auto;
}

.ages-net-blocker-inner {
  background: var(--ages-bg);
  color: var(--ages-fg);
  padding: 26px 28px;
  border-radius: var(--ages-radius);
  max-width: 380px;
  width: 100%;
  text-align: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* Reuses the @keyframes ages-spin defined for the save-bootstrap loader. */
.ages-net-blocker-spinner {
  width: 26px;
  height: 26px;
  border: 3px solid var(--ages-input-border);
  border-top-color: var(--ages-accent);
  border-radius: 50%;
  animation: ages-spin 0.8s linear infinite;
}

.ages-net-blocker-title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
  margin: 0;
}

.ages-net-blocker-wait {
  font-size: 13px;
  line-height: 1.4;
  margin: 0;
  color: var(--ages-muted);
}

/* ---------- Orientation overlay (S20) ----------
 * Full-screen overlay shown ONLY on mobile-portrait. The game is designed for
 * landscape — on a portrait phone the pixi canvas renders as a narrow strip.
 * Detection is purely CSS (no JS device-sniff):
 *   - (orientation: portrait)   — physical orientation
 *   - (max-width: 1024px)       — small viewport (rules out desktop windows
 *                                 narrowed by the user; the game targets
 *                                 ≥1024 logical width anyway)
 *   - (hover: none) and (pointer: coarse) — touch device (rules out a desktop
 *                                 dragged narrow with a mouse)
 * All three must hold → real mobile-portrait. On landscape the media query
 * stops matching and the overlay vanishes automatically (no JS rotate hook).
 *
 * The overlay is rendered in the DOM unconditionally (display:none by default)
 * and only revealed by the media query, so desktop pays zero runtime cost.
 * z-index is the highest in this stylesheet (above auth-modal 9999 / loader).
 * pointer-events:auto on the overlay blocks any tap from reaching the game
 * canvas underneath while portrait. */
#ages-orientation-overlay {
  display: none;
}

@media (orientation: portrait) and (max-width: 1024px) and (hover: none) and (pointer: coarse) {
  #ages-orientation-overlay {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 10000;
    background: #14141c;
    color: #f4f4f8;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 24px;
    padding: 24px;
    text-align: center;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
      Arial, sans-serif;
    pointer-events: auto;
  }
}

.ages-orientation-icon {
  width: 96px;
  height: 96px;
  animation: ages-orientation-rotate 2s ease-in-out infinite;
}

.ages-orientation-icon path,
.ages-orientation-icon rect {
  fill: none;
  stroke: #d4a25a;
  stroke-width: 6;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.ages-orientation-message {
  font-size: 18px;
  line-height: 1.4;
  max-width: 320px;
  margin: 0;
}

@keyframes ages-orientation-rotate {
  0%   { transform: rotate(0deg); }
  40%  { transform: rotate(-90deg); }
  60%  { transform: rotate(-90deg); }
  100% { transform: rotate(0deg); }
}
