/* ==========================================================================
   Conexus Thrive — motion system
   See docs/brand/MOTION_AND_INTERACTION.md for the reasoning behind each tier.

   Two hard rules, enforced throughout:
   1. Only transform / opacity / filter / box-shadow animate. Never width,
      height, top, left, or margin — those trigger layout on every frame.
   2. Everything degrades to "instant, final state" under
      prefers-reduced-motion. Nothing is reveal-gated on JS: the .reveal
      class only *hides* content once JS has confirmed it can restore it.
   ========================================================================== */

:root{
  /* Durations — short enough to feel like response, not playback. */
  --t-tap:90ms;      /* press feedback */
  --t-fast:150ms;    /* hover */
  --t-base:240ms;    /* state change */
  --t-slow:380ms;    /* accordion, larger surfaces */
  --t-reveal:620ms;  /* scroll entrance */

  /* Easings. Decelerate on entry, accelerate on exit — motion arrives
     under control and leaves eagerly. */
  --ease-out:cubic-bezier(.16,.84,.32,1);
  --ease-in:cubic-bezier(.5,0,.9,.4);
  --ease-soft:cubic-bezier(.4,0,.2,1);
  --ease-spring:cubic-bezier(.34,1.4,.64,1); /* overshoots ~8% — ticks and chevrons only */

  --lift:-2px;       /* the single hover lift distance, used everywhere */
}

/* ==========================================================================
   TIER 1 — Scroll entrance
   JS adds .js-motion to <html>, which is what arms the hidden state. Without
   JS the content is simply visible. Opacity + transform only, so no reflow
   and no layout shift: the element occupies its final box the whole time.
   ========================================================================== */

.js-motion .reveal{
  opacity:0;
  transform:translate3d(0,16px,0);
  transition:opacity var(--t-reveal) var(--ease-out),
             transform var(--t-reveal) var(--ease-out);
}
.js-motion .reveal.in{ opacity:1; transform:none; }

/* Stagger: children of a revealed group arrive in sequence. The delay is a
   custom property set by JS so the cascade works for any child count. */
.js-motion .reveal-group > *{
  opacity:0;
  transform:translate3d(0,18px,0);
  transition:opacity var(--t-reveal) var(--ease-out),
             transform var(--t-reveal) var(--ease-out);
  transition-delay:var(--d,0ms);
}
.js-motion .reveal-group.in > *{ opacity:1; transform:none; }

/* Last-resort visibility. JS adds .io-armed the moment the reveal observer is
   live; if that never happens (script error, dead embed context) this
   delayed animation brings every hidden element back on its own, so content
   can never be stranded invisible. */
.js-motion:not(.io-armed) .reveal,
.js-motion:not(.io-armed) .reveal-group > *{
  animation:revealFallback 480ms var(--ease-out) 1.8s forwards;
}
@keyframes revealFallback{ to{ opacity:1; transform:none; } }

/* Hero content is above the fold: it animates on load, never on scroll, so
   the first paint is never empty. */
.js-motion .hero-in{ animation:heroIn var(--t-reveal) var(--ease-out) both; }
.js-motion .hero-in:nth-child(1){ animation-delay:40ms; }
@keyframes heroIn{ from{ opacity:0; transform:translate3d(0,20px,0); } to{ opacity:1; transform:none; } }

/* ==========================================================================
   TIER 2 — Micro-interaction
   Target: visible response inside 100ms. Under that threshold an interface
   reads as "alive"; over it, as "loading".
   ========================================================================== */

.btn{
  transition:transform var(--t-fast) var(--ease-out),
             background-color var(--t-fast) var(--ease-soft),
             box-shadow var(--t-fast) var(--ease-out);
}
.btn-solid:hover{ transform:translateY(var(--lift)); box-shadow:0 16px 34px -12px rgba(9,98,192,.62); }
.btn-solid:active{ transform:translateY(0) scale(.975); transition-duration:var(--t-tap); }
.btn-ghost:hover{ transform:translateY(var(--lift)); border-color:var(--interactive); }
.btn-ghost:active{ transform:translateY(0) scale(.975); transition-duration:var(--t-tap); }

/* Magnetic pull: the CTA leans toward the cursor by at most 4px. Enough to
   register as responsiveness, not enough to make the target move away from
   the pointer. Fine pointers only — meaningless and janky on touch. */
@media (hover:hover) and (pointer:fine){
  .magnetic{ transition:transform var(--t-base) var(--ease-out); }
  .magnetic:hover{ transition-duration:var(--t-fast); }
}

/* Cards lift and warm their border. */
.card,.t-card,.price,.pill{
  transition:transform var(--t-base) var(--ease-out),
             box-shadow var(--t-base) var(--ease-out),
             border-color var(--t-base) var(--ease-soft);
}
.card:hover,.t-card:hover,.price:hover{
  transform:translateY(-4px);
  box-shadow:var(--shadow);
  border-color:color-mix(in srgb,var(--blue) 34%,var(--line));
}
.pill:hover{ transform:translateY(var(--lift)); border-color:var(--interactive); }

/* Nav links: an underline wipes in from the left rather than appearing. */
.nav-menu a{ position:relative; }
.nav-menu a::after{
  content:""; position:absolute; left:0; right:0; bottom:-5px; height:2px;
  background:var(--interactive); border-radius:2px;
  transform:scaleX(0); transform-origin:left;
  transition:transform var(--t-base) var(--ease-out);
}
.nav-menu a:hover::after,.nav-menu a[aria-current="true"]::after{ transform:scaleX(1); }
.nav-menu a[aria-current="true"]{ color:var(--interactive); }

/* Accordion chevron gets the spring — it's a small, discrete state flip,
   which is the only place overshoot reads as confident rather than silly. */
.acc-btn::after{ transition:transform var(--t-slow) var(--ease-spring),border-color var(--t-fast) linear; }
.acc-btn{ transition:color var(--t-fast) var(--ease-soft); }
.acc-btn:hover{ color:var(--interactive); }

/* FAQ rows tint on hover so the whole row reads as the hit area. */
.faq summary{ transition:color var(--t-fast) var(--ease-soft); }
.faq summary:hover{ color:var(--interactive); }
.faq summary::after{ transition:transform var(--t-base) var(--ease-spring); }

/* Blog cards: title shifts toward the reader on hover. */
.post-card h3{ transition:color var(--t-fast) var(--ease-soft); }
.post-card:hover h3{ color:var(--interactive); }
.post-card:hover{ transform:translateY(-4px); border-color:color-mix(in srgb,var(--blue) 34%,var(--line)); }

/* ==========================================================================
   TIER 3 — Data animation
   The product's own numbers animate into place. This is the section that
   does conversion work: it demonstrates the product instead of describing it.
   ========================================================================== */

/* Progress ring sweeps 0 → target. Driven by --p (0..1) which JS animates
   through @property so the conic-gradient is interpolatable. */
@property --p{ syntax:"<number>"; inherits:false; initial-value:0; }
.ring{ --p:0; background:conic-gradient(var(--blue) 0turn calc(var(--p) * 1turn), var(--line) calc(var(--p) * 1turn) 1turn); }
.js-motion .ring.go{ transition:--p 1100ms var(--ease-out); }

/* Chart bars grow from the baseline. scaleY, not height — height would
   relayout the flex row on every frame. */
.bars i{ transform-origin:bottom; }
.js-motion .bars.go i{ animation:barUp 620ms var(--ease-out) both; }
@keyframes barUp{ from{ transform:scaleY(0); } to{ transform:scaleY(1); } }

/* Checkboxes tick in sequence, so a static task list reads as a day's
   progress being made. */
.box{ transition:background-color var(--t-base) var(--ease-soft),border-color var(--t-base) var(--ease-soft); }
.js-motion .panel.go .box.on{ animation:tick 420ms var(--ease-spring) both; }
@keyframes tick{ 0%{ transform:scale(.6); opacity:.4; } 60%{ transform:scale(1.06); } 100%{ transform:scale(1); opacity:1; } }

/* The held-receipt countdown pulses — the brand's differentiator, breathing. */
.js-motion .live-countdown{ animation:pulseSoft 2.4s var(--ease-soft) infinite; }
@keyframes pulseSoft{ 0%,100%{ opacity:1; } 50%{ opacity:.55; } }

/* ==========================================================================
   TIER 4 — Continuity
   ========================================================================== */

/* Nav condenses once you've committed to the page. */
header.nav{ transition:box-shadow var(--t-base) var(--ease-out),
                       background-color var(--t-base) var(--ease-soft); }
header.nav .wrap{ transition:height var(--t-base) var(--ease-out); }
header.nav.stuck{ box-shadow:0 8px 30px -14px rgba(15,34,136,.28); }
header.nav.stuck .wrap{ height:58px; }

/* Reading progress on articles. Fixed, composited, 3px. */
.read-bar{
  position:fixed; top:0; left:0; height:3px; z-index:60;
  background:linear-gradient(90deg,var(--blue),var(--cyan));
  transform:scaleX(0); transform-origin:left; width:100%;
  will-change:transform;
}

/* Marquee pauses on hover so the labels are readable, and on focus-within
   so keyboard users aren't chasing moving text. */
.marquee:hover .mq-track,.marquee:focus-within .mq-track{ animation-play-state:paused; }
.mq-chip{ transition:transform var(--t-fast) var(--ease-out),border-color var(--t-fast) var(--ease-soft); }
.mq-chip:hover{ transform:translateY(-3px); border-color:var(--interactive); }

/* 3D tilt on the hero mockup. Rotation is capped at 4deg: past that the
   panel reads as a toy and the text inside starts to distort. */
@media (hover:hover) and (pointer:fine){
  .tilt{ transform-style:preserve-3d; transition:transform 380ms var(--ease-out); }
  .tilt-off{ transition:transform 620ms var(--ease-out); }
}

/* NOTE: content-visibility:auto was tried here and removed. It stops the
   browser rendering off-screen sections, which also stops IntersectionObserver
   reporting their children as intersecting — so scroll reveals never fired and
   the content stayed invisible. It also introduced ~0.01 CLS as intrinsic sizes
   resolved. The saving was not measurable on a 30KB page; correctness wins. */

/* ==========================================================================
   REDUCED MOTION — one block, total, non-negotiable
   Everything lands in its final state instantly. Note this also cancels the
   marquee and the countdown pulse: for a vestibular-sensitive user, an
   infinite animation is the actual problem, not the transitions.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
  .js-motion .reveal,
  .js-motion .reveal-group > *{ opacity:1 !important; transform:none !important; }
  .js-motion .ring{ --p:var(--p-final,0); }
  .read-bar{ display:none; }
  .tilt{ transform:none !important; }
}

/* ==========================================================================
   TIER 5 — Self-playing vignette
   Technique borrowed from the reference site (lialive.ai): every element in a
   mockup shares ONE long infinite duration, and each carves out a percentage
   window of that shared timeline. A scripted scenario plays out and loops with
   no JS timeline and no animation library.

   Here one keyframe plus four staggered delays replaces four keyframes: each
   step owns 3.5s of a 14s cycle. Fixed height, absolutely-positioned steps —
   so the panel never changes size and CLS stays at zero.

   Departure from the reference: it only plays while on screen, and it stops
   dead under prefers-reduced-motion. The reference ships 133 keyframes, many
   infinite, with no reduced-motion query at all — see the motion doc §5.
   ========================================================================== */

.vignette{ width:min(100%,420px); }
.vig{ position:relative; height:150px; margin-top:4px; }
.vig-step{ position:absolute; inset:0; opacity:0; display:flex; flex-direction:column;
  justify-content:center; gap:10px; }

/* Without JS or with reduced motion, step 1 is simply the visible state. */
.vig-step[style*="--i:0"]{ opacity:1; }

.js-motion .vig.playing .vig-step{
  animation:vigStep 14s var(--ease-soft) infinite both;
  animation-delay:calc(var(--i) * 3.5s);
}
/* Windows overlap on purpose: 28% of 14s = 3.92s against a 3.5s stagger, so
   the outgoing step is still fading as the next arrives. Without the overlap
   there is a ~0.8s frame where the panel is empty. */
@keyframes vigStep{
  0%{      opacity:0; transform:translateY(7px); }
  2%{      opacity:1; transform:none; }
  25%{     opacity:1; transform:none; }
  28%,100%{opacity:0; transform:translateY(-5px); }
}

.vig-msg{ background:var(--bg-soft); border:1px solid var(--line); border-radius:12px 12px 12px 4px;
  padding:11px 14px; font-size:14px; font-weight:500; color:var(--ink); }
.vig-q{ font-family:var(--serif); font-size:16.5px; font-weight:700; color:var(--ink); }
.vig-chips{ display:flex; gap:9px; }
.vig-chip{ border:1.5px solid var(--line); border-radius:999px; padding:7px 14px;
  font-size:13px; font-weight:700; color:var(--body); }
.vig-chip.on{ border-color:var(--interactive); color:var(--interactive);
  background:color-mix(in srgb,var(--blue) 10%,transparent); }
.vig-note{ font-size:12.8px; font-weight:500; color:var(--muted); line-height:1.45; }

/* Progress rail: shows the loop's position so the vignette reads as a
   sequence you can follow rather than content flickering. */
.vig-rail{ height:3px; background:var(--line); border-radius:3px; overflow:hidden; margin-top:2px; }
.vig-rail i{ display:block; height:100%; width:100%; border-radius:3px;
  background:linear-gradient(90deg,var(--blue),var(--cyan));
  transform:scaleX(0); transform-origin:left; }
.js-motion .vig.playing + .vig-rail i,
.js-motion .vig.playing ~ .vig-rail i{ animation:vigRail 14s linear infinite; }
@keyframes vigRail{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }

/* ==========================================================================
   TIER 5 — Ambient drift
   The hero glow moves slowly enough that you never catch it moving, but the
   page stops feeling printed. 26s, transform only, composited.
   ========================================================================== */
.js-motion .mock-glow{ animation:glowDrift 26s var(--ease-soft) infinite; }
@keyframes glowDrift{
  0%,100%{ transform:translate3d(0,0,0) scale(1); }
  33%    { transform:translate3d(3%,-4%,0) scale(1.06); }
  66%    { transform:translate3d(-3%,3%,0) scale(.96); }
}

/* Sheen across the primary CTA — on hover only. An infinite sheen next to a
   call to action is a distraction; a sheen that answers the pointer is a
   response. */
.btn-solid{ position:relative; overflow:hidden; }
.btn-solid::after{
  content:""; position:absolute; top:0; bottom:0; left:0; width:45%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.28),transparent);
  transform:translateX(-160%) skewX(-16deg); pointer-events:none;
}
.btn-solid:hover::after{ animation:sheen 720ms var(--ease-out) 1; }
@keyframes sheen{ to{ transform:translateX(320%) skewX(-16deg); } }

@media (prefers-reduced-motion:reduce){
  .js-motion .vig.playing .vig-step{ animation:none; opacity:0; }
  .js-motion .vig .vig-step[style*="--i:0"]{ opacity:1; }
  .js-motion .vig.playing ~ .vig-rail i{ animation:none; }
  .js-motion .mock-glow{ animation:none; }
  .btn-solid:hover::after{ animation:none; }
}

/* ==========================================================================
   TIER 5 — Floating panel bob
   The hero's floating cards breathe on their own. This animates the
   `translate` PROPERTY, not `transform` — the tilt controller owns
   style.transform, and the two compose, so bob and pointer-tilt never fight.
   ========================================================================== */
.js-motion .panel-float{ animation:floatBob 7s var(--ease-soft) infinite; }
.js-motion .pf-b{ animation-delay:-3.4s; }
@keyframes floatBob{ 0%,100%{ translate:0 0; } 50%{ translate:0 -7px; } }

/* ==========================================================================
   TIER 5 — Product tour
   Crossfade between screens. Visibility is delayed on exit so the outgoing
   slide can fade before it stops receiving clicks; the incoming slide is
   interactive immediately.
   ========================================================================== */
.tour-slide{
  transition:opacity 420ms var(--ease-out),
             transform 420ms var(--ease-out),
             visibility 0s linear 420ms;
  transform:translate3d(0,12px,0);
}
.tour-slide.on{
  transition:opacity 420ms var(--ease-out),
             transform 420ms var(--ease-out);
  transform:none;
}
/* Tab description expands only on the active tab — the accordion rhythm,
   reused. */
.tour-tab .tt-d{ transition:max-height 320ms var(--ease-soft),opacity 240ms var(--ease-soft); }
.tour-tab{ transition:background-color var(--t-base) var(--ease-soft),
                      border-color var(--t-base) var(--ease-soft),
                      box-shadow var(--t-base) var(--ease-out); }
.tour-tab:hover{ border-color:var(--line); }
/* The tab progress bar is JS-driven (rAF), so pausing on hover/focus and
   cancelling under reduced motion need no extra CSS. */
@media (prefers-reduced-motion:reduce){
  .tour-slide{ transition:none; transform:none; }
  .tour-tab .tt-d{ transition:none; }
}

/* ==========================================================================
   TIER 5 — Outline ribbon (lialive's giant scrolling type)
   Decorative and aria-hidden. Duplicate runs make the -50% loop seamless.
   ========================================================================== */
.js-motion .ribbon-track{ animation:ribbonSlide 46s linear infinite; }
.ribbon.rev .ribbon-track{ animation-direction:reverse; }
@keyframes ribbonSlide{ to{ transform:translate3d(-50%,0,0); } }
.ribbon:hover .ribbon-track{ animation-play-state:paused; }
@media (prefers-reduced-motion:reduce){
  .js-motion .ribbon-track{ animation:none; }
}

/* ==========================================================================
   TIER 5 — Coach demo widget
   The button arrives once, then a slow ring pulse says "you can talk to me"
   without text. The dialog and each message pop with the same spring the
   checkboxes use — one motion language across the page.
   ========================================================================== */
.js-motion .coach-fab{ animation:fabIn 620ms var(--ease-out) 1000ms both; }
@keyframes fabIn{ from{ opacity:0; transform:scale(.55); } to{ opacity:1; transform:scale(1); } }
.js-motion .coach-fab-ring{ animation:fabPulse 3.2s var(--ease-soft) infinite; }
@keyframes fabPulse{
  0%{ transform:scale(1); opacity:.85; }
  70%{ transform:scale(1.55); opacity:0; }
  100%{ transform:scale(1.55); opacity:0; }
}
.coach-fab{ transition:transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out); }
.coach-fab:hover{ transform:translateY(-3px); box-shadow:0 20px 40px -12px rgba(9,98,192,.7); }

.js-motion .coach-pop{ animation:popIn 340ms var(--ease-out); transform-origin:bottom right; }
@keyframes popIn{ from{ opacity:0; transform:translate3d(0,14px,0) scale(.96); } to{ opacity:1; transform:none; } }

.js-motion .cp-msg{ animation:msgIn 340ms var(--ease-spring) both; }
@keyframes msgIn{ from{ opacity:0; transform:translate3d(0,9px,0) scale(.98); } to{ opacity:1; transform:none; } }
.cp-replies button{ transition:border-color var(--t-fast) var(--ease-soft),
                               background-color var(--t-fast) var(--ease-soft),
                               transform var(--t-fast) var(--ease-out); }
.cp-replies button:not(:disabled):hover{ border-color:var(--interactive);
  background:color-mix(in srgb,var(--blue) 7%,transparent); transform:translateY(-1px); }
.js-motion .cp-typing i{ animation:typingDot 1.1s var(--ease-soft) infinite; }
.js-motion .cp-typing i:nth-child(2){ animation-delay:.15s; }
.js-motion .cp-typing i:nth-child(3){ animation-delay:.3s; }
@keyframes typingDot{ 0%,60%,100%{ transform:translateY(0); opacity:.45; } 30%{ transform:translateY(-4px); opacity:1; } }

@media (prefers-reduced-motion:reduce){
  .js-motion .coach-fab{ animation:none; opacity:1; }
  .js-motion .coach-fab-ring{ animation:none; opacity:0; }
  .js-motion .coach-pop,.js-motion .cp-msg{ animation:none; }
  .js-motion .cp-typing i{ animation:none; }
  .js-motion .panel-float{ animation:none; }
}
