/* ============================================================
 * APEX CLIENT CRM (James Demo) · MOTION & TRANSITIONS · CANONICAL
 *
 * Every shared keyframe and transition utility used across the 48
 * client screens lives here. This is the single source of truth for
 * MOTION. 02-primitives.css skins components and 03-layout.css places
 * them, but the TIMELINE of every shared animation is defined here, so
 * a tweak to a cadence happens in exactly one place.
 *
 * Mirrors the locked Apex Sales OS motion set
 * (donor: pages/call.html orb-pulse + rec-pulse, the home.html cockpit
 *  fades, and the wizard one-action-at-a-time handoff slides).
 *
 * Keyframes OWNED here (referenced from 02-primitives.css per its header):
 *   orbRing, orbBar, orb-pulse, rec-pulse,
 *   celebration-heavy-fade, route-fade, stage-cross
 * Plus the journey motion the F1->F10 client flow needs:
 *   orb-bloom, the side-rail/stage handoff slides, wizard step slides.
 *
 * Keyframes NOT owned here (do not redefine):
 *   tickPulse, livePulse, dotPulse, shimmer, spin   -> 01-base.css
 *   holdArc, washSweep, wfBarPulse                  -> 02-primitives.css (component-coupled)
 *
 * orbRing / orbBar / orb-pulse / rec-pulse are also written into
 * 02-primitives.css (component-coupled) with byte-identical timelines.
 * @keyframes of identical name + identical body resolve identically
 * regardless of cascade order, so listing them here as the canonical
 * source NEVER conflicts. If a cadence changes, change BOTH in lockstep.
 *
 * BRAND LAWS BAKED IN (do not break):
 *   - Cool-spectrum / purple only. Primary #635bff. Live pulse purple.
 *   - Done / success motion = indigo #4f46e5. NEVER traffic-light green.
 *   - Stuck / error / off = gunmetal #1a1f36. NEVER red. NO red here.
 *   - ZERO emoji. Real Apex orb. No-scroll 1440x900. Commas, no em-dashes.
 *   - No god / channel language anywhere.
 *
 * Color: only var(--*). Exception: the locked purple/white channels
 *   rgba(99,91,255,*), rgba(122,115,255,*), rgba(255,255,255,*) where no
 *   token carries the precise alpha a glow / wash / ring needs.
 *
 * Durations come from 00-tokens.css so they stay in sync:
 *   --t (160ms micro)       --dur-hold (1500ms)
 *   --dur-route-fade (200ms) --dur-celebration (400ms)
 *   --dur-orb-pulse (2400ms) --dur-rec-pulse (1600ms)
 *
 * Owner: Terminal 0 (shared-foundation / 04-transitions).
 * READ-ONLY to every other terminal. Do not modify outside this file.
 *
 * Sections (in order):
 *   1.  Route fade            (SPA hash-router screen swap)
 *   2.  Stage cross           (light cockpit <-> dark video stage)
 *   3.  Celebration heavy     (F10 training completion overlay)
 *   4.  Orb motion            (orbRing, orbBar, orb-pulse, orb-bloom)
 *   5.  Recording pulse       (live / on-air purple pulse)
 *   6.  Side-rail / stage handoff slides
 *   7.  Wizard step slides    (one-action-at-a-time advance / back)
 *   8.  Generic enter helpers (fade-in, rise-in, drawer/scrim)
 *   9.  GPU hints + reduced-motion guard
 * ============================================================ */


/* ============================================================
 * 1 · ROUTE FADE
 * The hash-router (router.js) swaps one screen for the next inside
 * the .frame-body. It toggles .is-leaving on the outgoing view and
 * .is-entering on the incoming one. 200ms, no layout shift: the
 * outgoing view is absolutely stacked so the two never reflow.
 *
 *   router.js convention:
 *     1. mark current view .route-view.is-leaving
 *     2. mount next view  .route-view.is-entering
 *     3. on animationend of the entering view, drop both flags
 * ============================================================ */
.route-view{
  /* the live, settled screen */
  opacity:1;
  will-change:opacity, transform;
}
.route-view.is-entering{
  animation:route-fade var(--dur-route-fade) var(--ease) both;
}
.route-view.is-leaving{
  /* stack the outgoing screen so enter + leave overlap with no jump */
  position:absolute;
  inset:0;
  pointer-events:none;
  animation:route-fade-out var(--dur-route-fade) var(--ease) both;
}

/* Static fallback class so a page can request the enter without the
 * router (e.g. a freshly loaded standalone screen fading itself in). */
.fx-route-fade{ animation:route-fade var(--dur-route-fade) var(--ease) both; }

@keyframes route-fade{
  from{ opacity:0; transform:translateY(6px); }
  to  { opacity:1; transform:none; }
}
@keyframes route-fade-out{
  from{ opacity:1; transform:none; }
  to  { opacity:0; transform:translateY(-4px); }
}


/* ============================================================
 * 2 · STAGE CROSS
 * The cross-fade when a route transitions BETWEEN the light cockpit
 * surfaces and the dark video stage (waiting-room -> call, call ->
 * payment). The theme swap on html[data-theme] already cross-fades
 * background/color over 300ms (00-tokens.css). This adds the content
 * dissolve so the stage does not pop. Apply to the .frame-wrap or the
 * .stage-host that is changing surface.
 *
 *   router.js: add .is-crossing to the frame for one cycle when the
 *   incoming route's data-surface differs from the outgoing route's.
 * ============================================================ */
.is-crossing,
.fx-stage-cross{
  animation:stage-cross 320ms var(--ease) both;
}

@keyframes stage-cross{
  0%   { opacity:0;    transform:scale(.992); filter:saturate(.9); }
  100% { opacity:1;    transform:none;        filter:none; }
}


/* ============================================================
 * 3 · CELEBRATION (HEAVY)
 * The F10 training / journey-complete overlay shell
 * (section-celebration.js). A weighty 400ms fade-and-rise for the
 * scrim, then the panel blooms in. Purple + indigo only, no confetti
 * red/green. The orb inside the panel uses orb-bloom (section 4).
 *
 *   section-celebration.js mounts:
 *     .celebration-overlay > .celebration-panel
 *   add .is-open to play in, remove it (with .is-closing) to play out.
 * ============================================================ */
.celebration-overlay{
  position:absolute;
  inset:0;
  z-index:60;
  display:grid;
  place-items:center;
  background:
    radial-gradient(120% 120% at 50% 40%, rgba(99,91,255,.16), transparent 70%),
    var(--modal-scrim);
  opacity:0;
  pointer-events:none;
}
.celebration-overlay.is-open{
  opacity:1;
  pointer-events:auto;
  animation:celebration-heavy-fade var(--dur-celebration) var(--ease) both;
}
.celebration-overlay.is-closing{
  animation:celebration-heavy-out 260ms var(--ease) both;
}
.celebration-panel{
  /* the card that lands inside the overlay */
  transform-origin:center;
}
.celebration-overlay.is-open .celebration-panel{
  animation:celebration-panel-bloom 520ms cubic-bezier(.16,.84,.34,1) 60ms both;
}

@keyframes celebration-heavy-fade{
  from{ opacity:0; }
  to  { opacity:1; }
}
@keyframes celebration-heavy-out{
  from{ opacity:1; }
  to  { opacity:0; }
}
@keyframes celebration-panel-bloom{
  0%   { opacity:0; transform:translateY(18px) scale(.96); }
  60%  { opacity:1; transform:translateY(0)    scale(1.012); }
  100% { opacity:1; transform:translateY(0)    scale(1); }
}


/* ============================================================
 * 4 · ORB MOTION
 * The canonical Apex orb (apex-orb.js / .apex-orb) is driven by
 * data-state in 02-primitives.css; those rules reference these
 * keyframes. orbRing + orbBar match 02-primitives byte-for-byte
 * (see header note). orb-pulse matches the donor call.html. orb-bloom
 * is the one-shot entrance the orb plays when a wizard step mounts.
 * ============================================================ */

/* The expanding focus ring around the orb (idle breathing / listening). */
@keyframes orbRing{
  0%   { transform:scale(1);    opacity:0.6; }
  100% { transform:scale(1.25); opacity:0;   }
}

/* The five voice bars inside the orb core. */
@keyframes orbBar{
  0%, 100% { height:12px; }
  50%      { height:36px; }
}

/* The soft radial glow that pulses behind a current node / orb.
 * Matches donor pages/call.html @keyframes orb-pulse exactly. */
@keyframes orb-pulse{
  0%, 100% { opacity:0; transform:scale(.92); }
  50%      { opacity:1; transform:scale(1.18); }
}

/* One-shot orb entrance: the orb scales up from a tucked state and the
 * glow blooms, used when a wizard / waiting-room mounts the big orb. */
.fx-orb-bloom,
.apex-orb.is-blooming{
  animation:orb-bloom 640ms cubic-bezier(.16,.84,.34,1) both;
}
@keyframes orb-bloom{
  0%   { opacity:0; transform:scale(.84); }
  55%  { opacity:1; transform:scale(1.04); }
  100% { opacity:1; transform:scale(1); }
}


/* ============================================================
 * 5 · RECORDING PULSE
 * The live / on-air purple pulse on the REC dot and the listening orb
 * ring. Purple only, NEVER red. Matches donor call.html @keyframes
 * rec-pulse exactly so the shared dot and the call-page dot are one.
 * ============================================================ */
@keyframes rec-pulse{
  0%  { box-shadow:0 0 0 0  rgba(122,115,255,.55); }
  70% { box-shadow:0 0 0 8px rgba(122,115,255,0);   }
  100%{ box-shadow:0 0 0 0  rgba(122,115,255,0);   }
}


/* ============================================================
 * 6 · SIDE-RAIL / STAGE HANDOFF SLIDES
 * On the video stage (03-layout .stage-shell), the closer-pushed
 * payload rail can appear / collapse, and a pushed payload (pricing,
 * doc, payment) slides into the rail feed. These are the handoff
 * motions between the stage and its side-rail.
 *
 *   sdk-bridge.js showPushPayload() appends a .rail-card and adds
 *   .is-arriving for one cycle. Hiding the rail toggles
 *   .stage-shell[data-rail] which we animate via the rail wrapper.
 * ============================================================ */

/* A freshly pushed payload card sliding into the rail feed from the
 * stage side (right), with the pp-toast feel. */
.rail-card.is-arriving{
  animation:rail-card-in 300ms cubic-bezier(.16,.84,.34,1) both;
}
@keyframes rail-card-in{
  0%   { opacity:0; transform:translateX(22px); }
  100% { opacity:1; transform:none; }
}

/* The whole side-rail revealing / handing off when the closer opens it. */
.side-rail.is-revealing{
  animation:side-rail-slide 280ms var(--ease) both;
}
@keyframes side-rail-slide{
  0%   { opacity:0; transform:translateX(28px); }
  100% { opacity:1; transform:none; }
}

/* The stage itself easing over when the rail opens / closes, so the
 * video does not snap as the grid column changes. */
.stage.is-handoff{
  animation:stage-handoff 280ms var(--ease) both;
}
@keyframes stage-handoff{
  0%   { transform:scale(.985); }
  100% { transform:none; }
}

/* The pushed-payload toast (sdk-bridge pp-toast) dropping from the top
 * of the stage to announce new content, then it is the rail's job. */
.pp-toast.is-in{
  animation:pp-toast-in 260ms cubic-bezier(.16,.84,.34,1) both;
}
.pp-toast.is-out{
  animation:pp-toast-out 200ms var(--ease) both;
}
@keyframes pp-toast-in{
  0%   { opacity:0; transform:translateY(-12px); }
  100% { opacity:1; transform:none; }
}
@keyframes pp-toast-out{
  0%   { opacity:1; transform:none; }
  100% { opacity:0; transform:translateY(-8px); }
}


/* ============================================================
 * 7 · WIZARD STEP SLIDES
 * The one-action-at-a-time client flows (sign-in, waiting-room,
 * onboarding, identity) advance a single .wizard-card body. journey.js
 * adds .step-next (forward) or .step-back (return) to the incoming
 * step. Forward slides in from the right, back slides in from the left.
 * The leaving step uses the -out variants and is absolutely stacked by
 * 03-layout so there is no reflow and no scroll.
 * ============================================================ */
.wizard-step.step-next  { animation:wizard-next-in  300ms cubic-bezier(.16,.84,.34,1) both; }
.wizard-step.step-back  { animation:wizard-back-in  300ms cubic-bezier(.16,.84,.34,1) both; }
.wizard-step.is-leaving-next{ animation:wizard-next-out 240ms var(--ease) both; }
.wizard-step.is-leaving-back{ animation:wizard-back-out 240ms var(--ease) both; }

@keyframes wizard-next-in{
  0%   { opacity:0; transform:translateX(26px); }
  100% { opacity:1; transform:none; }
}
@keyframes wizard-next-out{
  0%   { opacity:1; transform:none; }
  100% { opacity:0; transform:translateX(-22px); }
}
@keyframes wizard-back-in{
  0%   { opacity:0; transform:translateX(-26px); }
  100% { opacity:1; transform:none; }
}
@keyframes wizard-back-out{
  0%   { opacity:1; transform:none; }
  100% { opacity:0; transform:translateX(22px); }
}


/* ============================================================
 * 8 · GENERIC ENTER HELPERS
 * Small reusable entrances so page-local code never reinvents motion.
 * Cockpit cards rise in, KPIs fade in, drawers slide, scrims fade.
 * Use the .fx-* class or the [data-fx] attribute.
 * ============================================================ */
.fx-fade-in,  [data-fx="fade"] { animation:fx-fade 240ms var(--ease) both; }
.fx-rise-in,  [data-fx="rise"] { animation:fx-rise 280ms cubic-bezier(.16,.84,.34,1) both; }
.fx-scale-in, [data-fx="scale"]{ animation:fx-scale 220ms cubic-bezier(.16,.84,.34,1) both; }

/* Staggered cockpit cards: set --i on each child for a cascade. */
[data-fx-stagger] > *{
  animation:fx-rise 320ms cubic-bezier(.16,.84,.34,1) both;
  animation-delay:calc(var(--i, 0) * 45ms);
}

@keyframes fx-fade{
  from{ opacity:0; }
  to  { opacity:1; }
}
@keyframes fx-rise{
  from{ opacity:0; transform:translateY(10px); }
  to  { opacity:1; transform:none; }
}
@keyframes fx-scale{
  from{ opacity:0; transform:scale(.97); }
  to  { opacity:1; transform:none; }
}

/* Drawer + scrim (the drawer-scrim primitive, skinned in 02-primitives).
 * The scrim fades, the drawer slides from the right edge of the frame. */
.drawer-scrim.is-open{ animation:scrim-in 200ms var(--ease) both; }
.drawer-scrim.is-closing{ animation:scrim-out 180ms var(--ease) both; }
.drawer-panel.is-open{ animation:drawer-in 300ms cubic-bezier(.16,.84,.34,1) both; }
.drawer-panel.is-closing{ animation:drawer-out 240ms var(--ease) both; }

@keyframes scrim-in { from{ opacity:0; } to{ opacity:1; } }
@keyframes scrim-out{ from{ opacity:1; } to{ opacity:0; } }
@keyframes drawer-in{
  from{ transform:translateX(100%); }
  to  { transform:none; }
}
@keyframes drawer-out{
  from{ transform:none; }
  to  { transform:translateX(100%); }
}


/* ============================================================
 * 9 · GPU HINTS + REDUCED-MOTION GUARD
 * Promote the animated surfaces so the 1440x900 frame never janks,
 * and honor prefers-reduced-motion: keep the orb's quiet life-sign but
 * collapse the slides / blooms to instant cross-fades.
 * ============================================================ */
.route-view,
.celebration-overlay,
.celebration-panel,
.wizard-step,
.rail-card,
.drawer-panel,
.apex-orb,
.apex-orb-ring{
  backface-visibility:hidden;
}

@media (prefers-reduced-motion: reduce){
  /* Collapse the big entrances to a plain, quick fade. The orb keeps a
   * gentle breathing ring so the app still feels alive, but no scaling. */
  .route-view.is-entering,
  .route-view.is-leaving,
  .is-crossing,
  .fx-stage-cross,
  .fx-route-fade,
  .celebration-panel,
  .celebration-overlay.is-open .celebration-panel,
  .fx-orb-bloom,
  .apex-orb.is-blooming,
  .rail-card.is-arriving,
  .side-rail.is-revealing,
  .stage.is-handoff,
  .pp-toast.is-in,
  .pp-toast.is-out,
  .wizard-step.step-next,
  .wizard-step.step-back,
  .wizard-step.is-leaving-next,
  .wizard-step.is-leaving-back,
  .fx-fade-in, .fx-rise-in, .fx-scale-in,
  [data-fx], [data-fx-stagger] > *,
  .drawer-panel.is-open, .drawer-panel.is-closing{
    animation:fx-fade 120ms linear both !important;
    transform:none !important;
  }

  /* Keep the orb breathing but slow and still. Keep the live rec pulse
   * faint so on-air state stays legible without strobing. */
  .apex-orb-ring{ animation-duration:3600ms !important; }
  .apex-orb-bar{ animation:none !important; }
}
