/* ==========================================================================
   Animated mesh-gradient blobs — shared by the cover page (lively, 3-4
   blobs) and three homepage sections (calm, 2 blobs each: hero, selected
   work, pull quote). Pure CSS: absolutely positioned, heavily blurred
   radial-gradient divs, animated via keyframes (position drift + scale
   breathing + a hue-rotate sweep standing in for "color-stop shifting" —
   smoothly animating gradient color stops directly isn't practical in
   plain CSS, but rotating hue on an already-blurred blob reads the same
   way: a slow, soft color drift). No JS, no mouse interaction — ambient
   only, driven entirely by animation-duration/-delay per blob.

   Usage: a `.mesh-bg` container (position:relative ancestor required)
   holds 2-4 `.mesh-blob` elements, each parametrized via inline custom
   properties (--blob-color/--blob-size/--blob-top/--blob-left/
   --blob-opacity/--blob-duration/--blob-delay), plus one `.mesh-grain`
   overlay. `.mesh-bg--cover` / `.mesh-bg--section` set the container's
   positioning context (fixed full-bleed vs. absolute within a section)
   and base opacity ceiling for their blobs.
   ========================================================================== */

.mesh-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.mesh-bg--cover {
  position: fixed;
}

/* Applied to a section that hosts a .mesh-bg (besides the hero, which
   already has its own position:relative/overflow:hidden and a z-index'd
   .hero__inner for the mark-bleed image) — contains/clips the blobs to
   the section and lifts its .container above them. */
.section--mesh {
  position: relative;
  overflow: hidden;
}

.section--mesh > .container {
  position: relative;
  z-index: 1;
}

.mesh-blob {
  position: absolute;
  top: var(--blob-top, 0);
  left: var(--blob-left, 0);
  width: var(--blob-size, 50vw);
  height: var(--blob-size, 50vw);
  border-radius: 50%;
  background: radial-gradient(circle, var(--blob-color) 0%, transparent 70%);
  opacity: var(--blob-opacity, 0.5);
  filter: blur(var(--blob-blur, 80px));
  will-change: transform, filter;
}

@media (prefers-reduced-motion: no-preference) {
  .mesh-blob {
    animation: mesh-drift var(--blob-duration, 20s) ease-in-out infinite;
    animation-delay: var(--blob-delay, 0s);
  }
}

@keyframes mesh-drift {
  0%, 100% {
    transform: translate(0, 0) scale(1);
    filter: blur(var(--blob-blur, 80px)) hue-rotate(0deg);
  }
  33% {
    transform: translate(6%, -5%) scale(1.12);
    filter: blur(calc(var(--blob-blur, 80px) + 10px)) hue-rotate(14deg);
  }
  66% {
    transform: translate(-5%, 4%) scale(0.94);
    filter: blur(calc(var(--blob-blur, 80px) - 5px)) hue-rotate(-10deg);
  }
}

/* Lively variant — cover page only (.mesh-blob--lively-1 .. -4, paired
   with each blob's own --blob-duration, back to the original ~4.5-6s
   range). Same mechanics as mesh-drift but with a much wider scale
   swing and 5 keyframe stops instead of 3, so the morph reads as
   continuous, fluid movement rather than a gentle breathe. Kept
   separate from mesh-drift, not a bigger version of it, so the
   homepage sections' calm treatment is untouched regardless of what
   the cover's amplitude does.

   Translate distances are in vw/vh, not %: a transform percentage
   resolves against the element's OWN box, so on a blob that's already
   40-58vw wide, translating by even a generous-looking 9% only moved
   it ~5vw — barely perceptible against the full viewport. vw/vh units
   are viewport-relative regardless of the blob's own size, which is
   what actually reads as the blob roaming across the page. Contained
   safely by .mesh-bg's own overflow:hidden, so this can't cause page
   overflow no matter how far a blob travels.

   Four separate keyframes, not one shared shape reused at different
   delays/durations — sharing one path just staggers when each blob
   passes through the same relative points, which still reads as
   choreographed. Each blob gets its own genuinely different direction
   and turn sequence instead, so the composition doesn't have a single
   repeating "beat". */
@keyframes mesh-drift-lively-1 {
  0%, 100% {
    transform: translate(0, 0) scale(1);
    filter: blur(var(--blob-blur, 80px)) hue-rotate(0deg);
  }
  25% {
    transform: translate(22vw, 14vh) scale(1.3);
    filter: blur(calc(var(--blob-blur, 80px) + 16px)) hue-rotate(20deg);
  }
  50% {
    transform: translate(10vw, -16vh) scale(0.85);
    filter: blur(calc(var(--blob-blur, 80px) - 10px)) hue-rotate(-16deg);
  }
  75% {
    transform: translate(-14vw, 8vh) scale(1.2);
    filter: blur(calc(var(--blob-blur, 80px) + 6px)) hue-rotate(10deg);
  }
}

@keyframes mesh-drift-lively-2 {
  0%, 100% {
    transform: translate(0, 0) scale(1);
    filter: blur(var(--blob-blur, 80px)) hue-rotate(0deg);
  }
  25% {
    transform: translate(-20vw, -12vh) scale(1.25);
    filter: blur(calc(var(--blob-blur, 80px) - 8px)) hue-rotate(-18deg);
  }
  50% {
    transform: translate(14vw, 16vh) scale(0.88);
    filter: blur(calc(var(--blob-blur, 80px) + 12px)) hue-rotate(14deg);
  }
  75% {
    transform: translate(8vw, -14vh) scale(1.3);
    filter: blur(calc(var(--blob-blur, 80px) - 4px)) hue-rotate(-8deg);
  }
}

@keyframes mesh-drift-lively-3 {
  0%, 100% {
    transform: translate(0, 0) scale(1);
    filter: blur(var(--blob-blur, 80px)) hue-rotate(0deg);
  }
  25% {
    transform: translate(-16vw, 18vh) scale(1.2);
    filter: blur(calc(var(--blob-blur, 80px) + 10px)) hue-rotate(16deg);
  }
  50% {
    transform: translate(18vw, 10vh) scale(0.85);
    filter: blur(calc(var(--blob-blur, 80px) - 12px)) hue-rotate(-20deg);
  }
  75% {
    transform: translate(-10vw, -16vh) scale(1.3);
    filter: blur(calc(var(--blob-blur, 80px) + 4px)) hue-rotate(12deg);
  }
}

@keyframes mesh-drift-lively-4 {
  0%, 100% {
    transform: translate(0, 0) scale(1);
    filter: blur(var(--blob-blur, 80px)) hue-rotate(0deg);
  }
  25% {
    transform: translate(12vw, -20vh) scale(1.3);
    filter: blur(calc(var(--blob-blur, 80px) - 6px)) hue-rotate(-14deg);
  }
  50% {
    transform: translate(-18vw, 6vh) scale(0.85);
    filter: blur(calc(var(--blob-blur, 80px) + 14px)) hue-rotate(18deg);
  }
  75% {
    transform: translate(16vw, 14vh) scale(1.15);
    filter: blur(calc(var(--blob-blur, 80px) - 2px)) hue-rotate(-10deg);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .mesh-blob--lively-1 { animation-name: mesh-drift-lively-1; }
  .mesh-blob--lively-2 { animation-name: mesh-drift-lively-2; }
  .mesh-blob--lively-3 { animation-name: mesh-drift-lively-3; }
  .mesh-blob--lively-4 { animation-name: mesh-drift-lively-4; }
}

/* Reduced motion: blobs stay put as a static soft-focus wash — no
   position/scale/hue animation, just the plain blurred gradient. */
@media (prefers-reduced-motion: reduce) {
  .mesh-blob {
    filter: blur(var(--blob-blur, 80px));
  }
}

/* Fine fractal-noise grain, same ~5% opacity everywhere for continuity
   between the cover and the toned-down section treatments. Generated
   inline via an SVG feTurbulence data URI — no external asset. */
.mesh-grain {
  position: absolute;
  inset: 0;
  opacity: 0.05;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}
