/*
  Veyra: the intelligence core.
  ==========================================================================

  A digital brain, drawn as nothing but the network it is made of: 1,420 fine
  fibres between 788 junctions, folded into a cerebrum, a cerebellum and a
  brainstem. No plate, no panel, no label, no mark - the section's words are
  beside it, and the object itself says only one thing: this is something
  that thinks.

  WHY IT IS ONE SVG

  Every fibre has to meet its node exactly, at every viewport. Overlaying
  HTML on an SVG drifts the moment the two scale differently, and a fibre
  that stops just short of its node reads as a bug. One coordinate space
  makes that impossible by construction.

  The geometry is generated, not hand-placed: points are sampled inside a
  lateral silhouette, denser through the cortical ring than the interior,
  excluded from the sulci, and joined to their nearest neighbours - with any
  link that would cross a groove discarded. That is why the mesh is
  asymmetrical and why the folds read as folds rather than as decoration.

  HOW IT STAYS CHEAP

  No canvas, no WebGL, no library, no per-frame JavaScript.

  The 1,420 fibres are 16 <path> elements, not 1,420: each path carries many
  subpaths, bundled by depth (band 1 core to band 4 rim, which is what makes
  the edges dissolve into the ground) and by firing group. The 122 lit nodes
  are six <g> elements whose opacity animates, so six things animate rather
  than 122. The travelling signals are a dashed stroke whose offset moves:
  one short dash in a very long gap is a dot following the exact fibre route,
  for one CSS animation each.

  231 elements in total, 48 of them animating.

  Bloom is two circles - a soft radial gradient behind a hard point - rather
  than an SVG filter, because filters re-rasterise every frame they animate.

  MOTION BUDGET

  opacity, transform, stroke-dashoffset. Nothing here moves the brain: it
  breathes, its nodes come and go, and signals travel through it. Animations
  only run while the section is on screen - veyra-brain.js adds .is-live - so
  scrolling past costs nothing.

  The section paints on the first frame; nothing is revealed by script, so a
  slow network or a blocked file leaves the brain visible and still.
*/

.vbrain {
  position: relative;
  width: 100%;
  /*
    The stage keeps the drawing's own proportions rather than a fixed height,
    so the composition never crops and never letterboxes.
  */
  aspect-ratio: 760 / 600;
}

.vbrain-svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* ==========================================================================
   1. The atmosphere
   ==========================================================================
   A radial gradient has no edge, so it separates the brain from the ground
   without drawing a shape around it - and there is deliberately no panel,
   card or plate anywhere in this file.
   ========================================================================== */

.vbrain-atmos {
  opacity: 1;
  transform-box: fill-box;
  transform-origin: center;
}

.is-live .vbrain-atmos {
  animation: vbrain-atmos 9s ease-in-out infinite;
}

@keyframes vbrain-atmos {
  0%,
  100% {
    opacity: 0.74;
    transform: scale(0.975);
  }
  50% {
    opacity: 1;
    transform: scale(1.025);
  }
}

/* ==========================================================================
   2. The breath
   ==========================================================================
   One slow rise and fall across the whole network, longer than any other
   period on the page so it never locks step with the node or signal timing.
   It is the only animation that touches everything at once, which is why it
   is a group opacity: the compositor can carry it on its own layer.
   ========================================================================== */

.vbrain-net {
  opacity: 0.94;
}

.is-live .vbrain-net {
  will-change: opacity;
  animation: vbrain-breathe 7.5s ease-in-out infinite;
}

@keyframes vbrain-breathe {
  0%,
  100% {
    opacity: 0.82;
  }
  50% {
    opacity: 1;
  }
}

/* ==========================================================================
   3. Silhouette and folds
   ==========================================================================
   Faint enough to be read as a boundary rather than drawn as an outline -
   the fibres are what the eye should follow.
   ========================================================================== */

.vbrain-edge {
  fill: none;
  stroke: rgba(108, 178, 234, 0.18);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/*
  The grooves are dark by construction: no fibre crosses one and no node sits
  in one. These strokes only put a trace of light on the walls, so a fold
  reads as a fold rather than as a gap in the mesh.
*/
.vbrain-groove {
  fill: none;
  stroke: rgba(70, 152, 220, 0.3);
  stroke-width: 0.9;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The cerebellum's folia are its signature texture, so they carry a little
   more light than the cortical sulci and are finer. */
.vbrain-folium {
  stroke: rgba(124, 206, 255, 0.42);
  stroke-width: 0.7;
}

/* ==========================================================================
   4. The fibres
   ==========================================================================
   --o is the bundle's settled opacity, set by depth; the firing animation
   works as a multiple of it, so depth and pulse stay independent of each
   other and neither has to be restated in the other's rules.
   ========================================================================== */

.vbrain-fibre {
  fill: none;
  stroke: #63b9f5;
  stroke-width: 0.8;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  opacity: var(--o);
}

.vbrain-fibre[data-band='1'] {
  --o: 0.88;
  stroke: #8ed6ff;
  stroke-width: 0.95;
}
.vbrain-fibre[data-band='2'] {
  --o: 0.68;
}
.vbrain-fibre[data-band='3'] {
  --o: 0.48;
}
/* The rim, where the brain has to stop being a shape and start being light. */
.vbrain-fibre[data-band='4'] {
  --o: 0.28;
  stroke: #4ea2e6;
}

.is-live .vbrain-fibre {
  animation: vbrain-fire 5.4s ease-in-out infinite;
}

/* Deliberately unequal periods: in unison this would read as a panel
   flashing rather than as traffic. */
.is-live .vbrain-fibre[data-fire='1'] { animation-duration: 6.3s; animation-delay: -1.7s; }
.is-live .vbrain-fibre[data-fire='2'] { animation-duration: 4.9s; animation-delay: -3.4s; }
.is-live .vbrain-fibre[data-fire='3'] { animation-duration: 7.6s; animation-delay: -0.8s; }
.is-live .vbrain-fibre[data-fire='4'] { animation-duration: 5.7s; animation-delay: -4.6s; }

@keyframes vbrain-fire {
  0%,
  100% {
    opacity: calc(var(--o) * 0.66);
  }
  50% {
    opacity: calc(var(--o) * 1.06);
  }
}

/* ==========================================================================
   5. The nodes
   ==========================================================================
   Each circle carries its own fill-opacity from the generator - brighter at
   the core, near-dark at the extremities. The six clusters only animate a
   group opacity on top of that, so 129 nodes cost six animations, and a node
   never loses its place in the depth ramp while it pulses.
   ========================================================================== */

.vbrain-nodes {
  fill: #a6dcff;
}

.is-live .vbrain-cluster {
  animation: vbrain-node 5.6s ease-in-out infinite;
}

.is-live .vbrain-cluster[data-cluster='1'] { animation-duration: 4.4s; animation-delay: -0.6s; }
.is-live .vbrain-cluster[data-cluster='2'] { animation-duration: 6.1s; animation-delay: -2.9s; }
.is-live .vbrain-cluster[data-cluster='3'] { animation-duration: 5.2s; animation-delay: -4.1s; }
.is-live .vbrain-cluster[data-cluster='4'] { animation-duration: 7.0s; animation-delay: -1.5s; }
.is-live .vbrain-cluster[data-cluster='5'] { animation-duration: 6.6s; animation-delay: -3.7s; }
.is-live .vbrain-cluster[data-cluster='6'] { animation-duration: 4.9s; animation-delay: -2.2s; }

@keyframes vbrain-node {
  0%,
  100% {
    opacity: 0.5;
  }
  55% {
    opacity: 1;
  }
}

/* ==========================================================================
   6. Signals in transit
   ==========================================================================
   One short dash inside a very long gap, moved along the path by its offset:
   a point following the exact fibre route, without a second element and
   without a frame loop.

   The gap is longer than any route, which is the whole trick - the dash runs
   off the end of a short path and stays invisible until the cycle comes
   round again. So every signal fires, travels, and then rests for a while,
   and the twelve of them fall out of step on their own.
   ========================================================================== */

.vbrain-signal {
  fill: none;
  stroke: #dbf3ff;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 3.5 760;
  stroke-dashoffset: 763.5;
  vector-effect: non-scaling-stroke;
}

.is-live .vbrain-signal {
  animation: vbrain-travel 5s linear infinite;
}

.is-live .vbrain-signal[data-sig='1'] { animation-duration: 4.4s; animation-delay: -0.3s; }
.is-live .vbrain-signal[data-sig='2'] { animation-duration: 5.8s; animation-delay: -2.1s; }
.is-live .vbrain-signal[data-sig='3'] { animation-duration: 3.9s; animation-delay: -3.4s; }
.is-live .vbrain-signal[data-sig='4'] { animation-duration: 6.3s; animation-delay: -1.2s; }
.is-live .vbrain-signal[data-sig='5'] { animation-duration: 4.7s; animation-delay: -4.0s; }
.is-live .vbrain-signal[data-sig='6'] { animation-duration: 5.4s; animation-delay: -0.9s; }
.is-live .vbrain-signal[data-sig='7'] { animation-duration: 3.6s; animation-delay: -2.6s; }
.is-live .vbrain-signal[data-sig='8'] { animation-duration: 6.9s; animation-delay: -5.1s; }
.is-live .vbrain-signal[data-sig='9'] { animation-duration: 4.2s; animation-delay: -1.8s; }
.is-live .vbrain-signal[data-sig='10'] { animation-duration: 5.1s; animation-delay: -3.9s; }
.is-live .vbrain-signal[data-sig='11'] { animation-duration: 6.1s; animation-delay: -0.5s; }
.is-live .vbrain-signal[data-sig='12'] { animation-duration: 4.9s; animation-delay: -2.8s; }

@keyframes vbrain-travel {
  from {
    stroke-dashoffset: 763.5;
  }
  to {
    stroke-dashoffset: 0;
  }
}

/* ==========================================================================
   7. The brightest nodes
   ==========================================================================
   Thirteen of them, kept at least 78 units apart so the bloom never pools
   into one hot patch. The bloom is a soft radial gradient behind a hard
   point rather than a Gaussian filter: same read, and it does not force a
   re-rasterisation on every frame it animates.
   ========================================================================== */

.vbrain-star {
  transform-box: fill-box;
  transform-origin: center;
}

.vbrain-spark {
  fill: #e9f8ff;
}

.vbrain-bloom {
  opacity: 0.8;
}

.is-live .vbrain-star {
  animation: vbrain-spark 6.4s ease-in-out infinite;
}

.is-live .vbrain-star[data-star='1'] { animation-duration: 5.3s; animation-delay: -1.1s; }
.is-live .vbrain-star[data-star='2'] { animation-duration: 7.2s; animation-delay: -3.8s; }
.is-live .vbrain-star[data-star='3'] { animation-duration: 4.6s; animation-delay: -2.4s; }
.is-live .vbrain-star[data-star='4'] { animation-duration: 8.1s; animation-delay: -5.6s; }
.is-live .vbrain-star[data-star='5'] { animation-duration: 6.0s; animation-delay: -0.7s; }
.is-live .vbrain-star[data-star='6'] { animation-duration: 5.7s; animation-delay: -4.3s; }
.is-live .vbrain-star[data-star='7'] { animation-duration: 6.8s; animation-delay: -2.9s; }

@keyframes vbrain-spark {
  0%,
  100% {
    opacity: 0.35;
    transform: scale(0.82);
  }
  50% {
    opacity: 1;
    transform: scale(1.1);
  }
}

/* ==========================================================================
   8. Reduced motion
   ==========================================================================
   Nothing moves, and nothing is hidden: the brain is the message, so it is
   shown settled and fully lit rather than withheld behind an animation that
   will never run. The signals become the stationary points they would
   otherwise carry along their routes.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .is-live .vbrain-atmos,
  .is-live .vbrain-net,
  .is-live .vbrain-fibre,
  .is-live .vbrain-cluster,
  .is-live .vbrain-signal,
  .is-live .vbrain-star {
    animation: none;
  }

  .is-live .vbrain-net {
    will-change: auto;
  }

  .is-live .vbrain-signal {
    stroke-dashoffset: 620;
  }
}
