/* Links Transfer and Tour Limited — interactive visual effects.
   Companion stylesheet to main.css. Reuses the brand tokens defined in
   main.css (:root). Every effect animates only transform / opacity.

   Safety model:
   - Reveal/split effects hide content ONLY when the <html> element carries the
     `js-fx` class (added by effects.js after it runs). With JS off, or if the
     script throws before it can add the class, content stays fully visible.
   - Continuous pointer effects (cursor trail, magnetic, tilt, underbar) are
     gated behind @media (hover: hover) and pointer: fine, so they never engage
     on touch devices. effects.js mirrors this in JS.
   - Everything collapses to the visible/static state under
     prefers-reduced-motion: reduce. */

/* ============================================================
   Effect-specific tokens (fall back if main.css is ever missing)
   ============================================================ */
:root {
  --fx-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --fx-reveal-duration: 0.7s;
  --fx-reveal-distance: 28px;
  --fx-underbar-color: var(--brand-magenta, #E00060);
  --fx-cursor-color: var(--brand-magenta, #E00060);
  --fx-cursor-ring: rgba(224, 0, 96, 0.35);
}

/* ============================================================
   1. Header menu underbar
   The underbar element is injected by JS into .main-nav .primary-menu.
   Only shown on real pointer devices and on the desktop (horizontal) nav.
   ============================================================ */
.fx-underbar {
  position: absolute;
  bottom: -4px;
  left: 0;
  height: 3px;
  width: 0;
  border-radius: 3px;
  background: var(--fx-underbar-color);
  opacity: 0;
  transform: translateX(0);
  /* Animate width + transform + opacity only. */
  transition:
    transform 0.32s var(--fx-ease),
    width 0.32s var(--fx-ease),
    opacity 0.25s ease;
  pointer-events: none;
  will-change: transform, width, opacity;
}
@media (hover: hover) and (pointer: fine) {
  /* Anchor the underbar to the horizontal menu. */
  .js-fx .main-nav .primary-menu {
    position: relative;
  }
  .js-fx .fx-underbar.is-active {
    opacity: 1;
  }
}
/* Never show the sliding underbar on the mobile stacked menu. */
@media (max-width: 760px) {
  .fx-underbar { display: none !important; }
}

/* ============================================================
   2. Interactive buttons — magnetic pull + cursor glow
   JS sets --fx-mx / --fx-my (translate offset, px) and
   --fx-gx / --fx-gy (glow position, %). CSS consumes them.
   ============================================================ */
.fx-magnetic {
  position: relative;
  /* JS-driven offset; 0,0 at rest. transform only. */
  transform: translate(var(--fx-mx, 0px), var(--fx-my, 0px));
  transition: transform 0.2s var(--fx-ease);
  will-change: transform;
}
/* Soft radial glow that tracks the cursor. Sits above the button bg,
   below the label. Uses ::after so it needs no extra markup. */
.fx-glow { position: relative; overflow: hidden; }
.fx-glow > * { position: relative; z-index: 1; }
.fx-glow::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 180px; height: 180px;
  margin: -90px 0 0 -90px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.55), transparent 65%);
  opacity: 0;
  /* Position via transform (compositor-friendly), not left/top. */
  transform: translate(var(--fx-gx, 0px), var(--fx-gy, 0px));
  transition: opacity 0.25s ease;
  pointer-events: none;
  z-index: 0;
}
@media (hover: hover) and (pointer: fine) {
  .fx-glow:hover::after { opacity: 1; }
}
/* On touch / no-hover, neutralise the magnetic transform entirely. */
@media (hover: none), (pointer: coarse) {
  .fx-magnetic { transform: none !important; }
}

/* ============================================================
   3. Scroll reveal (fade + slide up)
   ============================================================ */
.js-fx .fx-reveal {
  opacity: 0;
  transform: translateY(var(--fx-reveal-distance));
  transition:
    opacity var(--fx-reveal-duration) var(--fx-ease),
    transform var(--fx-reveal-duration) var(--fx-ease);
  will-change: transform, opacity;
}
.js-fx .fx-reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
/* Directional variants (all transform-only). */
.js-fx .fx-reveal.fx-reveal--left  { transform: translateX(calc(var(--fx-reveal-distance) * -1.4)); }
.js-fx .fx-reveal.fx-reveal--right { transform: translateX(calc(var(--fx-reveal-distance) *  1.4)); }
.js-fx .fx-reveal.fx-reveal--left.is-visible,
.js-fx .fx-reveal.fx-reveal--right.is-visible { transform: translateX(0); }

/* ============================================================
   4. Custom cursor trail (dot + trailing ring)
   Elements injected by JS. Fixed, above everything, non-interactive.
   ============================================================ */
.fx-cursor-dot,
.fx-cursor-ring {
  position: fixed;
  top: 0; left: 0;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  /* JS positions via translate3d; opacity toggled on enter/leave. */
  opacity: 0;
  transition: opacity 0.25s ease;
  will-change: transform, opacity;
}
.fx-cursor-dot {
  width: 8px; height: 8px;
  margin: -4px 0 0 -4px;
  background: var(--fx-cursor-color);
}
.fx-cursor-ring {
  width: 38px; height: 38px;
  margin: -19px 0 0 -19px;
  border: 2px solid var(--fx-cursor-ring);
}
.fx-cursor-ring.is-hovering {
  /* Grow the ring over interactive targets. Combined with the JS translate3d
     via a CSS var so we don't clobber positioning. */
  width: 58px; height: 58px;
  margin: -29px 0 0 -29px;
  border-color: var(--fx-cursor-color);
}
.fx-cursor-dot.is-visible,
.fx-cursor-ring.is-visible { opacity: 1; }
/* Only real pointers get a custom cursor. */
@media (hover: none), (pointer: coarse) {
  .fx-cursor-dot, .fx-cursor-ring { display: none !important; }
}

/* ============================================================
   5. 3D card tilt
   JS sets --fx-rx / --fx-ry (deg). Perspective on a wrapper isn't needed
   because we apply perspective() inside the transform itself.
   ============================================================ */
@media (hover: hover) and (pointer: fine) {
  .js-fx .fx-tilt {
    transform:
      perspective(900px)
      rotateX(var(--fx-rx, 0deg))
      rotateY(var(--fx-ry, 0deg))
      translateZ(0);
    transition: transform 0.25s var(--fx-ease);
    transform-style: preserve-3d;
    will-change: transform;
  }
  /* While actively tracking, JS removes the transition for 1:1 response. */
  .js-fx .fx-tilt.is-tilting { transition: none; }
  /* Optional inner layer that lifts toward the viewer. */
  .js-fx .fx-tilt .fx-tilt__layer {
    transform: translateZ(40px);
    transition: transform 0.25s var(--fx-ease);
  }
}

/* ============================================================
   6. Staggered text reveal (word / char split)
   JS wraps pieces in .fx-split__word / .fx-split__char and sets
   --fx-i (index) for the stagger delay.
   ============================================================ */
.js-fx .fx-split { }
.js-fx .fx-split .fx-split__word,
.js-fx .fx-split .fx-split__char {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.6em);
  transition:
    opacity 0.5s var(--fx-ease),
    transform 0.5s var(--fx-ease);
  transition-delay: calc(var(--fx-i, 0) * 45ms);
  will-change: transform, opacity;
}
/* Preserve spaces between split words. */
.js-fx .fx-split .fx-split__space { display: inline-block; white-space: pre; }
.js-fx .fx-split.is-visible .fx-split__word,
.js-fx .fx-split.is-visible .fx-split__char {
  opacity: 1;
  transform: translateY(0);
}

/* ============================================================
   7. Image parallax
   JS sets --fx-py (translateY px) on scroll via rAF. The element should be
   slightly oversized by the theme/markup so the shift never reveals edges.
   ============================================================ */
.js-fx .fx-parallax {
  transform: translate3d(0, var(--fx-py, 0px), 0);
  will-change: transform;
}
/* Convenience: an oversized inner image that won't gap when it shifts. */
.fx-parallax--cover {
  position: absolute;
  inset: -12% 0;
  width: 100%;
  height: 124%;
  object-fit: cover;
}

/* ============================================================
   Global reduced-motion guard
   main.css already kills animations/transitions; here we also force every
   effect back to its resting, fully-visible state so nothing stays hidden.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .js-fx .fx-reveal,
  .js-fx .fx-reveal--left,
  .js-fx .fx-reveal--right,
  .js-fx .fx-split .fx-split__word,
  .js-fx .fx-split .fx-split__char {
    opacity: 1 !important;
    transform: none !important;
  }
  .fx-magnetic,
  .js-fx .fx-tilt,
  .js-fx .fx-parallax {
    transform: none !important;
  }
  .fx-cursor-dot, .fx-cursor-ring, .fx-underbar { display: none !important; }
}
