/* ==========================================================================
   Stage — persistent phone, green morph panel, floating CTA, pause toggle.
   Also holds the layer rules the scroll engine depends on.

   Layer order (all fixed, full viewport):
     #stage-bg    0   green morph panel, behind the deck
     #scroll-root 1   pinned deck sections
     #stage       4   phone, floating CTA, pause toggle
     #after-deck  6   grid + footer
     #site-nav   50

   Deck sections must stay transparent: the stage owns every full-bleed
   colour. `body.stage-panel-behind` enforces that while a panel is live.
   ========================================================================== */

html, body {
  overscroll-behavior: none;
}
body {
  /* vertical gestures belong to the engine; pinch zoom stays with the user */
  touch-action: pan-x pinch-zoom;
}

#scroll-root { z-index: 1; }
#stage-bg    { z-index: 0; }
#stage       { z-index: 4; }
#after-deck  { z-index: 6; }

.deck-section.active { will-change: opacity, transform; }

/* --------------------------------------------------------------------------
   green morph panel
   -------------------------------------------------------------------------- */

#stage-bg {
  position: fixed;
  inset: 0;
  pointer-events: none;
  transform: translate3d(0, var(--deck-y, 0px), 0);
  opacity: var(--deck-o, 1);
}
#stage-bg.is-front { z-index: 3; }

.stage-panel {
  position: absolute;
  inset: 0;
  background-color: var(--panel-color, var(--green));
  opacity: var(--panel-opacity, 0);
  /* one rounded rectangle that morphs: full bleed -> inset panel -> phone */
  clip-path: inset(
    var(--panel-iy, 0vh) var(--panel-ix, 0vw)
    var(--panel-iy, 0vh) var(--panel-ix, 0vw)
    round var(--panel-radius, 0px)
  );
  transition:
    clip-path 90ms linear,
    background-color 360ms var(--ease-out),
    opacity 420ms var(--ease-out);
  will-change: clip-path, opacity;
}

/* --------------------------------------------------------------------------
   media zone — the phone that never remounts
   -------------------------------------------------------------------------- */

#stage {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.stage-media {
  position: absolute;
  inset: 0;
  transform: translate3d(0, calc(var(--deck-y, 0px) + var(--media-y, 0px)), 0);
  opacity: var(--deck-o, 1);
}

/* visibility layer: long fade when a section hands the phone off */
.stage-phone-wrap {
  position: absolute;
  inset: 0;
  opacity: 1;
  transform: scale(1);
  transform-origin: 50% 50%;
  transition:
    opacity 520ms var(--ease-out),
    transform 620ms var(--ease-out);
  will-change: opacity, transform;
}
.stage-phone-wrap.is-hidden {
  opacity: 0;
  transform: scale(0.94);
}

/* Resting phone size: the smaller, fully visible frame from the reference
   (~280x606 @1280). Sections animate --ph-s around 1 and never touch this.

   The hero needs the full 400x866 frame that runs past the fold. It asks for it
   through --ph-hero-s (stage.setPhoneHeroScale), which it ramps from 1/0.70
   back to 1 across its own docking window — see sections/01-hero.js. The size
   is therefore continuous through the hero -> plus cut, which docs/SPEC.md
   requires ("the PHONE PERSISTS in place"); making the base itself a per-section
   value stepped the frame 867 -> 607px in the single engine frame that flipped
   body[data-section].

   The gate below is what keeps that ramp honest. --ph-hero-s is written inline
   and only the DOMINANT section drives the stage, so a jump that skips the hero
   — reduced motion, engine.scrollTo, a resize restore — would otherwise leave a
   later section holding a hero-sized frame. Gating on the active section makes
   the extra size a pure function of where the reader is, and costs nothing at
   the cut: the ramp reaches 1 at p 0.9, well before dominance changes at p 1. */
:root { --ph-base-s: 0.70; }
body:not([data-section]) .stage-phone,
body[data-section="hero"] .stage-phone { --ph-hero: var(--ph-hero-s, 1); }

/* pose layer: short transition so scroll scrubbing only gets smoothed.
   Centred with margins, not grid alignment: the phone is taller than the
   viewport, and grid/flex centring clamps an overflowing item to the top. */
.stage-phone {
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: calc(var(--phone-w) / -2);
  margin-top: calc(var(--phone-h) / -2);
  width: var(--phone-w);
  height: var(--phone-h);
  border-radius: var(--phone-radius);
  display: grid;
  place-items: center;
  transform:
    translate3d(var(--ph-x, 0px), var(--ph-y, 0px), 0)
    scale(calc(var(--ph-base-s, 1) * var(--ph-s, 1) * var(--ph-hero, 1)))
    rotate(var(--ph-r, 0deg));
  opacity: var(--ph-o, 1);
  transition:
    transform 110ms linear,
    opacity 260ms var(--ease-out);
  will-change: transform;
}
.stage-phone > * {
  width: 100%;
  height: 100%;
}

/* fallback frame, used only if js/phone.js is missing */
.stage-phone-fallback {
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--black);
  border-radius: var(--phone-radius);
  padding: 0.63vw;
  box-shadow: 0 2.4vw 5vw rgba(0, 0, 0, 0.22);
}
.stage-phone-slot {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: calc(var(--phone-radius) - 0.63vw);
  background: var(--white);
}

/* screen crossfade used by the fallback swap path */
.stage-screen {
  position: absolute;
  inset: 0;
  opacity: 1;
  transition: opacity 400ms var(--ease-out);
}
.stage-screen.is-entering { opacity: 0; }
.stage-screen.is-leaving { opacity: 0; }

/* --------------------------------------------------------------------------
   floating "Sign up" pill — appears once the reader leaves the hero
   -------------------------------------------------------------------------- */

/* Bottom offset (QA-18): the non-hero phone bottom sits at
   50vh + var(--phone-h) * 0.70 / 2 — about 57px above the viewport edge at
   1280x720 — so any offset above ~2vh parks the pill on the frame's bottom
   bezel at 16:9. max(2vh, 16px) keeps the pill clear of the device (worst
   case ~4px of corner-radius contact) without pinning it to the edge on
   short windows. */
.stage-cta {
  position: fixed;
  left: 50%;
  bottom: max(2vh, 16px);
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  height: var(--pill-h);
  padding: 0 1.7vw;
  border-radius: 999px;
  background: var(--green);
  color: var(--black);
  font-size: var(--fs-nav);
  font-weight: 500;
  white-space: nowrap;
  /* A tight contact shadow plus a wide, very soft one: green on white has
     almost no edge contrast, and the ambient layer is what lifts the pill off
     whatever section is passing underneath it. */
  box-shadow:
    0 0.15vw 0.4vw rgba(0, 0, 0, 0.1),
    0 0.6vw 1.5vw rgba(0, 0, 0, 0.14),
    0 1.6vw 3.6vw rgba(0, 0, 0, 0.12);
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-50%, 140%, 0) scale(0.96);
  transition:
    opacity 420ms var(--ease-out),
    transform 560ms var(--ease-out);
}
.stage-cta.is-on {
  opacity: 1;
  transform: translate3d(-50%, 0, 0) scale(1);
  pointer-events: auto;
}
.stage-cta:hover { transform: translate3d(-50%, -2px, 0) scale(1); }
.stage-cta:hover .stage-cta-arrow { transform: translateX(3px); }
.stage-cta-arrow {
  display: inline-block;
  transition: transform 320ms var(--ease-out);
}

/* --------------------------------------------------------------------------
   pause toggle — bottom right of the media zone
   -------------------------------------------------------------------------- */

.stage-pause {
  position: fixed;
  bottom: 8vh;
  left: calc(50% + var(--phone-w) / 2 + 0.9vw);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--black);
  background: rgba(255, 255, 255, 0.55);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
  pointer-events: none;
  opacity: 0;
  transform: scale(0.9);
  transition:
    opacity 340ms var(--ease-out),
    transform 340ms var(--ease-out),
    background-color 240ms var(--ease-out);
}
.stage-pause.is-on {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}
.stage-pause:hover { background: rgba(255, 255, 255, 0.78); }
.stage-pause svg { width: 16px; height: 16px; display: block; }

/* --------------------------------------------------------------------------
   menu overlay — the stage furniture is fixed and floats above the deck, so it
   would sit on top of a full-screen menu. `body.menu-open` is the switch; the
   menu itself ships with the nav, and this guard is written to be harmless
   until then (no element carries the class yet).
   -------------------------------------------------------------------------- */

body.menu-open .stage-cta,
body.menu-open .stage-cta.is-on {
  opacity: 0;
  pointer-events: none;
  transform: translate3d(-50%, 140%, 0) scale(0.96);
}
body.menu-open .stage-pause,
body.menu-open .stage-pause.is-on {
  opacity: 0;
  pointer-events: none;
}

/* the decorative pause: freeze every CSS animation on stage and deck.
   The stage root carries both `.is-paused` and `.paused`. */
#stage.is-paused .stage-media *,
body.stage-paused #scroll-root *,
body.stage-paused #stage-bg * {
  animation-play-state: paused !important;
}

/* --------------------------------------------------------------------------
   safety net — a panel behind the deck must not be covered by a section that
   paints its own opaque background. Only active while such a panel is live.
   -------------------------------------------------------------------------- */

body.stage-panel-behind .deck-section {
  background-color: transparent !important;
}

/* --------------------------------------------------------------------------
   reduced motion — morphs become plain crossfades, no easing lag
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .stage-panel {
    transition: opacity 200ms linear, background-color 200ms linear;
  }
  .stage-phone {
    transition: opacity 200ms linear;
  }
  .stage-phone-wrap {
    transition: opacity 200ms linear;
    transform: none;
  }
  .stage-phone-wrap.is-hidden { transform: none; }
  .stage-cta,
  .stage-pause {
    transition: opacity 200ms linear;
  }
  .stage-cta { transform: translate3d(-50%, 0, 0); }
  .stage-cta.is-on { transform: translate3d(-50%, 0, 0); }
  .stage-cta:hover { transform: translate3d(-50%, 0, 0); }
  body.menu-open .stage-cta,
  body.menu-open .stage-cta.is-on { transform: translate3d(-50%, 0, 0); }
  .stage-pause,
  .stage-pause.is-on { transform: none; }
  .stage-screen { transition: opacity 200ms linear; }
}

/* --------------------------------------------------------------------------
   mobile
   -------------------------------------------------------------------------- */

@media (max-width: 768px) {
  :root {
    --phone-w: 58vw;
    --phone-h: 125.6vw;
    --phone-radius: 8vw;
  }
  .stage-media { --media-y: 2vh; }

  /* The floating pill is retired on a handset.
     It duplicates the nav "Sign up" pill, which is never hidden at this width
     (nav.css only drops "Log in"), so nothing is lost. What it cost was real:
     the sections are pinned to the viewport and base.css stacks their body
     block against the bottom edge, so a fixed pill parked bottom-centre lands
     on the section's own CTA in plus, tags, save and security, and on the card
     fan at tablet width. Two interactive controls on top of each other is not
     a layering problem the sections can solve — the frame is 58vw wide here and
     there is no gutter left to move anything into. */
  .stage-cta,
  .stage-cta.is-on { display: none; }
  .stage-pause {
    left: auto;
    right: 5vw;
    bottom: calc(var(--nav-margin) + 56px);
  }
}
