/* ===========================================================================
   Ambient motion.

   Two layers, deliberately kept apart:

     .has-motion   entrance work. Stamped only on the first load of a session,
                   so a return visit is a flat page. Reveals, staggers, wipes.
     .has-ambient  continuous work. Stamped on every load, because a dot field
                   that answers the cursor and a wordmark that sits behind the
                   page are properties of the surface, not an entrance.

   Both come off under prefers-reduced-motion, and neither hides anything by
   default: if the script never runs, every page is fully visible and still.
   =========================================================================== */

@media (prefers-reduced-motion: no-preference) {

/* =========================================================================
   1. The dot field. One parameterised mask, three behaviours.
   ========================================================================= */

/* The mask centre and size become variables so the script can move them.
   Defaults reproduce exactly what each hero painted before. */
.has-ambient .hero::before {
  mask-image: radial-gradient(ellipse var(--dot-w, 80%) var(--dot-h, 60%)
              at var(--dot-x, 50%) var(--dot-y, 35%), #000 35%, transparent 78%);
  -webkit-mask-image: radial-gradient(ellipse var(--dot-w, 80%) var(--dot-h, 60%)
              at var(--dot-x, 50%) var(--dot-y, 35%), #000 35%, transparent 78%);
}
.has-ambient .phero::before {
  mask-image: radial-gradient(ellipse var(--dot-w, 70%) var(--dot-h, 70%)
              at var(--dot-x, 22%) var(--dot-y, 30%), #000 30%, transparent 76%);
  -webkit-mask-image: radial-gradient(ellipse var(--dot-w, 70%) var(--dot-h, 70%)
              at var(--dot-x, 22%) var(--dot-y, 30%), #000 30%, transparent 76%);
}

/* -- a. cursor reactive ---------------------------------------------------
   The script lerps --dot-x/--dot-y toward the pointer, so the field brightens
   around the cursor and falls away from it. No transition here: the easing is
   the lerp, which stays smooth where a CSS transition on a mask would not. */
.has-ambient [data-dots="cursor"]::before {
  --dot-w: 62%;
  --dot-h: 58%;
}
/* the field gathers a little when the pointer is actually over the block */
.has-ambient [data-dots="cursor"].is-tracking::before {
  --dot-w: 54%;
  --dot-h: 50%;
}

/* -- b. slow drift --------------------------------------------------------
   44px and 66px are exactly two and three tiles of the 22px grid, so the loop
   is seamless and there is no jump at the restart. 40s per cycle is below the
   threshold at which the eye reads it as movement. */
@keyframes dot-drift { to { background-position: 44px 66px; } }
.has-ambient [data-dots="drift"]::before {
  animation: dot-drift 40s linear infinite;
}

/* -- c. scroll parallax ---------------------------------------------------
   The field is oversized top and bottom so translating it never exposes an
   edge. The script writes --dot-shift from the block's progress through the
   viewport. */
.has-ambient [data-dots="parallax"]::before {
  inset: -22% 0;
  transform: translate3d(0, var(--dot-shift, 0px), 0);
  will-change: transform;
}

/* =========================================================================
   2. The wordmark, behind the page
   ========================================================================= */

/* only where the closing block opts in (.endcap--motion, About) */
.has-ambient .endcap--motion .endcap__mark span {
  transform: translate3d(0, var(--mark-y, 0px), 0);
  will-change: transform;
}

/* =========================================================================
   3. The closing block: aurora, and the join drifting as it arrives
   ========================================================================= */

.has-ambient [data-endcap="aurora"] > .container { position: relative; z-index: 1; }
.has-ambient [data-endcap="aurora"]::before {
  content: "";
  position: absolute; z-index: 0;
  left: 50%; top: 22%;
  width: min(1100px, 92%); aspect-ratio: 2 / 1;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(ellipse 46% 58% at 34% 42%, rgba(48, 111, 117, 0.30), transparent 66%),
    radial-gradient(ellipse 40% 52% at 68% 56%, rgba(156, 201, 197, 0.16), transparent 68%);
  filter: blur(46px);
  pointer-events: none;
}
/* the glow fades in and drifts, and the join slides, only on .endcap--motion (About) */
.has-ambient .endcap--motion [data-endcap="aurora"]::before {
  will-change: transform;   /* the blur is rasterised once; the drift only moves the layer */
  opacity: 0;
  animation: aurora-in 1.1s var(--ease, cubic-bezier(.22,.61,.36,1)) forwards,
             aurora-drift 19s ease-in-out 1.1s infinite alternate;
}
@keyframes aurora-in   { to { opacity: 1; } }
@keyframes aurora-drift {
  from { transform: translate(-50%, -50%) scale(1)    rotate(0deg); }
  to   { transform: translate(-48%, -53%) scale(1.09) rotate(4deg); }
}
.has-ambient .endcap--motion [data-endcap="aurora"]::after {
  transform: translate3d(0, var(--join-shift, 0px), 0);
}

/* =========================================================================
   4. Home: the floating cards, the pillar chips, the highlight plate
   ========================================================================= */

/* Independent slow cycles. Each card gets its own duration and delay so they
   never fall into step, which is what makes a set of cards read as floating
   rather than as one animated group. The entrance still owns the first
   1.2s; drift only starts afterwards. */
@keyframes fcard-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(var(--fx, 4px), var(--fy, -9px), 0); }
}
.has-ambient .fcard {
  animation: fcard-float var(--fdur, 13s) ease-in-out var(--fdelay, 0s) infinite;
}
.has-ambient .fcard--1 { --fdur: 12.5s; --fdelay: 0.2s;  --fx:  5px; --fy: -10px; }
.has-ambient .fcard--2 { --fdur: 16s;   --fdelay: 1.4s;  --fx: -6px; --fy:  -7px; }
.has-ambient .fcard--3 { --fdur: 14.5s; --fdelay: 0.8s;  --fx:  4px; --fy:   8px; }
.has-ambient .fcard--4 { --fdur: 17.5s; --fdelay: 2.1s;  --fx: -4px; --fy:   9px; }
/* the entrance must land before the drift picks it up */
.has-motion.is-intro:not(.is-open) .fcard { animation: none; }

/* the four CRUD chips light in sequence, once */
.has-ambient.has-motion .hero__pillar { position: relative; }
.has-ambient.has-motion .hero__pillar::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: var(--identity-on-dark); opacity: 0;
  pointer-events: none;
}
.has-ambient.has-motion .hero__pillar.is-lit::after { animation: chip-lit 900ms ease-out forwards; }
@keyframes chip-lit {
  0%   { opacity: 0;    transform: scale(0.94); }
  35%  { opacity: 0.20; transform: scale(1); }
  100% { opacity: 0;    transform: scale(1); }
}

/* the highlight plate wipes in behind the words instead of appearing with
   them. clip-path keeps the text legible throughout the wipe. */
.has-ambient.has-motion .hl {
  -webkit-clip-path: inset(0 100% 0 0 round 14px);
  clip-path: inset(0 100% 0 0 round 14px);
}
.has-ambient.has-motion .hl.is-wiped {
  animation: hl-wipe 620ms cubic-bezier(.22,.61,.36,1) forwards;
}
@keyframes hl-wipe {
  to { -webkit-clip-path: inset(0 0 0 0 round 14px); clip-path: inset(0 0 0 0 round 14px); }
}

/* =========================================================================
   5. Hover lifts. Ambient, because a hover is not an entrance.
   ========================================================================= */

.has-ambient .cap,
.has-ambient .why__row .why__art,
.has-ambient .plan {
  transition: transform var(--t-standard, 320ms) var(--ease, cubic-bezier(.22,.61,.36,1)),
              box-shadow var(--t-standard, 320ms) var(--ease, cubic-bezier(.22,.61,.36,1));
}
.has-ambient .cap:hover  { transform: translateY(-4px); }
.has-ambient .plan:hover { transform: translateY(-3px); }

/* the popular plan sits a step above its neighbours, at rest */
.has-ambient .plan--popular { transform: translateY(-10px); }
.has-ambient .plan--popular:hover { transform: translateY(-14px); }

/* the billing toggle slides rather than cuts */
.has-ambient .pbill__group { position: relative; }
.has-ambient .pbill__group::before {
  content: ""; position: absolute; z-index: 0;
  top: 0; left: 0;
  width: var(--pill-w, 0px); height: var(--pill-h, 0px);
  transform: translate(var(--pill-x, 0px), var(--pill-y, 0px));
  border-radius: 10px; background: var(--teal-black);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08), 0 1px 3px rgba(0, 0, 0, 0.45);
}
/* slide only after the first click, so the capsule never grows in on load */
.has-ambient .pbill__group.is-animated::before {
  transition: transform 320ms var(--ease, cubic-bezier(.22,.61,.36,1)),
              width 320ms var(--ease, cubic-bezier(.22,.61,.36,1));
}
.has-ambient .pbill__group.is-ready button[aria-pressed="true"] { background: transparent; box-shadow: none; }
.has-ambient .pbill__group button { position: relative; z-index: 1; }

/* the Event Pass plan selector slides the same way */
.has-ambient .ppass__seg { position: relative; }
.has-ambient .ppass__seg::before {
  content: ""; position: absolute; z-index: 0;
  top: 0; left: 0;
  width: var(--pill-w, 0px); height: var(--pill-h, 0px);
  transform: translate(var(--pill-x, 0px), var(--pill-y, 0px));
  border-radius: 7px; background: var(--teal-black);
}
/* slide only after the first change, so the capsule never grows in on load */
.has-ambient .ppass__seg.is-animated::before {
  transition: transform 320ms var(--ease, cubic-bezier(.22,.61,.36,1)),
              width 320ms var(--ease, cubic-bezier(.22,.61,.36,1));
}
.has-ambient .ppass__seg.is-ready input:checked + span { background: transparent; }
.has-ambient .ppass__seg span { position: relative; z-index: 1; }

/* =========================================================================
   6. Entrance staggers. First load only, and only while the revealer is live.
   ========================================================================= */
/* FAIL OPEN. Everything below hid content under .has-motion, which the inline
   snippet in the head sets, while the reveal is added by ambient.js. If this
   file's script never ran, the content was hidden with nothing left to reveal
   it. The hide now also requires .has-ambient, which ONLY ambient.js sets, so
   the hidden state cannot exist unless the revealer is present. .has-motion
   stays in the selector because the entrance still belongs to the first load
   of a session and motion.js strips it on every load after. */

.has-ambient.has-motion [data-stagger] > * {
  opacity: 0;
  transform: translate3d(0, 16px, 0);
}
.has-ambient.has-motion [data-stagger].is-in > * {
  animation: stagger-in 620ms cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i, 0) * var(--step, 70ms));
}
@keyframes stagger-in { to { opacity: 1; transform: none; } }

/* a featured card arrives a beat ahead of the rest of its grid */
.has-ambient.has-motion [data-stagger] > .is-lead { --i: -1; }

/* the two compare columns arrive from opposite sides */
.has-ambient.has-motion [data-sides] > *:nth-child(odd)  { opacity: 0; transform: translate3d(-26px, 0, 0); }
.has-ambient.has-motion [data-sides] > *:nth-child(even) { opacity: 0; transform: translate3d( 26px, 0, 0); }
.has-ambient.has-motion [data-sides].is-in > * {
  animation: stagger-in 700ms cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i, 0) * 90ms);
}

/* a list revealing item by item */
.has-ambient.has-motion [data-reveal-list] > li { opacity: 0; transform: translate3d(-10px, 0, 0); }
.has-ambient.has-motion [data-reveal-list].is-in > li {
  animation: stagger-in 520ms cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i, 0) * 85ms);
}

/* the mission band arrives as one unit rather than piece by piece */
.has-ambient.has-motion [data-unit] { opacity: 0; transform: translate3d(0, 22px, 0); }
.has-ambient.has-motion [data-unit].is-in {
  animation: stagger-in 760ms cubic-bezier(.22,.61,.36,1) forwards;
}

/* Body groups on return visits (site animation rule): same reveal, once per page
   load when seen. Keyed on [data-reveal-body], which ambient.js sets only on
   motion pages and only for groups between the first section and the footer. */
.has-ambient [data-stagger][data-reveal-body] > * { opacity: 0; transform: translate3d(0, 16px, 0); }
.has-ambient [data-stagger][data-reveal-body].is-in > * {
  animation: stagger-in 620ms cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i, 0) * var(--step, 70ms));
}
.has-ambient [data-stagger][data-reveal-body] > .is-lead { --i: -1; }
.has-ambient [data-sides][data-reveal-body] > *:nth-child(odd)  { opacity: 0; transform: translate3d(-26px, 0, 0); }
.has-ambient [data-sides][data-reveal-body] > *:nth-child(even) { opacity: 0; transform: translate3d( 26px, 0, 0); }
.has-ambient [data-sides][data-reveal-body].is-in > * {
  animation: stagger-in 700ms cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i, 0) * 90ms);
}
.has-ambient [data-reveal-list][data-reveal-body] > li { opacity: 0; transform: translate3d(-10px, 0, 0); }
.has-ambient [data-reveal-list][data-reveal-body].is-in > li {
  animation: stagger-in 520ms cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i, 0) * 85ms);
}
.has-ambient [data-unit][data-reveal-body] { opacity: 0; transform: translate3d(0, 22px, 0); }
.has-ambient [data-unit][data-reveal-body].is-in { animation: stagger-in 760ms cubic-bezier(.22,.61,.36,1) forwards; }

/* the approach draws its connecting line as the block is passed */
[data-draw] { position: relative; }
.has-ambient [data-draw]::before {
  content: ""; position: absolute; z-index: 0;
  left: 0; right: 0; top: var(--draw-top, 26px); height: 2px;
  background: var(--divider, rgba(8,24,23,0.10));
  transform: scaleX(var(--draw, 0)); transform-origin: left center;
}
.has-ambient [data-draw] > * { position: relative; z-index: 1; }

/* =========================================================================
   7. Article: reading progress, and images resolving from blur
   ========================================================================= */

.has-ambient [data-blur] {
  opacity: 0; filter: blur(14px); transform: scale(1.015);
  transition: opacity 760ms var(--ease, cubic-bezier(.22,.61,.36,1)),
              filter 900ms var(--ease, cubic-bezier(.22,.61,.36,1)),
              transform 900ms var(--ease, cubic-bezier(.22,.61,.36,1));
}
.has-ambient [data-blur].is-in { opacity: 1; filter: blur(0); transform: none; }

/* =========================================================================
   8. Search results
   ========================================================================= */

.has-ambient .sfind__out > * {
  animation: stagger-in 340ms cubic-bezier(.22,.61,.36,1) backwards;
  animation-delay: calc(var(--i, 0) * 34ms);
}

} /* end prefers-reduced-motion: no-preference */

/* The reading progress bar is structure, not motion. It was defined inside
   the reduced-motion media query, so under that preference it would have
   rendered unstyled. */
.aprog { display: grid; gap: 8px; }
.aprog__track {
  height: 3px; border-radius: 3px; overflow: hidden;
  background: var(--divider, rgba(8,24,23,0.10));
}
.aprog__bar {
  display: block; height: 100%; width: 100%;
  border-radius: 3px; background: var(--identity, #306F75);
  transform: scaleX(var(--read, 0)); transform-origin: left center;
}
.aprog__pct {
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted);
}
