/**
 * Wosol — Motion Layer
 *
 * Scroll-reveal animations + hover / focus micro-interactions.
 *
 * Reveal system:
 *   - Add data-animate="fade-up" (or fade-in, fade-right, fade-left) to any
 *     element you want to reveal on scroll.
 *   - assets/js/animations.js adds .is-visible when the element enters the
 *     viewport (Intersection Observer, one-shot).
 *   - Stagger children with data-animate-stagger on a parent; each direct
 *     child animates with a small delay based on its index.
 *
 * Easing: cubic-bezier(0.16, 1, 0.3, 1) is a smooth, slow-ease-out that
 * feels luxurious and unhurried. All durations lean long (0.9s+) to match
 * the brand.
 *
 * prefers-reduced-motion honored globally: reveals fall back to plain
 * opacity 1 with zero motion; hover animations stay in place but shorter.
 */

/* ------- Root motion tokens ------- */

:root {
  --wosol-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --wosol-ease-in-out: cubic-bezier(0.83, 0, 0.17, 1);
  --wosol-motion-slow: 0.9s;
  --wosol-motion-med:  0.6s;
  --wosol-motion-fast: 0.32s;
  --wosol-stagger-step: 90ms;
}

html {
  scroll-behavior: smooth;
}

/* ------- Scroll-reveal base states ------- */

[data-animate] {
  opacity: 0;
  transition:
    opacity var(--wosol-motion-slow) var(--wosol-ease-out),
    transform var(--wosol-motion-slow) var(--wosol-ease-out);
  will-change: opacity, transform;
}
[data-animate="fade-up"]    { transform: translate3d(0, 24px, 0); }
[data-animate="fade-in"]    { transform: none; }
[data-animate="fade-right"] { transform: translate3d(-32px, 0, 0); }
[data-animate="fade-left"]  { transform: translate3d(32px, 0, 0); }
[data-animate="zoom-in"]    { transform: scale(1.04); }
/* Slide reveals swap direction in RTL so "fade-right" still reveals from the start side */
[dir="rtl"] [data-animate="fade-right"] { transform: translate3d(32px, 0, 0); }
[dir="rtl"] [data-animate="fade-left"]  { transform: translate3d(-32px, 0, 0); }

[data-animate].is-visible {
  opacity: 1;
  transform: none;
}

/* animations.js adds .is-settled once the reveal has finished. Dropping the
   hint there releases the compositing layer, and with it the containing block
   will-change forces — a position:fixed descendant of a revealed element
   anchors to the element rather than to the viewport while it is set. */
[data-animate].is-settled,
[data-animate-stagger].is-settled,
[data-animate-stagger].is-settled > * {
  will-change: auto;
}

/* Stagger children of any parent tagged with data-animate-stagger.
   Each direct child animates in sequence when the parent is revealed. */
[data-animate-stagger] > * {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition:
    opacity var(--wosol-motion-slow) var(--wosol-ease-out),
    transform var(--wosol-motion-slow) var(--wosol-ease-out);
  will-change: opacity, transform;
}
[data-animate-stagger].is-visible > * {
  opacity: 1;
  transform: none;
}
[data-animate-stagger].is-visible > *:nth-child(1) { transition-delay: 0ms; }
[data-animate-stagger].is-visible > *:nth-child(2) { transition-delay: calc(var(--wosol-stagger-step) * 1); }
[data-animate-stagger].is-visible > *:nth-child(3) { transition-delay: calc(var(--wosol-stagger-step) * 2); }
[data-animate-stagger].is-visible > *:nth-child(4) { transition-delay: calc(var(--wosol-stagger-step) * 3); }
[data-animate-stagger].is-visible > *:nth-child(5) { transition-delay: calc(var(--wosol-stagger-step) * 4); }
[data-animate-stagger].is-visible > *:nth-child(6) { transition-delay: calc(var(--wosol-stagger-step) * 5); }
[data-animate-stagger].is-visible > *:nth-child(7) { transition-delay: calc(var(--wosol-stagger-step) * 6); }
[data-animate-stagger].is-visible > *:nth-child(8) { transition-delay: calc(var(--wosol-stagger-step) * 7); }
[data-animate-stagger].is-visible > *:nth-child(9) { transition-delay: calc(var(--wosol-stagger-step) * 8); }
[data-animate-stagger].is-visible > *:nth-child(10) { transition-delay: calc(var(--wosol-stagger-step) * 9); }
/* Keep this list at least as long as the longest staggered group on the site —
   a child past the end gets no rule, so it falls back to 0ms and arrives with
   the first item instead of in sequence, which reads as a broken reveal rather
   than a stagger. The world map's city row is the current longest at 15, and
   it is Customizer copy, so it can grow. */
[data-animate-stagger].is-visible > *:nth-child(11) { transition-delay: calc(var(--wosol-stagger-step) * 10); }
[data-animate-stagger].is-visible > *:nth-child(12) { transition-delay: calc(var(--wosol-stagger-step) * 11); }
[data-animate-stagger].is-visible > *:nth-child(13) { transition-delay: calc(var(--wosol-stagger-step) * 12); }
[data-animate-stagger].is-visible > *:nth-child(14) { transition-delay: calc(var(--wosol-stagger-step) * 13); }
[data-animate-stagger].is-visible > *:nth-child(15) { transition-delay: calc(var(--wosol-stagger-step) * 14); }
[data-animate-stagger].is-visible > *:nth-child(16) { transition-delay: calc(var(--wosol-stagger-step) * 15); }

/* ------- Hero: cinematic entrance on page load ------- */

.wosol-hero__eyebrow,
.wosol-hero__title {
  animation: wosol-hero-in var(--wosol-motion-slow) var(--wosol-ease-out) both;
}
.wosol-hero__eyebrow { animation-delay: 0.15s; }
.wosol-hero__title   { animation-delay: 0.30s; }

@keyframes wosol-hero-in {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
  to   { opacity: 1; transform: none; }
}

/* Slow subtle zoom of hero background image, once on load. */
.wosol-hero-contact,
.wosol-services-hero {
  animation: wosol-hero-bg 20s ease-out both;
}

@keyframes wosol-hero-bg {
  0%   { background-size: 104% auto, 104% auto, 108% auto; }
  100% { background-size: 100% auto, 100% auto, 100% auto; }
}

/* ------- Nav pill press feedback ------- */

.wosol-btn--pill {
  transition:
    transform var(--wosol-motion-fast) var(--wosol-ease-out),
    background-color var(--wosol-motion-fast) ease,
    border-color var(--wosol-motion-fast) ease,
    box-shadow var(--wosol-motion-fast) ease;
}
.wosol-btn--pill:hover  { transform: translateY(-1px); }
.wosol-btn--pill:active { transform: translateY(0);    }

/* Curated ("Private Experiences") and offer ("The WOSOL World") cards used to
   run their own lighter hover here — a 4px card lift and a scale(1.04) zoom.
   They now share the services tiles' treatment instead; see the IMAGE CARD
   HOVER block further down. */

/* ------- Nav underline slide on menu / footer links ------- */

.wosol-menu__link,
.wosol-footer__col--nav a {
  position: relative;
}
.wosol-menu__link::after,
.wosol-footer__col--nav a::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--wosol-motion-med) var(--wosol-ease-out);
}
.wosol-menu__link:hover::after,
.wosol-footer__col--nav a:hover::after {
  transform: scaleX(1);
}
/* Underline slides from the reading-start side — reverse origin in RTL */
[dir="rtl"] .wosol-menu__link::after,
[dir="rtl"] .wosol-footer__col--nav a::after {
  transform-origin: right center;
}

/* ------- Menu open: stagger the nav items ------- */

.wosol-menu__nav ul li,
.wosol-menu__cta {
  opacity: 0;
  transform: translate3d(0, 10px, 0);
  transition:
    opacity var(--wosol-motion-med) var(--wosol-ease-out),
    transform var(--wosol-motion-med) var(--wosol-ease-out);
}
.wosol-menu[data-open="true"] .wosol-menu__nav ul li,
.wosol-menu[data-open="true"] .wosol-menu__cta {
  opacity: 1;
  transform: none;
}
.wosol-menu[data-open="true"] .wosol-menu__nav ul li:nth-child(1) { transition-delay: 0.20s; }
.wosol-menu[data-open="true"] .wosol-menu__nav ul li:nth-child(2) { transition-delay: 0.28s; }
.wosol-menu[data-open="true"] .wosol-menu__nav ul li:nth-child(3) { transition-delay: 0.36s; }
.wosol-menu[data-open="true"] .wosol-menu__nav ul li:nth-child(4) { transition-delay: 0.44s; }
.wosol-menu[data-open="true"] .wosol-menu__cta { transition-delay: 0.56s; }

/* ------- Contact form: focus feedback ------- */

.wosol-contact-form input,
.wosol-contact-form select,
.wosol-contact-form textarea {
  transition:
    border-color var(--wosol-motion-fast) ease,
    box-shadow var(--wosol-motion-fast) ease,
    background-color var(--wosol-motion-fast) ease;
}
.wosol-contact-form input:focus,
.wosol-contact-form select:focus,
.wosol-contact-form textarea:focus {
  outline: none;
  border-color: var(--wosol-navy-2);
  box-shadow: 0 0 0 4px rgba(18, 38, 62, 0.08);
}

/* ------- Menu Close X: quarter-turn on hover ------- */

.wosol-menu__close-icon {
  transition: transform var(--wosol-motion-med) var(--wosol-ease-out);
}
.wosol-menu__close:hover .wosol-menu__close-icon {
  transform: rotate(90deg);
}

/* ------- Language toggle transition ------- */

.wosol-menu__lang-btn {
  transition:
    background-color var(--wosol-motion-fast) ease,
    color var(--wosol-motion-fast) ease;
}

/* ------- Map card: subtle lift on hover (desktop only) ------- */

@media (hover: hover) {
  .wosol-map__card {
    transition: transform var(--wosol-motion-slow) var(--wosol-ease-out),
                box-shadow var(--wosol-motion-slow) var(--wosol-ease-out);
  }
  .wosol-map__card:hover {
    transform: translateY(-2px);
    box-shadow: 0 30px 60px -20px rgba(17, 36, 59, 0.35);
  }
}

/* ------- WOSOL App phone mockups ("Member Access"): the image-card hover -------
   Same gesture as the services / curated / offer cards — a 1.06 zoom with a
   touch more saturation, rising into a deep shadow. It replaces the 8px lift
   this section used to have.

   Two parts of that treatment are deliberately left out. The artwork is a
   transparent cut-out of two handsets that deliberately overruns the section
   (see the sizing notes in pages.css), not an opaque photo in a clipping
   frame: a rectangular scrim and light sweep would paint across empty
   background, and there is no frame to clip them to. The elevation is a
   drop-shadow rather than a box-shadow for the same reason — drop-shadow
   follows the alpha channel, so the shadow takes the shape of the phones
   instead of hanging a rectangle behind them.

   Both filter functions are declared at rest too: a filter list interpolates
   only against a matching list, so without the resting drop-shadow the
   elevation would pop in rather than rise. */

@media (hover: hover) {
  .wosol-app__art {
    filter: saturate(1) drop-shadow(0 1px 2px rgba(16, 26, 40, 0.05));
    transition:
      transform 1.1s var(--wosol-ease-out),
      filter var(--wosol-motion-med) ease;
  }
  .wosol-app__mockups:hover .wosol-app__art {
    transform: scale(1.06);
    /* drop-shadow takes no spread parameter, so the card shadow's -18px comes
       out of the blur instead to land on roughly the same weight. */
    filter: saturate(1.05) drop-shadow(0 26px 34px rgba(16, 26, 40, 0.42));
  }
}

/* ------- Our Philosophy image: 3D tilt toward the pointer -------
   animations.js writes --wosol-tilt-x / --wosol-tilt-y (in degrees) onto the
   figure as the pointer crosses it; the transform below reads them.

   The signs are what make the corner nearest the cursor come toward the
   viewer: a positive rotateX tips the top away (the direction the Star Wars
   crawl leans) and a positive rotateY swings the right edge away, so the
   vertical axis takes the pointer offset as-is and the horizontal one negated.

   The transform sits on the <img>, never the <figure> — the figure is a
   data-animate-stagger child and its transform belongs to the scroll reveal.
   Tilt is physical, not directional, so RTL needs no mirror. */

@media (hover: hover) and (pointer: fine) {
  .wosol-world__media {
    /* Long enough that 6 degrees reads as depth rather than as a skew. */
    perspective: 900px;
  }

  .wosol-world__media img {
    transform:
      rotateX(var(--wosol-tilt-x, 0deg))
      rotateY(var(--wosol-tilt-y, 0deg));
    /* Short: the pointer is sampled once per frame, so this only smooths the
       gap between samples. Any longer and the image visibly trails the cursor. */
    transition: transform 0.2s var(--wosol-ease-out);
    will-change: transform;
  }

  /* Pointer has left — unwind on the section's own slow curve instead of
     snapping flat. animations.js sets the class on the way out. */
  .wosol-world__media.is-releasing img {
    transition: transform var(--wosol-motion-slow) var(--wosol-ease-out);
  }
}

/* =============================================================
   SERVICES TILE GRID — reveal + filter motion
   Markup: .wosol-services-card-wrap > .wosol-services-card
   The wrapper owns reveal + filter motion; the card owns hover.
   Keeping them on separate elements means a lifted card never
   fights the reveal transform (and vice versa).
   The hover half of this now lives in the IMAGE CARD HOVER block
   below, shared with the curated and offer cards.
   ============================================================= */

/* ------- Reveal: each row cascades from the reading-start side ------- */

/* Delay is declared on the wrapper's revealed state only, so it can
   never leak into the card's hover timing. */
@media (min-width: 901px) {
  .wosol-services-card-wrap.is-visible:nth-child(3n + 2) { transition-delay: calc(var(--wosol-stagger-step) * 1); }
  .wosol-services-card-wrap.is-visible:nth-child(3n + 3) { transition-delay: calc(var(--wosol-stagger-step) * 2); }
}
@media (min-width: 641px) and (max-width: 900px) {
  .wosol-services-card-wrap.is-visible:nth-child(2n) { transition-delay: calc(var(--wosol-stagger-step) * 1); }
}

/* ============ IMAGE CARD HOVER — zoom, scrim, sweep line, gold rule ============
   One treatment, three sections, so the site's image cards read as a single
   component wherever they appear:
     - services tiles      .wosol-services-card       (services page)
     - curated carousel    .wosol-curated__card       ("Private Experiences")
     - offer tiles         .wosol-offer__card-link    ("The WOSOL World")
   Each has the same three parts — a clipping media frame, an <img>, and a
   title over a line of subtext — which is all this treatment needs.

   Two deliberate asymmetries:
   - The gold rule is services-only. It draws across a hairline under the meta
     block, and the other two sections have no such hairline; giving them one
     would change their resting design, not just their motion.
   - The offer tile is the only card that is a link, so it alone keys the
     treatment off :focus-visible as well. The global focus ring says "focused"
     but not "this is the tile that will open".
   Nothing here lifts the card or draws a corner cue: services and curated
   cards aren't clickable, and a hover that implies otherwise misleads. */

.wosol-services-card__media,
.wosol-curated__media,
.wosol-offer__media {
  box-shadow: 0 1px 2px rgba(16, 26, 40, 0.05);
  transition: box-shadow var(--wosol-motion-med) var(--wosol-ease-out);
}
.wosol-services-card__img,
.wosol-curated__img,
.wosol-offer__img {
  transition:
    transform 1.1s var(--wosol-ease-out),
    filter var(--wosol-motion-med) ease;
}

/* Scrim — grounds the corner cue against bright photography. */
.wosol-services-card__media::after,
.wosol-curated__media::after,
.wosol-offer__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(16, 26, 40, 0.42) 0%, rgba(16, 26, 40, 0) 55%);
  opacity: 0;
  transition: opacity var(--wosol-motion-med) var(--wosol-ease-out);
  pointer-events: none;
}

/* Light sweep — a single slow pass across the image. */
.wosol-services-card__media::before,
.wosol-curated__media::before,
.wosol-offer__media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(100deg, transparent 38%, rgba(255, 255, 255, 0.28) 50%, transparent 62%);
  opacity: 0;
  transform: translateX(-100%);
  pointer-events: none;
}

.wosol-services-card__title,
.wosol-services-card__suffix,
.wosol-curated__card-title,
.wosol-curated__card-caption,
.wosol-offer__card-title,
.wosol-offer__kicker {
  transition: color var(--wosol-motion-med) ease;
}

/* Gold rule draws in from the reading-start side. */
.wosol-services-card__meta::after {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--wosol-motion-med) var(--wosol-ease-out);
}
[dir="rtl"] .wosol-services-card__meta::after {
  transform-origin: right center;
}

.wosol-services-card:hover .wosol-services-card__media,
.wosol-curated__card:hover .wosol-curated__media,
.wosol-offer__card-link:hover .wosol-offer__media,
.wosol-offer__card-link:focus-visible .wosol-offer__media {
  box-shadow: 0 26px 44px -18px rgba(16, 26, 40, 0.42);
}
.wosol-services-card:hover .wosol-services-card__img,
.wosol-curated__card:hover .wosol-curated__img,
.wosol-offer__card-link:hover .wosol-offer__img,
.wosol-offer__card-link:focus-visible .wosol-offer__img {
  transform: scale(1.06);
  filter: saturate(1.05);
}
.wosol-services-card:hover .wosol-services-card__media::after,
.wosol-curated__card:hover .wosol-curated__media::after,
.wosol-offer__card-link:hover .wosol-offer__media::after,
.wosol-offer__card-link:focus-visible .wosol-offer__media::after {
  opacity: 1;
}
.wosol-services-card:hover .wosol-services-card__media::before,
.wosol-curated__card:hover .wosol-curated__media::before,
.wosol-offer__card-link:hover .wosol-offer__media::before,
.wosol-offer__card-link:focus-visible .wosol-offer__media::before {
  animation: wosol-card-sweep 1.1s var(--wosol-ease-out) 1;
}
[dir="rtl"] .wosol-services-card:hover .wosol-services-card__media::before,
[dir="rtl"] .wosol-curated__card:hover .wosol-curated__media::before,
[dir="rtl"] .wosol-offer__card-link:hover .wosol-offer__media::before,
[dir="rtl"] .wosol-offer__card-link:focus-visible .wosol-offer__media::before {
  animation-direction: reverse;
}
.wosol-services-card:hover .wosol-services-card__title,
.wosol-curated__card:hover .wosol-curated__card-title,
.wosol-offer__card-link:hover .wosol-offer__card-title,
.wosol-offer__card-link:focus-visible .wosol-offer__card-title {
  color: #12263e;
}
/* The offer tile's kicker is already gold at rest, so it holds still here —
   listed for symmetry rather than effect. */
.wosol-services-card:hover .wosol-services-card__suffix,
.wosol-curated__card:hover .wosol-curated__card-caption,
.wosol-offer__card-link:hover .wosol-offer__kicker,
.wosol-offer__card-link:focus-visible .wosol-offer__kicker {
  color: #9d958c;
}
.wosol-services-card:hover .wosol-services-card__meta::after {
  transform: scaleX(1);
}

@keyframes wosol-card-sweep {
  0%   { opacity: 0; transform: translateX(-100%); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: translateX(100%); }
}

/* ------- Filter: cards fade out, survivors cascade back in -------
   services-filter.js sets --enter-i to the card's position among the
   visible results so the incoming set staggers. */

.wosol-services-card-wrap.is-leaving {
  animation: wosol-card-leave 0.24s ease-in both;
  pointer-events: none;
}
.wosol-services-card-wrap.is-entering {
  animation: wosol-card-enter 0.5s var(--wosol-ease-out) both;
  animation-delay: calc(var(--enter-i, 0) * 45ms);
}

@keyframes wosol-card-enter {
  from { opacity: 0; transform: translate3d(0, 14px, 0) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes wosol-card-leave {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translate3d(0, 8px, 0) scale(0.97); }
}

/* ------- Reduced motion: honor user preference ------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
    scroll-behavior: auto !important;
  }

  /* Make sure content is visible even if JS reveal never fires. */
  [data-animate],
  [data-animate-stagger] > *,
  .wosol-menu__nav ul li,
  .wosol-menu__cta {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Hero video is decorative motion — drop it and let the poster still
     (already the section background, same frame) stand in its place. */
  .wosol-hero__video {
    display: none !important;
  }
}
