/* =============================================================================
 * SHIFTMAKER — 8 CINEMATIC CARD ANIMATIONS
 * =============================================================================
 * Each animation runs 8-10 seconds total, ends in a held final state suitable
 * for a t=10s screenshot, and is composed entirely of compositor-friendly
 * properties (transform, opacity, filter, clip-path) so Playwright 30fps capture
 * stays smooth.
 *
 * Trigger model:
 *   1. Card has `data-anim="<name>"` attribute and `.anim-<name>` class.
 *   2. IntersectionObserver adds `.in` to the card when it enters view.
 *   3. CSS transitions/animations fire off the `.anim-<name>.in` selector.
 *   4. Some animations call into card-animations.js to split text into
 *      per-char/per-digit/per-word spans BEFORE adding `.in`.
 *
 * Themes (editorial / inverted / india / bold) provide --c-bg, --c-text,
 * --c-accent, --c-muted — every animation reads those variables so the same
 * keyframe looks distinct in each palette.
 * ============================================================================= */

/* All cards: prep the stage. Cards default to opacity 0 + transform until .in.
   Final state is set in the keyframe so screenshots at t=10s look right. */
.card[data-anim]{
  --anim-dur: 9s;             /* total animation length, held at end */
  --anim-ease: cubic-bezier(.16,1,.3,1);
}
.card[data-anim]::after{ pointer-events:none; }

/* Reduced motion: skip everything, show end state */
@media (prefers-reduced-motion: reduce){
  .card[data-anim] *,
  .card[data-anim]::before,
  .card[data-anim]::after{
    animation: none !important;
    transition: none !important;
  }
}

/* =============================================================================
 * 1. LETTERPRESS — chars stamp in with red ink bloom + paper-grain shadow.
 *    Headlines, quotes. JS splits into .lp-char spans.
 * ============================================================================= */
/* Each word is wrapped in a non-breaking inline-block so the per-char spans
   inside it can animate independently but the browser can only wrap at word
   boundaries — not mid-character. Fixes "price" splitting as "pr | ice". */
.anim-letterpress .lp-word{
  display:inline-block;
  white-space:nowrap;
}
.anim-letterpress .lp-char{
  display:inline-block;
  opacity:0;
  transform:translateY(-14px) scale(1.6) rotate(-3deg);
  filter:blur(2px);
  transition: opacity .28s ease, transform .42s var(--anim-ease), filter .3s ease, text-shadow .8s ease;
}
.anim-letterpress.in .lp-char{
  opacity:1;
  transform:translateY(0) scale(1) rotate(0);
  filter:blur(0);
  text-shadow: 0 1px 0 rgba(0,0,0,.04), 0 0 0 var(--c-accent);
}
/* Red ink bloom that fades */
.anim-letterpress.in .lp-char{
  animation: lp-bloom 1.2s ease forwards;
  animation-delay: calc(var(--lp-i, 0) * 40ms + 280ms);
}
@keyframes lp-bloom{
  0%   { text-shadow: 0 0 0 transparent; }
  35%  { text-shadow: 0 0 14px var(--c-accent), 0 0 4px var(--c-accent); }
  100% { text-shadow: 0 1px 0 rgba(0,0,0,.05); }
}
/* Stagger per char via inline --lp-i */
.anim-letterpress .lp-char{ transition-delay: calc(var(--lp-i, 0) * 40ms); }

/* =============================================================================
 * 2. CINEMA_MASK — red full-frame sweep, content rises behind it.
 *    Flagship intro. Pure CSS.
 * ============================================================================= */
.anim-cinema_mask{ overflow:hidden; position:relative; }
.anim-cinema_mask .c-body,
.anim-cinema_mask .c-top,
.anim-cinema_mask .c-foot{
  opacity:0;
  transform:translateY(18px) scale(.985);
  transition: opacity .9s var(--anim-ease) .65s, transform 1.4s var(--anim-ease) .65s;
}
.anim-cinema_mask.in .c-body,
.anim-cinema_mask.in .c-top,
.anim-cinema_mask.in .c-foot{
  opacity:1;
  transform:translateY(0) scale(1);
}
.anim-cinema_mask::before{
  content:"";
  position:absolute; inset:0;
  background: var(--c-accent);
  transform: translateX(-101%);
  z-index:10;
  pointer-events:none;
}
.anim-cinema_mask.in::before{
  animation: cm-sweep .85s cubic-bezier(.77,0,.18,1) forwards;
}
@keyframes cm-sweep{
  0%   { transform: translateX(-101%); }
  45%  { transform: translateX(0); }
  100% { transform: translateX(101%); }
}
/* Slow zoom that holds at end */
.anim-cinema_mask .c-body{
  transform-origin:center;
}
.anim-cinema_mask.in .c-body{
  animation: cm-zoom 8s ease-out .85s forwards;
}
@keyframes cm-zoom{
  from{ transform: translateY(0) scale(1); }
  to  { transform: translateY(0) scale(1.025); }
}

/* =============================================================================
 * 3. DIGIT_DROP — numbers fall, rotate, bounce-settle. JS splits digits.
 *    Used on number cards, stats, comparison values.
 * ============================================================================= */
.anim-digit_drop .dd-digit{
  display:inline-block;
  opacity:0;
  transform: translateY(-80px) rotateX(-90deg) scale(1.2);
  transform-origin: center 50%;
  transition: opacity .25s ease;
}
.anim-digit_drop.in .dd-digit{
  opacity:1;
  animation: dd-drop .9s cubic-bezier(.34,1.56,.64,1) forwards;
  animation-delay: calc(var(--dd-i, 0) * 110ms + 400ms);
}
@keyframes dd-drop{
  0%   { opacity:0; transform: translateY(-80px) rotateX(-90deg) scale(1.2); }
  60%  { opacity:1; transform: translateY(6px)   rotateX(8deg)   scale(.96); }
  78%  { transform: translateY(-3px)  rotateX(-3deg)  scale(1.02); }
  100% { opacity:1; transform: translateY(0)    rotateX(0)     scale(1); }
}
/* Unit/suffix slides in last */
.anim-digit_drop .unit,
.anim-digit_drop .dd-suffix{
  opacity:0;
  transform: translateX(-14px);
  transition: opacity .5s ease, transform .6s var(--anim-ease);
  transition-delay: 1.4s;
}
.anim-digit_drop.in .unit,
.anim-digit_drop.in .dd-suffix{ opacity:.75; transform: translateX(0); }

/* =============================================================================
 * 4. CAMERA_PUSH — slow 8s dolly-in with parallaxing text layers + micro-shake.
 *    Image-led, pull quotes. Pure CSS.
 * ============================================================================= */
.anim-camera_push .c-body{
  transform: scale(.88);
  opacity:.4;
  transform-origin: center 55%;
  filter: blur(3px);
}
.anim-camera_push.in .c-body{
  animation: cp-push 9s cubic-bezier(.22,1,.36,1) forwards;
}
@keyframes cp-push{
  0%   { transform: scale(.88); opacity:.3; filter: blur(3px); }
  18%  { opacity:1; filter: blur(0); }
  92%  { transform: scale(1.045); }
  100% { transform: scale(1.04); opacity:1; filter: blur(0); }
}
/* Parallax layers: foot drifts faster than body */
.anim-camera_push .c-top{
  opacity:0;
  transform: translateY(-12px);
  transition: opacity .8s ease 1.6s, transform 1.2s var(--anim-ease) 1.6s;
}
.anim-camera_push.in .c-top{ opacity:1; transform: translateY(0); }
.anim-camera_push .c-foot{
  opacity:0;
  transform: translateY(20px);
  transition: opacity 1s ease 2.4s, transform 1.4s var(--anim-ease) 2.4s;
}
.anim-camera_push.in .c-foot{ opacity:1; transform: translateY(0); }
/* Micro-shake on impact at t=1.8s */
.anim-camera_push.in{ animation: cp-shake 9s ease forwards; }
@keyframes cp-shake{
  0%, 19%, 21%, 100% { transform: translate(0,0); }
  20%                { transform: translate(1.5px, -1px); }
}

/* =============================================================================
 * 5. GLITCH_RESOLVE — 0.4s RGB chromatic split + scanlines, snap to clean.
 *    Model releases, tech announcements.
 * ============================================================================= */
.anim-glitch_resolve .c-body{
  opacity:0;
  filter: blur(0);
}
.anim-glitch_resolve.in .c-body{
  animation: gr-resolve 1.1s steps(8, end) forwards;
}
@keyframes gr-resolve{
  0%   { opacity:1; transform: translate(0,0);
         text-shadow: 0 0 0 transparent;
         filter: drop-shadow(-3px 0 0 #00e0ff) drop-shadow(3px 0 0 var(--c-accent)); }
  20%  { transform: translate(-4px, 1px);
         filter: drop-shadow(-6px 0 0 #00e0ff) drop-shadow(6px 0 0 var(--c-accent)) hue-rotate(20deg); }
  40%  { transform: translate(3px, -2px);
         filter: drop-shadow(-2px 0 0 #00e0ff) drop-shadow(2px 0 0 var(--c-accent)); }
  60%  { transform: translate(-1px, 1px);
         filter: drop-shadow(-1px 0 0 #00e0ff) drop-shadow(1px 0 0 var(--c-accent)); }
  100% { opacity:1; transform: translate(0,0); filter:none; }
}
/* Scanline overlay during glitch */
.anim-glitch_resolve::after{
  content:"";
  position:absolute; inset:0; z-index:8; pointer-events:none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0,0,0,.0) 0px, rgba(0,0,0,.0) 2px,
    rgba(0,0,0,.08) 2px, rgba(0,0,0,.08) 3px
  );
  opacity:0;
}
.anim-glitch_resolve.in::after{
  animation: gr-scan .9s steps(6,end) forwards;
}
@keyframes gr-scan{
  0%   { opacity:0; }
  25%  { opacity:1; }
  85%  { opacity:.4; }
  100% { opacity:0; }
}
/* Red accent flash at the end */
.anim-glitch_resolve::before{
  content:"";
  position:absolute; inset:0; z-index:7; pointer-events:none;
  background: var(--c-accent);
  opacity:0;
}
.anim-glitch_resolve.in::before{
  animation: gr-flash .25s ease forwards;
  animation-delay: 1.05s;
}
@keyframes gr-flash{
  0%   { opacity:0; }
  35%  { opacity:.45; }
  100% { opacity:0; }
}

/* =============================================================================
 * 6. TYPE_CASCADE — staggered text blocks land at varying speeds,
 *    headline lands last with weight shift 300→800 + underline wipe.
 *    Comparisons, source stacks, timelines.
 * ============================================================================= */
.anim-type_cascade .tc-block{
  opacity:0;
  transform: translateY(28px) scale(.96);
  transition: opacity .55s ease, transform .75s var(--anim-ease);
}
.anim-type_cascade.in .tc-block{
  opacity:1;
  transform: translateY(0) scale(1);
}
/* Stagger via --tc-i */
.anim-type_cascade .tc-block{ transition-delay: calc(var(--tc-i, 0) * 180ms + 200ms); }

/* Headline-class block lands last with font-weight transition */
.anim-type_cascade .tc-headline{
  font-weight: 300;
  letter-spacing: .04em;
  transition: opacity .55s ease, transform .75s var(--anim-ease),
              font-weight 1.2s ease, letter-spacing 1.2s ease;
  transition-delay: 1.1s, 1.1s, 1.4s, 1.4s;
}
.anim-type_cascade.in .tc-headline{
  font-weight: 800;
  letter-spacing: -.015em;
}
/* Underline wipe under .tc-headline */
.anim-type_cascade .tc-headline::after{
  content:"";
  display:block;
  height:3px;
  width:0;
  background: var(--c-accent);
  margin-top:10px;
  transition: width 1s var(--anim-ease) 2.1s;
}
.anim-type_cascade.in .tc-headline::after{ width:92px; }

/* =============================================================================
 * 7. MARQUEE_PUNCH — horizontal context marquee loops, key callout
 *    punches forward (scale 0.3 → 1.2 → 1) breaking through with shadow.
 *    Stat cards, India lens, big-number context.
 * ============================================================================= */
.anim-marquee_punch{ overflow:hidden; }
.anim-marquee_punch .mp-marquee{
  position:absolute;
  left:0; right:0;
  top:50%;
  transform: translateY(-50%);
  z-index:1;
  white-space:nowrap;
  pointer-events:none;
  font-family: var(--serif, Georgia, serif);
  font-weight: 800;
  font-style: italic;
  font-size: clamp(64px, 14vw, 140px);
  color: var(--c-muted);
  opacity: 0;
  letter-spacing: -.02em;
  line-height:1;
  user-select:none;
}
.anim-marquee_punch.in .mp-marquee{
  opacity: 1;
  animation: mp-scroll 12s linear infinite;
}
@keyframes mp-scroll{
  from{ transform: translateY(-50%) translateX(0); }
  to  { transform: translateY(-50%) translateX(-50%); }
}
.anim-marquee_punch .c-body{
  position:relative;
  z-index:2;
  opacity:0;
  transform: scale(.3);
  transform-origin: center;
  filter: drop-shadow(0 8px 24px rgba(0,0,0,.18));
  transition: opacity .4s ease, transform 1s cubic-bezier(.34,1.56,.64,1);
  transition-delay: .8s;
}
.anim-marquee_punch.in .c-body{
  opacity:1;
  animation: mp-punch 1.4s cubic-bezier(.34,1.56,.64,1) .8s forwards;
}
@keyframes mp-punch{
  0%   { opacity:0; transform: scale(.3); }
  55%  { opacity:1; transform: scale(1.18); }
  80%  { transform: scale(.96); }
  100% { transform: scale(1); }
}

/* =============================================================================
 * 8. PAGE_TURN — 3D Y-axis rotation reveals card like turning a magazine page,
 *    back-page peek visible mid-rotation. Closing cards, "more →" beats.
 * ============================================================================= */
.anim-page_turn{ perspective: 1400px; perspective-origin: left center; }
.anim-page_turn .c-body,
.anim-page_turn .c-top,
.anim-page_turn .c-foot{
  transform-style: preserve-3d;
  backface-visibility: hidden;
}
.anim-page_turn .c-body{
  transform: rotateY(-92deg);
  transform-origin: left center;
  opacity: 0;
  transition: transform 1.4s cubic-bezier(.45,.05,.2,1) .25s,
              opacity .55s ease .25s;
}
.anim-page_turn.in .c-body{
  transform: rotateY(0);
  opacity: 1;
}
/* Back-page peek: a soft gradient behind that's visible mid-rotation */
.anim-page_turn::before{
  content:"";
  position:absolute;
  inset:0;
  background:
    linear-gradient(to right, rgba(0,0,0,.12) 0%, rgba(0,0,0,0) 18%),
    repeating-linear-gradient(135deg, var(--c-muted) 0 9px, var(--c-bg) 9px 18px);
  z-index:0;
  opacity:0;
  transition: opacity .4s ease;
}
.anim-page_turn.in::before{
  animation: pt-peek 1.8s ease forwards;
}
@keyframes pt-peek{
  0%   { opacity:0; }
  30%  { opacity:.85; }
  70%  { opacity:.4; }
  100% { opacity:0; }
}
.anim-page_turn .c-top,
.anim-page_turn .c-foot{
  opacity:0;
  transform: translateX(20px);
  transition: opacity .6s ease, transform .8s var(--anim-ease);
  transition-delay: 1.5s;
}
.anim-page_turn.in .c-top,
.anim-page_turn.in .c-foot{ opacity:1; transform: translateX(0); }

/* =============================================================================
 * SHARED: when an animation overrides base card-* selectors, suppress those
 * base transitions so we don't see ghost fades behind the cinematic version.
 * ============================================================================= */
.card[data-anim] .hl .word,
.card[data-anim] .qmark,
.card[data-anim] blockquote,
.card[data-anim] .attrib,
.card[data-anim] .num-context,
.card[data-anim] .cmp-side,
.card[data-anim] .verdict,
.card[data-anim] .stk-item,
.card[data-anim] .tl-item,
.card[data-anim] .underline,
.card[data-anim] .deck{
  /* let cinematic animations drive the show — clear stale transition delays */
  transition-delay: 0s;
}

/* When an animation is active, we still want existing per-base CSS to provide
   layout. We only suppress the base micro-animations by neutralising opacity
   on the elements the cinematic anim controls directly. */
.anim-letterpress.in .hl .word{ opacity:1; transform:none; }
.anim-cinema_mask.in .hl .word{ opacity:1; transform:none; }
.anim-cinema_mask.in .underline{ width:88px; }
.anim-cinema_mask.in .deck{ opacity:.75; transform:translateY(0); }
.anim-cinema_mask.in blockquote,
.anim-cinema_mask.in .qmark,
.anim-cinema_mask.in .attrib{ opacity:1; transform:none; }
