/* Micro-animations from the transitions.dev skill (.claude/skills/transitions-dev),
   used on v2.html: "Texts reveal" (18) for the hero copy and the key-figures statement
   (played per word instead of per line), "Number pop-in" (02) for the figures and
   "Card hover tilt" (19) for the solutions cards. Snippets are pasted verbatim except where
   noted; site-specific additions are at the bottom. */

:root {
  /* Texts reveal */
  --stagger-dur: 500ms;
  --stagger-distance: 12px;
  --stagger-stagger: 40ms;
  --stagger-blur: 3px;
  --stagger-ease: cubic-bezier(0.22, 1, 0.36, 1);
  /* Number pop-in */
  --digit-dur: 500ms;
  --digit-distance: 8px;
  --digit-stagger: 70ms;
  --digit-blur: 2px;
  --digit-ease: cubic-bezier(0.34, 1.45, 0.64, 1);
  --digit-dir-x: 0;
  --digit-dir-y: 1;
  /* Card hover tilt */
  --tilt-perspective: 1000px;
  --tilt-return: 1000ms;
  --tilt-return-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --tilt-follow: 400ms;
  --tilt-follow-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --tilt-glare-opacity: 0.32;
  --tilt-glare-fade: 300ms;
  --tilt-glare-ease: cubic-bezier(0.22, 1, 0.36, 1);
  /* Spinning counter */
  --reel-dur: 1400ms;
  --reel-cell: 30px;
  --reel-spin-blur: 3px;
  --reel-stagger: 90ms;
  --reel-ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* motion token: offset between the three figure cells */
  --duration-micro: 80ms;
}

/* ---------- Texts reveal (18-texts-reveal.md), per word ---------- */
/* The recipe's line rules, moved onto words: scripts/motion.js wraps every word of a
   [data-words] element in .t-word with its index --i. Each word waits
   --word-start + --word-step × --i, so the stagger runs on through the headline and
   carries on into the lead (motion.js sets both per element; --word-step defaults to
   --stagger-stagger). Words start translated down + blurred + invisible; .is-shown
   on the parent .t-stagger flips them to their resting state. */
.t-word {
  display: inline-block;
  opacity: 0;
  transform: translateY(var(--stagger-distance));
  filter: blur(var(--stagger-blur));
  transition:
    opacity   var(--stagger-dur) var(--stagger-ease),
    transform var(--stagger-dur) var(--stagger-ease),
    filter    var(--stagger-dur) var(--stagger-ease);
  transition-delay: calc(var(--word-start, 0ms) + var(--word-step, var(--stagger-stagger)) * var(--i, 0));
  will-change: transform, opacity, filter;
  /* Safari clips each blurred word to its box, and with a tight line-height (the headline
     uses 1) that cuts off descenders: grow the box by padding, give the space back by margin */
  padding-block: 0.2em;
  margin-block: -0.2em;
}

.t-stagger.is-shown .t-word {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}
/* Exit decouples from the stagger: same fade for every word,
   no Y return, no blur — so the disappearance reads as a
   single quiet fade instead of a reverse reveal. */
.t-stagger.is-hiding .t-word {
  opacity: 0;
  transform: translateY(0);
  filter: blur(0);
  transition:
    opacity 200ms ease,
    transform 0s linear,
    filter 0s linear;
  transition-delay: 0s;
}

/* a whole element revealed as one "word" (links, list items): same motion, no split */
.t-reveal {
  opacity: 0;
  transform: translateY(var(--stagger-distance));
  filter: blur(var(--stagger-blur));
  transition:
    opacity   var(--stagger-dur) var(--stagger-ease),
    transform var(--stagger-dur) var(--stagger-ease),
    filter    var(--stagger-dur) var(--stagger-ease);
  transition-delay: calc(var(--word-start, 0ms) + var(--word-step, var(--stagger-stagger)) * var(--i, 0));
  will-change: transform, opacity, filter;
}
.t-stagger.is-shown .t-reveal { opacity: 1; transform: none; filter: blur(0); }
.t-stagger.is-hiding .t-reveal { opacity: 0; transform: none; filter: blur(0); transition: opacity 200ms ease, transform 0s linear, filter 0s linear; transition-delay: 0s; }

@media (prefers-reduced-motion: reduce) {
  .t-word, .t-reveal { transition: none !important; }
}

/* ---------- Number pop-in (02-number-pop-in.md) ---------- */
@keyframes t-digit-pop-in {
  0%   {
    transform: translate(
      calc(var(--digit-distance) * var(--digit-dir-x)),
      calc(var(--digit-distance) * var(--digit-dir-y))
    );
    opacity: 0;
    filter: blur(var(--digit-blur));
  }
  100% { transform: translate(0, 0); opacity: 1; filter: blur(0); }
}

.t-digit-group {
  display: inline-flex;
  align-items: baseline;
}
.t-digit {
  display: inline-block;
  will-change: transform, opacity, filter;
}
.t-digit-group.is-animating .t-digit {
  animation: t-digit-pop-in var(--digit-dur) var(--digit-ease) both;
}
.t-digit-group.is-animating .t-digit[data-stagger="1"] {
  animation-delay: var(--digit-stagger);
}
.t-digit-group.is-animating .t-digit[data-stagger="2"] {
  animation-delay: calc(var(--digit-stagger) * 2);
}
/* Digits that did not change (the leading ones the old and new number
   share) stay still; only the digits that changed pop in. */
.t-digit-group.is-animating .t-digit[data-same] {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .t-digit-group .t-digit { animation: none !important; }
}

/* ---------- Card hover tilt (19-card-tilt.md) ---------- */
/* The outer wrapper is the flat hit area; touch-action:none
   lets a finger drag tilt the card instead of scrolling the
   page, so tap-hold-drag works on mobile. */
.t-tilt { touch-action: none; }
/* The card tilts toward the pointer via rotateX/rotateY fed
   from JS; on leave it eases back to flat. A separate
   .is-tilting class swaps in a short linear follow while the
   pointer moves so the tilt tracks the cursor 1:1. */
.t-tilt-card {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  transform:
    perspective(var(--tilt-perspective))
    rotateX(var(--tilt-rx, 0deg))
    rotateY(var(--tilt-ry, 0deg));
  transform-style: preserve-3d;
  transition: transform var(--tilt-return) var(--tilt-return-ease);
  will-change: transform;
}
.t-tilt-card.is-tilting {
  transition: transform var(--tilt-follow) var(--tilt-follow-ease);
}
/* Cursor-tracked glare: layered soft circles that add like
   light (screen blend) at the pointer position. */
.t-tilt-glare {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: screen;
  background:
    radial-gradient(circle 95px at var(--tilt-gx, 50%) var(--tilt-gy, 50%),
      rgba(255,255,255,0.48), rgba(255,255,255,0.06) 52%, rgba(255,255,255,0) 84%),
    radial-gradient(circle 200px at var(--tilt-gx, 50%) var(--tilt-gy, 50%),
      rgba(255,255,255,0.22), rgba(255,255,255,0.04) 58%, rgba(255,255,255,0) 78%),
    radial-gradient(circle 360px at var(--tilt-gx, 50%) var(--tilt-gy, 50%),
      rgba(255,255,255,0.10), rgba(255,255,255,0) 88%);
  transition: opacity var(--tilt-glare-fade) var(--tilt-glare-ease);
}
.t-tilt.is-hover .t-tilt-glare { opacity: var(--tilt-glare-opacity); }

@media (prefers-reduced-motion: reduce) {
  .t-tilt-card { transform: none !important; transition: none !important; }
}

/* ---------- Spinning counter (26-spinning-counter.md) ---------- */
.t-reel { display: inline-flex; align-items: center; height: var(--reel-cell); font-variant-numeric: tabular-nums; }
.t-reel-col {
  position: relative; height: var(--reel-cell); overflow: hidden;
  /* Soft-fade the window edges instead of hard-cropping. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%);
}
.t-reel-strip { display: flex; flex-direction: column; will-change: transform, filter; }
.t-reel-digit { height: var(--reel-cell); display: flex; align-items: center; justify-content: center; }
/* JS drives the tween: strip.style.transition =
     'transform var(--reel-dur) var(--reel-ease) ' + (col*var(--reel-stagger)) + 'ms';
   and decays each column's feGaussianBlur stdDeviation from
   var(--reel-spin-blur) to 0 over its own window. */

@media (prefers-reduced-motion: reduce) {
  .t-reel-strip { transition: none !important; filter: none !important; }
}

/* ---------- Site additions ---------- */
/* Figures wait out of sight until their card scrolls into view (only when the script runs
   and motion is allowed), so the pop-in is the first time they are seen. */
@media (prefers-reduced-motion: no-preference) {
  .js-motion .proof__value .t-digit-group:not(.is-animating) .t-digit { opacity: 0; }
}
/* the unit superscript sits on the digits' top line inside the flex digit group */
.proof__value .t-digit-group sup { align-self: flex-start; line-height: 1; top: .08em; }
/* Word reveal: until motion.js has split the text, keep it out of sight so the raw copy
   never flashes before the words rise in (the inline script in v2.html drops .js-motion
   again if motion.js never runs). */
@media (prefers-reduced-motion: no-preference) {
  .js-motion [data-words]:not(.is-split), .js-motion [data-reveal]:not(.is-split) { opacity: 0; }
}

/* Tilt on the solutions cards: the <li> is the flat wrapper; the card keeps the design
   system radius (the recipe's 12px would win, this file loads last) and fills its row. */
.solutions .t-tilt { display: flex; }
.solution.t-tilt-card { flex: 1 1 auto; min-width: 0; border-radius: var(--radius-lg); }
/* The recipe's white screen-blend glare cannot show on a white card, so on light cards the
   same three cursor-tracked circles are a faint lavender (the hero glass tint) instead. */
.solution .t-tilt-glare {
  mix-blend-mode: multiply;
  background:
    radial-gradient(circle 95px at var(--tilt-gx, 50%) var(--tilt-gy, 50%),
      rgba(193,180,219,0.48), rgba(193,180,219,0.06) 52%, rgba(193,180,219,0) 84%),
    radial-gradient(circle 200px at var(--tilt-gx, 50%) var(--tilt-gy, 50%),
      rgba(193,180,219,0.22), rgba(193,180,219,0.04) 58%, rgba(193,180,219,0) 78%),
    radial-gradient(circle 360px at var(--tilt-gx, 50%) var(--tilt-gy, 50%),
      rgba(193,180,219,0.10), rgba(193,180,219,0) 88%);
}
/* On touch screens the stacked cards cover most of the page: let a vertical swipe scroll
   it (that cancels the tilt) and keep sideways drags for tilting. */
@media (hover: none) {
  .solutions .t-tilt { touch-action: pan-y; }
}


/* Spinning counter on the coverage figures (v3's totals cards, v5's totals along the shader card):
   the reel cell is the figure's own line (1em of the figure's type), the window shows the whole
   glyph, and the strip is hidden until motion.js builds it */
.coverage-v3 .totals .t-reel, .coverage-v5__facts .t-reel { --reel-cell: 1em; height: 1em; vertical-align: top; }
.coverage-v3 .totals .t-reel-col, .coverage-v5__facts .t-reel-col { -webkit-mask-image: none; mask-image: none; }
.coverage-v3 .totals .t-reel-strip, .coverage-v5__facts .t-reel-strip { filter: url("#t-reel-blur"); }
