/* =============================================================
   STAGE ENGINE — chrome (STAGE-ARCHITECTURE-PLAN.md §2, §5)
   Depends only on lemon.css tokens (--ink/--cream/--pink/--pink-dark/
   --lemon/--rule/--mute/--blush-2) — same convention as quiz-module.css.
   Brand skin (exact width/max-width, how it sits in the hero grid) is
   the page's job; this file owns the card chrome + transition mechanics
   shared by every arm that mounts a Stage.
   ============================================================= */

.stage { position: relative; }

/* ---------- compact (pre-commit) — Step 0 lives here, no card chrome,
   just whatever opts.mountStep0 renders (typically a ZIP input + CTA
   sized to match the page's hero button). Hidden once committed. ---------- */
.stage-compact { position: relative; }
.stage-compact--committed { display: none; }

/* ---------- expanded card ---------- */
.stage-card {
  background: var(--cream);
  border: 2px solid var(--ink);
  border-radius: 24px;
  box-shadow: 6px 6px 0 var(--ink);
  padding: 22px 22px 26px;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .32s cubic-bezier(.22,.61,.36,1), transform .32s cubic-bezier(.22,.61,.36,1);
}
.stage--expanded .stage-card { opacity: 1; transform: translateY(0); }
@media (min-width: 780px) { .stage-card { padding: 30px 32px 34px; } }
@media (prefers-reduced-motion: reduce) {
  .stage-card { transition: opacity .18s linear, transform 0s; }
}

/* ---------- header: back / progress / held-price chip ---------- */
.stage-header { display: flex; align-items: center; gap: 10px 14px; margin-bottom: 20px; min-height: 20px; flex-wrap: wrap; }

.stage-back {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  background: none; border: none; cursor: pointer; padding: 6px 0;
  font-family: 'Inter', sans-serif; font-weight: 600; font-size: 13px; color: var(--mute);
}
.stage-back svg { width: 15px; height: 15px; }
.stage-back:hover { color: var(--ink); }
.stage-back:focus-visible { outline: 2px solid var(--pink); outline-offset: 3px; border-radius: 4px; }

.stage-progress { flex: 1; min-width: 0; }
.stage-progress-track {
  width: 100%; height: 5px; border-radius: 999px; background: var(--rule);
  overflow: hidden; margin-bottom: 8px;
}
.stage-progress-fill {
  height: 100%; width: 0%; background: var(--pink); border-radius: 999px;
  transition: width .5s cubic-bezier(.22,.61,.36,1);
}
.stage-progress-label {
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 10.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--mute); font-weight: 600;
}

.stage-chip {
  flex-shrink: 1;
  max-width: 100%;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px; font-weight: 700; letter-spacing: .03em;
  color: var(--ink); background: var(--lemon-2, #FEF3C7);
  border: 1.5px solid var(--ink); border-radius: 999px;
  padding: 6px 12px;
  /* Task 3 fix (2026-07-22 quiz-cleanup) — the chip must never end in a
     mid-content ellipsis. white-space:nowrap + text-overflow:ellipsis
     used to clip whatever didn't fit on one line, which cut the
     accident-addon span mid-phrase even in the 380-899px band where
     carrier/detail are already dropped below (verified live: "Held:
     $389/mo · Health ProtectorGuard Premier 5000…" with the addon text
     sheared off). Wrapping instead of truncating means every width
     shows a complete phrase — the content-dropping rules below (carrier
     + detail under 900px, addon under 380px) still do the real
     narrow-width tidying; this is just the no-truncation safety net. */
  white-space: normal; overflow-wrap: break-word;
  box-sizing: border-box;
}
/* Card content is never wider than the card itself, even mid-transition
   or with a long held-price label. */
.stage-card { overflow: hidden; }

/* ---------- held-price chip — responsive truncation (F4, direct-fixes,
   2026-07-22). holdPrice() (stage.js) renders plan name/carrier/addon as
   separate spans instead of one flat string. Drop the LEAST essential
   piece first as room runs out — carrier (or a legacy flat `label`), then
   the accident-addon tag — before ever falling back to the last-resort
   2-line wrap below. Plan name is never hidden here; it's the one piece
   worth keeping visible. ---------- */
@media (max-width: 899px) {
  /* Verify residual (2026-07-22): the drop used to trigger at 560px, leaving
     a 561-899px band where a long carrier+plan string still ellipsized
     mid-word. Carrier/detail are the least essential pieces — drop them for
     the whole sub-900 range. */
  .stage-chip-carrier, .stage-chip-detail { display: none; }
}
@media (max-width: 380px) {
  .stage-chip-addon { display: none; }
  /* Task 3 fix (2026-07-22 quiz-cleanup) — the base .stage-chip rule
     already wraps (white-space:normal) instead of truncating, so this
     block no longer needs its own -webkit-line-clamp/overflow:hidden
     cap: that cap hard-cut a pathologically long plan name at 2 lines
     with NO ellipsis mark, which is its own flavor of the same
     mid-content-cut bug this task rules out. Let it wrap to as many
     lines as it genuinely needs; only the border-radius softens for
     the narrower pill shape. */
  .stage-chip { border-radius: 14px; }
}

/* ---------- exit affordance (§4.6) — stage.js toggles the [hidden]
   attribute (never CSS display) so the global "[hidden]{display:none}"
   rule at the foot of this file is the single source of truth for
   visibility; sits after the chip so it never crowds the back control
   on narrow widths. ---------- */
.stage-exit {
  display: inline-flex;
  align-items: center;
  background: none; border: none; cursor: pointer;
  padding: 6px 0; margin-left: auto; flex-shrink: 0;
  font-family: 'Inter', sans-serif; font-weight: 600; font-size: 12.5px;
  color: var(--mute); text-decoration: underline; text-underline-offset: 2px;
}
.stage-exit:hover { color: var(--ink); }
.stage-exit:focus-visible { outline: 2px solid var(--pink); outline-offset: 3px; border-radius: 4px; }

/* =============================================================
   TAKEOVER MODE — STAGE-ARCHITECTURE-PLAN.md §4.6 (owner mandate,
   2026-07-20). From reveal onward the Stage stops being a bordered
   card living in the hero's right column and becomes the page's
   protagonist: the card chrome falls away (the page's own hero section
   supplies the "own ground color" background via its onTakeoverEnter/
   onTakeoverExit hook — see homepage-38*.html), the body gets a real
   two-column grid at desktop widths (main content + a docked Nora
   drawer column), and a single-column full-width layout everywhere
   below that. Mobile never gets the docked drawer — the existing
   floating handle / bottom-sheet already IS the mobile takeover
   affordance (wireDrawerDock() in stage.js is a deliberate no-op below
   its breakpoint). ============================================= */
.stage--takeover .stage-card {
  max-width: none; width: 100%;
  background: transparent; border: none; box-shadow: none;
  padding: 0; border-radius: 0;
  /* The base .stage-card overflow:hidden is a sticky killer: position:
     sticky silently no-ops inside any overflow-hidden ancestor, which
     made the docked Nora panel scroll away with the page (owner-verified
     live: dock at -945px after a 900px scroll). Takeover strips the card
     chrome anyway, and the slide strip clips itself, so visible is safe
     here and lets .stage-drawer-dock's sticky actually stick. */
  overflow: visible;
}
.stage--takeover .stage-header { margin-bottom: 22px; }
@media (min-width: 780px) { .stage--takeover .stage-header { margin-bottom: 28px; } }

.stage-body { display: block; }
.stage--takeover .stage-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}
.stage-drawer-dock { display: none; }
@media (min-width: 900px) {
  /* Reveal/identity cap at 880px (matches plan-cards.css's own desktop
     card max-width — a wider single-column stack just gets more empty
     margin, not more information). Compare is the exception: 3 columns
     genuinely need the room, so its slide gets the full main-column
     width instead of the 880px reading cap. */
  .stage--takeover .stage-viewport { max-width: 880px; }
  .stage--takeover .stage-viewport:has(.stage-slide--active .stg-compare) { max-width: none; }
  /* V38 Takeover Width Agent — N1 fix (walkthrough-evidence 38c-rewalk):
     identity/enroll now renders a two-column layout (enroll-step.css
     .es-layout — narrow form column + a summary column carrying the
     locked-plan card), so it earns more than the single-column reading
     cap other 'result' steps get. Universal (every arm mounts the same
     .stg-identity via EnrollStep), unlike the reveal exception below
     which is opt-in per page. */
  .stage--takeover .stage-viewport:has(.stage-slide--active .stg-identity) { max-width: 1000px; }
  /* N2 fix (38c-rewalk N2/D1a): reveal's plan cards can render side by
     side instead of one 880px-capped vertical stack (see 38c's own
     #stage-reveal-cards override). Opt-in via .stg-reveal--wide (a page
     adds it only once its reveal content is actually laid out to use
     the extra room) so 38a/38b/38d — already inside the walk's <15%
     target — render byte-identical to before. */
  .stage--takeover .stage-viewport:has(.stage-slide--active .stg-reveal--wide) { max-width: none; }
}

/* ---------- NORA-SURFACE-REDESIGN.md Option C — 38b's first-class
   panel column. Opt-in via .stage-panel (only wireDrawerDock({panel:
   true}) adds it, i.e. only 38b) so every other arm's takeover stays
   plain single-column full-width — no reserved column, no dead space,
   Nora lives inline in the page content instead. ---------- */
@media (min-width: 900px) {
  .stage--takeover.stage-panel .stage-body {
    grid-template-columns: minmax(0, 1fr) 380px;
    /* B2 fix (fix-round-1, 2026-07-22 audit) — `align-items: start` means
       this row's own height is just the max of its two children's natural
       heights, and one of those children (the dock, right below) now
       carries an EXPLICIT height of its own (the D2 fix). On any slide
       whose main column is shorter than that explicit dock height — the
       compare slide's `.stg-compare-scroll` caps at 480px, way under a
       typical ~860px dock — the row collapses to exactly the dock's own
       footprint (repro: bodyRect.height === dockRect.height, live-measured
       via 8095 Playwright walk), leaving the sticky dock ~0px of legitimate
       room to travel in before it runs off the bottom of its own
       containing block (the row) and detaches, scrolling fully off-screen
       with the rest of the page on the very next scroll tick — reproduced
       identically on 38a and 38b. A `min-height` floor on the ROW itself
       (not on either column, so it never fights the shorter column's own
       layout) guarantees the sticky dock a real, content-independent
       scroll range on every panel-arm slide, not just the ones whose main
       column happens to be tall (reveal's plan-card grid already clears
       this floor on its own, so this is a no-op there). */
    min-height: calc(100vh + 200px);
  }
  /* D2 fix (2026-07-22 audit) — `max-height` alone never gives the dock a
     real, resolvable height: percentage heights (the panel's own
     `.np-root { height: 100% }`) only resolve against an ancestor with a
     SPECIFIED height, not a max-height-only auto box. Without that, the
     panel's height computes to auto, `.nd-thread { flex: 1 }` never gets a
     bounded basis, and its own `overflow-y: auto` never has anything to
     clip against — the whole panel just grows with the thread (measured
     5,976px tall live). Giving the dock a real `height` (not just a cap)
     plus `overflow: hidden` lets the panel's 100% resolve to an actual
     pixel value, so the thread's internal scroll finally activates and the
     dock itself never grows past the viewport. */
  .stage--takeover.stage-panel .stage-drawer-dock {
    display: block;
    position: sticky;
    top: 20px;
    min-height: 480px;
    height: calc(100vh - 40px);
    max-height: calc(100vh - 40px);
    overflow: hidden;
  }
}

/* Mobile (<900px) — the panel column can't sit beside the main content
   at this width, so a sticky "Plans | Ask Nora" segmented control
   swaps which normal-flow block is visible. Tabs, not sheets: the user
   chooses, nothing pounces, nothing overlays. */
.stage-mobile-tabs { display: none; }
@media (max-width: 899px) {
  .stage--takeover.stage-panel .stage-mobile-tabs {
    display: flex;
    gap: 4px;
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--cream);
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: 4px;
    margin-bottom: 16px;
  }
  .stage-tab {
    flex: 1;
    min-height: 40px;
    border: none;
    border-radius: 999px;
    background: none;
    color: var(--ink);
    font-family: 'Inter', sans-serif;
    font-weight: 700;
    font-size: 13px;
    cursor: pointer;
  }
  .stage-tab--active { background: var(--ink); color: var(--cream); }
  .stage-tab:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
  .stage--takeover.stage-panel .stage-drawer-dock { display: none; }
  .stage--takeover.stage-panel.stage-mobile-view-nora .stage-viewport { display: none; }
  /* B4 fix (verify-round-3, 2026-07-22 audit) — the D2/B3 fixes above gave
     `.stage-drawer-dock` a real, resolvable height, but never gave the ROW
     it sits in (`.stage-body`) any height of its own beyond that: with no
     min-height here, the row's own height is just the dock's explicit
     height (live-measured: bodyRect.height === dockRect.height === 724px)
     — the exact same zero-sticky-travel bug B2 fixed on desktop, just on
     the mobile Ask Nora tab instead. `position: sticky` needs its
     containing block (this row) to be TALLER than the sticky element
     itself, or there is no range for it to travel across before it hits
     the row's own bottom edge and behaves like a plain static child — so
     the instant the composer or Send button is focused, the browser's
     native scroll-into-view has nothing sticky to lean on and drags the
     whole page (live-measured: 450-850px) down past the row and onto the
     legal footer beneath it. Mirrors the desktop B2 fix exactly, just
     scoped to this mobile tab and sized against the mobile dock's own
     `top: 68px` + `calc(100vh - 88px)` numbers below instead of the
     desktop dock's `calc(100vh - 40px)`. */
  .stage--takeover.stage-panel.stage-mobile-view-nora .stage-body {
    min-height: calc(100vh + 150px);
  }
  /* B3 fix (fix-round-2, 2026-07-22 audit) — this rule used to set only
     `display: block`, the exact D2 gap on the one breakpoint where the
     docked-panel architecture hands off to the tab pattern: with no
     height/overflow rule, `.np-root { height: 100% }` (nora-drawer.css)
     resolves to auto against this auto-height ancestor, `.nd-thread`'s
     `flex: 1` never gets a bounded basis, and the panel grows with the
     thread (measured 6,545px after one long answer, composer ~6,657px
     down the page). Mirrors the desktop dock fix above: `top` clears the
     sticky tabs bar's own footprint (52px tall + 16px margin-bottom, see
     `.stage-mobile-tabs` above) so the dock tucks in right below it once
     both are pinned, and `height`/`max-height` reserve a further 20px of
     breathing room at the bottom — same top/bottom symmetry as the
     desktop `calc(100vh - 40px)` rule. */
  .stage--takeover.stage-panel.stage-mobile-view-nora .stage-drawer-dock {
    display: block;
    position: sticky;
    top: 68px;
    height: calc(100vh - 88px);
    max-height: calc(100vh - 88px);
    overflow: hidden;
  }
}

/* ---------- NORA-SURFACE-REDESIGN.md Option A — the quiet helper line
   under the Stage during question steps (stage.js's syncHelper, opt-in
   via Stage.init({nora:{arm,locale}})). Sits below the card body,
   never inside it, so it never competes with the question's own copy.
   ---------- */
.stage-helper { margin-top: 16px; }

/* ---------- viewport / track / slides — the horizontal-slide,
   measured-height grammar shared by every step. ---------- */
.stage-viewport {
  overflow: hidden;
  overflow-anchor: none; /* Loop4 fix agent — walkthrough-evidence/loop4-38d.
    animateHeight() (stage.js) intentionally grows/shrinks this element's own
    height on every step change (e.g. sex's shorter card -> pregnancy's taller
    one). The browser's scroll-anchoring heuristic treats that as exactly the
    kind of above-the-fold size change it exists to compensate for, and can
    intermittently nudge window scroll by the height delta to "hold" a node
    inside this viewport in place — which, on a page with a sticky .tk-nav,
    reads as the header sliding down over the incoming question's first line.
    This resize is already self-contained and already correctly handled by
    Stage's own height transition; opting this element out of anchoring stops
    the browser from separately, unpredictably re-scrolling the page for it. */
  transition: height .42s cubic-bezier(.22,.61,.36,1);
}
.stage-track {
  display: flex;
  align-items: flex-start;
  width: 100%;
  transition: transform .42s cubic-bezier(.22,.61,.36,1);
  will-change: transform;
}
.stage-slide {
  flex: 0 0 100%;
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
}
/* Loop1 F2 (walkthrough-evidence/loop1-38b) — .stage-viewport's own
   overflow:hidden only clips at the OUTER edges of the whole horizontal
   track; it does nothing to stop one slide's own decoration (e.g. a plan
   card's `6px 6px 0 0` box-shadow) from painting into the NEIGHBORING
   slide's on-screen space, since adjacent slides share a boundary well
   inside that outer clip. Reproduced live: the reveal step's recommended
   plan card sits immediately to the left of compare in the track: its
   ink box-shadow (offset right+down) bled 6px across that shared
   boundary and rendered as a solid dark-navy vertical bar over the
   compare step's category rows on mobile (375px). Clip every INACTIVE
   slide to its own box — never the active one, so nothing about the
   current step's own rendering (tooltips/popovers that may intentionally
   extend past the slide edge) changes.
   ------------------------------------------------------------- */
.stage-slide:not(.stage-slide--active) {
  overflow: hidden;
}

/* Loop2 D2 (38c, walkthrough-evidence/loop2-38c) — the F2 fix above only
   ever addressed a slide's own decoration (box-shadow) painting past its
   box; it did nothing about the outgoing and incoming slide's own TEXT
   both being fully opaque and fully readable during the ~150-350ms
   window mid-transition where translateX has each only partially
   on-screen (verified live: two questions' full text overlapping,
   mid-word-clipped at both card edges, chip rows doubled). A first pass
   here made outgoing/incoming crossfade opacity in perfect lockstep (both
   .42s, symmetric) — but Loop3 D3 (walkthrough-evidence/loop3-38a)
   reproduced the SAME garbled overlap on two independent pairs
   (household->sex, sex->pregnancy): a true 50/50 symmetric crossfade still
   spends its whole middle third with BOTH slides substantially opaque at
   once, which reads as two questions jumbled together rather than a clean
   dissolve, however the opacity curve is eased.
   Fix: make the fade asymmetric instead of symmetric. The OUTGOING slide
   fades out fast (.16s, no delay) — effectively gone well before the
   translateX motion finishes. The INCOMING slide's fade-in doesn't even
   start until the outgoing one has finished disappearing (.16s delay),
   then fades in over the remainder of the same .42s window the transform
   already runs. The two are never both substantially visible at once —
   by the time the incoming slide is legible, the outgoing one is already
   fully transparent. Class toggle in paintSlidePosition() still flips
   synchronously with the transform start; only the transition timing
   changed, no JS needed. */
.stage-slide {
  transition: opacity .16s cubic-bezier(.22,.61,.36,1);
}
.stage-slide:not(.stage-slide--active) {
  opacity: 0;
}
.stage-slide.stage-slide--active {
  transition: opacity .26s cubic-bezier(.22,.61,.36,1) .16s;
}

/* ---------- reduced motion: crossfade, never translate ---------- */
.stage--reduced .stage-viewport { transition: none; }
.stage--reduced .stage-track {
  display: block;
  transform: none !important;
  transition: none;
}
.stage--reduced .stage-slide { display: none; width: 100%; }
.stage--reduced .stage-slide.stage-slide--active {
  display: block;
  animation: stage-fade .22s linear;
}
@keyframes stage-fade { from { opacity: 0; } to { opacity: 1; } }

[hidden] { display: none !important; }
