/* Animated hero background (Youssef's 3-blob radial-gradient drift spec)
   + scroll-reveal system. Pure CSS keyframes — transform/opacity only, GPU-composited.
   JS involvement is limited to a prefers-reduced-motion check and an
   IntersectionObserver play/pause guard (js/background.js) — no per-frame JS. */

.blob {
  position: absolute;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, var(--accent) 0%, var(--accent-soft-2) 55%, transparent 75%);
  filter: blur(90px);
  mix-blend-mode: screen;
  will-change: transform;
}

@media (max-width: 767px) {
  .blob { filter: blur(50px); }
}

.blob--a {
  width: 720px;
  height: 720px;
  inset-inline-end: -10%;
  top: -15%;
  opacity: 0.55;
  animation: drift-a 26s var(--ease-ambient) infinite;
}

.blob--b {
  width: 560px;
  height: 560px;
  inset-inline-start: 10%;
  bottom: -20%;
  opacity: 0.35;
  animation: drift-b 34s var(--ease-ambient) infinite;
}

.blob--c {
  width: 480px;
  height: 480px;
  inset-inline-end: 15%;
  top: 30%;
  opacity: 0.25;
  animation: drift-c 22s var(--ease-ambient) infinite reverse;
}

@media (max-width: 767px) {
  .blob--a { width: 432px; height: 432px; }
  .blob--b { width: 336px; height: 336px; }
  .blob--c { width: 288px; height: 288px; }
}

@keyframes drift-a {
  0%   { transform: translate(0, 0) scale(1); }
  33%  { transform: translate(-4%, 6%) scale(1.08); }
  66%  { transform: translate(3%, -3%) scale(0.96); }
  100% { transform: translate(0, 0) scale(1); }
}

@keyframes drift-b {
  0%   { transform: translate(0, 0) scale(1); }
  40%  { transform: translate(5%, -5%) scale(1.05); }
  70%  { transform: translate(-3%, 4%) scale(0.94); }
  100% { transform: translate(0, 0) scale(1); }
}

@keyframes drift-c {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(-6%, -4%) scale(1.1); }
  100% { transform: translate(0, 0) scale(1); }
}

/* Parallax — hero background drifts a few % slower than scroll on desktop.
   --scroll-y is set by background.js (rAF-throttled, desktop-only listener);
   --parallax-factor is 0 on mobile (variables.css), so this is inert there
   even if the listener somehow ran. */
.hero-bg {
  transform: translateY(calc(var(--scroll-y, 0px) * var(--parallax-factor)));
  will-change: transform;
}

.hero-bg__grain {
  position: absolute;
  inset: 0;
  opacity: 0.025;
  mix-blend-mode: overlay;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

@media (prefers-reduced-motion: reduce) {
  .blob--a, .blob--b, .blob--c {
    animation: none;
  }
}

@keyframes scroll-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(6px); }
}

/* ---------------------------------------------------------
   Hero on-load stagger — plays once on page load (above the fold,
   never scroll-triggered). Same settle character as the scroll
   reveals (fade + upward drift, no bounce), just driven by
   animation-delay instead of an IntersectionObserver.
--------------------------------------------------------- */
@keyframes hero-settle-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hero .eyebrow,
.hero__subhead,
.hero__actions {
  opacity: 0;
  animation: hero-settle-in var(--duration-onload) var(--ease-ambient) both;
}

.hero .eyebrow  { animation-delay: 0ms; }
.hero__subhead  { animation-delay: calc(var(--stagger-hero) * 2); }
.hero__actions  { animation-delay: calc(var(--stagger-hero) * 3); }

@media (prefers-reduced-motion: reduce) {
  .hero .eyebrow,
  .hero__subhead,
  .hero__actions {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* ---------------------------------------------------------
   Hero headline mask reveal — the line stays clipped by an
   overflow:hidden wrapper while the inner span slides up from
   below. One mask for the whole headline (not per visual line):
   line count varies by language (AR/FR run longer than EN), so
   masking per rendered line would need re-measuring on every
   language switch. data-i18n sits on the inner span only, so
   swapping languages via i18n.js never disturbs the mask markup.
--------------------------------------------------------- */
.hero__headline-mask {
  display: block;
  overflow: hidden;
}

.hero__headline-inner {
  display: block;
  transform: translateY(110%);
  animation: headline-mask-in var(--duration-onload) var(--ease-out-expo) both;
  animation-delay: var(--stagger-hero);
}

@keyframes headline-mask-in {
  to { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .hero__headline-inner {
    animation: none;
    transform: none;
  }
}

/* ---------------------------------------------------------
   Preloader — one-time curtain wipe on first load per session.
   Gated by html.no-intro (set synchronously in <head>, before first
   paint) so repeat homepage visits in the same session never flash it.
   clip-path only — no layout properties — so it's GPU-composited.
--------------------------------------------------------- */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 999;
  background: var(--bg-base);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  clip-path: inset(0 0 0% 0);
  transition: clip-path var(--intro-wipe) var(--ease-out-expo);
}

.preloader.is-leaving {
  clip-path: inset(0 0 100% 0);
}

.preloader__bar {
  width: 96px;
  height: 2px;
  border-radius: 2px;
  background: var(--border-subtle);
  overflow: hidden;
}

.preloader__bar-fill {
  width: 100%;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: var(--preload-origin) center;
  animation: preloader-fill var(--intro-fill) var(--ease-out-expo) var(--intro-hold) forwards;
}

@keyframes preloader-fill {
  to { transform: scaleX(1); }
}

html.no-intro .preloader {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .preloader {
    transition: none;
    clip-path: none;
    display: none;
  }
}

/* ---------------------------------------------------------
   About visual — barely-perceptible scroll-driven drift/scale,
   progressive enhancement over the standard [data-reveal] fallback.
   Feature-detected so unsupported browsers just keep the reveal.
--------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @keyframes about-visual-drift {
    from { transform: translateY(-6px) scale(0.99); }
    to   { transform: translateY(6px) scale(1.01); }
  }

  .about__visual-img {
    animation: about-visual-drift linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 50%;
  }

  @media (prefers-reduced-motion: reduce) {
    .about__visual-img {
      animation: none;
      transform: none;
    }
  }
}

/* ---------------------------------------------------------
   Scroll reveals — understated settle-in, one-time trigger.
   Class toggled by IntersectionObserver in js/main.js.
--------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(var(--reveal-distance));
  transition: opacity var(--reveal-duration) var(--ease-out-expo),
              transform var(--reveal-duration) var(--ease-out-expo);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger within a group — transition-delay set inline via JS from
   --reveal-stagger (mobile: 70ms/index, desktop: 90ms/index, capped at 4 items) */

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
