/* ─────────────────────────────────────────────────────────────────────
 * Flowella — Motion overlay
 *
 * Native CSS Scroll-Driven Animations + a small set of perpetual
 * micro-motions. This file is the single home for every animation in
 * the theme. It is loaded after design-system.css and main.css from
 * every site template.
 *
 * Authoring rules (must hold for every rule below):
 *   1. Final-state-first: nothing in this file moves an element off its
 *      final visual position by default. Animations only override.
 *   2. prefers-reduced-motion: reduce → all motion stops cleanly.
 *      Static "ambient" shadows / glows from tasks 5b + 6 remain.
 *   3. Scroll-driven rules sit inside
 *        @media (prefers-reduced-motion: no-preference) {
 *          @supports (animation-timeline: view()) { ... }
 *        }
 *      so unsupported browsers (Safari ≤ 17, Firefox without flag) get
 *      the static final state, no jank.
 *   4. Tasks 5b (italic glow breathe) and 6 (CTA glow pulse + hover
 *      sheen) are NOT scroll-driven and only need the prefers-reduced-
 *      motion guard.
 *   5. Easing for entrances: cubic-bezier(0.22, 1, 0.36, 1). No bounce.
 *   6. animation-duration on scroll-driven rules is 1ms — Firefox needs
 *      a non-zero duration even when timeline drives progress.
 *   7. Tokens come from design-system.css. Where the spec called for
 *      explicit rgba() values, those numbers are the same ones already
 *      compiled into --primary / --secondary / --green.
 * ─────────────────────────────────────────────────────────────────── */

/* ── Tokens ─────────────────────────────────────────────────────────── */
:root {
  --fr-ease-entry: cubic-bezier(0.22, 1, 0.36, 1);
  --fr-glow-secondary-soft: 0 0 12px rgba(79, 182, 236, .25);
  --fr-glow-secondary-strong: 0 0 20px rgba(79, 182, 236, .45);
  --fr-glow-green-soft: 0 0 12px rgba(37, 211, 102, .25);
  --fr-glow-green-strong: 0 0 20px rgba(37, 211, 102, .45);
}


/* Task 1 — Section entry reveal: REMOVED.
   The full-section opacity-0 → 1 fade was reading as a grey placeholder
   on the surrounding sections (especially light ones), which conflicted
   with the "final-state-first" intent. Sections now render in their
   final state on every browser, no cross-fade. The fr-reveal-up
   keyframe below survives because the comparison-card stagger (task 4)
   still depends on it. */
@keyframes fr-reveal-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}


/* ─────────────────────────────────────────────────────────────────────
 *  Task 3 — Hero subtle motion (flowella-hero)
 *
 *  hero-art-glow + hero-eyebrow .dot are perpetual micro-motions. They
 *  only need the prefers-reduced-motion guard (no scroll involvement,
 *  no @supports needed for animation-timeline). hero-bg-word IS scroll
 *  driven and sits inside the @supports block.
 *
 *  Tuning: the original 0.85→1.0 / 0.97→1.03 envelope read as too
 *  subtle behind the mascot. We bump the base intensity of the radial
 *  gradient AND widen the breathing envelope to 0.7→1.0 / 0.9→1.1 so
 *  the glow is felt without becoming a strobe.
 * ─────────────────────────────────────────────────────────────────── */

/* More punchy ambient base. Stops match the original sky-blue → mint
   gradient but with higher alpha at the centre. The `.hero` ancestor
   selector lifts specificity to (0,2,0) so we win against the module
   CSS definition (specificity 0,1,0) that loads after motion.css. */
.hero .hero-art-glow {
  background: radial-gradient(circle at 50% 50%,
    rgba(79, 182, 236, .80),
    rgba(48, 213, 200, .55) 35%,
    transparent 70%);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-art-glow {
    animation: fr-hero-glow 6s ease-in-out infinite alternate;
    will-change: opacity, transform;
  }

  .hero-eyebrow .dot {
    animation: fr-hero-dot 1.6s ease-in-out infinite alternate;
    will-change: transform, box-shadow;
  }

  @supports (animation-timeline: scroll()) {
    .hero-bg-word {
      animation: fr-hero-parallax 1ms linear both;
      animation-timeline: scroll();
      animation-range: 0% 100%;
      will-change: transform;
    }
  }
}

@keyframes fr-hero-glow {
  from { opacity: .7; transform: scale(.9);  }
  to   { opacity: 1;  transform: scale(1.1); }
}

@keyframes fr-hero-dot {
  from {
    transform: scale(1);
    box-shadow: 0 0 8px var(--green);
  }
  to {
    transform: scale(1.25);
    box-shadow: 0 0 16px var(--green);
  }
}

@keyframes fr-hero-parallax {
  from { transform: translateY(0); }
  to   { transform: translateY(-60px); }
}


/* ─────────────────────────────────────────────────────────────────────
 *  Task 4 — Comparison chat row stagger
 *
 *  The two cards in flowella-comparison-cards are .vs-card.bad (the
 *  "broadcast mode") and .vs-card.good (the "Flowella mode"). Inside
 *  each, the chat-style messages (.vs-msg) and field rows
 *  (.vs-form-row) fade up on view() entry. Cap stagger at 4 children
 *  per card — anything past nth-child(4) reuses the 4th delay so we
 *  never accumulate runaway lag on long lists. animation-range: entry
 *  10% entry 60%; the offset is via animation-range-start step.
 * ─────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .vs-card .vs-msg,
    .vs-card .vs-form-row {
      animation: fr-reveal-up 1ms linear both;
      animation-timing-function: var(--fr-ease-entry);
      animation-timeline: view();
      animation-range: entry 10% entry 60%;
    }

    /* 80ms-ish stagger via animation-range-start offsets. With a 50%
       view-progress window, ~4% of viewport ≈ a perceptible step. */
    .vs-card .vs-msg:nth-child(2),
    .vs-card .vs-form-row:nth-child(2) {
      animation-range: entry 14% entry 64%;
    }
    .vs-card .vs-msg:nth-child(3),
    .vs-card .vs-form-row:nth-child(3) {
      animation-range: entry 18% entry 68%;
    }
    .vs-card .vs-msg:nth-child(n + 4),
    .vs-card .vs-form-row:nth-child(n + 4) {
      animation-range: entry 22% entry 72%;
    }
  }
}


/* ─────────────────────────────────────────────────────────────────────
 *  Task 5a — Italic accent shimmer (continuous gradient pulse)
 *
 *  Continuous time-driven loop. Every italic accent pulses on its own
 *  rhythm whether the user is scrolling or sitting still.
 *
 *  Per-context --shimmer-base sets the gradient base colour so the
 *  keyframe is shared. Three families:
 *     - blue (var(--secondary))  — italics on dark headings + hero
 *     - blue (var(--primary))    — italics on light .ph-headline
 *     - green (var(--green))     — .acc accents
 *
 *  Hero h1 stagger: .acc lags 500ms behind so "conversations →
 *  conversions" pulses in reading order each cycle.
 *
 *  WHY THE @supports GATE IS background-clip: text, NOT view():
 *  -----------------------------------------------------------
 *  An earlier version gated this on @supports (animation-timeline:
 *  view()), a leftover from when the sweep was scroll-driven. Now that
 *  the animation is time-driven, that gate is wrong — it leaves out
 *  Safari and any other browser that supports background-clip: text
 *  but not animation-timeline: view(). Replaced with the actually-
 *  needed feature gate: background-clip: text. (Without the gate, an
 *  old browser would render `color: transparent` against no painted
 *  background and the text would just disappear.)
 *
 *  Apparent text colour at the rest endpoints: with background-size
 *  320% the visible window is 31% of the gradient, narrower than the
 *  white plateau region (45-55%) plus shoulders (35-65%), so both rest
 *  positions land in the solid --shimmer-base region only. Text colour
 *  at rest is therefore identical to the base colour — no gradient is
 *  visible while the highlight is parked off-screen.
 * ─────────────────────────────────────────────────────────────────── */

/* ── Shimmer colours per context ──────────────────────────────────── */
/* Two custom properties per context:
 *   --shimmer-base     → the gradient base colour (token reference)
 *   --shimmer-glow-rgb → the same colour decomposed as an "r, g, b"
 *                        triple so it can be plugged into rgba() inside
 *                        the drop-shadow keyframes. We need the triple
 *                        because we animate the alpha channel and CSS
 *                        can't animate alpha alone of a custom prop
 *                        without @property declarations. */
.hero h1 i,
.hero h1 em,
.hero h1 .it,
.ph--dark .ph-headline i,
.ph--dark .ph-headline em,
.ph--dark .ph-headline .it,
.cta-strip h2 i,
.cta-strip h2 em,
.big-quote p i,
section.s.s-dark-1 .h-display i,
section.s.s-dark-2 .h-display i {
  --shimmer-base: var(--secondary);
  --shimmer-glow-rgb: 79, 182, 236;
}

.ph--light .ph-headline i,
.ph--light .ph-headline em,
.ph--light .ph-headline .it,
.about-feature h3 i,
.about-feature h3 em,
.legal-block h2 i,
.legal-block h2 em,
.flowella-rich .rich-prose h2 i,
.flowella-rich .rich-prose h2 em,
.flowella-rich .rich-prose h3 i,
.flowella-rich .rich-prose h3 em,
.feature-heading i,
.feature-heading em,
.h-display i {
  --shimmer-base: var(--primary);
  --shimmer-glow-rgb: 14, 110, 166;
}

.hero h1 .acc,
.ph-headline .acc {
  --shimmer-base: var(--green);
  --shimmer-glow-rgb: 37, 211, 102;
}

/* ── Shimmer animation ────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  @supports ((background-clip: text) or (-webkit-background-clip: text)) {
    .hero h1 i,
    .hero h1 em,
    .hero h1 .it,
    .hero h1 .acc,
    .ph-headline i,
    .ph-headline em,
    .ph-headline .it,
    .ph-headline .acc,
    .about-feature h3 i,
    .about-feature h3 em,
    .legal-block h2 i,
    .legal-block h2 em,
    .big-quote p i,
    .cta-strip h2 i,
    .cta-strip h2 em,
    .flowella-rich .rich-prose h2 i,
    .flowella-rich .rich-prose h2 em,
    .flowella-rich .rich-prose h3 i,
    .flowella-rich .rich-prose h3 em,
    .feature-heading i,
    .feature-heading em,
    .h-display i {
      /* Peaked highlight: soft shoulders (35→45 / 55→65) ramp base
         to full white, with a 10% flat-white plateau (45-55) so the
         text holds at peak brightness for a beat rather than just
         brushing past. */
      background-image: linear-gradient(110deg,
        var(--shimmer-base, currentColor) 0%,
        var(--shimmer-base, currentColor) 35%,
        rgba(255, 255, 255, 1) 45%,
        rgba(255, 255, 255, 1) 55%,
        var(--shimmer-base, currentColor) 65%,
        var(--shimmer-base, currentColor) 100%);
      /* 320% (was 250%) means the visible window is 31% of the
         gradient — narrower than the highlight region — so both rest
         positions paint the text in pure --shimmer-base. No partial-
         gradient bleed at rest. */
      background-size: 320% 100%;
      background-position: 100% 50%;
      background-repeat: no-repeat;
      -webkit-background-clip: text;
              background-clip: text;
      -webkit-text-fill-color: transparent;
              color: transparent;
      /* 4s linear infinite loop. Single animation, no filter pair —
         the previous filter: drop-shadow() pair was failing to
         interpolate cleanly from 0-blur transparent to 22px coloured
         on at least one engine, leaving the whole shorthand inert. */
      animation: fr-shimmer-sweep 4s linear infinite both;
    }

    /* Hero / placeholder .acc lags 500ms behind the surrounding
       italics so "conversations → conversions" pulses in reading
       order each cycle, not as a chord. */
    .hero h1 .acc,
    .ph-headline .acc {
      animation-delay: 500ms;
    }
  }
}

/* Hold-then-sweep rhythm at 4s/cycle:
     0%   →  45% : rest off-screen-right (≈1.8s of solid base colour)
     45%  →  85% : sweep across (≈1.6s — visible "shine" passes)
     85%  → 100% : hold off-screen-left (≈0.6s of solid base colour)
   The loop snap from 0%-pos to 100%-pos at the cycle boundary is
   invisible because both endpoint positions paint the visible window
   in pure --shimmer-base. */
@keyframes fr-shimmer-sweep {
  0%, 45%   { background-position: 100% 50%; }
  85%       { background-position: 0% 50%; }
  100%      { background-position: 0% 50%; }
}


/* ─────────────────────────────────────────────────────────────────────
 *  Task 5b — Italic accent persistent glow (low-amplitude breathing)
 *
 *  text-shadow is the persistent ambient — it stays as a soft static
 *  glow even with prefers-reduced-motion: reduce. The breathing
 *  animation only runs when the user hasn't asked us to be still.
 *
 *  Applied ONLY to italic accents that act as heading accents. Body
 *  copy contexts (.tone-line, .ed-drop, etc.) are deliberately
 *  excluded — see the selector list in 5a.
 * ─────────────────────────────────────────────────────────────────── */

.hero h1 i,
.hero h1 em,
.hero h1 .it,
.ph-headline i,
.ph-headline em,
.ph-headline .it,
.about-feature h3 i,
.about-feature h3 em,
.legal-block h2 i,
.legal-block h2 em,
.big-quote p i,
.cta-strip h2 i,
.cta-strip h2 em,
.flowella-rich .rich-prose h2 i,
.flowella-rich .rich-prose h2 em,
.flowella-rich .rich-prose h3 i,
.flowella-rich .rich-prose h3 em,
.feature-heading i,
.feature-heading em,
.h-display i {
  text-shadow: var(--fr-glow-secondary-soft);
}

.hero h1 .acc,
.ph-headline .acc {
  text-shadow: var(--fr-glow-green-soft);
}

@media (prefers-reduced-motion: no-preference) {
  .hero h1 i,
  .hero h1 em,
  .hero h1 .it,
  .ph-headline i,
  .ph-headline em,
  .ph-headline .it,
  .about-feature h3 i,
  .about-feature h3 em,
  .legal-block h2 i,
  .legal-block h2 em,
  .big-quote p i,
  .cta-strip h2 i,
  .cta-strip h2 em,
  .flowella-rich .rich-prose h2 i,
  .flowella-rich .rich-prose h2 em,
  .flowella-rich .rich-prose h3 i,
  .flowella-rich .rich-prose h3 em,
  .feature-heading i,
  .feature-heading em,
  .h-display i {
    animation: fr-glow-breathe-blue 5.5s ease-in-out infinite alternate;
  }

  .hero h1 .acc,
  .ph-headline .acc {
    animation: fr-glow-breathe-green 5.5s ease-in-out infinite alternate;
  }
}

@keyframes fr-glow-breathe-blue {
  from { text-shadow: var(--fr-glow-secondary-soft); }
  to   { text-shadow: var(--fr-glow-secondary-strong); }
}

@keyframes fr-glow-breathe-green {
  from { text-shadow: var(--fr-glow-green-soft); }
  to   { text-shadow: var(--fr-glow-green-strong); }
}


/* ─────────────────────────────────────────────────────────────────────
 *  Task 6 — CTA glow (per-button colour, subtle)
 *
 *  Reworked: the glow now picks up the button's own brand colour so it
 *  reads as a soft halo rather than a contrasting accent.
 *    .btn-primary  → sky-blue halo  (var(--primary))
 *    .btn-wa       → WhatsApp green (var(--green))
 *  Other variants (.btn-ghost, .btn-secondary, text links) stay
 *  untouched.
 *
 *  Amplitudes are deliberately low — felt-not-seen. Hover lifts the
 *  shadow to a stronger fixed state (no breathing), and a 200ms sheen
 *  sweeps once across the button via a ::before. The sheen tints
 *  match the button colour family.
 *
 *  The button needs `position: relative; overflow: hidden` to host the
 *  sheen pseudo-element. Specificity is bumped to (0,2,1) via the
 *  `body` ancestor so the `.cta-strip .btn-primary` override in the
 *  cta-banner module (which loads AFTER motion.css) doesn't win.
 *
 *  Per-CTA colour comes from --fr-cta-glow-rgb, set per selector. The
 *  keyframes consume rgb(var(...) / opacity) so a single keyframe pair
 *  serves every variant.
 * ─────────────────────────────────────────────────────────────────── */
body .btn.btn-primary,
body .btn.btn-wa {
  position: relative;
  overflow: hidden;
}

body .btn.btn-primary {
  --fr-cta-glow-rgb: 14, 110, 166;
  box-shadow: 0 6px 18px -8px rgba(14, 110, 166, .35);
}
body .btn.btn-primary:hover {
  box-shadow: 0 10px 26px -8px rgba(14, 110, 166, .55);
}

body .btn.btn-wa {
  --fr-cta-glow-rgb: 37, 211, 102;
  box-shadow: 0 6px 18px -8px rgba(37, 211, 102, .35);
}
body .btn.btn-wa:hover {
  box-shadow: 0 10px 26px -8px rgba(37, 211, 102, .55);
}

body .btn.btn-primary::before,
body .btn.btn-wa::before {
  content: "";
  position: absolute;
  inset: 0;
  /* Sheen tint stays on-brand. White at .25 across all variants reads
     as a soft brushed-metal pass; tinting it would over-saturate. */
  background: linear-gradient(110deg,
    transparent 0%,
    transparent 38%,
    rgba(255, 255, 255, .28) 50%,
    transparent 62%,
    transparent 100%);
  background-size: 250% 100%;
  background-position: 100% 50%;
  background-repeat: no-repeat;
  pointer-events: none;
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  body .btn.btn-primary,
  body .btn.btn-wa {
    animation: fr-cta-glow 6s ease-in-out infinite alternate;
  }

  /* Suspend the breathing while hovered so the stronger static hover
     shadow reads as a single, deliberate state. */
  body .btn.btn-primary:hover,
  body .btn.btn-wa:hover {
    animation: none;
  }

  body .btn.btn-primary:hover::before,
  body .btn.btn-wa:hover::before {
    animation: fr-cta-sheen 220ms linear forwards;
  }
}

@keyframes fr-cta-glow {
  from { box-shadow: 0 6px 18px -8px rgba(var(--fr-cta-glow-rgb, 14, 110, 166), .25); }
  to   { box-shadow: 0 8px 22px -8px rgba(var(--fr-cta-glow-rgb, 14, 110, 166), .42); }
}

@keyframes fr-cta-sheen {
  0%   { opacity: 1; background-position: 100% 50%; }
  100% { opacity: 1; background-position: 0% 50%; }
}
