/*
  Veyra: the closing call to action.
  ==========================================================================

  The last thing on the page, and the only one that has to say the whole
  proposition in one picture:

      your data  ->  Veyra  ->  what to do next

  Four sources on the left, one intelligence core in the middle, one answer
  on the right. Nothing else. The section that used to sit here was a
  headline with a button stranded across a gap and seven drifting glass
  panels behind them; that decoration is gone, and so are its rules.

  WHY THE PARTS ARE SPLIT THE WAY THEY ARE

  The core, the orbits, the feed lines and the mark are one SVG: the lines
  have to meet the sphere exactly at every viewport, and HTML laid over an
  SVG drifts the moment the two scale differently.

  The four source labels and the output panel are HTML, deliberately. Text
  inside an SVG scales with the drawing, so on a phone a 900-unit-wide stage
  puts an 11-unit label at about four real pixels. As HTML it stays at a real
  type size, it reflows, and - the part that matters for the brief - it can
  move below the core on a narrow screen instead of being squashed beside it.

  They still line up, because the stage has a locked aspect-ratio: a
  percentage on the HTML and a user unit in the viewBox resolve to the same
  place at every size, so the connector always meets the panel.

  HOW IT STAYS CHEAP

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

  Travelling particles are a dashed stroke whose offset animates: one short
  dash inside a very long gap is a point following the exact feed line, for
  one CSS animation each. The orbits are three ellipses rotating on periods
  of a minute and a half or more - slow enough to read as depth rather than
  as spin. Glow is layered radial gradients, not blur.

  Everything animated is opacity, transform or stroke-dashoffset, and it only
  runs while the section is on screen: veyra-closer.js adds .is-live.

  The composition paints on the first frame. Nothing is revealed by script,
  so a blocked file or a slow network leaves the whole picture visible and
  still - there is no loading state to sit through.
*/

.closer {
  --vc-cyan: #5ecbff;
  --vc-blue: #3ea2e3;
  --vc-pale: #cfe9ff;
  --vc-line: rgba(126, 200, 255, 0.26);

  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: var(--space-24) 0;

  /*
    Lighter where the core sits and darker towards the edges, so the object
    appears to be lighting the section rather than sitting on a panel. The
    top matches the engine section above it and the bottom meets the footer,
    so the page ends as one continuous ground.
  */
  background:
    radial-gradient(820px 660px at 64% 46%, rgba(32, 114, 194, 0.19), transparent 68%),
    radial-gradient(1200px 520px at 18% 108%, rgba(18, 70, 130, 0.1), transparent 70%),
    linear-gradient(180deg, #061020 0%, #040a16 52%, #050b16 100%);
  color: var(--on-ink);
}

/* ==========================================================================
   1. The horizon
   ==========================================================================
   A single hairline arc across the bottom, fading out well before either
   edge so it has no ends to notice. It gives the section a floor - and a
   deliberate ending to the page - without being a graphic in its own right.
   ========================================================================== */

.closer-ground {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: clamp(90px, 14vw, 150px);
  pointer-events: none;
  z-index: 0;
}

.closer-horizon {
  display: block;
  width: 100%;
  height: 100%;
}

.closer-horizon path {
  fill: none;
  stroke: url(#closer-horizon-fade);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* ==========================================================================
   2. The two halves
   ==========================================================================
   The reading is deliberately the narrower column: the core has to be big
   enough to answer the headline, and the headline is already loud.
   ========================================================================== */

.closer-inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 37fr) minmax(0, 63fr);
  align-items: center;
  justify-content: normal;
  gap: clamp(var(--space-8), 4vw, var(--space-16));
}

.closer-lead {
  max-width: none;
}

.closer-title {
  margin-bottom: var(--space-6);
  max-width: 12ch;
}

.closer-body {
  max-width: 34ch;
}

.closer-actions {
  margin-top: var(--space-8);
  padding-bottom: 0;
}

/* ==========================================================================
   3. The stage
   ==========================================================================
   .vcore-stage is locked to the viewBox's proportions, and that lock is what
   lets the HTML sources be positioned in percentages and still land exactly
   where the SVG expects them. It holds the drawing and the source labels and
   nothing else: an in-flow child would stretch the box past its ratio, the
   SVG would letterbox inside it while the labels spread over the taller box,
   and every feed line would come away from its source.

   .vcore wraps it so the answer panel can be a sibling rather than a child -
   absolutely positioned against the same box on a wide screen, and simply
   stacked underneath on a narrow one.

   Both carry container-type so that cqi means the same width in either.
   ========================================================================== */

.vcore {
  position: relative;
  container-type: inline-size;
}

.vcore-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 900 / 620;
  container-type: inline-size;
}

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

/* ==========================================================================
   4. The sphere
   ==========================================================================
   Glass rather than a glowing ball: the rim catches light, the middle does
   not, and what is bright is the core suspended inside it.
   ========================================================================== */

.vcore-atmos {
  opacity: 0.9;
  transform-box: fill-box;
  transform-origin: center;
}

.is-live .vcore-atmos {
  animation: vcore-breathe 11s ease-in-out infinite;
}

@keyframes vcore-breathe {
  0%,
  100% {
    opacity: 0.7;
    transform: scale(0.98);
  }
  50% {
    opacity: 1;
    transform: scale(1.02);
  }
}

/* The shell: a rim light, not an outline. */
.vcore-shell {
  stroke: rgba(126, 200, 255, 0.3);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The latitude rings inside it, brighter towards the middle. */
.vcore-ring {
  fill: none;
  stroke: rgba(112, 186, 244, 0.16);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.vcore-ring[data-ring='2'] { stroke: rgba(120, 196, 250, 0.2); }
.vcore-ring[data-ring='3'] { stroke: rgba(134, 210, 255, 0.26); }

/*
  Three orbits on long, unequal, counter-running periods. This is the only
  rotation in the section and it is deliberately slower than it looks: a
  full turn takes between a minute and a half and two and a half minutes,
  which reads as a system holding position, not as something spinning.
*/
.vcore-orbit {
  fill: none;
  stroke: rgba(120, 196, 250, 0.22);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  transform-box: fill-box;
  transform-origin: center;
  transform: rotate(var(--tilt));
}

.vcore-orbit[data-orbit='1'] { --tilt: -18deg; }
.vcore-orbit[data-orbit='2'] { --tilt: 24deg; stroke: rgba(134, 210, 255, 0.26); }
.vcore-orbit[data-orbit='3'] { --tilt: 68deg; stroke: rgba(112, 186, 244, 0.17); }

.is-live .vcore-orbit[data-orbit='1'] { animation: vcore-spin 96s linear infinite; }
.is-live .vcore-orbit[data-orbit='2'] { animation: vcore-spin-back 148s linear infinite; }
.is-live .vcore-orbit[data-orbit='3'] { animation: vcore-spin 122s linear infinite; }

@keyframes vcore-spin {
  to { transform: rotate(calc(var(--tilt) + 360deg)); }
}

@keyframes vcore-spin-back {
  to { transform: rotate(calc(var(--tilt) - 360deg)); }
}

/* The meridians: two narrow ellipses are enough to say "sphere". */
.vcore-meridian {
  fill: none;
  stroke: rgba(108, 180, 240, 0.13);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Two energy streams running the curve of the sphere, opposed, so there is
   something in the glass that is neither a ring nor an orbit. */
.vcore-stream {
  fill: none;
  stroke: rgba(150, 216, 255, 0.3);
  stroke-width: 1.4;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* ==========================================================================
   5. The core inside the glass
   ==========================================================================
   The gradient peaks at about a third out rather than at dead centre, which
   leaves the middle calm enough for the mark to stay legible against it.
   ========================================================================== */

.vcore-halo {
  transform-box: fill-box;
  transform-origin: center;
}

.is-live .vcore-halo {
  animation: vcore-throb 3.4s ease-in-out infinite;
}

@keyframes vcore-throb {
  0%,
  100% {
    opacity: 0.8;
    transform: scale(0.97);
  }
  46% {
    opacity: 1;
    transform: scale(1.035);
  }
}

.vcore-heart {
  opacity: 0.9;
}

/*
  The reaction. A ring leaving the core on the same 3.4s rhythm the halo
  throbs on, so the core visibly does something rather than merely glowing.
  It is a rhythm, not a true arrival trigger: the four feeds run on unequal
  periods precisely so they never arrive together, and chasing a real
  handshake would mean a frame loop for an effect nobody can time by eye.
*/
.vcore-impact {
  fill: none;
  stroke: rgba(150, 216, 255, 0.5);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
}

.is-live .vcore-impact {
  animation: vcore-impact 3.4s cubic-bezier(0.25, 0.7, 0.4, 1) infinite;
}

@keyframes vcore-impact {
  0% {
    opacity: 0;
    transform: scale(0.42);
  }
  18% {
    opacity: 0.85;
  }
  100% {
    opacity: 0;
    transform: scale(1);
  }
}

/* Points of light held in the glass. */
.vcore-mote {
  fill: var(--vc-pale);
  transform-box: fill-box;
  transform-origin: center;
}

.is-live .vcore-mote {
  animation: vcore-mote 6.2s ease-in-out infinite;
}

.is-live .vcore-mote:nth-of-type(2n) { animation-duration: 7.8s; animation-delay: -2.6s; }
.is-live .vcore-mote:nth-of-type(3n) { animation-duration: 5.3s; animation-delay: -4.1s; }
.is-live .vcore-mote:nth-of-type(4n) { animation-duration: 9.1s; animation-delay: -1.3s; }

@keyframes vcore-mote {
  0%,
  100% {
    opacity: 0.22;
    transform: scale(0.8);
  }
  50% {
    opacity: 0.95;
    transform: scale(1.15);
  }
}

/* The mark, floating on nothing. veyra-mark-lg.png is the asset with its own
   alpha; veyra-mark.png has an opaque ground baked in, which would put
   exactly the box this design must not have at the centre of the core. */
.vcore-mark {
  opacity: 0.97;
}

/* ==========================================================================
   6. The feeds
   ========================================================================== */

.vcore-feed {
  fill: none;
  stroke: rgba(118, 194, 250, 0.34);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/*
  One short dash inside a very long gap, moved along the path by its offset:
  a point following the exact feed line, without a second element and
  without a frame loop. The gap outruns the path, so each particle travels,
  leaves, and the line rests before the next one sets off.
*/
.vcore-particle {
  fill: none;
  stroke: var(--vc-pale);
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-dasharray: 3 300;
  stroke-dashoffset: 303;
  vector-effect: non-scaling-stroke;
}

.is-live .vcore-particle {
  animation: vcore-travel 6s linear infinite;
}

/* Two particles to a line, half a cycle apart, on four unequal periods. */
.is-live .vcore-particle[data-feed='1'] { animation-duration: 5.2s; }
.is-live .vcore-particle[data-feed='2'] { animation-duration: 6.4s; }
.is-live .vcore-particle[data-feed='3'] { animation-duration: 5.8s; }
.is-live .vcore-particle[data-feed='4'] { animation-duration: 7.1s; }

.is-live .vcore-particle[data-phase='b'] { animation-delay: -2.9s; }

@keyframes vcore-travel {
  from { stroke-dashoffset: 303; }
  to { stroke-dashoffset: 0; }
}

/* The answer leaving the core, on the output panel's own rhythm. */
.vcore-emit {
  fill: none;
  stroke: rgba(134, 210, 255, 0.4);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.vcore-emit-pulse {
  fill: none;
  stroke: var(--vc-pale);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-dasharray: 3 60;
  stroke-dashoffset: 63;
  vector-effect: non-scaling-stroke;
}

.is-live .vcore-emit-pulse {
  animation: vcore-emit 6.8s linear infinite;
}

@keyframes vcore-emit {
  from { stroke-dashoffset: 63; }
  to { stroke-dashoffset: 0; }
}

/* ==========================================================================
   7. The four sources
   ==========================================================================
   Data sources, not dashboard cards: an indicator and a word. No container,
   no border, no fill - the only thing that says "this is a feed" is the
   line leaving it.
   ========================================================================== */

.vcore-sources {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: none;
}

.vcore-sources li {
  position: absolute;
  left: 3.1%;
  top: var(--y);
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--mono);
  font-size: clamp(0.5rem, 1.35cqi, 0.6875rem);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(198, 219, 242, 0.84);
}

.vcore-dot {
  width: 0.45em;
  height: 0.45em;
  border-radius: 50%;
  background: var(--vc-cyan);
  box-shadow: 0 0 0 0.28em rgba(94, 203, 255, 0.14);
}

.is-live .vcore-dot {
  animation: vcore-dot 5.6s ease-in-out infinite;
}

.vcore-sources li:nth-child(2) .vcore-dot { animation-duration: 6.8s; animation-delay: -1.9s; }
.vcore-sources li:nth-child(3) .vcore-dot { animation-duration: 6.1s; animation-delay: -3.7s; }
.vcore-sources li:nth-child(4) .vcore-dot { animation-duration: 7.4s; animation-delay: -0.8s; }

@keyframes vcore-dot {
  0%,
  100% {
    opacity: 0.45;
    box-shadow: 0 0 0 0.2em rgba(94, 203, 255, 0.08);
  }
  50% {
    opacity: 1;
    box-shadow: 0 0 0 0.36em rgba(94, 203, 255, 0.18);
  }
}

/* ==========================================================================
   8. The answer
   ==========================================================================
   One panel, and it is the only bordered surface in the section - which is
   what makes it read as an output rather than as another piece of furniture.
   Its left edge sits at the percentage the connector in the SVG stops just
   short of, so the two always meet.
   ========================================================================== */

.vcore-out {
  position: absolute;
  left: 67.5%;
  top: 48.4%;
  width: 29%;
  transform: translateY(-50%);
  margin: 0;
  padding: clamp(0.75rem, 2cqi, 1.15rem) clamp(0.85rem, 2.2cqi, 1.35rem);
  border: 1px solid var(--vc-line);
  border-radius: 10px;
  background:
    linear-gradient(152deg, rgba(20, 44, 74, 0.55), rgba(8, 18, 34, 0.42) 62%, rgba(14, 32, 56, 0.5));
  box-shadow:
    inset 0 1px 0 rgba(180, 226, 255, 0.12),
    0 0 44px -14px rgba(62, 162, 227, 0.5);
  pointer-events: none;
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .vcore-out {
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }
}

.is-live .vcore-out {
  animation: vcore-out 6.8s ease-in-out infinite;
}

/* The activation: the panel is lit a little harder just after the answer
   reaches it, then settles. Same period as the emitted pulse. */
@keyframes vcore-out {
  0%,
  100% {
    border-color: rgba(126, 200, 255, 0.2);
    box-shadow:
      inset 0 1px 0 rgba(180, 226, 255, 0.1),
      0 0 38px -16px rgba(62, 162, 227, 0.4);
  }
  62% {
    border-color: rgba(150, 216, 255, 0.42);
    box-shadow:
      inset 0 1px 0 rgba(180, 226, 255, 0.16),
      0 0 52px -10px rgba(62, 162, 227, 0.62);
  }
}

.vcore-out-tag {
  margin: 0 0 0.5em;
  font-family: var(--mono);
  font-size: clamp(0.5rem, 1.3cqi, 0.6875rem);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--vc-cyan);
}

.vcore-out-line {
  margin: 0;
  font-size: clamp(0.8rem, 2.15cqi, 1.1rem);
  font-weight: 500;
  line-height: 1.32;
  letter-spacing: -0.015em;
  color: #f2f8ff;
  text-wrap: balance;
}

/* ==========================================================================
   9. Narrower layouts
   ==========================================================================
   Below the breakpoint the reading stacks above the picture, and the answer
   leaves the stage entirely: it sits under the core as its own block, which
   is the order the story is told in anyway.
   ========================================================================== */

@media (max-width: 1080px) {
  .closer-inner {
    grid-template-columns: 1fr;
    gap: var(--space-12);
  }

  .closer-title,
  .closer-body {
    max-width: 42ch;
  }

  /*
    The stage's aspect-ratio is never changed, here or anywhere: it is what
    makes a percentage on the HTML and a user unit in the viewBox land in
    the same place. With the answer panel no longer beside the core, the
    right third of the drawing is empty, so the composition is recentred and
    enlarged with one transform applied to both layers at once - they are
    the same box, so they move together and stay in register.
  */
  .vcore-svg,
  .vcore-sources {
    transform: translateX(13%) scale(1.08);
    transform-origin: center;
  }

  .vcore-out {
    position: static;
    width: 100%;
    max-width: 30rem;
    transform: none;
    margin-top: var(--space-6);
  }

  /* The connector has nothing to connect to once the panel is below. */
  .vcore-emit,
  .vcore-emit-pulse {
    display: none;
  }
}

@media (max-width: 620px) {
  .closer {
    padding: var(--space-20) 0 var(--space-16);
  }

  .vcore-sources li {
    font-size: 0.5625rem;
    letter-spacing: 0.12em;
  }
}

/* ==========================================================================
   10. Reduced motion
   ==========================================================================
   Nothing moves and nothing is hidden. The particles become the stationary
   points they would otherwise carry along the feeds, so the picture still
   says that information is on its way in.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .is-live .vcore-atmos,
  .is-live .vcore-orbit,
  .is-live .vcore-halo,
  .is-live .vcore-impact,
  .is-live .vcore-mote,
  .is-live .vcore-particle,
  .is-live .vcore-emit-pulse,
  .is-live .vcore-dot,
  .is-live .vcore-out {
    animation: none;
  }

  .is-live .vcore-particle {
    stroke-dashoffset: 190;
  }

  .is-live .vcore-emit-pulse {
    stroke-dashoffset: 40;
  }

  .is-live .vcore-impact {
    opacity: 0.3;
    transform: scale(0.78);
  }
}
