/* Motion: a few purposeful moments, never decoration on content people read.
   Curves come from --ease-out / --ease-in-out in theme.css. Entrances use CSS
   animations so they stay smooth while the page is still loading; every rule
   has a gentler reduced-motion variant at the end of this file. */

@keyframes motion-rise { from { opacity: 0; transform: translateY(14px); } }
@keyframes motion-lift { from { opacity: 0; transform: translateY(32px); } }
@keyframes motion-settle { from { opacity: 0; transform: scale(.97); } }
@keyframes motion-fade { from { opacity: 0; } }
@keyframes motion-fill { from { transform: scaleX(0); } }
@keyframes motion-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes motion-live { 50% { opacity: .35; } }

/* Hero copy: eyebrow, headline, lede, actions arrive as one short cascade on
   page open (and on client-side navigation, where it doubles as the page
   transition). 60ms steps keep the whole sequence under half a second. */
:is(.hero-copy, .product-hero-copy, .chat-stories-hero-copy, .page-hero-grid > :first-child, .about-hero-grid > :first-child) > * {
  animation: motion-rise .6s var(--ease-out) both;
}
:is(.hero-copy, .product-hero-copy, .chat-stories-hero-copy, .page-hero-grid > :first-child, .about-hero-grid > :first-child) > :nth-child(2) { animation-delay: 60ms; }
:is(.hero-copy, .product-hero-copy, .chat-stories-hero-copy, .page-hero-grid > :first-child, .about-hero-grid > :first-child) > :nth-child(3) { animation-delay: 120ms; }
:is(.hero-copy, .product-hero-copy, .chat-stories-hero-copy, .page-hero-grid > :first-child, .about-hero-grid > :first-child) > :nth-child(4) { animation-delay: 180ms; }
:is(.hero-copy, .product-hero-copy, .chat-stories-hero-copy, .page-hero-grid > :first-child, .about-hero-grid > :first-child) > :nth-child(n + 5) { animation-delay: 240ms; }

/* Client stories hero: same cascade as the other page heroes. */
.stories-hero .site-container > * { animation: motion-rise .6s var(--ease-out) both; }
.stories-hero .site-container > :nth-child(2) { animation-delay: 60ms; }
.stories-hero .site-container > :nth-child(3) { animation-delay: 120ms; }
.stories-hero .site-container > :nth-child(n + 4) { animation-delay: 180ms; }

/* Home hero: the phone is the product, so it rises onto its shelf. */
.hero-art-stage { animation: motion-fade .5s ease both; }
.hero-phone { animation: motion-lift .8s var(--ease-out) .15s both; }
.hero-art-caption { animation: motion-rise .6s var(--ease-out) .35s both; }

/* Product heroes: the mockup plays out what the product does. The window
   settles, the conversation arrives turn by turn, and vote and slider bars
   fill to their values. Explanation, seen once per visit. */
.product-mockup-stage .mock-window { animation: motion-settle .5s var(--ease-out) .1s both; }
.product-mockup-stage .mock-thread > * { animation: motion-rise .45s var(--ease-out) both; }
.product-mockup-stage .mock-thread > :nth-child(1) { animation-delay: .45s; }
.product-mockup-stage .mock-thread > :nth-child(2) { animation-delay: .85s; }
.product-mockup-stage .mock-thread > :nth-child(3) { animation-delay: 1.25s; }
.product-mockup-stage .mock-thread > :nth-child(4) { animation-delay: 1.6s; }
.product-mockup-stage .mock-thread > :nth-child(n + 5) { animation-delay: 1.9s; }
.product-mockup-stage .mock-bar > span { transform-origin: left; animation: motion-fill 1s var(--ease-out) .7s both; }
.product-mockup-stage .mock-thread .mock-bar > span { animation-delay: 1.6s; }

/* A live indicator reads as live. */
.mock-pill-live::before { animation: motion-live 2s var(--ease-in-out) infinite; }

/* FAQ answers open and close instead of snapping (browsers without
   ::details-content keep the instant toggle). */
@supports (interpolate-size: allow-keywords) {
  .faq-list details { interpolate-size: allow-keywords; }
  .faq-list details::details-content {
    block-size: 0;
    overflow: clip;
    opacity: 0;
    transition: block-size .26s var(--ease-out), opacity .2s ease, content-visibility .26s allow-discrete;
  }
  .faq-list details[open]::details-content { block-size: auto; opacity: 1; }
}

/* Mobile menu grows from the button that opened it. */
.mobile-menu nav {
  transform-origin: top right;
  transition: opacity .2s var(--ease-out), transform .2s var(--ease-out);
  @starting-style { opacity: 0; transform: scale(.96) translateY(-4px); }
}

/* How it works: each step's rule draws in, left to right, as the list
   scrolls into view, so the sequence reads as a sequence. Tied to scroll
   position, no scroll-jacking; unsupported browsers keep the static rule. */
@supports (animation-timeline: view()) {
  .process-list li { position: relative; border-top-color: transparent; }
  .process-list li::before {
    content: '';
    position: absolute;
    inset: -1px 0 auto;
    height: 1px;
    background: var(--navy);
    transform-origin: left;
    animation: motion-draw linear both;
    animation-timeline: view();
    animation-range: entry 40% cover 35%;
  }
  .product-page-chat .process-list li::before { background: var(--purple-deep); }
  .process-list li:nth-child(2)::before { animation-range: entry 55% cover 38%; }
  .process-list li:nth-child(3)::before { animation-range: entry 70% cover 41%; }
  .process-list li:nth-child(4)::before { animation-range: entry 85% cover 44%; }
}

/* Client stories: each testimonial's closing rule draws in, left to right,
   as the row scrolls into view. Static rule where unsupported. */
@supports (animation-timeline: view()) {
  .story-row::after {
    content: '';
    position: absolute;
    inset: auto 0 -1px;
    height: 1px;
    background: var(--navy);
    transform-origin: left;
    animation: motion-draw linear both;
    animation-timeline: view();
    animation-range: entry 70% cover 40%;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the fades that help things read as arriving; drop the movement. */
  :is(.hero-copy, .product-hero-copy, .chat-stories-hero-copy, .page-hero-grid > :first-child, .about-hero-grid > :first-child) > *,
  .stories-hero .site-container > *,
  .hero-phone, .hero-art-caption,
  .product-mockup-stage .mock-window,
  .product-mockup-stage .mock-thread > * { animation-name: motion-fade; animation-duration: .3s; }
  .product-mockup-stage .mock-bar > span, .mock-pill-live::before, .process-list li::before, .story-row::after { animation: none; }
  .process-list li::before, .story-row::after { transform: none; }
  .faq-list details::details-content { transition: opacity .2s ease, content-visibility .2s allow-discrete; }
  .mobile-menu nav { transition: opacity .2s ease; @starting-style { transform: none; } }
}

/* CU Voice: the waveforms speak, softly and out of step, like a live call. */
@keyframes voice-speak { 50% { transform: scaleY(1); } }
@keyframes voice-ring { from { transform: scale(.9); opacity: .9; } to { transform: scale(1.35); opacity: 0; } }
.voice-wave > span { animation: voice-speak 1.1s var(--ease-in-out) infinite; }
.voice-wave > span:nth-child(2n) { animation-duration: .9s; animation-delay: -.3s; }
.voice-wave > span:nth-child(3n) { animation-duration: 1.3s; animation-delay: -.6s; }
.voice-wave > span:nth-child(5n) { animation-delay: -.15s; }
.mock-voice-ring { animation: voice-ring 2.4s var(--ease-out) infinite; }
@media (prefers-reduced-motion: reduce) {
  .voice-wave > span, .mock-voice-ring { animation: none; }
}

/* Member app phone: sign in, accounts, loan, repeat. Each screen holds long
   enough to read; the swap is a short slide like a real page push. */
@keyframes app-login { 0%, 27% { opacity: 1; visibility: visible; transform: none; } 31%, 97% { opacity: 0; visibility: hidden; transform: translateX(-12px); } 100% { opacity: 1; visibility: visible; transform: none; } }
@keyframes app-home { 0%, 27% { opacity: 0; visibility: hidden; transform: translateX(16px); } 31%, 61% { opacity: 1; visibility: visible; transform: none; } 65%, 100% { opacity: 0; visibility: hidden; transform: translateX(-12px); } }
@keyframes app-loan { 0%, 61% { opacity: 0; visibility: hidden; transform: translateX(16px); } 65%, 94% { opacity: 1; visibility: visible; transform: none; } 98%, 100% { opacity: 0; visibility: hidden; } }
@keyframes app-tabs-in { 0%, 27% { opacity: 0; } 31%, 94% { opacity: 1; } 98%, 100% { opacity: 0; } }
@keyframes app-spin { to { transform: rotate(360deg); } }
@keyframes app-tab-home { 0%, 61% { color: var(--blue); } 65%, 100% { color: var(--ink-faint); } }
@keyframes app-tab-loans { 0%, 61% { color: var(--ink-faint); } 65%, 94% { color: var(--blue); } 98%, 100% { color: var(--ink-faint); } }
@media (prefers-reduced-motion: no-preference) {
  .mock-app-login { animation: app-login 12s var(--ease-in-out) infinite; }
  .mock-app-home { animation: app-home 12s var(--ease-in-out) infinite; }
  .mock-app-loan { animation: app-loan 12s var(--ease-in-out) infinite; }
  .mock-phone .mock-app-tabs { animation: app-tabs-in 12s var(--ease-in-out) infinite; }
  .mock-app-spinner { animation: app-spin .8s linear infinite; }
  .mock-app-tabs > span:first-child { animation: app-tab-home 12s var(--ease-in-out) infinite; }
  .mock-app-tabs > span:nth-child(3) { animation: app-tab-loans 12s var(--ease-in-out) infinite; }
  .mock-phone-screen .mock-bar > span { transform-origin: left; animation: motion-fill 1s var(--ease-out) both; }
}

/* CU Chat on a website: as the window scrolls into view, the launcher is
   there first, then the widget opens and the conversation arrives. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .mock-site-widget { transform-origin: bottom right; animation: motion-settle linear both; animation-timeline: view(); animation-range: entry 60% cover 40%; }
    .mock-site-widget .mock-thread > * { animation: motion-rise linear both; animation-timeline: view(); }
    .mock-site-widget .mock-thread > :nth-child(1) { animation-range: entry 70% cover 44%; }
    .mock-site-widget .mock-thread > :nth-child(2) { animation-range: entry 90% cover 50%; }
  }
}
