/* ============================================================
   VISTEX — Motion primitives & background systems
   Everything here is opt-in and disabled under reduced-motion.
   ============================================================ */

/* ============ 1. SCROLL REVEAL ============ */
[data-anim] {
  opacity: 0;
  transition:
    opacity   .85s var(--ease-out) var(--d, 0s),
    transform .85s var(--ease-out) var(--d, 0s),
    filter    .85s var(--ease-out) var(--d, 0s),
    clip-path .95s var(--ease-out) var(--d, 0s);
  will-change: opacity, transform;
}
[data-anim="up"]    { transform: translateY(36px); }
[data-anim="down"]  { transform: translateY(-28px); }
[data-anim="left"]  { transform: translateX(-44px); }
[data-anim="right"] { transform: translateX(44px); }
[data-anim="scale"] { transform: scale(.94); }
[data-anim="blur"]  { filter: blur(14px); transform: translateY(20px); }
[data-anim="clip"]  { clip-path: inset(0 0 100% 0); transform: none; }
[data-anim="fade"]  { transform: none; }

[data-anim].is-in {
  opacity: 1; transform: none; filter: none;
  clip-path: inset(0 0 0 0);
}

/* Stagger children inside a .reveal-parent (index set by motion.js) */
.reveal-parent > [data-anim],
.reveal-parent [data-anim] { --d: calc(var(--i, 0) * 90ms); }

/* ============ 2. SPLIT TEXT ============ */
/* Each line is a mask; each unit slides out of it. */
.split-line {
  display: block; overflow: hidden;
  padding-bottom: .1em; margin-bottom: -.1em;
  padding-top: .06em;  margin-top: -.06em;
}
.split-unit {
  display: inline-block;
  transform: translateY(105%) rotate(2.5deg);
  opacity: 0;
  transition:
    transform .95s var(--ease-out) var(--d, 0s),
    opacity   .6s  var(--ease-out) var(--d, 0s);
  will-change: transform;
}
.is-in .split-unit, .split-ready.is-in .split-unit {
  transform: translateY(0) rotate(0deg); opacity: 1;
}

/* DROP variant — words fall in from above and settle with a slight overshoot.
   Used on the hero headline so it lands into position as the page loads. */
[data-split-anim="drop"] .split-unit {
  transform: translateY(-125%) rotate(-3deg) scale(1.06);
  opacity: 0;
  transition:
    transform .82s cubic-bezier(.2,1.35,.35,1) var(--d, 0s),
    opacity   .34s var(--ease-out) var(--d, 0s);
}
[data-split-anim="drop"].is-in .split-unit {
  transform: translateY(0) rotate(0deg) scale(1); opacity: 1;
}
.split-space { display: inline-block; width: .28em; }

/* chars mode: the word stays unbreakable, its characters animate individually */
.split-word { display: inline-block; white-space: nowrap; }
[data-split="chars"] .split-unit { transition-duration: .7s; }

/* ============ 3. PARALLAX ============ */
[data-parallax] { will-change: transform; }

/* ============ 4. SCRUB (sticky scrollytelling) ============ */
.scrub { position: relative; }
.scrub-sticky { position: sticky; top: calc(var(--header-h) + 6vh); }

/* ============ 5. BACKGROUND SYSTEM — AURORA ============ */
.aurora {
  position: absolute; inset: -20% -10%; z-index: 0; pointer-events: none;
  opacity: var(--aurora-opacity);
  filter: blur(60px) saturate(130%);
  contain: strict;
}
.aurora span {
  position: absolute; display: block; border-radius: 50%;
  mix-blend-mode: normal;
}
.aurora .a1 {
  width: 46vw; height: 46vw; left: -6%; top: 2%;
  background: radial-gradient(circle at 35% 35%, color-mix(in srgb, var(--aqua-400) 78%, transparent), transparent 66%);
  animation: aurora-1 26s var(--ease-in-out) infinite alternate;
}
.aurora .a2 {
  width: 40vw; height: 40vw; right: -4%; top: -8%;
  background: radial-gradient(circle at 60% 40%, color-mix(in srgb, var(--blue-400) 72%, transparent), transparent 66%);
  animation: aurora-2 32s var(--ease-in-out) infinite alternate;
}
.aurora .a3 {
  width: 52vw; height: 38vw; left: 22%; bottom: -18%;
  background: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--aqua-200) 62%, transparent), transparent 68%);
  animation: aurora-3 38s var(--ease-in-out) infinite alternate;
}
[data-theme="dark"] .aurora .a3 {
  background: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--blue-500) 70%, transparent), transparent 68%);
}
@keyframes aurora-1 { to { transform: translate3d(14%, 10%, 0) scale(1.16); } }
@keyframes aurora-2 { to { transform: translate3d(-12%, 14%, 0) scale(1.22); } }
@keyframes aurora-3 { to { transform: translate3d(8%, -12%, 0) scale(1.12); } }

/* ============ 6. BACKGROUND SYSTEM — CAUSTICS ============ */
.caustics {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .22; mix-blend-mode: screen; overflow: hidden;
}
.caustics::before, .caustics::after {
  content: ""; position: absolute; inset: -30%;
  background-image:
    repeating-radial-gradient(circle at 30% 40%, rgba(255,255,255,.5) 0 2px, transparent 2px 42px),
    repeating-radial-gradient(circle at 72% 66%, rgba(255,255,255,.4) 0 2px, transparent 2px 56px);
  filter: blur(9px);
  animation: caustic 24s linear infinite;
}
.caustics::after { animation-duration: 34s; animation-direction: reverse; opacity: .6; }
@keyframes caustic {
  0%   { transform: translate3d(0,0,0) scale(1) rotate(0deg); }
  100% { transform: translate3d(-6%, 5%, 0) scale(1.14) rotate(8deg); }
}

/* ============ 7. BACKGROUND SYSTEM — DRIFTING BUBBLES ============ */
.bubbles { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 1; }
/* No scroll coupling: the field is simply alive on its own. Bubbles rise, sway
   and occasionally burst whether or not anyone is touching the page, which is
   what an ambient background should do — reacting to scroll made the motion
   feel like a control rather than an atmosphere. */

/* Light theme: white bubbles on a light hero are invisible, so they are tinted
   with the brand aqua and given a real rim. Dark theme keeps them white. */
.bubble {
  position: absolute; bottom: -12%; border-radius: 50%;
  background:
    radial-gradient(circle at 30% 26%,
      color-mix(in srgb, #fff 92%, transparent) 0%,
      color-mix(in srgb, var(--aqua-200) 45%, transparent) 42%,
      color-mix(in srgb, var(--aqua-400) 12%, transparent) 68%,
      transparent 74%);
  border: 1px solid color-mix(in srgb, var(--aqua-500) 38%, transparent);
  box-shadow:
    inset -2px -3px 6px color-mix(in srgb, var(--blue-500) 16%, transparent),
    inset 3px 3px 8px rgba(255,255,255,.6),
    0 2px 10px color-mix(in srgb, var(--aqua-500) 14%, transparent);
  /* Two animations on two different properties. `transform` carries the rise,
     `translate` carries the sway, and because their periods are deliberately
     unrelated the path never visibly repeats — which is what reads as drifting
     rather than marching. */
  animation: bubble-rise linear infinite, bubble-sway ease-in-out infinite alternate;
}
/* Dark mode: the inset dark shadow turned these into grey marbles. They now
   read as lit bubbles — aqua rim, soft glow, no dark core. */
[data-theme="dark"] .bubble {
  background: radial-gradient(circle at 30% 26%,
    rgba(255,255,255,.55) 0%,
    color-mix(in srgb, var(--aqua-300) 26%, transparent) 38%,
    transparent 68%);
  border: 1px solid color-mix(in srgb, var(--aqua-300) 34%, transparent);
  box-shadow:
    inset 2px 3px 8px rgba(255,255,255,.18),
    0 0 14px color-mix(in srgb, var(--aqua-400) 18%, transparent);
  opacity: .8;
}
/* On brand-blue bands the light-theme tint would disappear — force white there */
.section--ink .bubble, .film-band .bubble, .cta-band .bubble, #vx-loader .bubble {
  background: radial-gradient(circle at 32% 28%, rgba(255,255,255,.88), rgba(255,255,255,.07) 58%, transparent 72%);
  border-color: rgba(255,255,255,.3);
  box-shadow: inset -2px -3px 6px rgba(0,0,0,.15), inset 3px 3px 8px rgba(255,255,255,.35);
}
/* --travel is set per field by motion.js from the container's own height, so a
   bubble crosses its section exactly once whether that section is 300px or a
   full viewport. The old fixed -118vh shot straight out of short sections. */
@keyframes bubble-rise {
  0%   { transform: translateY(0) scale(.6); opacity: 0; }
  12%  { opacity: .75; }
  85%  { opacity: .5; }
  100% { transform: translateY(calc(var(--travel, 118vh) * -1)) scale(1.05); opacity: 0; }
}
@keyframes bubble-sway {
  from { translate: calc(var(--sway, 18px) * -1) 0; }
  to   { translate: var(--sway, 18px) 0; }
}

/* ---- Pop ---------------------------------------------------------------
   A bubble that reaches the surface bursts. Two parts: the skin snaps
   outward and vanishes, and a thin ring expands past where the skin was.
   Both run on `scale` — the third independent transform property — so the
   rise and the sway keep running underneath and the bubble pops WHERE IT
   IS rather than jumping to a corner first. */
.bubble.pop {
  animation-play-state: paused;          /* freeze the drift at the moment of burst */
  scale: 1;
}
.bubble.pop::after {
  content: ""; position: absolute; inset: 0;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--aqua-400) 70%, transparent);
  animation: bubble-ring .42s cubic-bezier(.2, .8, .3, 1) forwards;
}
[data-theme="dark"] .bubble.pop::after {
  border-color: color-mix(in srgb, var(--aqua-300) 66%, transparent);
}
.bubble.pop {
  animation-name: bubble-rise, bubble-sway, bubble-burst;
  animation-duration: var(--rise-d), var(--sway-d), .34s;
  animation-timing-function: linear, ease-in-out, cubic-bezier(.3, .7, .4, 1);
  animation-iteration-count: infinite, infinite, 1;
  animation-fill-mode: none, none, forwards;
  animation-play-state: paused, paused, running;
}
@keyframes bubble-burst {
  0%   { scale: 1;    opacity: 1; }
  45%  { scale: 1.35; opacity: .55; }
  100% { scale: 1.7;  opacity: 0; }
}
@keyframes bubble-ring {
  0%   { transform: scale(.9);  opacity: .85; }
  100% { transform: scale(2.4); opacity: 0; }
}

/* Ambient fields: the same bubbles behind ordinary content sections, dialled
   back so they read as texture rather than as the subject. */
/* Measured on a screenshot first: at .5/.42 the field was so faint it read as
   dust on the monitor, which defeats the point of putting it there. These
   levels are visible as water without competing with the copy in front. */
.bubbles--ambient .bubble { opacity: .78; }
[data-theme="dark"] .bubbles--ambient .bubble { opacity: .62; }
.bubbles--ambient { z-index: 0; }

/* ============ 8. MARQUEE (velocity-reactive) ============
   Inverted band: deep brand ink with light type, so the product ticker reads as
   a deliberate rule between the hero and the page rather than a grey whisper. */
.marquee {
  position: relative; overflow: hidden;
  background: linear-gradient(100deg, var(--blue-800), var(--blue-950) 55%, var(--blue-900));
  border-block: 0;
  padding: 20px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
[data-theme="dark"] .marquee {
  background: linear-gradient(100deg, #0b1226, #05080f 55%, #0b1226);
  border-block: 1px solid var(--border);
}
.marquee-track {
  display: inline-flex; gap: 44px; white-space: nowrap; will-change: transform;
}
.marquee span {
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: var(--tr-mono);
  color: rgba(255,255,255,.88);
  display: inline-flex; align-items: center; gap: 44px;
}
.marquee span::after {
  content: "◆"; font-size: 7px; color: var(--aqua-400); transform: translateY(-1px);
}

/* ============ 9. COUNTERS ============ */
.count { font-variant-numeric: tabular-nums; }

/* ============ 10. HOVER MICRO-INTERACTIONS ============ */
[data-tilt] { transform-style: preserve-3d; transition: transform var(--t-mid); }
[data-tilt]:hover { transition: transform .1s linear; }

.arrow-slide svg { transition: transform var(--t-mid); }
.arrow-slide:hover svg { transform: translateX(4px); }

/* ============ 11. INTRO LOADER ============
   A brand-blue field built from three layers: a deep gradient, the same ripple
   texture the site uses further down, and two slow drifting glows. The mark
   fills with liquid, then the wordmark resolves under it. ============ */
#vx-loader {
  position: fixed; inset: 0; z-index: var(--z-loader);
  display: grid; place-items: center; overflow: hidden;
  background:
    radial-gradient(120% 85% at 50% -10%, color-mix(in srgb, var(--aqua-600) 55%, transparent) 0%, transparent 58%),
    radial-gradient(90% 70% at 12% 108%, color-mix(in srgb, var(--blue-500) 60%, transparent) 0%, transparent 62%),
    linear-gradient(158deg, var(--blue-700) 0%, var(--blue-900) 46%, var(--blue-950) 100%);
  transition: opacity .55s var(--ease-out), visibility .55s;
}
#vx-loader.hide { opacity: 0; visibility: hidden; }

/* ripple texture — the same asset as the systems band, tying the two together */
.vx-tex {
  position: absolute; inset: 0; pointer-events: none;
  background-image: url("../images/photos/ripple.webp"), url("../images/photos/ripple.jpg");
  background-size: cover; background-position: center;
  opacity: .26; mix-blend-mode: soft-light;
  animation: tex-drift 18s var(--ease-in-out) infinite alternate;
}
@keyframes tex-drift { to { transform: scale(1.12) translate3d(-2%, -3%, 0); } }

/* two slow glows so the field is never flat */
.vx-glow { position: absolute; inset: 0; pointer-events: none; filter: blur(70px); opacity: .55; }
.vx-glow span { position: absolute; display: block; border-radius: 50%; }
.vx-glow span:first-child {
  width: 46vw; height: 46vw; left: -8%; top: -12%;
  background: radial-gradient(circle, color-mix(in srgb, var(--aqua-400) 62%, transparent), transparent 68%);
  animation: aurora-1 22s var(--ease-in-out) infinite alternate;
}
.vx-glow span:last-child {
  width: 40vw; height: 40vw; right: -6%; bottom: -14%;
  background: radial-gradient(circle, color-mix(in srgb, var(--blue-400) 58%, transparent), transparent 68%);
  animation: aurora-2 28s var(--ease-in-out) infinite alternate;
}

/* fine grain so the gradient never bands */
#vx-loader::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode: overlay;
}

.vx-loader-inner {
  position: relative; z-index: 2; isolation: isolate;
  display: grid; justify-items: center; gap: 22px;
}
/* the ripple is bright in places — this pool of ink guarantees the white
   logo reads no matter which part of the texture sits behind it */
.vx-loader-inner::before {
  content: ""; position: absolute; inset: -70px -110px -56px; z-index: -1;
  border-radius: 50%;
  background: radial-gradient(ellipse at center,
    color-mix(in srgb, var(--blue-950) 72%, transparent) 0%,
    color-mix(in srgb, var(--blue-950) 42%, transparent) 45%,
    transparent 72%);
  filter: blur(26px);
}

/* Mark: a dim outline copy with a bright copy revealed by a rising fill */
.vx-loader-mark {
  position: relative;
  width: clamp(210px, 26vw, 290px);
  aspect-ratio: 806 / 279;
}
.vx-loader-mark img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.vx-loader-mark .m-ghost { opacity: .3; }
.vx-loader-mark .m-fill {
  clip-path: inset(0 0 0 0);                    /* resting state = fully drawn */
  animation: loader-fill .95s var(--ease-in-out) backwards;
  filter: drop-shadow(0 0 20px color-mix(in srgb, var(--aqua-400) 70%, transparent));
}
@keyframes loader-fill { from { clip-path: inset(100% 0 0 0); } }

@keyframes loader-word { from { opacity: 0; transform: translateY(6px); } }

.vx-loader-bar {
  width: 148px; height: 3px; border-radius: var(--r-full);
  background: rgba(255,255,255,.16); overflow: hidden;
}
.vx-loader-bar span {
  display: block; height: 100%; width: 100%;
  border-radius: inherit; transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--aqua-400), #fff);
  animation: loader-progress 1.25s var(--ease-out) backwards;
}
@keyframes loader-progress {
  from { transform: scaleX(.05); } 55% { transform: scaleX(.62); }
}
.vx-loader-text {
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--tr-label); text-transform: uppercase;
  color: rgba(255,255,255,.62);
}

@media (prefers-reduced-motion: reduce) {
  .vx-tex, .vx-glow span { animation: none; }
  .vx-loader-mark .m-fill { clip-path: inset(0 0 0 0); animation: none; }

}

/* ============ 12. PAGE TRANSITION ============ */
.page-fade { animation: page-in .5s var(--ease-out); }
@keyframes page-in { from { opacity: 0; transform: translateY(10px); } }
body.is-leaving main { opacity: 0; transform: translateY(-8px); transition: opacity .22s, transform .22s; }

/* ============ 13. SCROLL CUE ============ */
.scroll-cue {
  display: inline-flex; flex-direction: column; align-items: center; gap: 10px;
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--tr-label); text-transform: uppercase; color: var(--text-4);
}
.scroll-cue .line {
  width: 1px; height: 46px; background: linear-gradient(var(--text-4), transparent);
  position: relative; overflow: hidden;
}
.scroll-cue .line::after {
  content: ""; position: absolute; inset: 0; background: var(--water);
  animation: cue-run 2s var(--ease-in-out) infinite;
}
@keyframes cue-run { 0% { transform: translateY(-100%); } 60%,100% { transform: translateY(100%); } }

/* ============ 14. REDUCED MOTION ============ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  [data-anim] { opacity: 1 !important; transform: none !important; filter: none !important; clip-path: none !important; }
  .split-unit { opacity: 1 !important; transform: none !important; }
  .aurora, .caustics, .bubbles { display: none; }
  /* The product ticker is content, not decoration — it keeps panning, just
     at a calmer pace (motion.js halves its speed under this setting). */
}

/* ============================================================
   SECTION GLOWS — soft colour blooms behind ordinary sections
   Radial gradients, NOT blur(): a blur pass on a full-width layer
   is expensive at this count, and a filter on a promoted layer is
   exactly what painted a visible rectangle behind the Swift mark.
   A gradient has no filter region to get wrong.
   Four variants, dealt in rotation so no two adjacent sections
   carry the same arrangement.
   ============================================================ */
.sec-glow {
  position: absolute; inset: 0;
  overflow: hidden; pointer-events: none; z-index: 0;
}
.sec-glow i {
  position: absolute; display: block;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, var(--c), transparent 70%);
}
/* Light theme keeps these near the threshold of perception — enough to lift a
   flat panel, not enough to tint the text sitting on it. */
.sec-glow { --aq: color-mix(in srgb, var(--aqua-400) 34%, transparent);
            --bl: color-mix(in srgb, var(--blue-400) 28%, transparent);
            --dp: color-mix(in srgb, var(--blue-600) 20%, transparent); }
[data-theme="dark"] .sec-glow {
  --aq: color-mix(in srgb, var(--aqua-500) 24%, transparent);
  --bl: color-mix(in srgb, var(--blue-500) 30%, transparent);
  --dp: color-mix(in srgb, var(--blue-400) 18%, transparent);
}

.sec-glow--1 i:nth-child(1) { width: 46%; aspect-ratio: 1; left: -12%;  top: -26%;    --c: var(--aq); }
.sec-glow--1 i:nth-child(2) { width: 38%; aspect-ratio: 1; right: -8%;  bottom: -30%; --c: var(--bl); }

.sec-glow--2 i:nth-child(1) { width: 42%; aspect-ratio: 1; right: -14%; top: -22%;    --c: var(--aq); }
.sec-glow--2 i:nth-child(2) { width: 34%; aspect-ratio: 1; left: 8%;    bottom: -26%; --c: var(--dp); }

.sec-glow--3 i:nth-child(1) { width: 50%; aspect-ratio: 1; left: 26%;   top: -34%;    --c: var(--bl); }
.sec-glow--3 i:nth-child(2) { width: 30%; aspect-ratio: 1; left: -10%;  bottom: -18%; --c: var(--aq); }

.sec-glow--4 i:nth-child(1) { width: 36%; aspect-ratio: 1; right: 4%;   bottom: -28%; --c: var(--aq); }
.sec-glow--4 i:nth-child(2) { width: 44%; aspect-ratio: 1; left: -16%;  top: -20%;    --c: var(--dp); }

/* On a phone the blooms are proportionally huge and start to read as a wash
   over the whole panel rather than an accent in a corner. */
@media (max-width: 700px) {
  .sec-glow i { opacity: .72; }
  .sec-glow--1 i:nth-child(1), .sec-glow--3 i:nth-child(1) { width: 68%; }
}
