/* The loop between the three main pages.

   Each page's rebound footer already carries the next page's hero portrait, and
   its white button already names where it goes. This only connects what is
   there: the portrait travels from the footer into the hero, the rest of the
   page changes underneath it, and the pointer says so before you click.

   Home -> About -> Approach -> Home. */

:root {
  --loop-travel: 640ms;
  --loop-ease: cubic-bezier(0.25, 0.1, 0.25, 1);
  /* The reference's own hover curve, and the one its cursor moves on. */
  --loop-cursor-ease: cubic-bezier(0.165, 0.84, 0.44, 1);
}

/* ── The portrait that travels ───────────────────────────────────────────── */

/* Named by destination, so each page holds two portraits with two different
   names: the one it shows at the top, and the one it hands to the next page.

   The two boxes are already identical — 488 x 621, centred — so this is very
   nearly an identity: the picture does not resize or reshape, it moves. Home to
   About is literally the same file on both sides.

   The home's hero is deliberately not in here. It is a video carrying
   `mix-blend-mode: darken`, and a snapshot is composited on its own, away from
   the backdrop that blend needs — it would change appearance mid-flight. Its
   footer portrait leaves on its own instead, just below. */
[data-portrait="about"]    { view-transition-name: portrait-about; }
[data-portrait="approach"] { view-transition-name: portrait-approach; }
[data-portrait="home"]     { view-transition-name: portrait-home; }

:root.vt-loop::view-transition-group(portrait-about),
:root.vt-loop::view-transition-group(portrait-approach),
:root.vt-loop::view-transition-group(portrait-home) {
  animation-duration: var(--loop-travel);
  animation-timing-function: var(--loop-ease);
}

/* Approach -> Home has nothing to pair with, so its portrait is left to go on
   its own rather than blinking out: it drifts up the way the page above it is
   going and fades on the way. */
:root.vt-loop::view-transition-old(portrait-home) {
  animation: loop-portrait-leave var(--loop-travel) var(--loop-ease) both;
}

@keyframes loop-portrait-leave {
  to { opacity: 0; transform: translateY(-80px) scale(1.04); }
}

/* ── The page underneath ─────────────────────────────────────────────────── */

/* These are sibling pages, so nothing covers anything: one lifts away and the
   next rises into its place. The portrait and the five nav buttons are named,
   so they are lifted out of this and travel on their own — which is what makes
   the text look like it is being exchanged rather than the whole page sliding.

   The outgoing text sits at the bottom of the viewport and the incoming
   headline sits just under the portrait, so they arrive at roughly the same
   place: the swap reads as one line of thought replacing another. */
/* One leaves, THEN the other arrives. Running both across the same 640ms put
   the two texts on top of each other for most of the transition — a paragraph
   dissolving into a headline of another size, which is the mess Flora saw. An
   exchange needs a gap: the old is gone by 260ms, the new starts at 240 and
   settles at 620.

   Between the two there is a beat of nothing but the page's own grey and the
   portrait floating on it, which is what makes it read as one thing replacing
   another rather than two things sharing a screen.

   And a normal blend, not the browser's `plus-lighter`. That one exists to keep
   a cross-fade clean by adding the two together; with nothing overlapping there
   is nothing to add, and adding anyway is what washes the greys out. */
:root.vt-loop::view-transition-old(root),
:root.vt-loop::view-transition-new(root) { mix-blend-mode: normal; }

:root.vt-loop::view-transition-old(root) {
  animation: loop-leave 260ms var(--loop-ease) both;
}

:root.vt-loop::view-transition-new(root) {
  animation: loop-arrive 380ms var(--loop-ease) 240ms both;
}

@keyframes loop-leave {
  to { opacity: 0; transform: translateY(-48px); }
}

@keyframes loop-arrive {
  from { opacity: 0; transform: translateY(48px); }
}

/* ── The pointer ─────────────────────────────────────────────────────────── */

/* A ring that trails the pointer and opens when it enters the footer, on the
   model of nahelmoussi.com: 64px at half scale at rest, full size over the
   footer, everything on .4s cubic-bezier(.165, .84, .44, 1).

   What sits inside it is temporary — an arrow and the name of the page you
   would land on — and is meant to be replaced by Flora's own visual. The
   mechanism does not depend on it.

   It exists only where there is a pointer to draw: never on a touch screen,
   where there is nothing to follow, and never for anyone who asked for less
   motion. */

/* It exists over the footer and nowhere else. Being visible the moment the
   pointer moved anywhere on the page put a ring and a dot over the whole site,
   which is not what any of this is for: this marks one thing, so it appears
   over that thing and is gone everywhere else. */
.loop-cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 260ms ease, visibility 260ms;
}

.loop-cursor.is-over { opacity: 1; visibility: visible; }

.loop-cursor-ring,
.loop-cursor-core {
  position: fixed;
  top: 0;
  left: 0;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Placed by script every frame; nothing here may animate it or the two would
     fight over the same property. */
  will-change: transform;
}

/* The ring's size lives on an inner layer, because the outer one is carrying
   the position and a transform cannot be two things at once. */
.loop-cursor-ring span {
  width: 100%;
  height: 100%;
  border: 1px solid var(--text-secondary);
  border-radius: 50%;
  transform: scale(0.5);
  transition: transform 400ms var(--loop-cursor-ease),
              opacity 400ms var(--loop-cursor-ease);
}

.loop-cursor.is-over .loop-cursor-ring span { transform: scale(1); }

/* The dot marks the pointer exactly while the ring trails behind it. It gives
   way once the ring opens, so the two never sit on top of each other. */
.loop-cursor-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-secondary);
  transition: opacity 400ms var(--loop-cursor-ease);
}

.loop-cursor.is-over .loop-cursor-dot { opacity: 0; }

/* Temporary contents: an arrow pointing the way you are already going, and the
   name of the page it leads to. */
.loop-cursor-mark {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  opacity: 0;
  transform: scale(0.6);
  transition: transform 400ms var(--loop-cursor-ease),
              opacity 400ms var(--loop-cursor-ease);
}

.loop-cursor.is-over .loop-cursor-mark { opacity: 1; transform: scale(1); }

.loop-cursor-arrow {
  width: 9px;
  height: 9px;
  border-right: 1.5px solid var(--text-secondary);
  border-bottom: 1.5px solid var(--text-secondary);
  transform: rotate(45deg);
  margin-top: -3px;
}

.loop-cursor-next {
  position: absolute;
  top: 40px;
  white-space: nowrap;
  font-family: var(--font-body);
  font-size: 12px;
  line-height: 16px;
  color: var(--text-secondary);
}

/* The footer answers the pointer as a whole, since the whole of it is the link
   now. Its own buttons and links keep their own cursor: they lead elsewhere. */
.contact.is-loop { cursor: pointer; }
.contact.is-loop a { cursor: pointer; }

@media (hover: none), (prefers-reduced-motion: reduce) {
  .loop-cursor { display: none; }
}

/* ── The page yielding while you push ────────────────────────────────────── */

/* Scrolling past the bottom does not count in silence any more. `--loop-pull`
   runs from 0 to 1 as you push, set from loop.js each frame, and the footer
   answers by exactly that fraction.

   It answers by starting to leave: the same lift and fade `loop-leave` will
   finish. So the gesture and the departure are one continuous movement — you
   are not pushing against a hidden counter, you are pulling the next page in,
   and when it goes it carries on from where your hand left it.

   At rest the fraction is 0 and every one of these resolves to exactly what
   contact.css already says, so nothing here changes the footer as designed. */

.contact { --loop-pull: 0; }

.contact-content {
  transform: translateX(-50%) translateY(calc(var(--loop-pull) * -40px));
  opacity: calc(1 - var(--loop-pull) * 0.35);
}

.contact-portrait {
  transform: translateX(-50%) translateY(calc(var(--loop-pull) * -22px));
}

@media (prefers-reduced-motion: reduce) {
  .contact-content,
  .contact-portrait { transform: translateX(-50%); opacity: 1; }
}


/* ── The veil travels with the portrait ──────────────────────────────────── */

/* The black wedge. Both pages veil the bottom of this portrait — the footer
   with `.contact-blur`, the hero with `.page-hero-sticky::after` — and in both
   cases the veil is a separate element painted over it. Naming the portrait
   lifts it out of the page and draws it above everything, which means above its
   own veil: the dark polo neck it hides was suddenly at full strength, floating
   over the page as a black shape belonging to nothing.

   So for the length of the capture the portrait carries that fade itself, as
   part of its own mask. 50% to 90% is where both veils do their work — the
   hero's reaches full page grey at the portrait's foot, the footer's about
   seven tenths — so the picture ends where it already appeared to end.

   Only during the capture. `vt-capture` is added just before each snapshot is
   taken and dropped when the transition is over, so the footer and the hero at
   rest are exactly as they were designed, down to the pixel. */
:root.vt-capture .contact-portrait,
:root.vt-capture .page-portrait {
  -webkit-mask-image:
    linear-gradient(to bottom, #000 0, #000 50%, rgba(0, 0, 0, 0) 90%),
    linear-gradient(to right, rgba(0, 0, 0, 0) 0, #000 9%, #000 91%, rgba(0, 0, 0, 0) 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to bottom, #000 0, #000 50%, rgba(0, 0, 0, 0) 90%),
    linear-gradient(to right, rgba(0, 0, 0, 0) 0, #000 9%, #000 91%, rgba(0, 0, 0, 0) 100%);
  mask-composite: intersect;
}
