/* ============================================================================
   GEC — Per-slide hero diagrams

   One schematic per service, in the clear column to the right of the copy.

   FIVE RULES HOLD THIS TOGETHER, and breaking any one breaks the set. The
   fifth is stated where it bites, just above the TRAVEL section: never animate
   `transform` on an element that also carries a `transform` attribute.

   1. Every drawn shape carries pathLength="100" in the markup, so one
      stroke-dasharray value normalises every figure regardless of its real
      perimeter and the draw timing is exact rather than estimated.

   2. STAGGERS ARE SET WITH --dly, NEVER WITH animation-delay. The `animation`
      shorthand resets animation-delay to 0. Several base rules here need a
      descendant selector (`.v-win rect`, `.v-grid line`), which makes them one
      element more specific than a class-only per-shape rule — so a separate
      `animation-delay` lost to the shorthand and every stagger was silently
      discarded. A custom property sidesteps specificity and inherits, so a
      delay set on a group reaches its children. --dly2 is a second delay for
      elements running two animations.

   3. Animations are declared only under `.hero__viz.is-active`. Removing the
      class removes the declaration, so the next activation restarts from zero.
      animation-play-state: paused would resume mid-figure on the second cycle.

   4. Anything animated by vDraw must carry a dash-array. Animating
      stroke-dashoffset against `stroke-dasharray: none` does nothing at all and
      leaves the shape drawn from the first frame.

   Durations are fractions of --hero-dur, so the set re-times itself with the
   slideshow.
   ========================================================================= */

.hero__viz-stack {
  position: absolute;
  top: 50%;
  inset-inline-end: var(--gutter);
  transform: translateY(-50%);
  width: clamp(240px, 27vw, 400px);
  aspect-ratio: 1;
  z-index: -1;                 /* over the scrim, under the copy */
  pointer-events: none;
}

/* A soft ground for the drawing. The light theme's directional wash leaves the
   right of the frame at nearly full photographic strength, so the line-work has
   to bring its own.

   These alphas are measured, not chosen by eye: each of the six photographs was
   sampled in exactly the region the diagram occupies, the vignette composited
   over it, and the line over that. The first pass put the light theme at 1.18:1
   over the dark arch frame — invisible. These land the whole set in a 2.6–4.5:1
   band, which reads clearly as line-work while staying well under the headline
   so it never competes for the eye. */
.hero__viz-stack::before {
  content: "";
  position: absolute;
  inset: -14%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 0, 0, .3) 0%, rgba(0, 0, 0, .16) 52%, transparent 72%);
}

:root[data-theme="light"] .hero__viz-stack::before {
  background: radial-gradient(circle, rgba(247, 245, 240, .6) 0%, rgba(247, 245, 240, .32) 52%, transparent 72%);
}

.hero__viz {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  color: var(--gold);
  opacity: 0;
  visibility: hidden;
  transition: opacity calc(var(--hero-fade) * .8) var(--ease), visibility var(--hero-fade);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.hero__viz.is-active { opacity: .7; visibility: visible; }

/* Gold at #C8A55B over a pale photograph is barely there, so the light theme
   takes a deeper gold and more presence — see the measurement note above. */
:root[data-theme="light"] .hero__viz { color: #5C4218; }
:root[data-theme="light"] .hero__viz.is-active { opacity: .85; }

/* ---- shared primitives ------------------------------------------------- */
.v-draw { stroke-dasharray: 100; stroke-dashoffset: 100; }

@keyframes vDraw  { to { stroke-dashoffset: 0; } }
@keyframes vIn    { from { opacity: 0; } to { opacity: 1; } }
@keyframes vPop   {
  0%   { opacity: 0; transform: scale(.35); }
  62%  { opacity: 1; transform: scale(1.18); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes vGlow {
  0%, 100% { transform: scale(1);    opacity: .7; }
  50%      { transform: scale(1.26); opacity: 1; }
}

/* NEVER animate `transform` on an element that also carries a `transform`
   presentation attribute. A CSS transform replaces the attribute outright — it
   does not compose with it. Animating scale on the place-setting groups wiped
   their rotate() and stacked all eight settings at twelve o'clock. Put the
   rotate on an outer group and animate an inner one (see `.v-seat`/`.v-cover`).

   Anything that scales needs its own box as the origin, or it flies toward the
   viewBox origin instead of growing in place. */
.v-city, .v-lock, .v-you, .v-lamp-glow, .v-flame, .v-cover {
  transform-box: fill-box;
  transform-origin: center;
}

/* ============================================================ 1. TRAVEL ==
   traverse — the globe is built and turning, the route drawn, the aircraft
   flies it, and the legs confirm underneath. */

/* One revolution of the world, shared by the cage and the mark at its centre so
   they turn as one object. Deliberately slower than the slide: at 1.7x the hold
   you see about 210 degrees of turn, which reads as continuous rather than as a
   loop completing. */
.v-travel { --globe-turn: calc(var(--hero-dur) * 1.7); }

.v-travel .v-globe { stroke-width: .75; opacity: .55; }
.is-active.v-travel .v-globe .v-draw {
  animation: vDraw calc(var(--hero-dur) * .22) var(--ease) var(--dly, 0s) forwards;
}

/* Placed AFTER the rule above and at equal (0,4,0) specificity, so it wins on
   source order and the meridians get the spin as a second animation alongside
   their draw. The negative delays are phases, not waits: they start each
   meridian part-way round so the cage opens at its designed width instead of
   snapping there. -0.06 puts o4 at ~55, -0.18 puts o3 at ~26. */
.is-active.v-travel .v-globe .v-mer {
  animation: vDraw calc(var(--hero-dur) * .22) var(--ease) var(--dly, 0s) forwards,
             vizMer var(--globe-turn) linear var(--spin, 0s) infinite;
}
.v-travel .o3 { --dly: calc(var(--hero-dur) * .08); --spin: calc(var(--globe-turn) * -0.18); }
.v-travel .o4 { --dly: calc(var(--hero-dur) * .11); --spin: calc(var(--globe-turn) * -0.06); }

/* |cos| sampled every 45 degrees — what a meridian's projected width actually
   does as the globe turns. Never quite reaches zero: a degenerate ellipse
   flickers. */
@keyframes vizMer {
  0%     { rx: 64px; }
  12.5%  { rx: 45px; }
  25%    { rx: 2px;  }
  37.5%  { rx: 45px; }
  50%    { rx: 64px; }
  62.5%  { rx: 45px; }
  75%    { rx: 2px;  }
  87.5%  { rx: 45px; }
  100%   { rx: 64px; }
}
.v-travel .o1 { --dly: calc(var(--hero-dur) * .01); stroke-width: 1; opacity: .8; }
.v-travel .o2 { --dly: calc(var(--hero-dur) * .05); }
.v-travel .o5 { --dly: calc(var(--hero-dur) * .14); }
.v-travel .o6 { --dly: calc(var(--hero-dur) * .17); }
/* o3 / o4 are the meridians — their --dly is set with their spin phase below. */

.v-travel .v-city { opacity: 0; fill: var(--bg); stroke-width: 1.3; }
.is-active.v-travel .v-city { animation: vPop calc(var(--hero-dur) * .09) var(--ease) var(--dly, 0s) forwards; }
.v-travel .c-from { --dly: calc(var(--hero-dur) * .18); }
.v-travel .c-to   { --dly: calc(var(--hero-dur) * .21); }

.v-travel .v-route { stroke-width: 1.5; }
.is-active.v-travel .v-route {
  animation: vDraw calc(var(--hero-dur) * .3) cubic-bezier(.4, 0, .25, 1) calc(var(--hero-dur) * .22) forwards;
}

/* The aircraft rides the same curve the route is drawn from. offset-path is the
   only clean way to do this in CSS; where it is unsupported the craft never
   appears and the route still draws, which is the part that matters. */
.v-travel .v-craft { opacity: 0; }
@supports (offset-path: path("M0,0 L1,1")) {
  .v-travel .v-craft {
    offset-path: path("M58,128 Q104,26 150,96");
    offset-rotate: auto;
    offset-distance: 0%;
  }
  .is-active.v-travel .v-craft {
    animation: vIn calc(var(--hero-dur) * .05) var(--ease) calc(var(--hero-dur) * .23) forwards,
               vFly calc(var(--hero-dur) * .29) cubic-bezier(.4, 0, .25, 1) calc(var(--hero-dur) * .23) forwards;
  }
}
@keyframes vFly { to { offset-distance: 100%; } }

/* Arrival: one ring expands off the destination as the aircraft lands on it. */
.v-travel .v-lock { opacity: 0; stroke-width: 1.2; }
.is-active.v-travel .v-lock {
  animation: vArrive calc(var(--hero-dur) * .16) cubic-bezier(.2, .7, .3, 1) calc(var(--hero-dur) * .52) forwards;
}
@keyframes vArrive {
  0%   { opacity: .9; transform: scale(1); }
  100% { opacity: 0;  transform: scale(4.2); }
}

.v-travel .v-leg { stroke-width: 2.6; opacity: .75; stroke-dasharray: 100; stroke-dashoffset: 100; }
.is-active.v-travel .v-leg { animation: vDraw calc(var(--hero-dur) * .07) var(--ease) var(--dly, 0s) forwards; }
/* Spread so the last leg confirms at ~0.83 of the hold: the sequence should
   still be resolving late in the slide, then settle before the change. */
.v-travel .g1 { --dly: calc(var(--hero-dur) * .55); }
.v-travel .g2 { --dly: calc(var(--hero-dur) * .62); }
.v-travel .g3 { --dly: calc(var(--hero-dur) * .69); }
.v-travel .g4 { --dly: calc(var(--hero-dur) * .76); }

/* ---- The mark at the centre of the world ----
   Positioned on the globe's own centre: cy=112 of a 200 viewBox is 56%, not
   50%, and using 50% would float it above the equator.

   Shown only while slide 1 is up. The stack carries data-active, which is also
   what restarts the turn on each visit — the animation is declared under the
   attribute selector, so leaving the slide removes it entirely. */
.viz-mark {
  position: absolute;
  left: 50%;
  top: 56%;
  width: 21%;
  aspect-ratio: 1.0922;              /* the monogram artwork's own ratio */
  transform: translate(-50%, -50%);
  perspective: 620px;
  opacity: 0;
  visibility: hidden;
  transition: opacity calc(var(--hero-fade) * .8) var(--ease), visibility var(--hero-fade);
  pointer-events: none;
}

.hero__viz-stack[data-active="0"] .viz-mark { opacity: .8; visibility: visible; }
:root[data-theme="light"] .hero__viz-stack[data-active="0"] .viz-mark { opacity: .92; }

.viz-mark__spin {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
}

.hero__viz-stack[data-active="0"] .viz-mark__spin {
  animation: vizMarkSpin var(--globe-turn, 20s) linear infinite;
}

@keyframes vizMarkSpin { to { transform: rotateY(360deg); } }

/* Two faces of one plate. Without the second, pre-rotated face, the logo reads
   mirrored for half of every revolution. */
.viz-mark .viz-mark__face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--gold);
  background-color: currentColor;
  background-image: none;
  -webkit-mask-image: url("/assets/brand/mk-monogram.png");
          mask-image: url("/assets/brand/mk-monogram.png");
  -webkit-mask-repeat: no-repeat;   mask-repeat: no-repeat;
  -webkit-mask-position: center;    mask-position: center;
  -webkit-mask-size: contain;       mask-size: contain;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}

.viz-mark .viz-mark__face--back { transform: rotateY(180deg); }

:root[data-theme="light"] .viz-mark .viz-mark__face { color: #5C4218; }

/* No mask support: fall back to the baked gold artwork rather than a gold box. */
@supports not ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .viz-mark .viz-mark__face {
    background-color: transparent;
    background-image: url("/assets/brand/monogram-gold.png");
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
  }
}

/* The globe-turn variable is declared on .v-travel, which is a sibling of
   .viz-mark rather than an ancestor, so it does not inherit down. Declared here
   too, from the same expression, so the cage and the mark cannot fall apart. */
.hero__viz-stack { --globe-turn: calc(var(--hero-dur) * 1.7); }

/* ========================================================= 2. LIFESTYLE ==
   attend — the horizon, the hours along it, and the sun's path across a day.
   The lit arc draws for almost the whole slide and each of the six things
   settles as the day reaches its hour, leaving its record standing on the hour
   it happened.

   There is deliberately no travelling object: the motion is a day passing, not
   something moving through it. That is what keeps this clear of the travel
   diagram, whose signature is an aircraft flying an arc.

   The earlier version was six threads into a hub and one line out — a funnel,
   which could as easily have been a data pipeline, and which said nothing at
   all about *time*, the entire promise of this service. */
.v-life .v-horizon { stroke-width: .9; opacity: .6; }
.is-active.v-life .v-horizon { animation: vDraw calc(var(--hero-dur) * .16) var(--ease) forwards; }

.v-life .v-hours line { stroke-width: .8; opacity: .4; }
.is-active.v-life .v-hours .v-draw {
  animation: vDraw calc(var(--hero-dur) * .1) var(--ease) calc(var(--hero-dur) * .1) forwards;
}

.v-life .v-day { stroke-width: .8; opacity: .35; }
.is-active.v-life .v-day {
  animation: vDraw calc(var(--hero-dur) * .2) var(--ease) calc(var(--hero-dur) * .06) forwards;
}

/* The day being attended — one slow, unbroken pass from first light to last.
   Linear on purpose: a day does not ease. */
.v-life .v-day-lit { stroke-width: 1.5; stroke-dasharray: 100; stroke-dashoffset: 100; }
.is-active.v-life .v-day-lit {
  animation: vDraw calc(var(--hero-dur) * .74) linear calc(var(--hero-dur) * .1) forwards;
}

/* Each settles as the lit arc reaches its hour. The --dly values ARE those
   crossing times, so nothing can resolve before the day has got to it. */
.v-life .v-stem { stroke-width: .8; opacity: .45; stroke-dasharray: 100; stroke-dashoffset: 100; }
.v-life .v-dot  { stroke-width: 1.3; fill: currentColor; fill-opacity: 0; }

.is-active.v-life .v-ask .v-stem {
  animation: vDraw calc(var(--hero-dur) * .08) var(--ease) var(--dly, 0s) forwards;
}
.is-active.v-life .v-ask .v-dot {
  animation: vSettle calc(var(--hero-dur) * .1) var(--ease) var(--dly, 0s) forwards;
}
@keyframes vSettle { to { fill-opacity: .85; } }

.v-life .k1 { --dly: calc(var(--hero-dur) * .20); }
.v-life .k2 { --dly: calc(var(--hero-dur) * .30); }
.v-life .k3 { --dly: calc(var(--hero-dur) * .41); }
.v-life .k4 { --dly: calc(var(--hero-dur) * .53); }
.v-life .k5 { --dly: calc(var(--hero-dur) * .64); }
.v-life .k6 { --dly: calc(var(--hero-dur) * .75); }
/* =========================================================== 3. BESPOKE ==
   construct — setting-out, then the figure, then the guides withdrawn. The one
   diagram here that takes something away. */
.v-bespoke .v-guides { stroke-width: .55; opacity: .45; }
.is-active.v-bespoke .v-guides .v-draw {
  animation: vDraw calc(var(--hero-dur) * .15) var(--ease) var(--dly, 0s) forwards;
}
.v-bespoke .c1 { --dly: calc(var(--hero-dur) * .02); }
.v-bespoke .c2 { --dly: calc(var(--hero-dur) * .05); }
.v-bespoke .c3 { --dly: calc(var(--hero-dur) * .08); }
.v-bespoke .r1 { --dly: calc(var(--hero-dur) * .10); }
.v-bespoke .r2 { --dly: calc(var(--hero-dur) * .12); }
.v-bespoke .r3 { --dly: calc(var(--hero-dur) * .14); }
.v-bespoke .r4 { --dly: calc(var(--hero-dur) * .16); }
.v-bespoke .r5 { --dly: calc(var(--hero-dur) * .18); }
.v-bespoke .r6 { --dly: calc(var(--hero-dur) * .20); }
.v-bespoke .r7 { --dly: calc(var(--hero-dur) * .22); }
.v-bespoke .r8 { --dly: calc(var(--hero-dur) * .24); }

/* The guides recede once they have done their job — they do not vanish, which
   would read as a glitch; they settle back to a trace. */
.is-active.v-bespoke .v-guides {
  animation: vFade calc(var(--hero-dur) * .18) var(--ease) calc(var(--hero-dur) * .62) forwards;
}
@keyframes vFade { to { opacity: .1; } }

.v-bespoke .v-petal { stroke-width: 1.15; stroke-dasharray: 100; stroke-dashoffset: 100; }
.is-active.v-bespoke .v-petal {
  animation: vDraw calc(var(--hero-dur) * .17) cubic-bezier(.4, 0, .25, 1) var(--dly, 0s) forwards;
}
.v-bespoke .q1 { --dly: calc(var(--hero-dur) * .26); }
.v-bespoke .q2 { --dly: calc(var(--hero-dur) * .30); }
.v-bespoke .q3 { --dly: calc(var(--hero-dur) * .34); }
.v-bespoke .q4 { --dly: calc(var(--hero-dur) * .38); }
.v-bespoke .q5 { --dly: calc(var(--hero-dur) * .42); }
.v-bespoke .q6 { --dly: calc(var(--hero-dur) * .46); }
.v-bespoke .q7 { --dly: calc(var(--hero-dur) * .50); }
.v-bespoke .q8 { --dly: calc(var(--hero-dur) * .54); }

.v-bespoke .v-you { opacity: 0; }
.is-active.v-bespoke .v-you { animation: vPop calc(var(--hero-dur) * .08) var(--ease) calc(var(--hero-dur) * .2) forwards; }

/* Once the guides withdraw the figure would simply sit there. It breathes
   instead — barely, once. */
.v-bespoke .v-bloom-rose { transform-box: fill-box; transform-origin: center; }
.is-active.v-bespoke .v-bloom-rose {
  animation: vBreathe calc(var(--hero-dur) * .24) var(--ease-in-out) calc(var(--hero-dur) * .74) forwards;
}
@keyframes vBreathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.035); }
}

/* ========================================================== 4. PROPERTY ==
   illuminate — drawn cold, then woken. The only diagram that fills rather than
   draws. */
.v-prop .v-ground { stroke-width: .8; opacity: .5; }
.v-prop .v-parapet, .v-prop .v-left, .v-prop .v-right { stroke-width: 1.15; }
.v-prop .v-band { stroke-width: .8; opacity: .6; }
.is-active.v-prop .v-ground  { animation: vDraw calc(var(--hero-dur) * .1) var(--ease) forwards; }
.is-active.v-prop .v-left    { animation: vDraw calc(var(--hero-dur) * .12) var(--ease) calc(var(--hero-dur) * .04) forwards; }
.is-active.v-prop .v-right   { animation: vDraw calc(var(--hero-dur) * .12) var(--ease) calc(var(--hero-dur) * .04) forwards; }
.is-active.v-prop .v-parapet { animation: vDraw calc(var(--hero-dur) * .2) var(--ease) calc(var(--hero-dur) * .1) forwards; }
.is-active.v-prop .v-band    { animation: vDraw calc(var(--hero-dur) * .12) var(--ease) calc(var(--hero-dur) * .22) forwards; }

.v-prop .v-arch { stroke-width: 1.15; stroke-dasharray: 100; stroke-dashoffset: 100; }
.is-active.v-prop .v-arch { animation: vDraw calc(var(--hero-dur) * .13) var(--ease) var(--dly, 0s) forwards; }
.v-prop .a1 { --dly: calc(var(--hero-dur) * .26); }
.v-prop .a2 { --dly: calc(var(--hero-dur) * .30); }
.v-prop .a3 { --dly: calc(var(--hero-dur) * .34); }

/* Frame drawn on --dly, light raised on --dly2. */
.v-prop .v-win rect { stroke-width: .9; fill: currentColor; fill-opacity: 0; }
.is-active.v-prop .v-win rect {
  animation: vDraw calc(var(--hero-dur) * .07) var(--ease) var(--dly, 0s) forwards,
             vLit  calc(var(--hero-dur) * .12) var(--ease) var(--dly2, 0s) forwards;
}
@keyframes vLit { to { fill-opacity: .5; } }

.v-prop .p1 { --dly: calc(var(--hero-dur) * .30); --dly2: calc(var(--hero-dur) * .46); }
.v-prop .p2 { --dly: calc(var(--hero-dur) * .33); --dly2: calc(var(--hero-dur) * .54); }
.v-prop .p3 { --dly: calc(var(--hero-dur) * .36); --dly2: calc(var(--hero-dur) * .62); }
.v-prop .p4 { --dly: calc(var(--hero-dur) * .39); --dly2: calc(var(--hero-dur) * .70); }
.v-prop .p5 { --dly: calc(var(--hero-dur) * .42); --dly2: calc(var(--hero-dur) * .78); }

.v-prop .v-lamp { opacity: 0; stroke-width: .9; }
.is-active.v-prop .v-lamp { animation: vIn calc(var(--hero-dur) * .08) var(--ease) calc(var(--hero-dur) * .40) forwards; }
.is-active.v-prop .v-lamp-glow {
  animation: vGlow calc(var(--hero-dur) * .28) var(--ease-in-out) calc(var(--hero-dur) * .46) infinite;
}

/* ========================================================== 5. BUSINESS ==
   clear the way — a city, and one route through it. The grid stands still and
   stopped; a single line is opened corner to corner, and the traffic sitting on
   that line lifts as it is reached. Everything off the route stays exactly
   where it was, which is the point: the line that moves is yours.

   The earlier version was bars on lanes — a project board, which is what an
   agency's internal tooling looks like rather than anything a client would
   recognise, and being static it could not show *movement*, the one word the
   headline turns on. */
.v-biz .v-grid line { stroke-width: .7; opacity: .35; }
.is-active.v-biz .v-grid .v-draw {
  animation: vDraw calc(var(--hero-dur) * .16) var(--ease) var(--dly, 0s) forwards;
}
.v-biz .g1 { --dly: calc(var(--hero-dur) * .01); }
.v-biz .g2 { --dly: calc(var(--hero-dur) * .03); }
.v-biz .g3 { --dly: calc(var(--hero-dur) * .05); }
.v-biz .g4 { --dly: calc(var(--hero-dur) * .07); }
.v-biz .g5 { --dly: calc(var(--hero-dur) * .02); }
.v-biz .g6 { --dly: calc(var(--hero-dur) * .04); }
.v-biz .g7 { --dly: calc(var(--hero-dur) * .06); }
.v-biz .g8 { --dly: calc(var(--hero-dur) * .08); }

/* Standing traffic: thick and short, so it reads as stopped rather than drawn. */
.v-biz .v-jam line { stroke-width: 3.4; stroke-linecap: round; opacity: 0; }
.is-active.v-biz .v-jam line {
  animation: vIn calc(var(--hero-dur) * .1) var(--ease) var(--dly, calc(var(--hero-dur) * .13)) forwards;
}

/* The route. Slow, unbroken, and the only thing in this frame that moves at
   all — which is the entire argument. */
.v-biz .v-way { stroke-width: 1.8; }
.is-active.v-biz .v-way {
  animation: vDraw calc(var(--hero-dur) * .52) linear calc(var(--hero-dur) * .18) forwards;
}

/* Lifted, in order, as the line reaches each one. These --dly values are the
   route's own crossing times, not guesses: the path runs 280 user units and is
   drawn from .18 to .70 of the hold, so a mark at distance d along it clears at
   .18 + .52 * d/280. Nothing lifts before the route arrives. */
.v-biz .v-clears line { transform-box: fill-box; transform-origin: center; }
.is-active.v-biz .v-clears line {
  animation: vIn   calc(var(--hero-dur) * .1)  var(--ease) calc(var(--hero-dur) * .13) forwards,
             vLift calc(var(--hero-dur) * .09) var(--ease) var(--dly, 0s) forwards;
}
@keyframes vLift { to { opacity: 0; transform: translateY(-5px); } }

.v-biz .c1 { --dly: calc(var(--hero-dur) * .22); }   /* d=20  */
.v-biz .c2 { --dly: calc(var(--hero-dur) * .25); }   /* d=40  */
.v-biz .c3 { --dly: calc(var(--hero-dur) * .32); }   /* d=74  */
.v-biz .c4 { --dly: calc(var(--hero-dur) * .39); }   /* d=112 */
.v-biz .c5 { --dly: calc(var(--hero-dur) * .48); }   /* d=160 */
.v-biz .c6 { --dly: calc(var(--hero-dur) * .53); }   /* d=190 */
.v-biz .c7 { --dly: calc(var(--hero-dur) * .63); }   /* d=240 */
.v-biz .c8 { --dly: calc(var(--hero-dur) * .67); }   /* d=262 */

/* Where the route changes direction — a turn taken, not a stop. */
.v-biz .v-turns circle {
  stroke-width: 1.2;
  fill: var(--bg);
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}
.is-active.v-biz .v-turns circle {
  animation: vPop calc(var(--hero-dur) * .08) var(--ease) var(--dly, 0s) forwards;
}
.v-biz .t1 { --dly: calc(var(--hero-dur) * .28); }   /* d=56  */
.v-biz .t2 { --dly: calc(var(--hero-dur) * .36); }   /* d=96  */
.v-biz .t3 { --dly: calc(var(--hero-dur) * .43); }   /* d=136 */
.v-biz .t4 { --dly: calc(var(--hero-dur) * .58); }   /* d=216 */
/* ============================================================ 6. EVENTS ==
   arrange — covers placed around the table, the light raised last, one bloom.
   Placement outward, against the lifestyle diagram's pull inward. */
.v-events .v-guide { stroke-width: .6; opacity: .35; }
.v-events .v-table { stroke-width: 1.2; }
.is-active.v-events .v-guide { animation: vDraw calc(var(--hero-dur) * .2) var(--ease) forwards; }
.is-active.v-events .v-table { animation: vDraw calc(var(--hero-dur) * .22) var(--ease) calc(var(--hero-dur) * .04) forwards; }

.v-events .v-cover { opacity: 0; stroke-width: 1; }
.is-active.v-events .v-cover { animation: vPop calc(var(--hero-dur) * .09) var(--ease) var(--dly, 0s) forwards; }
.v-events .k1 { --dly: calc(var(--hero-dur) * .16); }
.v-events .k2 { --dly: calc(var(--hero-dur) * .21); }
.v-events .k3 { --dly: calc(var(--hero-dur) * .26); }
.v-events .k4 { --dly: calc(var(--hero-dur) * .31); }
.v-events .k5 { --dly: calc(var(--hero-dur) * .36); }
.v-events .k6 { --dly: calc(var(--hero-dur) * .41); }
.v-events .k7 { --dly: calc(var(--hero-dur) * .46); }
.v-events .k8 { --dly: calc(var(--hero-dur) * .51); }

.v-events .v-candle { opacity: 0; stroke-width: 1; }
.is-active.v-events .v-candle { animation: vIn calc(var(--hero-dur) * .08) var(--ease) calc(var(--hero-dur) * .58) forwards; }
.is-active.v-events .v-candle .v-draw { animation: vDraw calc(var(--hero-dur) * .1) var(--ease) calc(var(--hero-dur) * .58) forwards; }
.is-active.v-events .v-flame {
  animation: vGlow calc(var(--hero-dur) * .2) var(--ease-in-out) calc(var(--hero-dur) * .66) infinite;
}

/* One bloom, not a pulse train. The room comes together once. */
.v-events .v-bloom { stroke-width: 1; opacity: 0; transform-box: fill-box; transform-origin: center; }
.is-active.v-events .v-bloom {
  animation: vBloom calc(var(--hero-dur) * .26) cubic-bezier(.2, .7, .3, 1) calc(var(--hero-dur) * .70) forwards;
}
@keyframes vBloom {
  0%   { opacity: .7; transform: scale(1); }
  100% { opacity: 0;  transform: scale(2); }
}

/* ---- responsive --------------------------------------------------------
   Below the nav's own breakpoint the copy spans the full width and the clear
   right column stops existing, so the diagram goes rather than collide. */
@media (max-width: 1150px) {
  .hero__viz-stack { display: none; }
}

/* ---- reduced motion ----------------------------------------------------
   Spec §6.1. The finished figure, still. */
@media (prefers-reduced-motion: reduce) {
  .hero__viz *,
  .viz-mark__spin { animation: none !important; }
  /* The mark stays, square on, not turning. */
  .viz-mark__face--back { display: none; }
  /* Everything that would have been drawn, drawn. */
  .hero__viz .v-draw,
  .hero__viz .v-petal,
  .hero__viz .v-arch,
  .hero__viz .v-leg,
  .hero__viz .v-day-lit,
  .hero__viz .v-stem,
  .hero__viz .v-way { stroke-dashoffset: 0; }

  /* Everything that would have arrived, arrived. */
  .hero__viz .v-globe,
  .hero__viz .v-city,
  .hero__viz .v-you,
  .hero__viz .v-guides,
  .hero__viz .v-cover,
  .hero__viz .v-lamp,
  .hero__viz .v-candle,
  .hero__viz .v-jam line,
  .hero__viz .v-turns circle { opacity: 1; }
  .hero__viz .v-win rect { fill-opacity: .5; }
  .hero__viz .v-dot { fill-opacity: .85; }

  /* Everything that only exists mid-motion, gone: the traffic the route lifts,
     the aircraft, the arrival ring, the bloom. */
  .hero__viz .v-clears line,
  .hero__viz .v-bloom,
  .hero__viz .v-lock,
  .hero__viz .v-craft { opacity: 0; }
}
