/* ==========================================================================
   Oxford Optical — reveal motion
   A vanilla-CSS/JS reproduction of the augen.pro reveal system.

   Their numbers, reproduced exactly:
     words  blur(10px) → blur(0), opacity 0 → 1, 1300ms, stagger 100ms
     lines  translateY(40px) → 0,  opacity 0 → 1, 1000ms, stagger  80ms
     focus  blur(10px) → blur(0), opacity 0 → 1, 1000ms  (their drop icon)
     ease   GSAP's default power1.out ≈ cubic-bezier(0.5, 1, 0.89, 1)

   Two additions of our own, because a lens brand can justify them:
     lens   blur driven by scroll position — text pulls into focus as it
            reaches the centre of the viewport
     flick  a blur flicker on hover (their ArrowLink detail, 0 → 1px → 0)

   Every value below is a custom property, so timing can be re-tuned without
   touching the markup.
   ========================================================================== */

:root {
  --rv-blur:            10px;
  --rv-rise:            40px;

  --rv-dur-words:       1300ms;
  --rv-dur-lines:       1000ms;
  --rv-dur-focus:       1000ms;

  --rv-stagger-words:   100ms;
  --rv-stagger-lines:    80ms;

  /* GSAP power1.out (easeOutQuad) */
  --rv-ease: cubic-bezier(0.5, 1, 0.89, 1);

  /* lens mode */
  --rv-lens-blur:       14px;

  /* photo mode — imagery carries far more detail than type, so it needs a
     bigger radius before the blur reads as blur at all */
  --rv-photo-blur:      24px;
  --rv-dur-photo:       1400ms;
  --rv-photo-zoom:      1.06;
}

/* --------------------------------------------------------------------------
   Word shells

   Written by JS. Inline elements (<em>, <strong>) survive the split — the
   walker wraps words inside text nodes and leaves the elements standing.
   -------------------------------------------------------------------------- */

.rv-word {
  display: inline-block;
}

/* Deliberately no `will-change` on words. A long paragraph is hundreds of
   spans, and hinting every one promotes hundreds of layers before anything
   has even scrolled into view. The transition alone is enough — the browser
   promotes on demand. `will-change` is reserved below for the handful of
   whole-element effects. */

/* Delay = group offset + index × stagger. For lines mode the index is the
   *line* number, so every word on a line moves together. */

/* --------------------------------------------------------------------------
   words — blur → clear, staggered per word
   -------------------------------------------------------------------------- */

[data-rv="words"] .rv-word {
  opacity: 0;
  filter: blur(var(--rv-blur));
  transition:
    opacity var(--rv-dur-words) var(--rv-ease) var(--rv-delay, 0ms),
    filter  var(--rv-dur-words) var(--rv-ease) var(--rv-delay, 0ms);
}

[data-rv="words"].rv-in .rv-word {
  opacity: var(--rv-opacity, 1);
  filter: blur(0px);
}

/* --------------------------------------------------------------------------
   lines — rise + fade, staggered per line
   -------------------------------------------------------------------------- */

[data-rv="lines"] .rv-word {
  opacity: 0;
  transform: translateY(var(--rv-rise));
  transition:
    opacity   var(--rv-dur-lines) var(--rv-ease) var(--rv-delay, 0ms),
    transform var(--rv-dur-lines) var(--rv-ease) var(--rv-delay, 0ms);
}

[data-rv="lines"].rv-in .rv-word {
  opacity: var(--rv-opacity, 1);
  transform: none;
}

/* --------------------------------------------------------------------------
   focus — the whole element pulls into focus as one piece.
   No split, so it works on icons, images and the brand mark too.
   -------------------------------------------------------------------------- */

[data-rv="focus"].rv-armed {
  opacity: 0;
  filter: blur(var(--rv-blur));
  transition:
    opacity var(--rv-dur-focus) var(--rv-ease) var(--rv-delay, 0ms),
    filter  var(--rv-dur-focus) var(--rv-ease) var(--rv-delay, 0ms);
  will-change: opacity, filter;
}

[data-rv="focus"].rv-armed.rv-in {
  opacity: var(--rv-opacity, 1);
  filter: blur(0px);
}

/* --------------------------------------------------------------------------
   photo — focus pull for imagery.

   The scale is not decoration. `filter: blur()` samples beyond the element's
   own box, and outside an <img> there is nothing, so a blurred image fades
   towards transparent at all four edges — inside a rounded panel that reads
   as a soft grey vignette. Scaling up by 6% while blurred pushes those bled
   edges outside the panel's overflow, so the crop stays crisp throughout.
   -------------------------------------------------------------------------- */

[data-rv="photo"].rv-armed {
  opacity: 0;
  filter: blur(var(--rv-photo-blur));
  transform: scale(var(--rv-photo-zoom));
  transition:
    opacity   var(--rv-dur-photo) var(--rv-ease) var(--rv-delay, 0ms),
    filter    var(--rv-dur-photo) var(--rv-ease) var(--rv-delay, 0ms),
    transform var(--rv-dur-photo) var(--rv-ease) var(--rv-delay, 0ms);
  will-change: opacity, filter, transform;
}

[data-rv="photo"].rv-armed.rv-in {
  opacity: var(--rv-opacity, 1);
  filter: blur(0px);
  transform: none;
}

/* Any element wrapping a photo reveal must clip, or the bled edges show. */
.rv-frame {
  overflow: hidden;
  border-radius: var(--r-lg, 16px);
  /* Own stacking/paint context, so the child's filter cannot escape. */
  isolation: isolate;
}

.rv-frame img { display: block; width: 100%; height: auto; }

/* Add .rv-ratio to hold a panel to a fixed ratio, so a grid of photographs
   with mismatched source dimensions still lines up. Override per panel with
   --rv-ratio. */
.rv-frame.rv-ratio { aspect-ratio: var(--rv-ratio, 16 / 10); }
.rv-frame.rv-ratio img { height: 100%; object-fit: cover; }

/* --------------------------------------------------------------------------
   lens — blur scrubbed by scroll position.
   --rv-p is written by JS: 0 at the edges of the viewport, 1 at the centre.
   -------------------------------------------------------------------------- */

[data-rv="lens"].rv-armed {
  filter: blur(calc(var(--rv-lens-blur) * (1 - var(--rv-p, 0))));
  opacity: calc(0.25 + 0.75 * var(--rv-p, 0));
  will-change: filter, opacity;
}

/* Add .rv-zoom when scrubbing an image, for the same edge-bleed reason. */
[data-rv="lens"].rv-zoom.rv-armed {
  opacity: 1;
  transform: scale(calc(1 + (var(--rv-photo-zoom) - 1) * (1 - var(--rv-p, 0))));
}

/* --------------------------------------------------------------------------
   Instant reset, for replaying a reveal.

   Transitions must be off while the element is put back to its hidden state,
   otherwise the reset itself animates: the reverse tween starts, no time
   passes, and re-adding .rv-in transitions from "still visible" to "visible",
   which looks like nothing happening at all.
   -------------------------------------------------------------------------- */

.rv-reset,
.rv-reset .rv-word {
  transition: none !important;
}

/* --------------------------------------------------------------------------
   Drop the filter once the transition has finished.

   A live filter keeps the element on its own rasterised layer, which leaves
   text very slightly soft and costs memory on long pages. This is the same
   thing augen.pro does with `filter: none !important`.

   These need to out-specify the .rv-in rules above (0,3,0), not just come
   later — hence the [data-rv] prefix rather than a bare .rv-done.
   -------------------------------------------------------------------------- */

[data-rv].rv-done,
[data-rv].rv-armed.rv-done,
[data-rv].rv-done .rv-word {
  filter: none;
  will-change: auto;
}

/* --------------------------------------------------------------------------
   Hover flicker — a lens hunting for focus. Their ArrowLink detail.
   -------------------------------------------------------------------------- */

.rv-flick {
  transition: filter 400ms var(--rv-ease);
}

.rv-flick:hover {
  filter: blur(1px);
}

/* --------------------------------------------------------------------------
   Reduced motion — no split runs at all, so the DOM is never touched and
   text is never hidden.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  [data-rv] .rv-word,
  [data-rv].rv-armed {
    opacity: 1 !important;
    filter: none !important;
    transform: none !important;
    transition: none !important;
  }

  [data-rv="lens"].rv-armed { opacity: 1 !important; }
  .rv-flick { transition: none; }
  .rv-flick:hover { filter: none; }
}
