/* WeDevelop — Routed Mask page transition.

   Linked from the twelve major pages only: Home, the Services overview and
   its four detail pages, the Work hub and its five case studies. It is NOT
   linked from /website-review/, /website-review/thank-you/, /privacy.html or
   /terms.html, and that omission is deliberate — see the eligibility policy
   in assets/js/page-transition.js.

   A cross-document view transition requires BOTH documents to opt in, so
   keeping the @view-transition rule in this file — rather than in site.css,
   which every page loads — is what stops the rest of the site inheriting a
   transition it has not been approved for. Do not move this rule into
   site.css. Linking this file from a further page widens the scope, and the
   governing amendment is Visual Identity section 43.3.

   Everything else the transition needs is built by assets/js/page-transition.js
   at run time, so no page carries transition markup.

   Companion: a short inline snippet in the <head> of each of those documents sets
   [data-pt-arrive] before first paint when the visitor arrived through a
   transition. That is the one thing that cannot wait for a deferred script:
   the destination has to be covered on its very first frame or the takeover
   visibly breaks at the document boundary. */

/* --------------------------------------------------------------------------
   1. Cross-document handoff
   -------------------------------------------------------------------------- */

/* Both sides of the leave animation end and begin on a full Midnight cover,
   so this transition is crossfading two identical screens. That is the point:
   it is invisible, and it removes any blank frame between the two documents
   without contributing choreography of its own.

   page-transition.js calls skipTransition() on any navigation it did not
   itself start — back/forward traversals included — so this never fires on
   its own. */
@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 60ms;
  animation-timing-function: linear;
}

/* --------------------------------------------------------------------------
   2. Arrival pre-paint
   -------------------------------------------------------------------------- */

/* Painted by CSS alone, from an attribute set in <head>, so the destination
   is already covered on its first frame. page-transition.js removes the
   attribute the moment its own overlay is up in the identical covered state,
   which makes the swap pixel-equivalent rather than a visible change.

   The animation is a failsafe, not decoration. If page-transition.js is
   blocked, fails to parse, or simply has not run yet because the destination
   is slow to parse, this clears the cover on its own and the visitor is left
   with a normal, readable page. The inline snippet removes the attribute on
   the same deadline; this covers the case where script is frozen rather than
   absent.

   1.1s is chosen against measurement, not by feel. A warm destination reaches
   domInteractive — and therefore runs the release — in about 235ms, a cold one
   in about 790ms. The deadline sits clear of both, so a real visitor sees the
   route release; only a genuinely stuck page hits this path. When it does hit,
   it fades rather than cutting, because an instant jump from full Midnight to
   the page reads as a fault. page-transition.js will not re-cover a page whose
   attribute has already gone, so a late script cannot undo this. */
html[data-pt-arrive]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9000;
  background: var(--color-midnight);
  pointer-events: none;
  animation: pt-failsafe 200ms linear 1100ms forwards;
}

@keyframes pt-failsafe {
  to { opacity: 0; visibility: hidden; }
}

/* --------------------------------------------------------------------------
   3. The overlay
   -------------------------------------------------------------------------- */

/* pointer-events stays none for the overlay's whole life. A decorative layer
   that can swallow clicks is the failure mode that leaves a site feeling
   broken, and navigation is imminent in every state this thing exists in. */
/* translateZ(0) and will-change put the overlay on its own compositor layer.
   Thickening a stroke is a repaint, not a composite, and without the layer that
   repaint drags the page underneath into it. Measured on a 390x844 viewport at
   6x CPU throttle, promoting the layer moved the transition's 95th-percentile
   frame from 50ms to 33ms. The layer is short-lived — the overlay is built per
   transition and removed straight after — so it costs nothing while idle. */
.pt-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  pointer-events: none;
  contain: layout paint;
  transform: translateZ(0);
  will-change: opacity;
}

.pt-overlay__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Route vocabulary lifted from the live Connected Journey network so the
   transition is drawn in the same hand as the pages it runs between. Values
   track .cj-net__* in site.css — structural 1.25 at low alpha, principal 2 at
   0.44 on dark, junctions teal at 0.42, outcome Signal Lime. */
.pt-structural,
.pt-route,
.pt-mask {
  fill: none;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

.pt-structural {
  stroke: var(--color-digital-teal);
  stroke-width: 1.25;
  opacity: 0.26;
}

.pt-route {
  stroke: var(--color-digital-teal);
  stroke-width: 2;
  opacity: 0.44;
}

/* The takeover itself. One Midnight stroke per route, sharing that route's
   exact path, thickened until the lanes meet. The cover is therefore the
   route geometry at scale rather than a rectangle faded over the top. */
.pt-mask {
  stroke: var(--color-midnight);
  stroke-width: 0;
}

.pt-node {
  fill: var(--color-digital-teal);
  stroke: none;
  opacity: 0.42;
}

.pt-node--entry {
  fill: var(--color-white);
  stroke: var(--color-digital-teal);
  stroke-width: 1.5;
  opacity: 0.92;
}

/* The single Signal Lime marker. A div rather than an SVG circle so it can
   ride the route on offset-path, which keeps it on a compositor transform
   instead of per-frame attribute writes. Its path is the principal route's
   own geometry, in the same pixel space. */
/* No negative margin here, unlike .pt-terminal below. offset-path places the
   element by its offset-anchor, which already defaults to the box centre, so
   a centring margin would shift the marker half its own width off the route
   it is riding. The fallback path, which has no offset-path and positions by
   transform instead, applies that half-size offset in script. */
.pt-marker {
  position: absolute;
  top: 0;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-signal-lime);
  offset-rotate: 0deg;
  offset-anchor: 50% 50%;
  offset-distance: 0%;
  will-change: offset-distance;
}

/* Terminal state: the marker stops being a travelling signal and resolves
   into an outcome node, matching .cj-net__outcome on the destination. */
.pt-terminal {
  position: absolute;
  top: 0;
  left: 0;
  width: 19px;
  height: 19px;
  margin: -9.5px 0 0 -9.5px;
  border-radius: 50%;
  border: 1.5px solid var(--color-signal-lime);
  opacity: 0;
}

/* --------------------------------------------------------------------------
   4. Reduced motion
   -------------------------------------------------------------------------- */

/* Navigation happens normally and immediately: page-transition.js never
   intercepts a click, and the head snippet never sets the arrival attribute.
   These rules exist so that a preference changed mid-session, or an overlay
   already in the document, cannot leave anything moving or covering. No
   substitute fade is offered — nothing here carries meaning. */
@media (prefers-reduced-motion: reduce) {
  html[data-pt-arrive]::before { display: none; }
  .pt-overlay { display: none; }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
