/* /play session UI: daily-session prompt, migration-welcome modal, tally,
   and first-problem tutorial overlay. */

/* Centered Play (10 min) CTA — primary entry point to the daily session.
   Lives inside #main-menu so it inherits the menu's show/hide + dungeon
   transitions. #main-menu has pointer-events:none, so re-enable on the
   button itself. Sits below dialogs (z-index 101) so the auto-prompt
   covers it cleanly when it fires. Visual style comes from .ff-button
   (base.css); this rule only handles positioning. */
#play-cta-button {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 30;
  pointer-events: auto;
  /* Hidden by default. Only visible while the play gates are closed
     (first /play visit of each local day). After the panels open, the
     regular garden chrome takes over and the CTA stays hidden until
     tomorrow's day-rollover re-closes the gates. */
  display: none;
}
body.play-gated:not(.play-gated-opening) #play-cta-button {
  display: block;
}

/* .ff-button's :active translates the button down 0.5cqh to mimic a 3D
   press. Our positioning transform is translate(-50%, -50%), so we need
   to compose both — otherwise the press snaps the button back to the
   top-left corner. */
#play-cta-button:active {
  transform: translate(-50%, calc(-50% + 0.5cqh));
}

/* Hide legacy /play numpad chrome that doesn't apply to the session: the
   skip / start / retry / menu submenus, the per-round banked-stars row,
   the heart-counter, and the legacy header label. The radial
   #star-counter and #numpad-close-button are kept visible (driven by the
   session) — see further down. */
body.play-session-active #numpad-skip-button,
body.play-session-active #numpad-start-buttons,
body.play-session-active #numpad-display-menu,
body.play-session-active #numpad-menu-buttons,
body.play-session-active #star-counter-stars,
body.play-session-active #heart-counter,
body.play-session-active #numpad-container .numpad-header {
  display: none !important;
}

/* Show the menu (close) button during the session — lockMenuButtons sets
   inline display:none on it for the FUE state, which would otherwise hide
   it during gameplay. The button dispatches to playSession.openMenu()
   (pause) when the session is active. */
body.play-session-active #numpad-close-button {
  display: block !important;
}

/* Session element indicators: 4 white-icon chips across the top, between
   the radial star counter (top-left) and the menu button (top-right).
   Hidden outside of session mode. */
.numpad-elements {
  display: none;
}
body.play-session-active .numpad-elements {
  position: absolute;
  top: 2cqh;
  /* Left inset was 17cqh to clear the star counter that used to sit top-left.
     The star counter is now hidden, so pull the element chips left to fill
     that freed space. */
  left: 2cqh;
  right: 13cqh;
  height: 10cqh;
  display: flex;
  justify-content: space-around;
  align-items: center;
  gap: 1cqh;
  /* Equal z-index to .numpad-menu-buttons (pregame Start overlay) — the
     overlay is later in DOM so it paints on top, covering the chips
     during pregame. */
  z-index: 1;
  pointer-events: none;
}

.numpad-element-chip {
  display: flex;
  align-items: center;
  gap: 0.5cqh;
  padding: 0.5cqh 1.2cqh;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 1.2cqh;
  color: #f7f3ec;
  font-size: 3.2cqh;
  font-weight: 700;
}

.numpad-element-chip svg {
  width: 3.5cqh;
  height: 3.5cqh;
  fill: currentColor;
  flex-shrink: 0;
}

/* Pulse a chip when its element count increments after a correct answer.
   Glow + background brighten only — no transform: scale, because scaling
   the chip's flex container causes its icon + text to re-render at a
   different size on each animation frame and the text appears to wobble. */
@keyframes numpad-element-flash {
  0% {
    background-color: rgba(255, 255, 255, 0.55);
    box-shadow: 0 0 2cqh 0.3cqh rgba(255, 255, 255, 0.9);
  }
  100% {
    background-color: rgba(0, 0, 0, 0.45);
    box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
  }
}

.numpad-element-chip-flash {
  animation: numpad-element-flash 0.8s ease-out;
}

/* Pregame is the brief state between dialog-Start and the actual math round.
   Show the existing #numpad-start-button so the player has an explicit
   commit-to-play moment; its onclick (math.js startGame) dispatches to
   playSession.startRound when the session is active. */
body.play-session-pregame #numpad-start-buttons {
  display: block !important;
}
body.play-session-pregame #numpad-retry-button,
body.play-session-pregame #numpad-exit-button {
  /* Reset/Quit only make sense once the round has started; in pregame the
     only meaningful action is Start. */
  display: none !important;
}

/* Hide the Play CTA while a session is active. The numpad takes over the
   viewport anyway, but without this the CTA peeks through during the
   fade-out before the gates open. Doubled class selector bumps
   specificity above `body.play-gated:not(.play-gated-opening)` (1,2,1),
   which would otherwise keep `display: block` winning. */
body.play-session-active.play-session-active #play-cta-button {
  display: none;
}

/* Correction-mode visual feedback. The session's correction state applies
   `.beta-correction-mode` to <body>; dim + desaturate everything in the
   numpad EXCEPT the element chips, so the chip currently draining can
   pulse red without being knocked back to grey by a parent filter (CSS
   filters can't be undone on a descendant). */
body.beta-correction-mode #star-counter,
body.beta-correction-mode #numpad-close-button,
body.beta-correction-mode #numpad-row-answer,
body.beta-correction-mode #numpad-row-timebar,
body.beta-correction-mode #numpad-row-123,
body.beta-correction-mode #numpad-row-456,
body.beta-correction-mode #numpad-row-789,
body.beta-correction-mode #numpad-row-0 {
  filter: grayscale(1) brightness(0.7);
  transition: filter 0.15s;
}

/* Red pulse on the chip whose element is being drained during correction
   mode. Class is toggled by enter/exitCorrectionMode in session-game.js. */
@keyframes numpad-element-drain-pulse {
  0%, 100% {
    background-color: rgba(180, 60, 60, 0.85);
    box-shadow: 0 0 1.5cqh 0.2cqh rgba(255, 80, 80, 0.5);
    color: #ffe0e0;
  }
  50% {
    background-color: rgba(255, 100, 100, 0.95);
    box-shadow: 0 0 2.5cqh 0.4cqh rgba(255, 100, 100, 0.85);
    color: #ffffff;
  }
}

.numpad-element-chip-draining {
  animation: numpad-element-drain-pulse 0.7s ease-in-out infinite;
}

/* Correction-mode prompt label. Lives inside #numpad-container (see
   views/partials/game.html); hidden by default, shown via the
   body.beta-correction-mode toggle. Ported from beta.css. */
.beta-correction-prompt {
  position: absolute;
  top: 1.5cqh;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 4cqh;
  color: #ffffff;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
  z-index: 6;
  display: none;
  pointer-events: none;
}

body.beta-correction-mode .beta-correction-prompt {
  display: block;
  animation: beta-correction-pulse 0.7s ease-in-out infinite;
}

@keyframes beta-correction-pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.85;
  }
  50% {
    transform: scale(1.06);
    opacity: 1;
  }
}

/* Breathing-break markers on the session timebar. Default hidden so the
   legacy /play 60-second game doesn't show them (its timebar covers a
   different math-time range). Shown only when a session is active. */
.beta-timebar-mark {
  display: none;
}

body.play-session-active .beta-timebar-mark {
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  background: rgba(140, 140, 140, 0.85);
  z-index: 2;
  pointer-events: none;
}

/* Header + footer z-index is intentionally NOT lifted during the gameplay
   phase — the numpad takes over the viewport. During the tally fanfare,
   the modal is sized so it never overlaps the header (top 7%) or footer
   (bottom 15%) regions, so the credit tick-up animation lands on visible
   targets without any z-index trickery. */

/* ───────── End-of-session tally modal ───────── */
/* position: absolute (not fixed) so it resolves to the nearest positioned
   ancestor — #flower-container, which is the 4:3-locked .container. Keeps
   the modal inside the play area on every aspect ratio without relying on
   the container-type:size → fixed-positioning containing-block quirk. */
#play-session-tally {
  position: absolute;
  top: 7%;
  bottom: 15%;
  left: 0;
  right: 0;
  z-index: 50;
  background-color: rgba(0, 0, 0, 0.5);
  padding: 2cqh 4cqh;
  overflow-y: auto;
}

/* Match the .dialog-container[style*="block"] / [style*="flex"] pattern: any
   non-"none" inline display value should render as a centered flex column. */
#play-session-tally[style*="block"],
#play-session-tally[style*="flex"] {
  display: flex !important;
  justify-content: center;
  align-items: flex-start;
}

.play-session-tally-box {
  background: #373a59;
  color: #c2c2d4;
  max-width: 106cqh;
  width: 100%;
  margin: auto;
  max-height: 100%;
  border-radius: 1.1cqh;
  overflow-y: auto;
  font-family:
    "Outfit",
    -apple-system,
    BlinkMacSystemFont,
    sans-serif;
}

/* Inset content frame. The 2cqh margin leaves a band of the box background
   around the content; padding keeps content off the edge. */
.play-session-tally-inner {
  margin: 2cqh;
  padding: 2.5cqh;
}

/* Hide the centered Play CTA while the tally is open. */
body.play-session-tally-open #play-cta-button {
  display: none;
}

/* Black fade overlay used to bridge the dialog → numpad transition. JS
   toggles opacity 0↔1; the transition handles the timing. Sits above
   everything else (numpad z-index 100, tally z-index 50) at viewport
   level — letterbox bars are already black so this fades the whole
   viewport uniformly. */
.play-session-fade-overlay {
  position: fixed;
  inset: 0;
  background: #000;
  z-index: 1000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 250ms ease-in-out;
}

/* ───────── Tally content (ported from beta.css) ───────── */
.beta-tally-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2.2cqh;
  margin-bottom: 3.3cqh;
}
.beta-tally-header h2 {
  margin: 0;
  font-size: 3.6cqh;
  flex: 1;
}
.beta-tally-header .beta-primary-button-large {
  width: auto;
  flex-shrink: 0;
  margin-bottom: 0;
}
.beta-tally h2 {
  margin: 0 0 3.3cqh 0;
  font-size: 3.6cqh;
  text-align: center;
}
.beta-tally-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.1cqh;
  padding: 2.2cqh;
  background: #f8f9fa;
  border-radius: 0.8cqh;
  margin-bottom: 1.65cqh;
}
.beta-tally-stats div {
  font-size: 1.95cqh;
  color: #495057;
}
/* Panel heading: "Total", or the operation name when one is selected. Spans
   the full grid width so it sits at the top-left above the stats. Scoped with
   two classes so it outranks the `.beta-tally-stats div` rule above. */
.beta-tally-stats .beta-tally-stats-heading {
  grid-column: 1 / -1;
  font-size: 2.5cqh;
  font-weight: 700;
  color: #1a1d24;
  margin-bottom: 0.55cqh;
}
.beta-tally-banked h3 {
  margin: 0 0 1.65cqh 0;
  font-size: 2.5cqh;
}
.beta-tally-stars-headline {
  display: flex;
  align-items: center;
  gap: 1.65cqh;
  padding: 1.95cqh 2.5cqh;
  margin-bottom: 1.65cqh;
  border-radius: 1.1cqh;
  background: linear-gradient(135deg, #d4a017 0%, #f4c430 100%);
  color: #1a1d24;
}
.beta-tally-stars-headline svg {
  width: 5cqh;
  height: 5cqh;
  fill: #1a1d24;
}
.beta-tally-stars-headline strong {
  font-size: 4.4cqh;
  font-weight: 800;
  margin-right: 0.55cqh;
}
.beta-tally-stars-headline span {
  font-size: 1.95cqh;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.beta-primary-button,
.beta-secondary-button {
  display: inline-block;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  margin-right: 12px;
}
.beta-primary-button {
  background: #2b78c4;
  color: #fff;
}
.beta-primary-button:hover {
  background: #1f5c98;
}
.beta-secondary-button {
  background: #f4f6f8;
  color: #1a1d24;
  border: 1px solid #ced4da;
}
.beta-secondary-button:hover {
  background: #e9ecef;
}
.beta-primary-button-large {
  padding: 2.2cqh 3.3cqh;
  border-radius: 0.8cqh;
  font-size: 2.5cqh;
}
.beta-tally-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.1cqh;
  margin-bottom: 3.3cqh;
}
/* The bank cells double as filter buttons (like .beta-tally-op-button): a 3D
   drop shadow per element, a press-down :active, and a depressed selected
   state kept in sync with the op buttons. */
.beta-tally-grid > div {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 1.65cqh 0.55cqh;
  border-radius: 0.8cqh;
  color: #fff;
  cursor: pointer;
  transition: filter 150ms ease;
}
.beta-tally-grid > div:active,
.beta-tally-grid > div.beta-bank-selected {
  transform: translateY(0.5cqh);
  box-shadow: none !important;
}
/* Dim the bank cells whose op isn't the selected tally filter. */
.beta-tally-grid .beta-bank-dimmed {
  filter: brightness(0.5);
}
.beta-tally-grid > div span {
  font-size: 1.5cqh;
  text-transform: uppercase;
  opacity: 0.85;
}
.beta-tally-grid > div strong {
  font-size: 3cqh;
  font-weight: 700;
}
.beta-tally-grid .beta-bank-earth {
  background: var(--earth-color);
  color: #fff;
  box-shadow: 0 0.5cqh 0 0 var(--earth-color-shadow);
}
.beta-tally-grid .beta-bank-wind {
  background: var(--air-color);
  color: #fff;
  box-shadow: 0 0.5cqh 0 0 var(--air-color-shadow);
}
.beta-tally-grid .beta-bank-water {
  background: var(--water-color);
  color: #fff;
  box-shadow: 0 0.5cqh 0 0 var(--water-color-shadow);
}
.beta-tally-grid .beta-bank-fire {
  background: var(--fire-color);
  color: #fff;
  box-shadow: 0 0.5cqh 0 0 var(--fire-color-shadow);
}
/* Per-operation filter buttons beneath the tally stats panel. Elemental fill
   plus a footer-button-style 3D drop shadow (mirrors .ff-button). Clicking a
   button filters the stats panel to that op; clicking the selected one reverts
   to the aggregate view. */
.beta-tally-op-buttons {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.65cqh;
  margin-bottom: 3.3cqh;
}
.beta-tally-op-button {
  border: none;
  border-radius: 0.8cqh;
  padding: 1.4cqh 0;
  font-size: 3.6cqh;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  cursor: pointer;
  font-family: inherit;
  transition: filter 100ms ease;
}
.beta-tally-op-button:hover {
  filter: brightness(1.1);
}
/* 3D press: sink onto the shadow, matching .ff-button:active. */
.beta-tally-op-button:active {
  transform: translateY(0.5cqh);
  box-shadow: none !important;
}
/* The selected op stays visually pressed so it reads as the active filter. */
.beta-tally-op-button.beta-tally-op-selected {
  transform: translateY(0.5cqh);
  box-shadow: none !important;
  filter: brightness(1.1);
}
/* Non-selected ops dim while a filter is active, mirroring the bank cells. */
.beta-tally-op-button.beta-tally-op-dimmed {
  filter: brightness(0.5);
}
.beta-tally-op-earth {
  background: var(--earth-color);
  box-shadow: 0 0.5cqh 0 0 var(--earth-color-shadow);
}
.beta-tally-op-wind {
  background: var(--air-color);
  box-shadow: 0 0.5cqh 0 0 var(--air-color-shadow);
}
.beta-tally-op-water {
  background: var(--water-color);
  box-shadow: 0 0.5cqh 0 0 var(--water-color-shadow);
}
.beta-tally-op-fire {
  background: var(--fire-color);
  box-shadow: 0 0.5cqh 0 0 var(--fire-color-shadow);
}

/* Chronological problem log — every problem answered this session with the
   time taken. Scrolls within a fixed height so a long session doesn't blow out
   the tally. */
.beta-tally-problem-log {
  margin-bottom: 3.3cqh;
}
.beta-tally-problem-log h3 {
  margin: 0 0 1.65cqh 0;
  font-size: 2.2cqh;
}
.beta-problem-list {
  max-height: 30cqh;
  overflow-y: auto;
  border-radius: 0.8cqh;
  background: #f8f9fa;
}
.beta-problem-row {
  display: flex;
  align-items: center;
  gap: 1.1cqh;
  padding: 1cqh 1.95cqh;
  font-size: 1.95cqh;
  color: #495057;
  border-bottom: 1px solid #e3e6ea;
}
.beta-problem-row:last-child {
  border-bottom: none;
}
.beta-problem-expr,
.beta-problem-time {
  font-variant-numeric: tabular-nums;
}
/* Expression stays left; the record badge (if any) and time group at the right. */
.beta-problem-expr {
  margin-right: auto;
}
.beta-problem-time {
  color: #868e96;
}
/* Incorrectly answered problems stand out in the fire (penalty) color. */
.beta-problem-wrong .beta-problem-expr {
  color: var(--fire-color);
  font-weight: 700;
}
/* "New speed record" badge, shown at the left of the problem's row. */
.beta-problem-record {
  padding: 0.1cqh 0.7cqh;
  border-radius: 0.6cqh;
  background: #f4c430;
  color: #4a3500;
  font-size: 1.5cqh;
  font-weight: 700;
  white-space: nowrap;
}

/* New unlocks block — keeps the gold accent on the white card. */
.beta-tally-new-unlocks {
  background: #fff8e1;
  border-left: 0.55cqh solid #c9b85a;
  padding: 1.95cqh 2.5cqh;
  margin: 0 0 2.5cqh;
}
.beta-tally-new-unlocks h3 {
  margin: 0 0 1.4cqh;
  font-size: 2.5cqh;
  color: #6e5300;
  letter-spacing: 0.04em;
}
.beta-tally-unlocks-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.beta-tally-unlock {
  padding: 0.8cqh 0;
  font-size: 2.2cqh;
  line-height: 1.4;
  display: flex;
  flex-direction: column;
  gap: 0.28cqh;
}
.beta-tally-unlock-key {
  font-size: 1.65cqh;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #b88a00;
}

/* ───────── Runtime overlays (created by session-game.js) ───────── */

/* Speed-bonus floating text — `+3⚡` confetti appended to #numpad-container
   on fast correct answers. */
.beta-speed-bonus-text {
  position: absolute;
  top: 18%;
  left: 50%;
  transform: translateX(-50%);
  font-size: 6cqh;
  font-weight: 800;
  color: #ffd700;
  text-shadow:
    0 0 12px rgba(255, 215, 0, 0.8),
    0 2px 4px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  z-index: 8;
  opacity: 1;
  transition:
    transform 1.1s ease-out,
    opacity 1.1s ease-out;
}
.beta-speed-bonus-text.beta-speed-bonus-fade {
  transform: translateX(-50%) translateY(-80px);
  opacity: 0;
}

/* Drain text — "−1 [icon]" spawned each correction-mode decay tick.
   Anchors to the bottom-center of the element chip and falls downward as
   it fades. Created/removed by spawnElementDrainText in session-game.js. */
.beta-element-drain-text {
  position: absolute;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 0.25em;
  font-size: 3cqh;
  font-weight: 800;
  color: #ff6b6b;
  text-shadow:
    0 0 8px rgba(255, 80, 80, 0.6),
    0 2px 4px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  z-index: 8;
  opacity: 1;
  transition:
    transform 1s ease-out,
    opacity 1s ease-out;
}
.beta-element-drain-text svg {
  width: 1em;
  height: 1em;
  fill: currentColor;
}
.beta-element-drain-text.beta-element-drain-fade {
  transform: translateX(-50%) translateY(60px);
  opacity: 0;
}

/* Pre-break screen dimmer — full-viewport black overlay. JS animates the
   opacity (showScreenDimmer / fadeOutScreenDimmer in session-game.js); CSS
   only owns positioning and color. */
.beta-screen-dimmer {
  position: fixed;
  inset: 0;
  background: #000;
  opacity: 0;
  pointer-events: none;
  z-index: 150;
}

/* Pause overlay + breathing overlay shell. `#beta-overlay` is appended to
   <body> by showPauseOverlay / showBreathingOverlay; the JS toggles the
   .beta-overlay-visible class on the next frame to drive the opacity
   transition. */
.beta-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.6s ease-in-out;
}
.beta-overlay.beta-overlay-visible {
  opacity: 1;
}

.beta-overlay-pause {
  background: #000;
}
.beta-overlay-card {
  background: #fff;
  padding: 40px 48px;
  border-radius: 12px;
  text-align: center;
  min-width: 280px;
}
.beta-overlay-card h2 {
  margin: 0 0 12px 0;
  font-size: 24px;
  color: #1a1d24;
}
.beta-overlay-card p {
  margin: 0 0 20px 0;
  color: #495057;
  font-size: 14px;
}
.beta-overlay-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
}
#beta-pause-countdown {
  font-weight: 700;
  color: #1a1d24;
}

/* Breathing overlay — sits transparently over the numpad while the
   pre-break screen dimmer covers the rest of the page. */
.beta-overlay-breathing {
  background: transparent;
}
.beta-overlay-breathing.beta-breathing-fadeout {
  opacity: 0;
  /* Duration scaled to the break length by session-game.js (final 10% of
     the intermission). Falls back to 1s for the standalone beta page. */
  transition: opacity var(--breath-fade-dur, 1s) ease-in-out;
}

.beta-breath-countdown {
  position: absolute;
  top: 8vh;
  left: 50%;
  transform: translateX(-50%);
  font-size: 32px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  letter-spacing: 0.08em;
}

.beta-breath-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Gap clears the circle's full expansion at scale 3.5. */
  gap: 130px;
  position: relative;
}

.beta-breath-circle {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, #b8d8e8 70%, #6a93b8 100%);
  box-shadow: 0 0 60px rgba(255, 255, 255, 0.5);
  transform: scale(1);
  opacity: 0;
  /* transform duration scaled to the break length by session-game.js (each
     breathe-in / breathe-out phase spans 40% of the intermission). Falls
     back to 4s for the standalone beta page. */
  transition:
    transform var(--breath-transform-dur, 4s) cubic-bezier(0.4, 0, 0.6, 1),
    opacity 0.8s ease-in;
}
.beta-breath-circle.visible {
  opacity: 1;
}
.beta-breath-circle.expand {
  transform: scale(3.5);
}
.beta-breath-circle.contract {
  transform: scale(1);
}

.beta-breath-text {
  color: #fff;
  font-size: 32px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}

/* First-problem tutorial callout — narrative-intro Phase G. Shown above
   the numpad on the very first problem of the user's very first session;
   auto-dismissed on first digit tap (see session-game.js inputNumber). */
.beta-first-problem-tutorial {
  /* position: absolute (not fixed) so it resolves to #flower-container —
     the 4:3-locked .container — instead of the viewport. Sits above the
     numpad's digit rows on every aspect ratio, including mobile. */
  position: absolute;
  left: 50%;
  bottom: 50cqh;
  transform: translateX(-50%);
  z-index: 90;
  display: none;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
  animation: beta-tutorial-pulse 1.6s ease-in-out infinite;
}

.beta-first-problem-tutorial-bubble {
  background: #181b46;
  color: #f7f3ec;
  border: solid 1px #f7f3ec;
  padding: 14px 22px;
  font-size: 18px;
  font-weight: 300;
  line-height: 1.35;
  max-width: 340px;
  text-align: center;
  font-family:
    "Outfit",
    -apple-system,
    BlinkMacSystemFont,
    sans-serif;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}

.beta-first-problem-tutorial-arrow {
  width: 0;
  height: 0;
  border-left: 14px solid transparent;
  border-right: 14px solid transparent;
  border-top: 18px solid #181b46;
  margin-top: -1px;
}

@keyframes beta-tutorial-pulse {
  0%,
  100% {
    transform: translateX(-50%) translateY(0);
  }
  50% {
    transform: translateX(-50%) translateY(-6px);
  }
}

/* Migration welcome modal — single-step explainer shown to pre-launch users
   on first /play visit. */

.migration-welcome-dialog-box {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  background-color: #181b46;
  color: #f7f3ec;
  padding: 3cqh 4cqh;
  width: 86%;
  max-width: 60cqh;
  max-height: 90cqh;
  border: solid 1px white;
  font-family:
    "Outfit",
    -apple-system,
    BlinkMacSystemFont,
    sans-serif;
}

.migration-welcome-explainer {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 2cqh 0;
}

.migration-welcome-heading {
  font-size: 3.6cqh;
  font-weight: 300;
  letter-spacing: 0.01em;
  margin-bottom: 2cqh;
  text-align: center;
}

.migration-welcome-lede {
  font-size: 2.4cqh;
  font-weight: 300;
  text-align: center;
  margin-bottom: 2.5cqh;
  opacity: 0.92;
}

.migration-welcome-bullets {
  margin: 0 0 2.5cqh 4cqh;
  padding: 0;
  font-size: 2.2cqh;
  font-weight: 300;
  line-height: 1.7;
}

.migration-welcome-bullets li {
  margin-bottom: 0.6cqh;
}

.migration-welcome-closing {
  font-size: 2.2cqh;
  font-weight: 300;
  text-align: center;
  margin-top: 1cqh;
  margin-bottom: 1cqh;
  opacity: 0.8;
  font-style: italic;
}

.migration-welcome-nav {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: 1cqh;
}

.migration-welcome-nav-btn {
  flex: 1 1 auto;
  background: transparent;
  color: inherit;
  border: solid 1px rgba(247, 243, 236, 0.6);
  padding: 1.8cqh 0;
  font-size: 2.4cqh;
  font-family: inherit;
  font-weight: 300;
  cursor: pointer;
}

.migration-welcome-nav-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Daily-session auto-prompt overlay (parent plan §1.2). Fires on /play
   load when the user is fully onboarded and has no active session. */

/* Override .dialog-container's default position:fixed+viewport. The prompt
   lives inside #flower-container (a .container with container-type: size),
   and position:absolute makes the backdrop + dialog box resolve to
   .container's bounds — keeping the whole prompt consistent in size with
   the rest of the play-area UI regardless of window dimensions. */
#play-session-prompt-container {
  position: absolute;
}

/* Outer box matches the legacy .dialog-box pattern: dark navy fill, sized
   to the dialog, no border. The white-bordered inner panel is composed
   from .dialog-inner-border (see dialogs.css) plus layout extras below. */
.play-session-prompt-box {
  background-color: #181b46;
  color: #f7f3ec;
  width: 86%;
  max-width: 56cqh;
  max-height: 90cqh;
  overflow-y: auto;
  font-family:
    "Outfit",
    -apple-system,
    BlinkMacSystemFont,
    sans-serif;
}

.play-session-prompt-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 3cqh 3.5cqh;
}

.play-session-prompt-heading {
  font-size: 4cqh;
  font-weight: 300;
  text-align: center;
  letter-spacing: 0.01em;
  margin-bottom: 2cqh;
}

/* Row of element icons (earth / air / water / fire) below the heading. */
.play-session-prompt-subheading {
  display: flex;
  gap: 1cqh;
  margin-bottom: 2cqh;
}

.play-session-prompt-subheading svg {
  width: 3cqh;
  height: 3cqh;
  fill: #f7f3ec;
}

.play-session-prompt-lede {
  font-size: 2.2cqh;
  font-weight: 300;
  text-align: center;
  margin-bottom: 1.6cqh;
  line-height: 1.4;
  opacity: 0.85;
}

.play-session-prompt-buttons {
  display: flex;
  gap: 2cqh;
  width: 100%;
  /* Breathing room between the last lede and the buttons row. */
  margin-top: 3cqh;
}

/* Layout only — visual style comes from .ff-button. */
.play-session-prompt-btn {
  flex: 1 1 auto;
}

/* ───────── /play "gates" (mandatory-math gate) ───────── */
/* Two opaque black panels meeting at center cover the flower + sidebar
   on the first /play visit each day. Header (z:20), footer (z:20), Play
   CTA (z:30), and tally (z:50) paint above the panels by their existing
   z-index. Sidebar (z:19) matches the panel z-index but appears earlier
   in DOM, so the panels paint on top. */
.play-gate {
  display: none;
}

body.play-gated .play-gate {
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 19;
  pointer-events: auto;
  transition: transform 700ms cubic-bezier(0.6, 0, 0.4, 1);
}

body.play-gated .play-gate-left {
  left: 0;
  transform: translateX(0);
}

body.play-gated .play-gate-right {
  right: 0;
  transform: translateX(0);
}

/* Sliding-doors open animation. .play-gated-opening is applied after the
   lock-drop completes; the panels translate off-screen and the
   .play-gated class is removed on completion (in play-gates.js). */
body.play-gated.play-gated-opening .play-gate-left {
  transform: translateX(-100%);
}
body.play-gated.play-gated-opening .play-gate-right {
  transform: translateX(100%);
}

/* Hide the Play CTA once the gates start opening — by this point the
   player has just finished their session and the CTA shouldn't be
   visible behind the parting panels. Matches the body.play-session-tally-open
   rule already in place during the tally fanfare. */
body.play-gated.play-gated-opening #play-cta-button {
  display: none;
}

/* Locks on footer buttons while gates are closed. Reuses the existing
   .footer-lock-icon SVG (#lock_icon sprite) that the legacy disabled-button
   pattern already places on sub/mul/div buttons; we added a matching one
   to the add button so all four show locks. While gated, footer buttons
   cannot be clicked, and the whole container picks up the same dimmed
   "disabled" filter the legacy :disabled state uses. */
/* All footer buttons — including the dungeon nav — are inert until the gates
   open. The player must finish today's math session before navigating away. */
body.play-gated .menu-footer-button {
  pointer-events: none;
}

body.play-gated .menu-footer-button-container {
  filter: brightness(0.5);
}

/* The dungeon button sits directly in the footer (no .menu-footer-button-container
   wrapper), so dim it explicitly to match the disabled look of the op cells. */
body.play-gated #menu-footer-dungeon {
  filter: brightness(0.5);
}

body.play-gated .menu-footer-button .footer-lock-icon {
  display: inline-block;
  fill: #f7f3ec;
  filter: drop-shadow(0 0.3cqh 0.6cqh rgba(0, 0, 0, 0.6));
  /* Lift the lock above the math-op icon so they don't visually fight. */
  z-index: 3;
  /* Override the layout.css default (top: 1cqh) — sit flush at the top
     of the button. */
  top: 0;
}

/* The container that hosts the falling lock has overflow: hidden by
   default (to clip the legacy disabled-state dim). Override during the
   drop animation so the lock can fall past the button bounds. */
body.play-gated.play-gated-locks-dropping .menu-footer-button-container {
  overflow: visible;
}

@keyframes play-gate-lock-drop {
  0% {
    transform: translateX(-50%) translateY(0) rotate(0deg);
    opacity: 1;
  }
  15% {
    transform: translateX(-50%) translateY(0) rotate(-8deg);
    opacity: 1;
  }
  100% {
    transform: translateX(-50%) translateY(20cqh) rotate(35deg);
    opacity: 0;
  }
}

body.play-gated.play-gated-locks-dropping
  .menu-footer-button
  .footer-lock-icon {
  animation: play-gate-lock-drop 500ms cubic-bezier(0.4, 0, 1, 1) forwards;
}

/* ── First-day below-floor: keep locks on the three ops the player
   doesn't need to practice. body.first-day-needs-{op} drops the lock
   on op (via the normal drop animation) and re-shows the other three
   even after body.play-gated comes off. The corresponding buttons stay
   disabled. Driven by first-day-tutorial.js applyPersistentLocks().
   Cleared once SPROUT lands. ─────────────────────────────────────── */

body.first-day-needs-add #menu-footer-air-lock,
body.first-day-needs-add #menu-footer-water-lock,
body.first-day-needs-add #menu-footer-fire-lock,
body.first-day-needs-sub #menu-footer-earth-lock,
body.first-day-needs-sub #menu-footer-water-lock,
body.first-day-needs-sub #menu-footer-fire-lock,
body.first-day-needs-mul #menu-footer-earth-lock,
body.first-day-needs-mul #menu-footer-air-lock,
body.first-day-needs-mul #menu-footer-fire-lock,
body.first-day-needs-div #menu-footer-earth-lock,
body.first-day-needs-div #menu-footer-air-lock,
body.first-day-needs-div #menu-footer-water-lock {
  display: inline-block;
  fill: #f7f3ec;
  filter: drop-shadow(0 0.3cqh 0.6cqh rgba(0, 0, 0, 0.6));
  z-index: 3;
  top: 0;
  animation: none;
}

body.first-day-needs-add #menu-footer-sub-container,
body.first-day-needs-add #menu-footer-mul-container,
body.first-day-needs-add #menu-footer-div-container,
body.first-day-needs-sub #menu-footer-add-container,
body.first-day-needs-sub #menu-footer-mul-container,
body.first-day-needs-sub #menu-footer-div-container,
body.first-day-needs-mul #menu-footer-add-container,
body.first-day-needs-mul #menu-footer-sub-container,
body.first-day-needs-mul #menu-footer-div-container,
body.first-day-needs-div #menu-footer-add-container,
body.first-day-needs-div #menu-footer-sub-container,
body.first-day-needs-div #menu-footer-mul-container {
  filter: brightness(0.5);
}

body.first-day-needs-add #menu-footer-sub,
body.first-day-needs-add #menu-footer-mul,
body.first-day-needs-add #menu-footer-div,
body.first-day-needs-sub #menu-footer-add,
body.first-day-needs-sub #menu-footer-mul,
body.first-day-needs-sub #menu-footer-div,
body.first-day-needs-mul #menu-footer-add,
body.first-day-needs-mul #menu-footer-sub,
body.first-day-needs-mul #menu-footer-div,
body.first-day-needs-div #menu-footer-add,
body.first-day-needs-div #menu-footer-sub,
body.first-day-needs-div #menu-footer-mul {
  pointer-events: none;
}

/* ── Embedded game as the base layer (game-bridge.js) ─────────────────
   On /play the game iframe is the persistent base view: it mounts in
   #play-game-host and is shown on page load (body.play-game-active). While
   the embed is active, menu.html's original content (#flower-container) is
   parked OFF-SCREEN; host surfaces slide in over the game on request:
     • numpad (z-index 100) — shown by playSession, no rule needed here.
     • flower scene — slid in by body.play-flower-visible (below).
   Game host sits at z-index 40: above the parked scene's normal stacking but
   below the numpad (100) and tally (50) so those render over the game. */
#play-game-host {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: #000;
  visibility: hidden;
}
body.play-game-active #play-game-host {
  visibility: visible;
}
#play-game-host iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Park the original flower scene off-screen (below the viewport) while the
   game embed is active, lifted above the game host (z 40) so it slides in
   cleanly over the game when requested. Transform only — .container's internal
   layout and its container-query (cqh/cqw) context are unaffected. */
body.play-game-active #flower-container {
  z-index: 45;
  transform: translateY(100svh);
  transition: transform 0.4s ease;
}
/* Flower-scene request (host:show-flower-scene) slides it back on-screen over
   the game; host:hide-flower-scene removes the class to park it again. */
body.play-game-active.play-flower-visible #flower-container {
  transform: translateY(0);
}

/* Show-flower view: when the above-ground flower slides in over the game, show
   ONLY the element footer — hide the rest of the menu chrome (header, sidebar,
   the separate #main-menu container) so it's just the flower + footer controls.
   visibility (not display) so nothing reflows the aspect-locked canvas;
   pointer-events off so the hidden chrome is inert. #menu-footer is left alone
   (stays visible + interactive). */
body.play-flower-visible #menu-header,
body.play-flower-visible #menu-sidebar,
body.play-flower-visible #main-menu {
  visibility: hidden;
  pointer-events: none;
}

