/* ==========================================================================
   Solverra - Animations (#159)  |  Native-theme interactive layer (stylesheet)
   --------------------------------------------------------------------------
   Tasteful auto-decoration of the native solverra-base theme's own component
   classes. Sibling of solverra-motion.css (.solm-*, opt-in expressive). This
   file is the native-theme, zero-markup layer keyed on [data-sol-anim] (stamped
   by the JS from the PHP target map).

   PERFORMANCE CONTRACT (top 0.5% of the web)
     * Animates ONLY transform, opacity, and composited filter. NEVER width,
       height, top, left, right, bottom, margin, padding -> no layout thrash,
       no reflow, ZERO CLS.
     * Every "hidden start state" is scoped under html.sol-anim-on. If JS is
       absent or prefers-reduced-motion is set, the boot guard withholds that
       class and ALL content shows immediately at its resting state (no hidden
       content trap, fully accessible).
     * prefers-reduced-motion: reduce  -> hard reset: every effect snaps to its
       final state instantly and all transitions/animations are disabled. This
       is a redundant belt-and-suspenders on top of the boot-guard gate.
     * will-change is applied ONLY while an element is pre-reveal, then dropped
       once revealed (JS removes the start class) so the compositor layer is
       not pinned for the life of the page.

   TOKENS CONSUMED (solverra-design-system.php) with hard fallbacks for
   portability if the DS plugin is absent:
     --ease-out, --ease-in-out, --dur-1/2/3, --color-primary, --color-accent,
     --shadow-2, --shadow-3, --color-border, --r-md.
   ASCII-only per STANDARDS Section 19.1.
   ========================================================================== */

/* Local fallbacks so this file is correct even without the DS token sheet. */
html.sol-anim-on {
  --sol-anim-ease: var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
  --sol-anim-ease-soft: var(--ease-in-out, cubic-bezier(0.65, 0, 0.35, 1));
  --sol-anim-dur-fast: var(--dur-1, 120ms);
  --sol-anim-dur: var(--dur-2, 220ms);
  --sol-anim-dur-slow: var(--dur-3, 360ms);
  --sol-anim-reveal-dur: 620ms;
  --sol-anim-shadow-rest: var(--shadow-2, 0 4px 14px rgba(20, 32, 23, 0.08));
  --sol-anim-shadow-hover: var(--shadow-3, 0 14px 32px rgba(20, 32, 23, 0.14));
  --sol-anim-accent: var(--color-primary, #8CC63F);
}

/* ==========================================================================
   A. SCROLL REVEAL  ([data-sol-anim~="reveal"])
   Start state hidden + nudged down; .is-inview (added by IO) settles it.
   transform + opacity only. Stagger via --sol-anim-i index set by JS.
   ========================================================================== */
html.sol-anim-on [data-sol-anim~="reveal"] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  will-change: transform, opacity;
  transition:
    opacity var(--sol-anim-reveal-dur) var(--sol-anim-ease),
    transform var(--sol-anim-reveal-dur) var(--sol-anim-ease);
  transition-delay: calc(var(--sol-anim-i, 0) * 60ms);
}
html.sol-anim-on [data-sol-anim~="reveal"].is-inview {
  opacity: 1;
  transform: none;
  will-change: auto;
}
/* Directional variants (set by data-sol-anim-from in JS -> modifier class). */
html.sol-anim-on [data-sol-anim~="reveal"].from-left  { transform: translate3d(-26px, 0, 0); }
html.sol-anim-on [data-sol-anim~="reveal"].from-right { transform: translate3d(26px, 0, 0); }
html.sol-anim-on [data-sol-anim~="reveal"].from-zoom  { transform: scale(0.965); }
html.sol-anim-on [data-sol-anim~="reveal"].from-fade  { transform: none; }
html.sol-anim-on [data-sol-anim~="reveal"].is-inview.from-left,
html.sol-anim-on [data-sol-anim~="reveal"].is-inview.from-right,
html.sol-anim-on [data-sol-anim~="reveal"].is-inview.from-zoom { transform: none; }

/* ==========================================================================
   B. BUTTON PRESS / LIFT  ([data-sol-anim~="btn"])
   Hover raises a hair + deepens shadow; active scales down for a tactile press.
   Pure transform + box-shadow (shadow is composited, not layout).
   ========================================================================== */
html.sol-anim-on [data-sol-anim~="btn"] {
  transition:
    transform var(--sol-anim-dur) var(--sol-anim-ease),
    box-shadow var(--sol-anim-dur) var(--sol-anim-ease),
    filter var(--sol-anim-dur) var(--sol-anim-ease);
  transform: translateZ(0);
  backface-visibility: hidden;
}
@media (hover: hover) and (pointer: fine) {
  html.sol-anim-on [data-sol-anim~="btn"]:hover {
    transform: translate3d(0, -2px, 0);
    box-shadow: var(--sol-anim-shadow-hover);
    filter: brightness(1.03);
  }
}
html.sol-anim-on [data-sol-anim~="btn"]:active {
  transform: translate3d(0, 0, 0) scale(0.97);
  filter: brightness(0.99);
  transition-duration: var(--sol-anim-dur-fast);
}
/* Keyboard parity: same lift on focus-visible so it is not mouse-only. */
html.sol-anim-on [data-sol-anim~="btn"]:focus-visible {
  transform: translate3d(0, -2px, 0);
  box-shadow: var(--sol-anim-shadow-hover);
}

/* ==========================================================================
   C. CARD STATES  ([data-sol-anim~="card"])
   Hover raise + shadow; inner image gently zooms; a diagonal sheen sweeps once
   on hover via a composited ::after gradient (transform-only, content never
   repaints). overflow stays the theme's concern; we only transform.
   ========================================================================== */
html.sol-anim-on [data-sol-anim~="card"] {
  transition:
    transform var(--sol-anim-dur-slow) var(--sol-anim-ease),
    box-shadow var(--sol-anim-dur-slow) var(--sol-anim-ease);
  transform: translateZ(0);
}
@media (hover: hover) and (pointer: fine) {
  html.sol-anim-on [data-sol-anim~="card"]:hover {
    transform: translate3d(0, -4px, 0);
    box-shadow: var(--sol-anim-shadow-hover);
  }
  /* Image zoom: only when the card opts an image in via the JS-stamped child. */
  html.sol-anim-on [data-sol-anim~="card"] [data-sol-anim-zoom] {
    transition: transform 520ms var(--sol-anim-ease);
    transform: translateZ(0);
  }
  html.sol-anim-on [data-sol-anim~="card"]:hover [data-sol-anim-zoom] {
    transform: scale(1.045);
  }
  /* Sheen sweep -- a composited overlay, pointer-events:none, opacity+transform. */
  html.sol-anim-on [data-sol-anim~="card"][data-sol-anim-sheen]::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(115deg, transparent 38%,
      rgba(255, 255, 255, 0.16) 50%, transparent 62%);
    transform: translate3d(-130%, 0, 0);
    opacity: 0;
    transition: transform 0s;
  }
  html.sol-anim-on [data-sol-anim~="card"][data-sol-anim-sheen]:hover::after {
    opacity: 1;
    transform: translate3d(130%, 0, 0);
    transition: transform 760ms var(--sol-anim-ease), opacity 120ms linear;
  }
}

/* ==========================================================================
   D. LINK UNDERLINE DRAW  ([data-sol-anim~="link"])
   A scaleX underline grows from the left on hover. We paint it with a
   background-image so we do NOT touch the theme's text-decoration, and we
   animate background-size (composited as a paint, no layout). Skipped if the
   anchor already looks like a button (handled by the PHP selector exclusion).
   ========================================================================== */
@media (hover: hover) and (pointer: fine) {
  html.sol-anim-on [data-sol-anim~="link"] {
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 0% 1.5px;
    transition: background-size var(--sol-anim-dur) var(--sol-anim-ease);
  }
  html.sol-anim-on [data-sol-anim~="link"]:hover,
  html.sol-anim-on [data-sol-anim~="link"]:focus-visible {
    background-size: 100% 1.5px;
  }
}

/* ==========================================================================
   E. SUBTLE PARALLAX  ([data-sol-anim~="parallax"])
   JS writes --sol-anim-py (px) from one shared rAF ticker; CSS just consumes it
   as a transform. Capped by parallaxMax in JS so it can never push content far
   enough to cause CLS or reveal gaps.
   ========================================================================== */
html.sol-anim-on [data-sol-anim~="parallax"] {
  transform: translate3d(0, var(--sol-anim-py, 0px), 0);
  will-change: transform;
}

/* ==========================================================================
   F. IMAGE REVEAL  ([data-sol-anim~="img"])
   Blur-up + slight scale settle on enter. composited filter + transform only.
   ========================================================================== */
html.sol-anim-on [data-sol-anim~="img"] {
  opacity: 0;
  transform: scale(1.02);
  filter: blur(8px);
  will-change: transform, opacity, filter;
  transition:
    opacity 560ms var(--sol-anim-ease),
    transform 560ms var(--sol-anim-ease),
    filter 560ms var(--sol-anim-ease);
}
html.sol-anim-on [data-sol-anim~="img"].is-inview {
  opacity: 1;
  transform: none;
  filter: none;
  will-change: auto;
}

/* ==========================================================================
   G. MICRO NUDGE  ([data-sol-anim~="nudge"])
   Tiny chips / badges translate up a hair on hover -- the lightest of states.
   ========================================================================== */
@media (hover: hover) and (pointer: fine) {
  html.sol-anim-on [data-sol-anim~="nudge"] {
    transition: transform var(--sol-anim-dur-fast) var(--sol-anim-ease);
  }
  html.sol-anim-on [data-sol-anim~="nudge"]:hover {
    transform: translate3d(0, -1px, 0);
  }
}

/* ==========================================================================
   H. CLICK / TAP RIPPLE  (.sol-anim-ripple span, injected by JS on press)
   The JS appends ONE absolutely-positioned span sized + placed at the pointer;
   we animate transform (scale) + opacity ONLY -- never layout. The host already
   carries the press/lift transitions from section B. pointer-events:none so the
   ripple never eats the click. Tinted with currentColor so it adapts to the
   button's own text color in light AND dark themes automatically.
   ========================================================================== */
html.sol-anim-on .sol-anim-ripple {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  background: currentColor;
  opacity: 0.28;
  transform: scale(0);
  will-change: transform, opacity;
  animation: sol-anim-ripple-kf 600ms var(--sol-anim-ease) forwards;
  /* Keep the ripple inside the button's rounded corners + above its bg, below
     its label. z-index 0 sits over a 'auto'/static label safely without a
     stacking surprise; the host clips it via its own overflow. */
  z-index: 0;
}
@keyframes sol-anim-ripple-kf {
  to {
    transform: scale(2.6);
    opacity: 0;
  }
}
/* In dark mode a white-ish currentColor ripple can glare; soften it. The
   button's currentColor already flips with the theme, so we only trim alpha. */
html[data-theme="dark"].sol-anim-on .sol-anim-ripple {
  opacity: 0.22;
}

/* ==========================================================================
   I. ANIMATED COUNTER  ([data-sol-anim~="counter"])
   The JS tweens textContent. The ONLY job of CSS here is to lock the digit
   advance width so a changing number never reflows its box -> ZERO CLS. We do
   NOT set opacity/transform here; if you also want the number to fade in, add
   data-sol-anim="reveal counter" and section A handles the entrance.
   ========================================================================== */
html.sol-anim-on [data-sol-anim~="counter"] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ==========================================================================
   LITE MODE  (saveData / deviceMemory <= 2 / slow connection)
   Keep reveals + button/card states (cheap, high value) but kill the
   continuous-cost effects: parallax drift and image blur-up.
   ========================================================================== */
html.sol-anim-lite [data-sol-anim~="parallax"] {
  transform: none !important;
  will-change: auto;
}
html.sol-anim-lite [data-sol-anim~="img"] {
  opacity: 1;
  transform: none;
  filter: none;
  will-change: auto;
}

/* ==========================================================================
   REDUCED MOTION  -- belt-and-suspenders hard reset.
   The boot guard already withholds html.sol-anim-on under reduced motion, so
   these selectors normally never match; this block guarantees a clean resting
   state even if the class is force-applied (e.g. via a filter override) and a
   user later flips the OS setting.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html.sol-anim-on [data-sol-anim] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    background-size: auto !important;
    transition: none !important;
    animation: none !important;
    will-change: auto !important;
  }
  html.sol-anim-on [data-sol-anim]::after {
    display: none !important;
  }
  /* The JS-injected ripple span has no data-sol-anim attr -- neutralize it too. */
  html.sol-anim-on .sol-anim-ripple {
    display: none !important;
    animation: none !important;
  }
}
