/* ===========================================================================
   Pruvio Motion System  —  /assets/motion.css
   Site-wide, token-aware motion layer (Stripe / Linear feel): many small
   transform + opacity animations driven by scroll and hover.

   HARD RULES (enforced here):
   - Animate ONLY transform and opacity. Never width/height/top/left/margin.
     Nothing in this file changes box dimensions, so there is zero layout
     shift (CLS stays 0).
   - Easing + durations are CSS variables, reused by every component.
   - Full prefers-reduced-motion support lives at the bottom: entrances render
     in their final state instantly, all loops/parallax are disabled.
   - Never sets a color, background, or border color. Theme tokens
     (--card-*, --pv-*) are owned by css/theme.css and are never touched here.
   The JS engine (/assets/motion.js) adds the `pv-motion` class to <html> only
   when motion is allowed; every hidden initial state below is gated on that
   class, so with no JS — or under reduced motion — content is fully visible.
   =========================================================================== */

:root {
  --ease-out-soft: cubic-bezier(.22, 1, .36, 1);
  --ease-in-out-soft: cubic-bezier(.45, 0, .15, 1);
  --pv-dur-fast: 200ms;
  --pv-dur-1: 300ms;
  --pv-dur-2: 450ms;
  --pv-dur-3: 600ms;
  --pv-stagger: 80ms;     /* card-group cascade step */
  --pv-menu-stagger: 40ms;/* dropdown link cascade step */
}

/* ---------------------------------------------------------------------------
   1 + 2 + 13 + 15  Scroll reveal (fade-up 16px), stagger groups, dividers,
   footer columns. Initial hidden state only applies when the engine is live
   (html.pv-motion). Elements get `.pv-in` from the IntersectionObserver.
   --------------------------------------------------------------------------- */
html.pv-motion [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity var(--pv-dur-2) var(--ease-out-soft),
    transform var(--pv-dur-2) var(--ease-out-soft);
  transition-delay: var(--pv-reveal-delay, 0ms);
}
html.pv-motion [data-reveal].pv-in {
  opacity: 1;
  transform: none;
}
/* will-change is added by JS just before reveal and removed on transitionend;
   the class is the hook so it is never left on idle elements. */
html.pv-motion [data-reveal].pv-will { will-change: transform, opacity; }

/* Section divider accents: a thin gradient line that scales in from the left.
   scaleX only — the element keeps its box, so no reflow. */
.pv-divider {
  height: 2px;
  border: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--pv-accent, #3B82F6), transparent);
  transform: scaleX(0);
  transform-origin: left center;
  opacity: .9;
}
html.pv-motion .pv-divider {
  transition: transform var(--pv-dur-3) var(--ease-out-soft);
}
html.pv-motion .pv-divider.pv-in { transform: scaleX(1); }

/* ---------------------------------------------------------------------------
   3  Hero entrance cascade. Opt-in via [data-hero] on the hero section; its
   direct flagged children (data-hero-item) rise + fade over ~900ms total.
   Pages that already animate their hero with the legacy `.reveal` system keep
   that — this only runs where data-hero is present.
   --------------------------------------------------------------------------- */
html.pv-motion [data-hero] [data-hero-item] {
  opacity: 0;
  transform: translateY(20px);
}
html.pv-motion [data-hero].pv-in [data-hero-item] {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--pv-dur-3) var(--ease-out-soft),
    transform var(--pv-dur-3) var(--ease-out-soft);
  transition-delay: calc(var(--pv-i, 0) * 120ms);
}

/* ---------------------------------------------------------------------------
   4  Hero ambient mesh: a slow-drifting, low-amplitude radial gradient field
   injected behind the hero by motion.js (.pv-hero-mesh). Token-aware: the
   gradient uses --pv-accent so it works in both themes. Transform-only loop.

   PERF: trimmed from three stacked gradients + scale/rotate down to a single
   low-opacity layer with a longer, lower-amplitude translate loop — far less
   compositor/raster work per frame. Desktop only (JS does not inject the mesh
   under 768px). Paused by default; motion.js adds `.pv-play` (and, with it,
   will-change) only while the hero is actually in the viewport, and removes
   both when it scrolls away. Nothing drifts off-screen.
   --------------------------------------------------------------------------- */
.pv-hero-mesh {
  position: absolute;
  inset: -15%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(45% 45% at 28% 32%, color-mix(in srgb, var(--pv-accent, #3B82F6) 12%, transparent) 0%, transparent 72%);
  animation: pv-mesh-drift 40s ease-in-out infinite alternate;
  animation-play-state: paused;
}
.pv-hero-mesh.pv-play {
  animation-play-state: running;
  will-change: transform;
}
@keyframes pv-mesh-drift {
  0%   { transform: translate3d(-1%, -0.5%, 0) scale(1.03); }
  100% { transform: translate3d(1.2%, 1%, 0) scale(1.05); }
}

/* ---------------------------------------------------------------------------
   6  Industry pills marquee — continuous transform loop, pause on hover.
   The duplicated track (homepage) already drives the wrap; here we only add
   the hover pause + reduced-motion stop without re-declaring the animation.
   --------------------------------------------------------------------------- */
.marquee-viewport:hover .marquee-track,
.pv-marquee:hover .pv-marquee-track { animation-play-state: paused; }

/* ---------------------------------------------------------------------------
   7  Workflow mockup pulse loop on the verification + Paid badges. The
   left-to-right step sequence reuses the reveal-group cascade (data-reveal on
   each .pvwf-card / .pvwf-chev). Pulse is transform/opacity only.
   --------------------------------------------------------------------------- */
/* Paused until motion.js marks it `.pv-play` (in viewport); will-change is
   added with play and dropped when the card scrolls away — never persistent. */
.pv-pulse { animation: pv-pulse 3s var(--ease-in-out-soft) infinite; animation-play-state: paused; }
.pv-pulse.pv-play { animation-play-state: running; will-change: transform; }
@keyframes pv-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.04); opacity: .82; }
}

/* ---------------------------------------------------------------------------
   8  Hover micro-interactions. Additive transitions on transform only; colors
   stay owned by the page/theme. Cards lift, buttons nudge, arrows slide, nav
   links underline, logo mark scales.
   --------------------------------------------------------------------------- */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  /* Card lift. PERF: animate transform ONLY. The drop shadow is pre-rendered
     on an ::after layer and revealed by fading its opacity — the browser never
     re-rasterises an animating box-shadow. The pseudo carries the shadow
     outside the card box, so a card with overflow:visible (all of ours) shows
     it; transform still lifts regardless. */
  .feature-card, .industry-card, .team-card, .price-card, .testimonial-card,
  .problem-card, .proof-card, .blog-card, .post-card, .card, .pricing-card,
  [data-lift] {
    position: relative;
    transition: transform var(--pv-dur-fast) var(--ease-out-soft);
  }
  .feature-card::after, .industry-card::after, .team-card::after,
  .price-card::after, .testimonial-card::after, .problem-card::after,
  .proof-card::after, .blog-card::after, .post-card::after, .card::after,
  .pricing-card::after, [data-lift]::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    box-shadow: var(--pv-glow-card-hover, 0 18px 40px rgba(0,0,0,.18));
    opacity: 0;
    transition: opacity var(--pv-dur-fast) var(--ease-out-soft);
    pointer-events: none;
  }
  .feature-card:hover, .industry-card:hover, .team-card:hover, .price-card:hover,
  .testimonial-card:hover, .problem-card:hover, .proof-card:hover, .blog-card:hover,
  .post-card:hover, .card:hover, .pricing-card:hover, [data-lift]:hover {
    transform: translateY(-4px);
  }
  .feature-card:hover::after, .industry-card:hover::after, .team-card:hover::after,
  .price-card:hover::after, .testimonial-card:hover::after, .problem-card:hover::after,
  .proof-card:hover::after, .blog-card:hover::after, .post-card:hover::after,
  .card:hover::after, .pricing-card:hover::after,
  [data-lift]:hover::after { opacity: 1; }

  /* Primary buttons: lift only (transform); the inner arrow glyph nudges right.
     No animated box-shadow. */
  .hero-cta-primary, .btn-primary, .cta-primary, .pv-cta, [data-cta-primary] {
    transition: transform var(--pv-dur-fast) var(--ease-out-soft);
  }
  .hero-cta-primary:hover, .btn-primary:hover, .cta-primary:hover,
  .pv-cta:hover, [data-cta-primary]:hover {
    transform: translateY(-2px);
  }
  .pv-arrow, [data-arrow] { display: inline-block; transition: transform var(--pv-dur-fast) var(--ease-out-soft); }
  a:hover > .pv-arrow, button:hover > .pv-arrow,
  a:hover > [data-arrow], button:hover > [data-arrow] { transform: translateX(3px); }

  /* Nav links: animated underline that wipes in from the left. */
  .nav-link, .nav-links a, #main-nav-links > li > a {
    position: relative;
  }
  .nav-link::after, .nav-links a::after, #main-nav-links > li > a::after {
    content: "";
    position: absolute;
    left: 12px; right: 12px; bottom: 4px;
    height: 1.5px;
    background: currentColor;
    opacity: .65;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--pv-dur-fast) var(--ease-out-soft);
    pointer-events: none;
  }
  .nav-link:hover::after, .nav-links a:hover::after,
  #main-nav-links > li > a:hover::after { transform: scaleX(1); }

  /* Logo mark gentle scale. */
  .logo, .nav-logo, .nav-logo-img, .logo-icon, .nav-logo-icon, [data-logo] {
    transition: transform var(--pv-dur-fast) var(--ease-out-soft);
  }
  .logo:hover, .nav-logo:hover, .nav-logo-img:hover, .logo-icon:hover,
  .nav-logo-icon:hover, [data-logo]:hover { transform: scale(1.06); }
}

/* ---------------------------------------------------------------------------
   9  CTA sheen — one diagonal highlight sweep every 6s across primary CTAs.
   The element is clipped; the sheen is a transform-driven pseudo-element so it
   never affects layout. Applied to [data-sheen] by motion.js.
   --------------------------------------------------------------------------- */
[data-sheen] { position: relative; overflow: hidden; }
[data-sheen]::after {
  content: "";
  position: absolute;
  top: -50%; bottom: -50%;
  left: -40%; width: 40%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .35), transparent);
  transform: translateX(-260%) skewX(-18deg);
  pointer-events: none;
  animation: pv-sheen 6s ease-in-out infinite;
  animation-play-state: paused;
}
/* Sweep runs (and gets will-change) only while the hero CTA is on screen.
   motion.js applies [data-sheen] to a single primary hero CTA, desktop only. */
[data-sheen].pv-play::after {
  animation-play-state: running;
  will-change: transform;
}
@keyframes pv-sheen {
  0%, 72%, 100% { transform: translateX(-260%) skewX(-18deg); }
  86%           { transform: translateX(520%) skewX(-18deg); }
}

/* ---------------------------------------------------------------------------
   10  Nav scroll behavior — shadow + slight shrink past 50px, hide on scroll
   down, reveal on scroll up. Transform only. JS toggles the classes on the
   nav shell (#pv-topbar / #main-nav / nav).
   --------------------------------------------------------------------------- */
/* No persistent will-change: the nav transforms only on scroll-direction
   changes, which is infrequent — promoting it permanently just wastes a
   compositor layer. */
.pv-nav-shell {
  transform: translateY(var(--pv-nav-y, 0)) scale(var(--pv-nav-s, 1));
  transform-origin: top center;
  transition: transform var(--pv-dur-1) var(--ease-out-soft),
              box-shadow var(--pv-dur-1) var(--ease-out-soft);
}
.pv-nav-shell.pv-nav-scrolled {
  --pv-nav-s: .985;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .18);
}
.pv-nav-shell.pv-nav-hidden { --pv-nav-y: -115%; }

/* Shadow-only mode for navs that contain a position:fixed dropdown child
   (faq/compare). No persistent transform — so the fixed menu is never
   reparented — just a soft shadow once the page scrolls. */
.pv-nav-shadow { transition: box-shadow var(--pv-dur-1) var(--ease-out-soft); }
.pv-nav-shadow.pv-nav-scrolled { box-shadow: 0 8px 30px rgba(0, 0, 0, .18); }

/* ---------------------------------------------------------------------------
   11  Mobile dropdown menu animation. Open: 250ms fade + slide-down, links
   stagger 40ms. Close: quick 180ms fade. Same easing variables as the rest.
   The overlay panel + links flip color via the --card-* tokens (Part A); this
   only adds movement. Driven by .pv-open / .pv-closing on #mobile-nav-overlay.

   Visibility is fail-safe: the panel and its links REST at their visible state
   (opacity:1, no transform). The entrance is a self-completing @keyframes that
   only plays while .pv-open is set and always ends on the visible frame, so the
   nav items can never be left transparent if .pv-open is missing, delayed, or
   interrupted. Earlier this hid the panel and links by default and relied on JS
   adding .pv-open to reveal them, which left the menu blank whenever the open
   class was not applied. Movement is additive only; contrast still comes from
   the --card-* tokens. */
html.pv-motion #mobile-nav-overlay {
  opacity: 1;
  transform: none;
  transition: opacity 250ms var(--ease-out-soft), transform 250ms var(--ease-out-soft);
}
html.pv-motion #mobile-nav-overlay.pv-open {
  animation: pvMenuPanelIn 250ms var(--ease-out-soft) both;
}
html.pv-motion #mobile-nav-overlay.pv-closing {
  animation: none;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 180ms var(--ease-out-soft), transform 180ms var(--ease-out-soft);
}
/* Links rest visible; they cascade in via the entrance only while open. */
html.pv-motion #mobile-nav-overlay > a,
html.pv-motion #mobile-nav-overlay > div {
  opacity: 1;
  transform: none;
}
html.pv-motion #mobile-nav-overlay.pv-open > a,
html.pv-motion #mobile-nav-overlay.pv-open > div {
  animation: pvMenuItemIn 220ms var(--ease-out-soft) both;
  animation-delay: calc(var(--pv-i, 0) * var(--pv-menu-stagger));
}
/* The close button should not slide; keep it instantly present. */
html.pv-motion #mobile-nav-overlay > button { opacity: 1; transform: none; }
/* While closing, hold the links in place so the whole panel fades as one
   (otherwise they'd snap back through the entrance). */
html.pv-motion #mobile-nav-overlay.pv-closing > a,
html.pv-motion #mobile-nav-overlay.pv-closing > div {
  animation: none;
  opacity: 1;
  transform: none;
}

/* Entrances always finish on the visible frame, so an interrupted or skipped
   reveal leaves the item shown rather than hidden. */
@keyframes pvMenuPanelIn {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pvMenuItemIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------------
   12  FAQ accordion icon rotation. The open/close height animation is handled
   per-page (max-height) and refined by motion.js; here we only ensure the +
   rotates smoothly. Scoped to a class motion.js adds so we never fight a
   page's own ::after rules unless it has none.
   --------------------------------------------------------------------------- */
.pv-faq-icon { display: inline-block; transition: transform var(--pv-dur-1) var(--ease-out-soft); }
.pv-faq-open .pv-faq-icon { transform: rotate(45deg); }

/* ---------------------------------------------------------------------------
   14  Page-load top progress bar. 2px gradient bar that fills then fades.
   Fixed + transform-driven, never reflows the page.
   --------------------------------------------------------------------------- */
#pv-progress {
  position: fixed;
  top: 0; left: 0;
  height: 2px;
  width: 100%;
  z-index: 100000;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--pv-accent, #3B82F6), var(--pv-accent-2, #2563EB));
  opacity: 1;
  pointer-events: none;
}
#pv-progress.pv-progress-go {
  transform: scaleX(1);
  transition: transform 600ms var(--ease-out-soft);
}
#pv-progress.pv-progress-done {
  opacity: 0;
  transition: opacity 400ms ease 120ms;
}

/* ---------------------------------------------------------------------------
   16  Offscreen pause for page-owned loops. motion.js adds `.pv-anim-paused`
   to any continuously-animating page element (hero / final-CTA orbs, the
   badge dot, the industry marquee track) the moment it leaves the viewport,
   and removes it when it returns — so nothing animates off screen. The
   inverse (add-when-offscreen) class is used here, rather than the
   default-paused `.pv-play` scheme of the motion-system loops above, so it
   never fights the marquee's existing :hover pause when the track is visible.
   --------------------------------------------------------------------------- */
html.pv-motion .pv-anim-paused { animation-play-state: paused !important; }

/* ===========================================================================
   prefers-reduced-motion — everything renders in its final, static state.
   Entrances show instantly; every loop / parallax / sheen / drift is off.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html [data-reveal],
  html [data-hero] [data-hero-item],
  html #mobile-nav-overlay,
  html #mobile-nav-overlay > a,
  html #mobile-nav-overlay > div,
  .pv-divider {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
  }
  /* Motion-system loops + the common page-owned loops they sit beside. */
  .pv-hero-mesh,
  .marquee-track, .pv-marquee-track,
  .pv-pulse,
  [data-sheen]::after,
  .pv-nav-shell,
  .hero-orb, .final-cta-orb, .badge-dot {
    animation: none !important;
  }
  .pv-nav-shell { transition: none !important; }
  #pv-progress { display: none !important; }
}
