/* ==========================================================================
   VOYRA — AMBIENT BACKGROUND
   ==========================================================================
   One component, one file, mounted with .has-ambient. Never copied into a
   page: every section that wants it renders the same markup from the same
   builder function and inherits everything here.

   ARCHITECTURE — one layer PER SECTION, one rAF loop for all of them.
   An earlier draft used a single fixed layer for the whole page. It would
   have worked, but only by forcing .band and .page-head transparent so the
   layer showed through - and since those two carry different backgrounds,
   one of them would have changed colour. That is a visual change nobody
   asked for. Each section owning its own absolutely-positioned layer costs
   nothing extra: the performance guarantee was one scroll loop, not one
   element, and the loop below is still the only one.

   Z-ORDER: the layer is z-index 0 inside its section, .shell is z-index 1.
   The layer is painted OVER the section's own background and UNDER its
   content, so no background anywhere is touched.
   ========================================================================== */

/* ---- kill switches ------------------------------------------------------
   Global: one attribute on <body> disables every layer on the site.
   Per section: remove .has-ambient and that section renders nothing at all -
   not a hidden element, no element.

   */
body[data-ambient-off] .ambient { display: none; }

/* ---- host --------------------------------------------------------------- */
.has-ambient { position: relative; overflow: hidden; }
.has-ambient > .shell { position: relative; z-index: 1; }

.ambient {
  position: absolute;
  /* Negative inset gives the parallax somewhere to travel without ever
     exposing an edge inside the section's clipped box. */
  inset: -12%;
  z-index: 0;
  pointer-events: none;
  /* paint containment only. `strict` would add size containment and collapse
     a box whose whole job is to fill its parent. */
  contain: paint;
}

/* ---- 1. aurora field -----------------------------------------------------
   Colours come from the identity tokens, never from a hex typed here. Each
   blob has its own duration - 47s / 61s / 53s are mutually prime enough that
   they will not visibly resynchronise. */
.ambient__aurora { position: absolute; inset: 0; }

.ambient__blob {
  position: absolute;
  inline-size: 40vmax;
  aspect-ratio: 1;
  border-radius: 50%;
  /* Static. Animating a filter is the single most expensive thing this
     component could do, so the blur is rasterised once and only the
     transform moves afterwards. */
  filter: blur(clamp(60px, 6vw, 95px));
  opacity: 0.30;
  will-change: transform;
  animation: ambient-drift var(--blob-dur) ease-in-out infinite alternate;
  /* Explicitly paused until the section is on screen. Browsers usually
     throttle off-screen animation, but "usually" is an optimisation, not a
     guarantee - and this is one line for certainty. */
  animation-play-state: paused;
}
.ambient.is-live .ambient__blob { animation-play-state: running; }

.ambient__blob--1 {
  --blob-dur: 47s;
  inset-block-start: -18%;
  inset-inline-start: 26%;
  background: radial-gradient(circle closest-side, var(--amb-lead) 0%, transparent 70%);
}
.ambient__blob--2 {
  --blob-dur: 61s;
  inset-block-start: 18%;
  inset-inline-end: 8%;
  background: radial-gradient(circle closest-side, var(--amb-second) 0%, transparent 72%);
  animation-direction: alternate-reverse;
}
.ambient__blob--3 {
  --blob-dur: 53s;
  inset-block-end: -10%;
  inset-inline-start: -2%;
  background: radial-gradient(circle closest-side, var(--amb-third) 0%, transparent 74%);
  opacity: 0.22;
}
/* Short sections take two. Set by the builder, never measured at runtime -
   measuring a height here would reintroduce the layout read the scroll loop
   is carefully avoiding. */
.ambient--two .ambient__blob--3 { display: none; }

/* transform and opacity only. No filter, no width, no top/left, no
   background-position. */
@keyframes ambient-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(3.5%, -4.5%, 0) scale(1.14); }
}

/* ---- 2. precision dot grid ----------------------------------------------
   Pure CSS gradient, no image asset. Masked so it dissolves outward instead
   of ending on a visible straight edge. */
.ambient__dots {
  position: absolute;
  inset: 0;
  color: var(--navy);
  background-image: radial-gradient(currentColor 1px, transparent 1px);
  background-size: 28px 28px;
  opacity: 0.10;
  will-change: transform;
  -webkit-mask-image: radial-gradient(ellipse 68% 58% at 50% 46%, #000 30%, transparent 76%);
          mask-image: radial-gradient(ellipse 68% 58% at 50% 46%, #000 30%, transparent 76%);
}

/* ---- per-page variation -------------------------------------------------
   Same system everywhere, a different leading colour and different blob
   anchors per page, so the site does not read as one repeating wallpaper.
   The attribute sits on the SECTION, because the layers live in sections. */
[data-ambient="home"]         { --amb-lead: var(--turquoise); --amb-second: var(--gold);      --amb-third: var(--navy-400); }
[data-ambient="services"]     { --amb-lead: var(--gold);      --amb-second: var(--turquoise); --amb-third: var(--navy-400); }
[data-ambient="destinations"] { --amb-lead: var(--turquoise); --amb-second: var(--navy-400);  --amb-third: var(--gold); }
[data-ambient="approach"]     { --amb-lead: var(--navy-400);  --amb-second: var(--turquoise); --amb-third: var(--gold); }
[data-ambient="about"]        { --amb-lead: var(--gold);      --amb-second: var(--navy-400);  --amb-third: var(--turquoise); }
[data-ambient="impact"]       { --amb-lead: var(--turquoise); --amb-second: var(--gold);      --amb-third: var(--navy-400); }
[data-ambient="contact"]      { --amb-lead: var(--gold);      --amb-second: var(--turquoise); --amb-third: var(--navy-400); }
[data-ambient="place"]        { --amb-lead: var(--turquoise); --amb-second: var(--gold);      --amb-third: var(--navy-400); }
/* Legal pages get the quietest possible treatment: one hue, low presence. A
   privacy policy should not shimmer. */
[data-ambient="legal"]        { --amb-lead: var(--navy-400);  --amb-second: var(--navy-400);  --amb-third: transparent; }
[data-ambient="legal"] .ambient__blob { opacity: 0.16; }
[data-ambient="legal"] .ambient__dots { opacity: 0.06; }

/* Second-position blobs are anchored differently per page so two pages side
   by side never look like the same image. */
[data-ambient="services"] .ambient__blob--1 { inset-block-start: -10%; inset-inline-start: 8%; }
[data-ambient="approach"] .ambient__blob--1 { inset-block-start: -22%; inset-inline-start: 40%; }
[data-ambient="about"]    .ambient__blob--2 { inset-block-start: 4%;  inset-inline-end: 20%; }
[data-ambient="contact"]  .ambient__blob--2 { inset-block-start: 24%; inset-inline-end: 12%; }
[data-ambient="place"]    .ambient__blob--1 { inset-block-start: -8%;  inset-inline-start: 14%; }

/* ---- 4. stat reveal ------------------------------------------------------
   The rise is on transform, the fade on opacity. The count-up reserves its
   final width in JS before it starts, so the row cannot reflow mid-count. */
.has-ambient .band__item {
  opacity: 0;
  transform: translate3d(0, 16px, 0);
  transition: opacity .55s var(--ease-out), transform .55s var(--ease-out);
  transition-delay: var(--stat-delay, 0ms);
}
.has-ambient .band__item.is-counted { opacity: 1; transform: none; }
.band__n { font-variant-numeric: tabular-nums; }

/* ---- small screens -------------------------------------------------------
   Two blobs, half the blur, parallax disabled in script. A 140px blur across
   a full-width element is the most expensive thing on a mid-range phone. */
@media (max-width: 47.99rem) {
  .ambient__blob--3 { display: none; }
  .ambient__blob {
    inline-size: 64vmax;
    filter: blur(clamp(45px, 12vw, 70px));
  }
  .ambient__dots { background-size: 22px 22px; }
}

/* ---- reduced motion ------------------------------------------------------
   Static gradients, no drift, no parallax, and the numbers are rendered at
   their final value by the script. The figure is still there - it simply
   does not move. */
@media (prefers-reduced-motion: reduce) {
  .ambient__blob { animation: none; transform: none; }
  .ambient__aurora,
  .ambient__dots { transform: none !important; }
  .has-ambient .band__item { opacity: 1; transform: none; transition: none; }
}

/* No JavaScript: nothing animates and nothing is hidden. The layer is a
   static wash and the stats are simply visible. */
html:not(.js) .has-ambient .band__item { opacity: 1; transform: none; }
