@layer motion {
  @media (prefers-reduced-motion: no-preference) {
    /* C4 paper-fold — extends the existing fade-in-up with a small rotateX
       so revealed text blocks "lay down" from the top edge. transform-origin
       is set on the element; transform-style: preserve-3d on parents
       (main, section, .hero) is set in components.css to prevent flicker. */
    .fade-in-up {
      opacity: 0;
      transform: translateY(12px) rotateX(var(--canggih-fold-deg));
      transform-origin: top center;
      transition: opacity var(--dur-1) var(--ease-paper),
                  transform var(--dur-2) var(--ease-paper);
    }
    .fade-in-up.is-in { opacity: 1; transform: none; }

    /* Per-card scroll-reveal stagger for the staff and blog grids. The section
       wrapper no longer carries .fade-in-up (that revealed the whole grid as
       one block); each card is observed individually and reveals as it crosses
       the threshold. A row of three crosses in the same frame, so a 3-cycle
       delay ripples them left-to-right instead of together — the 3-cycle
       matches the wide 3-col layout these grids already key nth-child(3n) off,
       and resets every row so the delay never accumulates down a 38-card list.
       The reveal rides on a one-shot keyframe (fires once — cards are
       unobserved after) so the stagger delay sits on animation-delay and never
       touches the card's hover transform/box-shadow transition. The scoped
       transition here restores that hover transition, which the base
       .fade-in-up shorthand (same @layer, later) would otherwise clobber. */
    #staff-grid .staff-card.fade-in-up,
    #blog-grid .card.fade-in-up {
      opacity: 0;
      transform: none;
      transition: transform var(--dur-2) var(--ease-paper),
                  box-shadow var(--dur-2) var(--ease-paper);
    }
    #staff-grid .staff-card.fade-in-up.is-in,
    #blog-grid .card.fade-in-up.is-in {
      opacity: 1;
      animation: card-reveal var(--dur-2) var(--ease-paper) backwards;
    }
    @keyframes card-reveal {
      from { opacity: 0; transform: translateY(12px) rotateX(var(--canggih-fold-deg)); }
      to   { opacity: 1; transform: none; }
    }
    #staff-grid .staff-card.fade-in-up.is-in:nth-child(3n+2),
    #blog-grid .card.fade-in-up.is-in:nth-child(3n+2) { animation-delay: 70ms; }
    #staff-grid .staff-card.fade-in-up.is-in:nth-child(3n+3),
    #blog-grid .card.fade-in-up.is-in:nth-child(3n+3) { animation-delay: 140ms; }

    /* Lavender import — floating hero blobs. Gentle vertical drift; the
       reduced-motion block below neutralises the duration. */
    @keyframes uevFloat  { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-18px); } }
    @keyframes uevFloat2 { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(14px); } }
    .hero-blob--1 { animation: uevFloat 10s ease-in-out infinite; }
    .hero-blob--2 { animation: uevFloat2 12s ease-in-out infinite; }
    .hero-blob--3 { animation: uevFloat 8s ease-in-out infinite; }

    .chatbot-panel {
      animation: chatbot-unfurl var(--dur-fold) var(--ease-paper) both;
      transform-origin: bottom right;
    }
    @keyframes chatbot-unfurl {
      from { opacity: 0; transform: scale(0.6) rotate(-1.5deg) translateY(8px); }
      to   { opacity: 1; transform: scale(1) rotate(0deg) translateY(0); }
    }

    /* Locale toggle page-turn slide — pairs with chip-settle when aria-pressed flips */
    @keyframes page-turn-slide {
      from { opacity: 0; transform: translateX(6px); }
      to   { opacity: 1; transform: translateX(0); }
    }

    /* Chip pill settle — fires when aria-pressed flips to true */
    @keyframes chip-settle {
      0%   { transform: scale(0.95); }
      50%  { transform: scale(1.04); }
      100% { transform: none; }
    }
    .chip-pill[aria-pressed="true"] { animation: chip-settle 240ms var(--ease) both; }
    .locale-toggle [aria-pressed="true"] { animation: chip-settle 240ms var(--ease) both; }

    /* Form error shake — fires when [aria-invalid="true"] applies */
    @keyframes form-shake {
      0%, 100% { transform: none; }
      25%      { transform: translateX(-3px); }
      75%      { transform: translateX(3px); }
    }
    [aria-invalid="true"] { animation: form-shake 200ms var(--ease) both; }

    /* Chatbot launcher breathing — only when no one's opened it yet */
    @keyframes chatbot-pulse {
      0%, 100% { box-shadow: var(--shadow-2); }
      50%      { box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-primary) 20%, transparent), var(--shadow-2); }
    }
    .chatbot-launcher.is-idle { animation: chatbot-pulse 3s var(--ease) infinite; }

    /* Chat bubble entrance */
    @keyframes chat-bubble-in {
      from { opacity: 0; transform: translateY(8px); }
      to   { opacity: 1; transform: none; }
    }
    .chat-bubble { animation: chat-bubble-in var(--dur-2) var(--ease) both; }

    /* Personalization "Saved." inline feedback */
    [data-personalize-feedback] {
      transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
      opacity: 0; transform: translateY(6px);
    }
    [data-personalize-feedback]:not([hidden]) { opacity: 1; transform: none; }

    /* Consent — card entrance/exit */
    @keyframes consent-card-in {
      from { opacity: 0; transform: translateY(16px); }
      to   { opacity: 1; transform: none; }
    }
    @keyframes consent-card-out {
      from { opacity: 1; transform: none; }
      to   { opacity: 0; transform: translateY(8px); }
    }
    .consent-banner { animation: consent-card-in var(--dur-2) var(--ease) both; }
    .consent-banner.is-leaving { animation: consent-card-out 240ms var(--ease) both; }

    /* Consent — modal entrance/exit */
    @keyframes consent-modal-in {
      from { opacity: 0; transform: translateY(12px) scale(0.98); }
      to   { opacity: 1; transform: none; }
    }
    @keyframes consent-modal-backdrop-in {
      from { opacity: 0; }
      to   { opacity: 1; }
    }
    .consent-modal { animation: consent-modal-in var(--dur-2) var(--ease) both; }
    .consent-modal-backdrop { animation: consent-modal-backdrop-in var(--dur-2) var(--ease) both; }
    .consent-modal-root.is-leaving .consent-modal,
    .consent-modal-root.is-leaving .consent-modal-backdrop {
      animation: none; opacity: 0; transition: opacity 200ms var(--ease);
    }

    /* A1 — page-load ink-bloom. Cream overlay fades out + body saturate pulses
       in. Overlay approach (not opacity-on-main) preserves the "page exhales
       into place" feel without putting opacity<1 on <main>, which would
       reduce text contrast and fail WCAG AA during the bloom window. Filter
       on body affects all descendants including chrome — saturate pulse is
       site-wide. Uses --ease-out (tone settle, not movement curve). */
    @keyframes canggih-bloom-overlay {
      from { opacity: 0.7; }
      to   { opacity: 0; }
    }
    @keyframes canggih-bloom-tint {
      from { filter: saturate(125%); }
      to   { filter: saturate(100%); }
    }
    body.canggih-bloom-in::before {
      content: '';
      position: fixed;
      inset: 0;
      background: var(--color-cream);
      pointer-events: none;
      z-index: 5;
      animation: canggih-bloom-overlay var(--canggih-load-duration) var(--ease-out) both;
    }
    body.canggih-bloom-in {
      animation: canggih-bloom-tint var(--canggih-load-duration) var(--ease-out) both;
    }

    /* A4 — hero parallax. translate3d forces a compositor layer; clamp caps
       the depth at --canggih-parallax-depth so a wild scroll-Y can't exceed
       the calm budget. Reduced-motion: the JS module doesn't register a
       listener, so --scroll-y stays at 0 → transform is the identity. */
    body.home .hero,
    body.about .hero,
    body.services .hero {
      transform: translate3d(
        0,
        clamp(
          calc(var(--canggih-parallax-depth) * -1),
          calc(var(--scroll-y, 0) * -0.04px),
          0px
        ),
        0
      );
      will-change: transform;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.001ms !important; animation-delay: 0s !important; transition-duration: 0.001ms !important; }
  }
}
