/* Motion tokens and CSS-only patterns. Pair with motion.js for scroll scenes. */
:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1);
  --ease-in-quad: cubic-bezier(0.11, 0, 0.5, 0);
  --ease-spring: cubic-bezier(0.34, 1.36, 0.64, 1);

  /* duration follows mass: small things move fast, big surfaces move slow */
  --dur-press: 100ms;
  --dur-hover: 220ms;
  --dur-ui: 320ms;
  --dur-reveal: 900ms;
  --dur-scene: 1400ms;
}

@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-spring: linear(0, 0.054, 0.189, 0.367, 0.557, 0.737, 0.89, 1.009, 1.092, 1.142, 1.162, 1.159, 1.139, 1.109, 1.075, 1.042, 1.014, 0.993, 0.98, 0.974, 0.974, 0.978, 0.985, 0.992, 0.998, 1.002, 1.004, 1.004, 1.003, 1);
  }
}

/* Underline that wipes in from the left and leaves to the right. */
.u-link {
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) no-repeat right bottom / 0 1px;
  transition: background-size var(--dur-hover) var(--ease-out-quart);
}
.u-link:hover, .u-link:focus-visible {
  background-position: left bottom;
  background-size: 100% 1px;
}

/* Button fill that slides up from the bottom edge. */
.u-fill {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.u-fill::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--fill, currentColor);
  transform: translateY(101%);
  transition: transform var(--dur-ui) var(--ease-out-expo);
}
.u-fill:hover::before { transform: translateY(0); }

/* Image that moves inside its frame on hover instead of scaling the card. */
.u-frame { overflow: hidden; }
.u-frame > img, .u-frame > video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.06);
  transition: transform var(--dur-scene) var(--ease-out-expo);
}
.u-frame:hover > img, .u-frame:hover > video { transform: scale(1); }

/* Infinite marquee: duplicate the track content once inside .u-marquee__track. */
.u-marquee { overflow: hidden; display: flex; }
.u-marquee__track {
  display: flex;
  flex: none;
  gap: var(--marquee-gap, 3rem);
  padding-right: var(--marquee-gap, 3rem);
  animation: u-marquee var(--marquee-dur, 40s) linear infinite;
}
.u-marquee:hover .u-marquee__track { animation-play-state: paused; }
@keyframes u-marquee { to { transform: translateX(-50%); } }

/* Scroll-driven reveal without JS; motion.js upgrades it where GSAP is present. */
@supports (animation-timeline: view()) {
  .u-rise {
    animation: u-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 60%;
  }
  .u-drift {
    animation: u-drift linear both;
    animation-timeline: view();
    animation-range: cover;
  }
}
@keyframes u-rise { from { opacity: 0; transform: translateY(2.5rem); } }
@keyframes u-drift { from { transform: translateY(calc(var(--drift, 8) * 1%)); } to { transform: translateY(calc(var(--drift, 8) * -1%)); } }

/* Endless line built by data-marquee: speed and direction follow the scroll. */
.u-line { overflow: hidden; white-space: nowrap; }
.u-line__track { display: flex; width: max-content; will-change: transform; }
.u-line__unit { display: flex; flex: none; align-items: baseline; gap: var(--line-gap, 0.6em); padding-right: var(--line-gap, 0.6em); }

/* data-stack: cards stick under the header and pile up with a visible step. */
[data-stack] > * {
  position: sticky;
  top: calc(var(--stack-top, 14vh) + var(--i, 0) * var(--stack-step, 1.25rem));
}
[data-stack] > * > * { will-change: transform; }

/* data-zoom-window: one viewport tall, the .window layer is what opens. */
[data-zoom-window] { position: relative; height: 100svh; overflow: clip; }
[data-zoom-window] .window { position: absolute; inset: 0; will-change: clip-path; }
[data-zoom-window] .window > :is(img, video, canvas) { width: 100%; height: 100%; object-fit: cover; }

.u-progress { position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 60; background: var(--progress, currentColor); transform: scaleX(0); transform-origin: 0 50%; }

/* Follower: inverts whatever is under it, turns into a labelled disc over [data-cursor]. */
.u-cursor {
  position: fixed; left: 0; top: 0; z-index: 90;
  width: var(--cursor-size, 0.85rem); aspect-ratio: 1; margin: calc(var(--cursor-size, 0.85rem) / -2) 0 0 calc(var(--cursor-size, 0.85rem) / -2);
  display: grid; place-items: center;
  border-radius: 50%;
  background: #fff; mix-blend-mode: difference;
  pointer-events: none;
  opacity: 0;
  transition: width var(--dur-ui) var(--ease-out-expo), margin var(--dur-ui) var(--ease-out-expo), opacity var(--dur-hover) linear, background-color var(--dur-hover) linear;
}
.u-cursor.is-on { opacity: 1; }
.u-cursor.is-link { --cursor-size: 2.6rem; }
.u-cursor.is-down { --cursor-size: 0.5rem; }
.u-cursor.is-label { --cursor-size: 6.2rem; background: var(--cursor-fill, #fff); mix-blend-mode: normal; }
.u-cursor__label {
  font: 600 0.72rem/1.1 var(--cursor-font, var(--font-text, sans-serif)); letter-spacing: 0.06em; text-transform: uppercase; text-align: center;
  color: var(--cursor-ink, #000);
  opacity: 0; transform: scale(0.6);
  transition: opacity var(--dur-hover) linear, transform var(--dur-ui) var(--ease-out-expo);
}
.u-cursor.is-label .u-cursor__label { opacity: 1; transform: none; }
.has-cursor [data-cursor], .has-cursor [data-cursor] * { cursor: none; }

/* data-hover-list: rows dim around the hovered one, a picture follows the cursor. */
@media (hover: hover) and (pointer: fine) {
  [data-hover-list]:hover > * { transition: opacity var(--dur-ui) linear; }
  [data-hover-list]:hover > :not(:hover) { opacity: 0.32; }
}
.u-preview {
  position: fixed; left: 0; top: 0; z-index: 40;
  width: var(--preview-w, 26vw); aspect-ratio: var(--preview-ratio, 4 / 5); margin: 0;
  overflow: hidden;
  pointer-events: none;
  visibility: hidden;
}
.u-preview.is-on { visibility: visible; }
.u-preview > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; clip-path: inset(100% 0 0 0); transition: clip-path 0s 0.5s; }
.u-preview > img.is-active { clip-path: inset(0 0 0 0); z-index: 1; transition: clip-path 0.5s var(--ease-out-expo); }
.u-preview.is-gl > img { opacity: 0; }

/* data-trail: pictures dropped along the cursor path. */
:where([data-trail]) { position: relative; overflow: clip; }
.u-trail__img {
  position: absolute; left: 0; top: 0; z-index: 1;
  width: var(--trail-w, clamp(9rem, 16vw, 15rem)); aspect-ratio: var(--trail-ratio, 4 / 5); object-fit: cover;
  margin: 0; pointer-events: none; will-change: transform, opacity;
}

/* data-spotlight: the .u-spot__top layer shows only inside a circle around the cursor. */
:where([data-spotlight]) { position: relative; --spot: 0; --mx: 50%; --my: 50%; }
.u-spot__top {
  position: absolute; inset: 0;
  -webkit-mask-image: radial-gradient(circle calc(var(--spot-r, 13rem) * var(--spot)) at var(--mx) var(--my), #000 62%, transparent 100%);
  mask-image: radial-gradient(circle calc(var(--spot-r, 13rem) * var(--spot)) at var(--mx) var(--my), #000 62%, transparent 100%);
}

[data-tilt] { transform: perspective(60rem) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg)); transform-style: preserve-3d; will-change: transform; }

/* WebGL layers from gl.js. The element keeps its CSS background and <img> as the fallback. */
:where([data-gl-bg]) { position: relative; isolation: isolate; }
[data-gl-bg] > canvas.u-gl { position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%; }
:where([data-gl]) { position: relative; overflow: hidden; }
[data-gl] > canvas.u-gl { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
[data-gl].is-gl > img { opacity: 0; }

/* Preloader: shown once per session, head.js decides before first paint. */
.u-preloader { display: none; }
.is-preloading .u-preloader {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: var(--preloader-bg, #000); color: var(--preloader-ink, #fff);
}
.is-preloading body { overflow: hidden; }

/* Curtain for browsers without cross-document view transitions. */
.u-curtain { position: fixed; inset: 0; z-index: 95; background: var(--curtain, #000); transform: translateY(101%); pointer-events: none; }
.is-curtain .u-curtain { transform: none; }

/* Cross-document page transitions: the old page sinks back, the new one is uncovered from below.
   Going back in history plays it the other way. head.js names the shared element. */
@view-transition { navigation: auto; }
::view-transition-group(*) { animation-duration: var(--vt-dur, 900ms); animation-timing-function: var(--ease-in-out-quart); }
::view-transition-old(root) { animation: var(--vt-dur, 900ms) var(--ease-in-out-quart) both u-vt-out; }
::view-transition-new(root) { animation: var(--vt-dur, 900ms) var(--ease-in-out-quart) both u-vt-in; }
html:active-view-transition-type(back)::view-transition-old(root) { animation-name: u-vt-out-back; }
html:active-view-transition-type(back)::view-transition-new(root) { animation-name: u-vt-in-back; }
@keyframes u-vt-out { to { transform: translateY(-9vh) scale(0.95); filter: brightness(0.35); } }
@keyframes u-vt-in { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes u-vt-out-back { to { transform: translateY(9vh) scale(0.95); filter: brightness(0.35); } }
@keyframes u-vt-in-back { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
/* A shared picture keeps its crop while the box morphs. */
::view-transition-old(shot), ::view-transition-new(shot) { width: 100%; height: 100%; object-fit: cover; }
::view-transition-group(shot) { z-index: 2; }

/* Elements waiting for motion.js stay visible if the script never runs. */
.js-motion [data-reveal]:not(.is-in), .js-motion [data-scramble]:not(.is-in) { visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .js-motion [data-reveal]:not(.is-in), .js-motion [data-scramble]:not(.is-in) { visibility: visible; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .u-cursor, .u-preview { display: none; }
  [data-stack] > * { position: static; }
}
@media (hover: none), (pointer: coarse) {
  .u-cursor, .u-preview { display: none; }
}
