/* Cross-document view transitions.
 *
 * Loaded last on every page. Both documents in a navigation must carry this
 * file: the outgoing one opts into being captured, the incoming one opts into
 * rendering the transition and owns the animations.
 *
 * One idea. The page under the chrome is a single plane and it is the only
 * thing that moves; the fixed chrome is lifted out of that plane and holds
 * still. Anything whose box depends on the scroll position must NOT be named
 * here - see the note at the foot of this file. */

@view-transition {
  navigation: auto;
}

/* Both of these are position: fixed, so their border box is already
   viewport-relative and is captured against the snapshot containing block.
   Old rect and new rect are identical whatever the reader's scroll offset
   was, and that is the whole mechanism by which anything holds still across
   two documents. */

.site-header {
  view-transition-name: site-header;
}

.to-top {
  view-transition-name: to-top;
}

/* The ground plane. `root` is everything that was not named: the body
   ground, the main column, the footer. Because only the fixed chrome carries
   a name, what is left over is exactly the page.

   Both halves only ever scale up. A snapshot below scale 1 uncovers the edge
   of the snapshot containing block and shows a hairline of canvas through it.

   The default crossfade paints the pair in plus-lighter inside an isolated
   group. That is right for a pure opacity dissolve and wrong for anything
   carrying a transform: the overlap blooms. Both go back to normal. */

::view-transition-image-pair(root) {
  isolation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  mix-blend-mode: normal;
}

::view-transition-old(root) {
  animation: 220ms cubic-bezier(0.4, 0, 1, 1) both vt-ground-out;
}

::view-transition-new(root) {
  animation: 320ms cubic-bezier(0.22, 1, 0.36, 1) 40ms both vt-ground-in;
}

@keyframes vt-ground-out {
  to {
    opacity: 0;
    transform: scale(1.014);
  }
}

@keyframes vt-ground-in {
  from {
    opacity: 0;
    transform: scale(1.01);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* The header group is switched off rather than tuned. The two rects are
   already the same, so the default is close to a no-op - but a page with a
   scrollbar and a page without one differ by its width, and the default
   would squeeze the bar horizontally for no reason anybody asked for.

   The two images still dissolve, because the bar genuinely changes: dark on
   five pages, signal on the product page, light on the transactions page. It
   does not move, it changes colour. */

::view-transition-group(site-header) {
  animation: none;
}

::view-transition-image-pair(site-header) {
  isolation: auto;
}

::view-transition-old(site-header),
::view-transition-new(site-header) {
  mix-blend-mode: normal;
}

::view-transition-old(site-header) {
  animation: 260ms linear both vt-fade-out;
}

::view-transition-new(site-header) {
  animation: 260ms linear both vt-fade-in;
}

/* The back-to-top button is fixed too, so it has to be lifted out or the
   ground scale would drag it. It is authored with visibility and opacity
   rather than display, so it is captured on both sides and this name is
   never one-sided. */

::view-transition-group(to-top) {
  animation: none;
}

::view-transition-old(to-top) {
  animation: 180ms linear both vt-fade-out;
}

::view-transition-new(to-top) {
  animation: 180ms linear both vt-fade-in;
}

@keyframes vt-fade-out {
  to {
    opacity: 0;
  }
}

@keyframes vt-fade-in {
  from {
    opacity: 0;
  }
}

/* ::view-transition-new(site-header) is a live view of the real element, not
   a still. The shared script recomputes the header theme on its first frame
   in the new document, which lands inside the transition window; if that
   write changes the surface, the 420ms opacity transition on the header
   ground plays at the same time as the dissolve above and the bar visibly
   fades twice. Killing the element transition for the duration leaves the
   new bar already correct at frame zero. */

html:active-view-transition .site-header,
html:active-view-transition .site-header::before {
  transition: none;
}

/* Reduced motion. The site's existing floor zeroes transition-duration and
   says nothing about animation, so it does not reach a view transition. The
   switch is written twice on purpose: the at-rule is the real one and builds
   no pseudo tree at all, the pseudo rules are the floor for an engine that
   mis-cascades two @view-transition rules. */

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }

  ::view-transition-group(root),
  ::view-transition-group(site-header),
  ::view-transition-group(to-top),
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-old(site-header),
  ::view-transition-new(site-header),
  ::view-transition-old(to-top),
  ::view-transition-new(to-top) {
    animation: none !important;
  }
}

/* The footer is deliberately not named.
 *
 * It sits in normal flow at the bottom of the document, so its rect is a
 * function of scroll offset and document height and those differ on every
 * page. A name gives it a group that travels from wherever it was on the
 * outgoing page to wherever it is on the incoming one: click a footer link
 * on a long page and the footer flies thousands of pixels upward over the
 * transition. Switching that group's animation off is not better - the old
 * footer is then drawn at the new off-screen position and vanishes at frame
 * zero. There is no declarative way to hold a document-relative box still
 * across two documents at two scroll offsets. It stays inside the root
 * snapshot and crossfades, and because the footer markup is identical on six
 * of the seven live pages that crossfade is between identical pixels. */
