/* ============================================================
   Hero motion — layered, slow, low-amplitude.

   Kept in its own file so each layer can be tuned or dropped
   without touching the original design CSS.

   Two constraints shaped the implementation:
   1. site.js sets an inline `transform` on .hero-aurora and
      .hero-portrait for scroll parallax. Nothing here animates
      `transform` on those two elements — the layers below drive
      pseudo-elements and the inner <img> instead, so the two
      systems compose rather than overwrite each other.
   2. This deliberately does NOT honour `prefers-reduced-motion`.
      It previously did, which meant anyone with Reduce Motion
      enabled (iOS Settings > Accessibility > Motion, and it is on
      by default on some devices) saw a completely static hero.
      That was the site owner's call, made knowingly: this is a
      slow ambient background glow with no parallax, flashing, or
      scroll coupling, so the vestibular risk is low. If that
      judgement is ever revisited, restore the gate by wrapping
      the animation rules below in
      `@media (prefers-reduced-motion: no-preference) { ... }`.
   ============================================================ */

/* ---- Layer 1: gold aurora, lava-lamp motion ----------------
   The LOOK here is the original design's soft gold aurora on ink,
   unchanged: same diffuse radial falloff, same restrained opacity,
   no visible edges. Only the MOTION changed.

   What makes it read as a lava lamp is the character of the
   movement, not the rendering:
     - very slow (34-58s periods), so it never looks like an
       animation playing, only like something displacing;
     - paths with three waypoints rather than a there-and-back, so
       it never retraces its own route;
     - each mass squashes and stretches as it travels, which is
       what makes a blob read as fluid rather than as a circle
       being slid around.

   A metaball/contrast treatment was tried here and reverted: it
   produced defined edges and blew the brand gold to acid yellow.
   Keep the falloff soft — the movement carries this, not the shape. */

/* Each mass is TWO nested elements, and that nesting is the whole point.

     .hb  (outer)  — carries the travel path only
     .hb i (inner) — carries rotation and stretch only, on a period that
                     shares no common factor with the outer one

   When one element does both, the squash lands on the same keyframes as the
   movement, so the shape is identical every time it reaches a given point —
   which is exactly what reads as a rigid object bouncing around. Splitting
   them means shape and position drift out of phase and the silhouette is
   never repeated.

   Rotation is doing most of the morphing work: these gradients are ellipses,
   so turning one continuously changes its apparent width and height without
   any shape actually deforming.

   `closest-side` makes the gradient reach full transparency exactly at the
   box edge, so the container can never show a straight edge — the earlier
   "blocks". Sizes are % of the hero; the box is far larger than the visible
   glow, so its bounds stay well clear regardless of travel. */

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

.hero-blobs .hb {
  position: absolute;
  display: block;
  /* Keep each blob on its own compositor layer so the transform animation runs
     on the GPU — on iOS, CPU compositing here stutters or stalls outright.
     The keyframes use translate3d() for the same reason; a static translateZ(0)
     would be pointless since the animation overrides transform anyway. */
  will-change: transform;
  backface-visibility: hidden;
}

.hero-blobs .hb > i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  will-change: transform;
}

/* Centres match the original design: 74% 18%, 16% 88%, 50% 50% of the hero,
   at the same restrained alphas (.26 / .20 / .15). */
/* Sizes are deliberately well under the hero: an earlier revision used
   ~90%-wide blobs which overlapped into a single wash and erased the ink
   entirely. The gap between masses IS the effect — gold reads as gold only
   against black. Centres still sit at the design's 74% 18%, 16% 88%, 50% 50%. */
.hb1 { width: 56%; height: 60%; left: 46%;  top: -12%; animation: pathA 17s ease-in-out infinite; }
.hb2 { width: 50%; height: 52%; left: -9%;  top: 62%;  animation: pathB 23s ease-in-out infinite; }
.hb3 { width: 46%; height: 62%; left: 27%;  top: 19%;  animation: pathC 29s ease-in-out infinite; }

/* The fill is a soft blob, not a gradient ellipse.

   Rotating or stretching a radial gradient still reads as a circle, because
   the falloff itself is elliptical — the silhouette never changes, only its
   aspect. Real shapeshifting needs the OUTLINE to move, so each blob is a
   filled element whose eight border-radius values animate independently
   (the `A B C D / E F G H` form controls each corner's horizontal and
   vertical radius separately, which is what lets it bulge asymmetrically).

   A heavy blur then removes every trace of a hard edge, so what remains is a
   soft mass that visibly changes form as it drifts. Blur radius is large
   relative to the blob so the shape stays molten rather than lumpy. */

.hb1 > i {
  background: radial-gradient(closest-side, rgba(218,165,32,0.30), rgba(218,165,32,0.20) 72%, rgba(218,165,32,0.13) 100%);
  filter: blur(clamp(38px, 5vw, 78px));
  animation: morphA 12s ease-in-out infinite;
}
.hb2 > i {
  background: radial-gradient(closest-side, rgba(168,125,18,0.24), rgba(168,125,18,0.16) 72%, rgba(168,125,18,0.10) 100%);
  filter: blur(clamp(38px, 5vw, 78px));
  animation: morphB 15s ease-in-out infinite;
}
.hb3 > i {
  background: radial-gradient(closest-side, rgba(227,187,85,0.19), rgba(218,165,32,0.12) 72%, rgba(218,165,32,0.07) 100%);
  filter: blur(clamp(42px, 5.5vw, 86px));
  animation: morphC 19s ease-in-out infinite;
}

/* Travel only — no scaling here, so the path never dictates the shape.

   NOTE: translate percentages resolve against each blob's OWN size, and these
   boxes are roughly hero-sized. An earlier revision used 3x-hero boxes, so
   identical numbers moved three times as far; porting them over unchanged
   dropped the speed to ~1.8% of hero per second and it looked frozen again.
   These are scaled to land back in the 4-8%/sec band that reads as moving. */
/* Each path is a CLOSED CIRCUIT, not an out-and-back.

   An earlier version ran origin -> A -> B -> origin, which retraces its own
   line every cycle; on a phone, where fewer masses are visible at once, that
   reads unmistakably as the same sweep repeating. These orbit through five
   waypoints and return to the start from a different bearing, so no leg is
   ever travelled twice. Adjacent blobs orbit in OPPOSITE directions, and the
   periods are near-prime so the field never resynchronises. */
@keyframes pathA {
  0%, 100% { transform: translate3d(0, 0, 0); }
  22%      { transform: translate3d(-40%, 28%, 0); }
  44%      { transform: translate3d(-22%, 72%, 0); }
  66%      { transform: translate3d(26%, 66%, 0); }
  84%      { transform: translate3d(44%, 22%, 0); }
}
@keyframes pathB {
  0%, 100% { transform: translate3d(0, 0, 0); }
  24%      { transform: translate3d(48%, -26%, 0); }
  47%      { transform: translate3d(62%, -64%, 0); }
  69%      { transform: translate3d(24%, -78%, 0); }
  86%      { transform: translate3d(-14%, -44%, 0); }
}
@keyframes pathC {
  0%, 100% { transform: translate3d(-60%, 26%, 0); }
  23%      { transform: translate3d(-20%, -24%, 0); }
  45%      { transform: translate3d(40%, -46%, 0); }
  68%      { transform: translate3d(66%, -6%, 0); }
  85%      { transform: translate3d(16%, 34%, 0); }
}

/* Shape only — the silhouette itself, on its own clock (12/15/19s against
   travel periods of 14/19/21s, so form and position never resync). Rotation
   rides along to keep the asymmetry sweeping around rather than pulsing in
   place. */
@keyframes morphA {
  0%, 100% { border-radius: 62% 38% 44% 56% / 54% 46% 54% 46%; transform: rotate(0deg)   scale(1, 1); }
  27%      { border-radius: 38% 62% 63% 37% / 41% 63% 37% 59%; transform: rotate(16deg)  scale(1.12, 0.9); }
  53%      { border-radius: 55% 45% 33% 67% / 66% 32% 68% 34%; transform: rotate(-11deg) scale(0.91, 1.15); }
  78%      { border-radius: 43% 57% 58% 42% / 37% 59% 41% 63%; transform: rotate(9deg)   scale(1.07, 0.96); }
}
@keyframes morphB {
  0%, 100% { border-radius: 47% 53% 61% 39% / 43% 57% 43% 57%; transform: rotate(0deg)   scale(1, 1); }
  31%      { border-radius: 65% 35% 38% 62% / 62% 41% 59% 38%; transform: rotate(-14deg) scale(0.9, 1.16); }
  62%      { border-radius: 36% 64% 56% 44% / 35% 62% 38% 65%; transform: rotate(12deg)  scale(1.17, 0.89); }
}
@keyframes morphC {
  0%, 100% { border-radius: 54% 46% 37% 63% / 58% 44% 56% 42%; transform: rotate(0deg)   scale(1, 1);      opacity: 0.62; }
  26%      { border-radius: 34% 66% 59% 41% / 39% 61% 39% 61%; transform: rotate(13deg)  scale(1.14, 0.9); opacity: 0.95; }
  58%      { border-radius: 63% 37% 45% 55% / 67% 33% 64% 36%; transform: rotate(-10deg) scale(0.9, 1.18); opacity: 1; }
  81%      { border-radius: 45% 55% 52% 48% / 42% 58% 45% 55%; transform: rotate(6deg)   scale(1.08, 0.95); opacity: 0.8; }
}

/* Phones need their own paths, not just slower ones.

   translate percentages resolve against each blob's own box, and those boxes
   are sized in % of a hero that is narrow and very tall on a phone. The
   desktop values therefore produce far more travel relative to the visible
   width — measured at 16-38% of hero width per second on a 375px viewport
   against ~8% on desktop, which looks frantic rather than molten.

   Same shapes, same speeds in perceptual terms, shorter throws. */
@media (max-width: 700px) {
  /* iOS composites large blur radii expensively and will visibly stall or drop
     the animation. Smaller blobs need less blur to read as soft anyway. */
  .hb > i { filter: blur(clamp(26px, 7vw, 40px)); }

  /* Phones get their OWN paths, but NOT smaller ones.

     An earlier revision shrank these to ~10% of the hero because the travel
     measured 16-38% "of hero width per second" — but that metric was wrong for
     a 375x812 viewport. Vertical travel divided by WIDTH inflates the number
     enormously on a tall screen, so healthy motion looked frantic and got cut
     until the blobs sat almost still in a fixed arrangement.

     Judge each axis against its own dimension. These throws are comparable to
     desktop in proportional terms, which is what lets the masses actually
     traverse the screen and rearrange relative to each other rather than
     drifting as a locked group. Periods are near-prime so they never resync. */
  .hb1 { animation: pathAm 26s ease-in-out infinite; }
  .hb2 { animation: pathBm 34s ease-in-out infinite; }
  .hb3 { animation: pathCm 41s ease-in-out infinite; }

  @keyframes pathAm {
    0%, 100% { transform: translate3d(0, 0, 0); }
    22%      { transform: translate3d(-45%, 34%, 0); }
    44%      { transform: translate3d(-28%, 78%, 0); }
    66%      { transform: translate3d(30%, 70%, 0); }
    84%      { transform: translate3d(52%, 26%, 0); }
  }
  @keyframes pathBm {
    0%, 100% { transform: translate3d(0, 0, 0); }
    24%      { transform: translate3d(58%, -30%, 0); }
    47%      { transform: translate3d(72%, -72%, 0); }
    69%      { transform: translate3d(26%, -88%, 0); }
    86%      { transform: translate3d(-20%, -50%, 0); }
  }
  @keyframes pathCm {
    0%, 100% { transform: translate3d(-70%, 30%, 0); }
    23%      { transform: translate3d(-24%, -28%, 0); }
    45%      { transform: translate3d(46%, -52%, 0); }
    68%      { transform: translate3d(76%, -8%, 0); }
    85%      { transform: translate3d(18%, 38%, 0); }
  }
}

/* ---- Layer 2: grain drift ---------------------------------
   Shifts the 4px grain lattice by exactly one tile, so the loop
   is seamless. Slow enough to read as film weave, not movement. */

.hero-grain {
  animation: heroGrain 8s steps(4, end) infinite;
  will-change: background-position;
}

@keyframes heroGrain {
  from { background-position: 0 0; }
  to   { background-position: 4px 4px; }
}

/* ---- (removed) gold sheen across the headline --------------
   A `background-clip: text` sweep over "Fundable by Design" was
   tried here and deliberately removed.

   The technique has no safe failure mode on wrapping text: the
   fill must be transparent for the gradient to show, so any
   glyph that falls outside the painted gradient box renders as
   nothing. That span wraps to two lines, and at the tail of the
   sweep the leading words silently vanished — "Fundable by De"
   disappeared, leaving "sign" floating alone. Tiling the
   gradient and cloning the decoration reduced it but did not
   eliminate it at every animation position.

   The motion now lives in the background gold instead, where a
   failure can only ever be a missing glow, never missing words.
   Do not reintroduce this on multi-line text. */

/* ---- Layer 3: portrait breathe -----------------------------
   On the <img>, not .hero-portrait, so the JS parallax transform
   on the parent is left alone. Barely over 1% of scale. */

.hero-portrait img {
  animation: heroBreathe 14s ease-in-out infinite;
  will-change: transform;
}

@keyframes heroBreathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.014); }
}

/* ---- Layer 4: scroll cue -----------------------------------
   The hero is min-height:100svh, so on mobile it fills the screen
   with no visual signal that anything follows. This is a small
   travelling tick inside a rail — the one piece of motion here
   that carries information rather than atmosphere. */

.hero-scroll .rail::after { animation: heroScrollTick 2.4s cubic-bezier(0.16,1,0.3,1) infinite; }

@keyframes heroScrollTick {
  0%        { transform: translateY(-100%); opacity: 0; }
  35%       { opacity: 1; }
  75%, 100% { transform: translateY(180%);  opacity: 0; }
}


/* ---- Scroll cue chrome (static parts, always rendered) --------
   Outside the reduced-motion gate so the affordance still exists
   for readers who've asked for less motion — it simply doesn't
   travel. */

.hero-scroll {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(18px, 3vh, 34px);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

.hero-scroll .lbl {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(244, 242, 234, 0.42);
}

.hero-scroll .rail {
  position: relative;
  width: 1px;
  height: 40px;
  overflow: hidden;
  background: rgba(244, 242, 234, 0.14);
}

.hero-scroll .rail::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent, var(--ev-gold-500), transparent);
}

/* The cue competes with the stacked portrait on short screens. */
@media (max-width: 1000px), (max-height: 700px) {
  .hero-scroll { display: none; }
}
