/* Copied verbatim from variants/v3-family/transitions.css by site-src/build_v3.py. */
/* ==========================================================================
   Prismatic Data Solutions — V3 family: page transitions
   Seventeen selectable transitions, keyed on html[data-t]. `veil` is the
   original V3 behaviour and the default; the head script writes the name onto
   <html> before first paint, and the static markup carries data-t="veil" so a
   page with JavaScript disabled still gets a transition that clears itself.

   1-6    veil, beam, curtain, iris, wash, lift
   7-11   prism, disperse, facet, bands, glass                (light and prisms)
   12-17  lift, lift-fade, lift-slide, lift-settle,
          lift-stagger, lift-blur                             (the lift family,
          all sharing one rule: the header is never touched. `lift` itself is
          the revised version of 6 and lives with its siblings below.)

   Shared contract for all of them:
   - Two timing tokens, both tunable in one line per transition:
       --t-in   the overlay covering the page before a navigation
       --t-out  the overlay revealing the new page on load
     (site.js reads --t-in off <html> and navigates 50ms after it ends, so a
     transition sets its own leave length in one line and nothing else moves.)
   - The load ("enter") animation is pure CSS with `both` fill and a
     visibility: hidden end state, so the overlay is gone with or without JS.
   - site.js parks the overlay (display: none) once the enter animation ends.
   - The enter animation may be HELD at its first frame while the images above
     the fold decode (html[data-hold], see "Entrance hold" below).
   - Nothing runs under prefers-reduced-motion: reduce; the overlay is not
     rendered at all and no page content is animated.
   - The overlay is z-index 500, above the sticky header (400) and its menus.
   ========================================================================== */

/* --- Shared shell -------------------------------------------------------- */
/* iris origin and radius, overwritten per click by site.js. The radius is
   the distance from the click point to the farthest corner, so the circle
   spends the whole duration doing visible work instead of finishing early.
   150% is the safe cover-everything fallback. */
html { --cx: 50%; --cy: 50%; --ir: 150%; }

html[data-t] {
  --t-in: 900ms;
  --t-out: 1350ms;
}

.page-veil {
  position: fixed;
  inset: 0;
  z-index: 500;
  overflow: hidden;
  --veil-logo: 110px;
  --veil-wordmark: 92px;
  --veil-gap: 22px;
  /* Aspect ratio of img/wordmark-header-white.png (1081 x 299). Lets the
     prism transitions work out where the logo prism actually sits inside the
     centred brand row without measuring anything in JavaScript. */
  --veil-wordmark-ar: 3.6154;
}
.page-veil[data-veil="hidden"] { display: none; }
html:not([data-t]) .page-veil { display: none; }   /* belt and braces */

/* --- Entrance hold -------------------------------------------------------
   The exit is short and the entrance carries the character, so the entrance
   must not begin on a page whose photograph has not arrived (Jessica,
   2026-09-08: on CloudFront "the new page pops up, but the images aren't
   loaded right away, and then they load" — invisible on a local server).
   The inline head script sets data-hold before first paint when the visitor
   came from another page of this site, and clears it as soon as the images
   intersecting the first viewport have decoded, or after its own hard cap,
   whichever is first. Holding is `animation-play-state: paused`, not a delay:
   the animation is created at first paint and simply does not advance, so it
   still plays in full from its first frame whenever the hold lifts.
   Nothing here can strand a page: the attribute is only ever set by script
   (no script, no hold) and the script's release timer is registered in the
   same breath as the attribute. Reduced motion never sets it.
   Two attributes on <html>, not one: every transition sets its animation with
   the `animation` shorthand, which carries an implicit play-state of
   `running`, and `html[data-t="x"] main > *` would otherwise tie with
   `html[data-hold] main > *` and win on source order. [data-hold][data-t]
   outranks it, and reads as what it is: a hold on a transition in progress. */
@media (prefers-reduced-motion: no-preference) {
  html[data-hold][data-t] .page-veil,
  html[data-hold][data-t] .page-veil > *,
  html[data-hold][data-t] .veil__layers i,
  html[data-hold][data-t] main,
  html[data-hold][data-t] main > *,
  html[data-hold][data-t] .site-footer { animation-play-state: paused; }
}

.veil__a, .veil__b { position: absolute; inset: 0; }

/* Ten generic stacked layers, used by the transitions that need one element
   per facet (facet: the first six) or per module band (bands: all ten).
   Hidden for every other transition. */
.veil__layers { display: none; position: absolute; inset: 0; }
.veil__layers i { display: block; }
.veil__layers i:nth-child(1) { --i: 0; }
.veil__layers i:nth-child(2) { --i: 1; }
.veil__layers i:nth-child(3) { --i: 2; }
.veil__layers i:nth-child(4) { --i: 3; }
.veil__layers i:nth-child(5) { --i: 4; }
.veil__layers i:nth-child(6) { --i: 5; }
.veil__layers i:nth-child(7) { --i: 6; }
.veil__layers i:nth-child(8) { --i: 7; }
.veil__layers i:nth-child(9) { --i: 8; }
.veil__layers i:nth-child(10) { --i: 9; }

.veil__brand {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--veil-gap);
}
.veil__brand svg.logo-anim { width: var(--veil-logo); height: var(--veil-logo); }
.veil__brand .veil-wordmark { width: auto; height: var(--veil-wordmark); }

@media (max-width: 640px) {
  .page-veil { --veil-logo: 64px; --veil-wordmark: 54px; }
}

/* Shared keyframes ------------------------------------------------------- */
@keyframes veil-fade-out { from { opacity: 1; visibility: visible; } to { opacity: 0; visibility: hidden; } }
@keyframes veil-fade-in { from { opacity: 0; visibility: visible; } to { opacity: 1; visibility: visible; } }
/* Holds the overlay visible for the whole enter, then hides it. `visibility`
   stays visible for the duration whenever either endpoint is visible, so this
   only takes effect at the end. */
@keyframes veil-park { from { visibility: visible; } to { visibility: hidden; } }
/* Brand ramps shared by the light-and-prism set: in over the last third of a
   leave, out over the first third of an enter (same shape as iris's). */
@keyframes veil-brand-late-in { 0%, 66% { opacity: 0; } 100% { opacity: 1; } }
@keyframes veil-brand-early-out { 0% { opacity: 1; } 30%, 100% { opacity: 0; } }

/* ==========================================================================
   1. veil — the original. Solid ground with a soft interactive glow, brand
   centred, opacity in and out.
   ========================================================================== */
html[data-t="veil"] .page-veil {
  background-color: var(--ground);
  background-image: radial-gradient(46% 40% at 50% 46%, var(--glow), rgba(var(--ground-rgb), 0) 72%);
}
@media (prefers-reduced-motion: no-preference) {
  html[data-t="veil"] .page-veil { animation: veil-fade-out var(--t-out) ease-out both; }
  html[data-t="veil"] .page-veil[data-veil="shown"] { animation: veil-fade-in var(--t-in) ease-out both; }
}

/* ==========================================================================
   2. beam — the hero's angled spectrum beam becomes the transition. A ground
   panel whose leading edge is tilted about 10 degrees sweeps across, with a
   thin spectrum edge running ahead of it. No brand mark: the beam is the
   statement.
   ========================================================================== */
html[data-t="beam"] {
  --t-in: 850ms;
  --t-out: 1150ms;
  --beam-ease: cubic-bezier(.65, 0, .35, 1);
}
html[data-t="beam"] .veil__brand { display: none; }
html[data-t="beam"] .veil__a { background-image: var(--spectrum-v); }
html[data-t="beam"] .veil__b { background-color: var(--ground); }

@media (prefers-reduced-motion: no-preference) {
  /* enter: the panel's left edge sweeps right, spectrum edge trailing */
  html[data-t="beam"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="beam"] .veil__a { animation: beam-enter-a var(--t-out) var(--beam-ease) both; }
  html[data-t="beam"] .veil__b { animation: beam-enter-b var(--t-out) var(--beam-ease) both; }
  /* leave: the panel sweeps in from the left, spectrum edge leading */
  html[data-t="beam"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="beam"] .page-veil[data-veil="shown"] .veil__a { animation: beam-leave-a var(--t-in) var(--beam-ease) both; }
  html[data-t="beam"] .page-veil[data-veil="shown"] .veil__b { animation: beam-leave-b var(--t-in) var(--beam-ease) both; }
}
/* Four points in every keyframe so clip-path interpolates. */
@keyframes beam-leave-b {
  from { clip-path: polygon(-40% 0%, -2.5% 0%, -20.5% 100%, -40% 100%); }
  to   { clip-path: polygon(-40% 0%, 140% 0%, 122% 100%, -40% 100%); }
}
@keyframes beam-leave-a {
  from { clip-path: polygon(-40% 0%, 0% 0%, -18% 100%, -40% 100%); }
  to   { clip-path: polygon(-40% 0%, 142.5% 0%, 124.5% 100%, -40% 100%); }
}
/* The enter starts with the panel's leading edge exactly on the left margin
   (the viewport is still fully covered) so none of the travel is spent
   off-screen, and ends with the whole panel past the right margin. */
@keyframes beam-enter-b {
  from { clip-path: polygon(0% 0%, 160% 0%, 160% 100%, -18% 100%); }
  to   { clip-path: polygon(160% 0%, 160% 0%, 160% 100%, 142% 100%); }
}
@keyframes beam-enter-a {
  from { clip-path: polygon(-2.5% 0%, 160% 0%, 160% 100%, -20.5% 100%); }
  to   { clip-path: polygon(157.5% 0%, 160% 0%, 160% 100%, 139.5% 100%); }
}

/* ==========================================================================
   3. curtain — two ground halves slide in to meet in the middle, their two
   spectrum hairlines meeting as one line; the brand fades in at the seam.
   ========================================================================== */
html[data-t="curtain"] { --curtain-ease: cubic-bezier(.7, 0, .3, 1); }
html[data-t="curtain"] .veil__a {
  inset: 0 0 auto 0;
  height: 50%;
  background-color: var(--ground);
}
html[data-t="curtain"] .veil__b {
  inset: 50% 0 0 0;
  background-color: var(--ground);
}
html[data-t="curtain"] .veil__a::after,
html[data-t="curtain"] .veil__b::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: 2px;
  background-image: var(--spectrum);
  background-size: 100% 100%;
}
html[data-t="curtain"] .veil__a::after { bottom: 0; }
html[data-t="curtain"] .veil__b::after { top: 0; }

@media (prefers-reduced-motion: no-preference) {
  html[data-t="curtain"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="curtain"] .veil__a { animation: curtain-enter-a var(--t-out) var(--curtain-ease) both; }
  html[data-t="curtain"] .veil__b { animation: curtain-enter-b var(--t-out) var(--curtain-ease) both; }
  html[data-t="curtain"] .veil__brand { animation: curtain-brand-out var(--t-out) linear both; }
  html[data-t="curtain"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="curtain"] .page-veil[data-veil="shown"] .veil__a { animation: curtain-leave-a var(--t-in) var(--curtain-ease) both; }
  html[data-t="curtain"] .page-veil[data-veil="shown"] .veil__b { animation: curtain-leave-b var(--t-in) var(--curtain-ease) both; }
  html[data-t="curtain"] .page-veil[data-veil="shown"] .veil__brand { animation: curtain-brand-in var(--t-in) linear both; }
}
@keyframes curtain-leave-a { from { transform: translateY(-100%); } to { transform: translateY(0); } }
@keyframes curtain-leave-b { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes curtain-enter-a { from { transform: translateY(0); } to { transform: translateY(-100%); } }
@keyframes curtain-enter-b { from { transform: translateY(0); } to { transform: translateY(100%); } }
/* brand: in over the last 300ms of the leave, out over the first 300ms of the enter */
@keyframes curtain-brand-in { 0%, 66% { opacity: 0; } 100% { opacity: 1; } }
@keyframes curtain-brand-out { 0% { opacity: 1; } 22%, 100% { opacity: 0; } }

/* ==========================================================================
   4. iris — the overlay opens and closes as a circle centred on the point the
   visitor clicked (stored in sessionStorage so the new page opens from the
   same place).
   ========================================================================== */
html[data-t="iris"] .page-veil {
  background-color: var(--ground);
  background-image: radial-gradient(46% 40% at 50% 46%, var(--glow), rgba(var(--ground-rgb), 0) 72%);
}
@media (prefers-reduced-motion: no-preference) {
  html[data-t="iris"] .page-veil {
    animation: iris-enter var(--t-out) cubic-bezier(.4, 0, .2, 1) both,
               veil-park var(--t-out) linear both;
  }
  html[data-t="iris"] .veil__brand { animation: iris-brand-out var(--t-out) linear both; }
  html[data-t="iris"] .page-veil[data-veil="shown"] {
    animation: iris-leave var(--t-in) cubic-bezier(.4, 0, .2, 1) both;
    visibility: visible;
  }
  html[data-t="iris"] .page-veil[data-veil="shown"] .veil__brand { animation: iris-brand-in var(--t-in) linear both; }
}
@keyframes iris-leave {
  from { clip-path: circle(0% at var(--cx) var(--cy)); }
  to   { clip-path: circle(var(--ir) at var(--cx) var(--cy)); }
}
@keyframes iris-enter {
  from { clip-path: circle(var(--ir) at var(--cx) var(--cy)); }
  to   { clip-path: circle(0% at var(--cx) var(--cy)); }
}
@keyframes iris-brand-in { 0%, 66% { opacity: 0; } 100% { opacity: 1; } }
@keyframes iris-brand-out { 0% { opacity: 1; } 33%, 100% { opacity: 0; } }

/* ==========================================================================
   5. wash — the CTA band's drifting colour wash at full screen. The spectrum
   keeps moving behind the brand for as long as the overlay is up.
   ========================================================================== */
html[data-t="wash"] .page-veil { background-color: var(--ground); }
html[data-t="wash"] .veil__a {
  inset: -25%;
  background-image: var(--spectrum);
  background-size: 200% 100%;
  background-repeat: repeat-x;
  transform: rotate(-10deg);
  filter: blur(70px);
  opacity: .55;
}
@media (prefers-reduced-motion: no-preference) {
  html[data-t="wash"] .veil__a { animation: hairline-drift 46s linear infinite; }
  html[data-t="wash"] .page-veil { animation: veil-fade-out var(--t-out) ease-out both; }
  html[data-t="wash"] .page-veil[data-veil="shown"] { animation: veil-fade-in var(--t-in) ease-out both; }
}

/* ==========================================================================
   6 and 12-17. The lift family — no covering overlay, and nothing whatsoever
   happens to the header. The page itself leaves and the next one arrives;
   the spectrum hairline, the prism, the wordmark and the nav are visible and
   undisturbed for the whole journey.

   The rules that make that true, and that every variant here obeys:
   - The overlay is never rendered, in either direction. It is the only element
     in the document above the header's z-index (500 against 400), so the way
     to guarantee it never paints over the header is for it not to be in the
     picture at all. No tint layer, not even a partial one below the header.
   - Only `main` and `.site-footer` are transformed, filtered or faded. Never
     `body`, never `html`, never `.site-header` or anything inside it. The
     header's own animations (hairline drift, logo SMIL) keep running.
   - Calm timings, one easing, no bounce.
   The engine needs nothing new: it already flags `data-leaving` on <html>.
   `html[data-t^="lift"]` is safe as a family selector — no other transition
   name begins with "lift".
   ========================================================================== */
html[data-t^="lift"] {
  --t-in: 800ms;
  --t-out: 1000ms;
  --lift-ease: cubic-bezier(.2, .7, .2, 1);
}
html[data-t^="lift"] .page-veil,
html[data-t^="lift"] .page-veil[data-veil="shown"] { display: none; }

/* --- 12. lift (revised): rises out, rises in. Nothing else. -------------- */
@media (prefers-reduced-motion: no-preference) {
  html[data-t="lift"] main,
  html[data-t="lift"] .site-footer { animation: lift-enter var(--t-out) var(--lift-ease) both; }
  html[data-t="lift"][data-leaving] main,
  html[data-t="lift"][data-leaving] .site-footer { animation: lift-leave var(--t-in) var(--lift-ease) both; }
}
@keyframes lift-enter { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: translateY(0); } }
@keyframes lift-leave { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-22px); } }

/* --- 13. lift-fade: opacity only. The quietest of the seventeen. --------- */
html[data-t="lift-fade"] { --t-in: 700ms; --t-out: 900ms; }
@media (prefers-reduced-motion: no-preference) {
  html[data-t="lift-fade"] main,
  html[data-t="lift-fade"] .site-footer { animation: lift-fade-enter var(--t-out) ease-out both; }
  html[data-t="lift-fade"][data-leaving] main,
  html[data-t="lift-fade"][data-leaving] .site-footer { animation: lift-fade-leave var(--t-in) ease-in both; }
}
@keyframes lift-fade-enter { from { opacity: 0; } to { opacity: 1; } }
@keyframes lift-fade-leave { from { opacity: 1; } to { opacity: 0; } }

/* --- 14. lift-slide: the same idea sideways.

   The horizontal travel has to be clipped for real. `overflow-x: hidden` on
   `body` (base.css) does not do it: the HTML overflow rules propagate the
   body's overflow to the viewport, which hides the scrollbar but still leaves
   the page a few dozen pixels scrollable by script. Giving `html` an explicit
   `overflow-y: scroll` stops that propagation (the viewport takes its overflow
   from the root instead), and body's own `overflow-x: clip` then clips the
   travelling `main`. Measured: scrollWidth equals innerWidth at 390, 768 and
   1440 through both legs, and the sticky header still sticks — `overflow-x:
   clip` on the root itself would have been simpler but it breaks sticky.
   These two are clips, not motion, and the moving elements are still only
   `main` and `.site-footer`. ---------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  html[data-t="lift-slide"] { overflow-y: scroll; }
  html[data-t="lift-slide"] body { overflow-x: clip; }
  html[data-t="lift-slide"] main,
  html[data-t="lift-slide"] .site-footer { animation: lift-slide-enter var(--t-out) var(--lift-ease) both; }
  html[data-t="lift-slide"][data-leaving] main,
  html[data-t="lift-slide"][data-leaving] .site-footer { animation: lift-slide-leave var(--t-in) var(--lift-ease) both; }
}
@keyframes lift-slide-enter { from { opacity: 0; transform: translateX(48px); } to { opacity: 1; transform: translateX(0); } }
@keyframes lift-slide-leave { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(-40px); } }

/* --- 15. lift-settle: scale. The origin is the top edge, so the page
   settles towards the header instead of towards its own middle. ----------- */
html[data-t="lift-settle"] main,
html[data-t="lift-settle"] .site-footer { transform-origin: 50% 0; }
@media (prefers-reduced-motion: no-preference) {
  html[data-t="lift-settle"] main,
  html[data-t="lift-settle"] .site-footer { animation: lift-settle-enter var(--t-out) var(--lift-ease) both; }
  html[data-t="lift-settle"][data-leaving] main,
  html[data-t="lift-settle"][data-leaving] .site-footer { animation: lift-settle-leave var(--t-in) var(--lift-ease) both; }
}
@keyframes lift-settle-enter { from { opacity: 0; transform: scale(.985); } to { opacity: 1; transform: scale(1); } }
@keyframes lift-settle-leave { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(.985); } }

/* --- 16. lift-stagger: orchestrated. `main`'s sections go one after another,
   top to bottom, 45ms apart, and the footer goes last.

   Literal delays rather than a calc off an index: the sections carry no `--i`,
   and both pages in the family have exactly eight of them. Children beyond the
   eighth fall to the capped 315ms so a longer page cannot drag the leave past
   the navigation. Element durations are set so the last thing to move lands at
   1000ms on the enter (exactly --t-out).

   The leave runs the same choreography at half the stagger and a shorter
   duration: 420ms elements, delays halved, the footer landing at 590ms
   (--t-in), where it used to land at 940ms. That is the 2026-09-08 budget
   shift — the exit is an acknowledgement, the entrance is the performance —
   and it takes ~350ms of blank ground off the middle of every navigation,
   which is what the entrance hold then spends on the incoming photograph.
   (2026-09-09: Jessica found the 330ms/500ms version a touch brisk and asked
   for the fade-out slowed "just slightly"; 330 -> 420ms is that step.)
   Each element's delay is held in `--lift-d` and read by both directions'
   `animation` shorthand. It has to be a property rather than a separate
   `animation-delay` rule: the two shorthands tie on specificity with the
   per-child rules, so a shorthand (which resets the delay to 0) would win by
   source order and flatten the stagger. ---------------------------------- */
html[data-t="lift-stagger"] { --t-in: 590ms; --t-out: 1000ms; }
html[data-t="lift-stagger"] main > * { --lift-d: 315ms; }   /* the cap, 8th on */
html[data-t="lift-stagger"] main > :nth-child(1) { --lift-d: 0ms; }
html[data-t="lift-stagger"] main > :nth-child(2) { --lift-d: 45ms; }
html[data-t="lift-stagger"] main > :nth-child(3) { --lift-d: 90ms; }
html[data-t="lift-stagger"] main > :nth-child(4) { --lift-d: 135ms; }
html[data-t="lift-stagger"] main > :nth-child(5) { --lift-d: 180ms; }
html[data-t="lift-stagger"] main > :nth-child(6) { --lift-d: 225ms; }
html[data-t="lift-stagger"] main > :nth-child(7) { --lift-d: 270ms; }
html[data-t="lift-stagger"] .site-footer { --lift-d: 340ms; }   /* last of all */
@media (prefers-reduced-motion: no-preference) {
  html[data-t="lift-stagger"] main > *,
  html[data-t="lift-stagger"] .site-footer {
    animation: lift-stagger-enter 660ms var(--lift-ease) var(--lift-d) both;
  }
  html[data-t="lift-stagger"][data-leaving] main > *,
  html[data-t="lift-stagger"][data-leaving] .site-footer {
    animation: lift-stagger-leave 420ms var(--lift-ease) calc(var(--lift-d) / 2) both;
  }
}
@keyframes lift-stagger-enter { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: translateY(0); } }
@keyframes lift-stagger-leave { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-20px); } }

/* --- 17. lift-blur: the glass idea made quiet. Defocus instead of a
   spectrum sheen, because a sheen needs a full-screen layer and that is the
   one thing the header rule forbids. Filter on `main` and `.site-footer`
   only. ------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  html[data-t="lift-blur"] main,
  html[data-t="lift-blur"] .site-footer { animation: lift-blur-enter var(--t-out) var(--lift-ease) both; }
  html[data-t="lift-blur"][data-leaving] main,
  html[data-t="lift-blur"][data-leaving] .site-footer { animation: lift-blur-leave var(--t-in) var(--lift-ease) both; }
}
@keyframes lift-blur-enter {
  from { opacity: 0; filter: blur(6px); transform: translateY(16px); }
  to   { opacity: 1; filter: blur(0px); transform: translateY(0); }
}
@keyframes lift-blur-leave {
  from { opacity: 1; filter: blur(0px); transform: translateY(0); }
  to   { opacity: 0; filter: blur(6px); transform: translateY(-12px); }
}

/* ==========================================================================
   7. prism — the logo's triangular prism as a clip. A right-pointing triangle
   grows out of the middle of the screen until it covers everything, a spectrum
   rim running along its slanted edges, and shrinks back to a point on the next
   page.

   Geometry, with k the growth factor (0 -> 1), measured from the middle of
   the screen in vmax so the silhouette keeps the logo mark's proportions
   (width : height = 0.90) at every viewport shape:
     apex        (50% + 176k vmax, 50%)
     top back    (50% -  66k vmax, 50% - 134k vmax)
     bottom back (50% -  66k vmax, 50% + 134k vmax)
   Coverage is complete once the back edge passes the left of the screen,
   k = 0.757 on a 16:10 viewport, which the easing reaches at about 790ms of
   the 900ms leave: the page is covered well before the 950ms navigation and
   the rest of the growth is margin. The easing is deliberately slow to start,
   so the triangle stays legible as a prism for most of the leave instead of
   becoming a diagonal band immediately.
   The rim is the same triangle pushed out by a constant few pixels (not a
   percentage), so it stays a hairline instead of growing into a stripe, and
   it is filled with a conic spectrum centred on the triangle's own centre,
   which makes its colours scale-invariant.
   ========================================================================== */
html[data-t="prism"] {
  --t-in: 900ms;
  --t-out: 1300ms;
  --prism-ease: cubic-bezier(.45, 0, .75, .5);
  /* the exact mirror of --prism-ease, so the shrink on the next page carries
     on from the speed the growth had at the moment of the navigation */
  --prism-ease-out: cubic-bezier(.25, .5, .55, 1);
}
/* the ground fill (with veil's glow), clipped to the triangle */
html[data-t="prism"] .veil__b {
  background-color: var(--ground);
  background-image: radial-gradient(46% 40% at 50% 46%, var(--glow), rgba(var(--ground-rgb), 0) 72%);
}
/* the rim, clipped to the slightly larger triangle. Same stops as base.css
   --spectrum, in conic form, closing on the same red so there is no seam. */
html[data-t="prism"] .veil__a {
  background-image: conic-gradient(from 208deg at 50% 50%,
    #E50000 0%, #DD4B00 10%, #D2B200 21%, #A4CA00 31%, #39BF00 42%,
    #00C66E 52%, #00A9AE 62%, #001FD2 76%, #9A00D9 88%, #E10060 95%, #E50000 100%);
}

@media (prefers-reduced-motion: no-preference) {
  html[data-t="prism"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="prism"] .veil__a { animation: prism-enter-rim var(--t-out) var(--prism-ease-out) both; }
  html[data-t="prism"] .veil__b { animation: prism-enter-fill var(--t-out) var(--prism-ease-out) both; }
  html[data-t="prism"] .veil__brand { animation: veil-brand-early-out var(--t-out) linear both; }
  html[data-t="prism"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="prism"] .page-veil[data-veil="shown"] .veil__a { animation: prism-leave-rim var(--t-in) var(--prism-ease) both; }
  html[data-t="prism"] .page-veil[data-veil="shown"] .veil__b { animation: prism-leave-fill var(--t-in) var(--prism-ease) both; }
  html[data-t="prism"] .page-veil[data-veil="shown"] .veil__brand { animation: veil-brand-late-in var(--t-in) linear both; }
}
/* Three points, same order (apex, top back, bottom back), in every keyframe. */
@keyframes prism-leave-fill {
  from { clip-path: polygon(calc(50% + 0vmax) 50%, calc(50% - 0vmax) calc(50% - 0vmax), calc(50% - 0vmax) calc(50% + 0vmax)); }
  to   { clip-path: polygon(calc(50% + 176vmax) 50%, calc(50% - 66vmax) calc(50% - 134vmax), calc(50% - 66vmax) calc(50% + 134vmax)); }
}
@keyframes prism-leave-rim {
  from { clip-path: polygon(calc(50% + 0vmax + 5px) 50%, calc(50% - 0vmax - 4px) calc(50% - 0vmax - 4px), calc(50% - 0vmax - 4px) calc(50% + 0vmax + 4px)); opacity: 0; }
  8%   { opacity: 1; }
  to   { clip-path: polygon(calc(50% + 176vmax + 5px) 50%, calc(50% - 66vmax - 4px) calc(50% - 134vmax - 4px), calc(50% - 66vmax - 4px) calc(50% + 134vmax + 4px)); opacity: 1; }
}
@keyframes prism-enter-fill {
  from { clip-path: polygon(calc(50% + 176vmax) 50%, calc(50% - 66vmax) calc(50% - 134vmax), calc(50% - 66vmax) calc(50% + 134vmax)); }
  to   { clip-path: polygon(calc(50% + 0vmax) 50%, calc(50% - 0vmax) calc(50% - 0vmax), calc(50% - 0vmax) calc(50% + 0vmax)); }
}
@keyframes prism-enter-rim {
  from { clip-path: polygon(calc(50% + 176vmax + 5px) 50%, calc(50% - 66vmax - 4px) calc(50% - 134vmax - 4px), calc(50% - 66vmax - 4px) calc(50% + 134vmax + 4px)); opacity: 1; }
  92%  { opacity: 1; }
  to   { clip-path: polygon(calc(50% + 0vmax + 5px) 50%, calc(50% - 0vmax - 4px) calc(50% - 0vmax - 4px), calc(50% - 0vmax - 4px) calc(50% + 0vmax + 4px)); opacity: 0; }
}

/* ==========================================================================
   8. disperse — white light hits the prism and fans into the spectrum. A thin
   white beam is drawn from the top-left corner to the logo's prism, then a
   spectrum fan opens out of that point and fills the screen, and the ground
   settles over it with the brand on top. The next page closes the fan back
   into the prism and withdraws the beam.

   The convergence point is the prism inside the centred brand row, which sits
   half of (gap + wordmark width) to the left of the middle of the screen.
   ========================================================================== */
html[data-t="disperse"] {
  --t-in: 900ms;
  --t-out: 1250ms;
  --disperse-ease: cubic-bezier(.42, 0, .3, 1);
}
html[data-t="disperse"] .page-veil {
  --prism-x: calc(50% - (var(--veil-gap) + var(--veil-wordmark) * var(--veil-wordmark-ar)) / 2);
  /* same stops as base.css --spectrum, as a fan out of the prism */
  --spectrum-fan: conic-gradient(from 62deg at var(--prism-x) 50%,
    rgba(229, 0, 0, 0) 0deg, #E50000 14deg, #DD4B00 24deg, #D2B200 35deg,
    #A4CA00 45deg, #39BF00 56deg, #00C66E 66deg, #00A9AE 77deg,
    #001FD2 92deg, #9A00D9 104deg, rgba(154, 0, 217, 0) 124deg,
    rgba(154, 0, 217, 0) 360deg);
}
/* The beam. The box runs from the top-left corner of the screen to twice the
   prism point, so its own main diagonal ends exactly on the prism; a
   `to top right` gradient draws a hairline along that diagonal at any aspect
   ratio, and the corner clip reveals it from the corner outwards. */
html[data-t="disperse"] .veil__a {
  inset: 0 auto auto 0;
  width: calc(2 * var(--prism-x));
  height: 100%;
  background-image: linear-gradient(to top right,
    rgba(255, 255, 255, 0) calc(50% - 2px),
    rgba(255, 255, 255, .96) calc(50% - 1px),
    #FFFFFF 50%,
    rgba(255, 255, 255, .96) calc(50% + 1px),
    rgba(255, 255, 255, 0) calc(50% + 2px));
  filter: blur(1px);
}
html[data-t="disperse"] .veil__b {
  background-image: var(--spectrum-fan);
  transform-origin: var(--prism-x) 50%;
  /* softened by the gradient itself (wide angular feather, radial fade at the
     far end) rather than by a filter: a blur on a layer this size, scaled
     2.3x, is the one thing here heavy enough to drop frames */
  -webkit-mask-image: radial-gradient(farthest-side circle at var(--prism-x) 50%, #000 42%, rgba(0, 0, 0, .55) 74%, transparent 100%);
  mask-image: radial-gradient(farthest-side circle at var(--prism-x) 50%, #000 42%, rgba(0, 0, 0, .55) 74%, transparent 100%);
}
/* the ground that settles over the fan, above both layers, below the brand */
html[data-t="disperse"] .page-veil::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: var(--ground);
  background-image: radial-gradient(46% 40% at 50% 46%, var(--glow), rgba(var(--ground-rgb), 0) 72%);
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  html[data-t="disperse"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="disperse"] .page-veil::after { animation: disperse-enter-ground var(--t-out) ease-in-out both; }
  html[data-t="disperse"] .veil__a { animation: disperse-enter-beam var(--t-out) var(--disperse-ease) both; }
  html[data-t="disperse"] .veil__b { animation: disperse-enter-fan var(--t-out) var(--disperse-ease) both; }
  html[data-t="disperse"] .veil__brand { animation: disperse-enter-brand var(--t-out) linear both; }
  html[data-t="disperse"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="disperse"] .page-veil[data-veil="shown"]::after { animation: disperse-leave-ground var(--t-in) ease-in-out both; }
  html[data-t="disperse"] .page-veil[data-veil="shown"] .veil__a { animation: disperse-leave-beam var(--t-in) var(--disperse-ease) both; }
  html[data-t="disperse"] .page-veil[data-veil="shown"] .veil__b { animation: disperse-leave-fan var(--t-in) var(--disperse-ease) both; }
  html[data-t="disperse"] .page-veil[data-veil="shown"] .veil__brand { animation: veil-brand-late-in var(--t-in) linear both; }
}
/* Three points, same order, in every keyframe: the clip reaches the box
   centre (the prism) at 100%. */
@keyframes disperse-leave-beam {
  from   { clip-path: polygon(0% 0%, 0% 0%, 0% 0%); }
  32%    { clip-path: polygon(0% 0%, 100% 0%, 0% 100%); }
  to     { clip-path: polygon(0% 0%, 100% 0%, 0% 100%); }
}
@keyframes disperse-enter-beam {
  from   { clip-path: polygon(0% 0%, 100% 0%, 0% 100%); }
  46%    { clip-path: polygon(0% 0%, 100% 0%, 0% 100%); }
  to     { clip-path: polygon(0% 0%, 0% 0%, 0% 0%); }
}
@keyframes disperse-leave-fan {
  0%, 22% { transform: scale(.04); opacity: 0; }
  30%     { opacity: .9; }
  to      { transform: scale(2.3); opacity: .9; }
}
@keyframes disperse-enter-fan {
  from    { transform: scale(2.3); opacity: .9; }
  86%     { opacity: .9; }
  to      { transform: scale(.04); opacity: 0; }
}
@keyframes disperse-leave-ground {
  0%, 56% { opacity: 0; }
  90%, to { opacity: 1; }
}
@keyframes disperse-enter-ground {
  0%, 18% { opacity: 1; }
  46%, to { opacity: 0; }
}
@keyframes disperse-enter-brand { 0% { opacity: 1; } 24%, 100% { opacity: 0; } }

/* ==========================================================================
   9. facet — the screen becomes six prism faces. Six slanted panels in
   slightly different tints of the ground slide in from alternating
   directions, 60ms apart, each carrying a spectrum hairline on its leading
   seam. On the next page they carry on travelling the way they came, last
   panel first.

   Each panel is a plain box with skewX applied, so the 3px stripe at its left
   edge is skewed with it and sits exactly on the seam. The panels are taller
   than the screen and the outer two are padded by --facet-pad so the skew
   cannot open a gap at the top left or the bottom right.
   ========================================================================== */
html[data-t="facet"] {
  --t-in: 900ms;
  --t-out: 1250ms;
  --facet-ease: cubic-bezier(.45, 0, .3, 1);
  --facet-slant: -9deg;
  --facet-pad: 13vh;      /* > tan(9deg) x 70vh, the worst-case skew offset */
}
html[data-t="facet"] .veil__layers { display: block; }
html[data-t="facet"] .veil__layers i:nth-child(n+7) { display: none; }
html[data-t="facet"] .veil__layers i {
  position: absolute;
  top: -20vh;
  height: 140vh;
  left: calc(var(--i) * (100% / 6));
  width: calc(100% / 6 + 3px);
  transform: skewX(var(--facet-slant));
  background-image: linear-gradient(90deg,
    var(--seam) 0px, var(--seam) 3px, var(--tint) 3px);
}
html[data-t="facet"] .veil__layers i:nth-child(1) {
  left: calc(0px - var(--facet-pad));
  width: calc(100% / 6 + var(--facet-pad) + 3px);
}
html[data-t="facet"] .veil__layers i:nth-child(6) {
  width: calc(100% / 6 + var(--facet-pad));
}
/* The first panel's seam starts off the left of the screen (it is the one
   padded out to cover the skew), so the five seams that are actually visible
   are given a full spectral sweep, left to right. */
html[data-t="facet"] .veil__layers i:nth-child(1) { --seam: var(--h-security);   --tint: color-mix(in srgb, #FFFFFF 7%, var(--ground)); }
html[data-t="facet"] .veil__layers i:nth-child(2) { --seam: var(--h-incident);   --tint: color-mix(in srgb, #FFFFFF 3.5%, var(--ground)); }
html[data-t="facet"] .veil__layers i:nth-child(3) { --seam: var(--h-hazard);     --tint: var(--ground); }
html[data-t="facet"] .veil__layers i:nth-child(4) { --seam: var(--h-contractor); --tint: color-mix(in srgb, #000000 5%, var(--ground)); }
html[data-t="facet"] .veil__layers i:nth-child(5) { --seam: var(--h-committee);  --tint: color-mix(in srgb, #FFFFFF 5%, var(--ground)); }
html[data-t="facet"] .veil__layers i:nth-child(6) { --seam: var(--h-document);   --tint: color-mix(in srgb, #000000 8%, var(--ground)); }

@media (prefers-reduced-motion: no-preference) {
  html[data-t="facet"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="facet"] .veil__brand { animation: veil-brand-early-out var(--t-out) linear both; }
  html[data-t="facet"] .veil__layers i {
    animation: facet-enter-down calc(var(--t-out) - 300ms) var(--facet-ease) both;
    animation-delay: calc((5 - var(--i)) * 60ms);       /* reverse order */
  }
  html[data-t="facet"] .veil__layers i:nth-child(even) { animation-name: facet-enter-up; }
  html[data-t="facet"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="facet"] .page-veil[data-veil="shown"] .veil__brand { animation: veil-brand-late-in var(--t-in) linear both; }
  html[data-t="facet"] .page-veil[data-veil="shown"] .veil__layers i {
    animation: facet-leave-down calc(var(--t-in) - 300ms) var(--facet-ease) both;
    animation-delay: calc(var(--i) * 60ms);
  }
  html[data-t="facet"] .page-veil[data-veil="shown"] .veil__layers i:nth-child(even) { animation-name: facet-leave-up; }
}
/* odd panels travel downwards, even panels upwards, on both legs */
@keyframes facet-leave-down {
  from { transform: translateY(-142%) skewX(var(--facet-slant)); }
  to   { transform: translateY(0) skewX(var(--facet-slant)); }
}
@keyframes facet-leave-up {
  from { transform: translateY(142%) skewX(var(--facet-slant)); }
  to   { transform: translateY(0) skewX(var(--facet-slant)); }
}
@keyframes facet-enter-down {
  from { transform: translateY(0) skewX(var(--facet-slant)); }
  to   { transform: translateY(142%) skewX(var(--facet-slant)); }
}
@keyframes facet-enter-up {
  from { transform: translateY(0) skewX(var(--facet-slant)); }
  to   { transform: translateY(-142%) skewX(var(--facet-slant)); }
}

/* ==========================================================================
   10. bands — every module is a band of the spectrum. Ten bars in the ten
   module band hues, in spectral order, drop in from the top 40ms apart; the
   ground then settles over them so the brand arrives on a calm field. On the
   next page the bars come back into view and lift away upwards in the same
   order.
   ========================================================================== */
html[data-t="bands"] {
  --t-in: 900ms;
  --t-out: 1300ms;
  --bands-ease: cubic-bezier(.4, 0, .3, 1);
}
html[data-t="bands"] .veil__layers { display: block; }
html[data-t="bands"] .veil__layers i {
  position: absolute;
  top: 0;
  height: 100%;
  left: calc(var(--i) * 10%);
  width: calc(10% + 1px);
  /* the band hue itself, lit from the top and settling into the palette at
     the foot, so ten full-height bars read as lit columns and not as flat ink */
  background-image: linear-gradient(180deg,
    color-mix(in srgb, #FFFFFF 8%, var(--band)) 0%,
    var(--band) 40%,
    color-mix(in srgb, var(--ground) 34%, var(--band)) 100%);
}
html[data-t="bands"] .veil__layers i:nth-child(1) { --band: var(--h-incident); }
html[data-t="bands"] .veil__layers i:nth-child(2) { --band: var(--h-security); }
html[data-t="bands"] .veil__layers i:nth-child(3) { --band: var(--h-hazard); }
html[data-t="bands"] .veil__layers i:nth-child(4) { --band: var(--h-risk); }
html[data-t="bands"] .veil__layers i:nth-child(5) { --band: var(--h-inspections); }
html[data-t="bands"] .veil__layers i:nth-child(6) { --band: var(--h-contractor); }
html[data-t="bands"] .veil__layers i:nth-child(7) { --band: var(--h-rtw); }
html[data-t="bands"] .veil__layers i:nth-child(8) { --band: var(--h-committee); }
html[data-t="bands"] .veil__layers i:nth-child(9) { --band: var(--h-training); }
html[data-t="bands"] .veil__layers i:nth-child(10) { --band: var(--h-document); }
html[data-t="bands"] .page-veil::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: var(--ground);
  background-image: radial-gradient(46% 40% at 50% 46%, var(--glow), rgba(var(--ground-rgb), 0) 72%);
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  html[data-t="bands"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="bands"] .page-veil::after { animation: bands-enter-ground var(--t-out) ease-in-out both; }
  html[data-t="bands"] .veil__brand { animation: bands-enter-brand var(--t-out) linear both; }
  html[data-t="bands"] .veil__layers i {
    animation: bands-enter 620ms var(--bands-ease) both;
    animation-delay: calc(200ms + var(--i) * 40ms);
  }
  html[data-t="bands"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="bands"] .page-veil[data-veil="shown"]::after { animation: bands-leave-ground var(--t-in) ease-in-out both; }
  html[data-t="bands"] .page-veil[data-veil="shown"] .veil__brand { animation: bands-leave-brand var(--t-in) linear both; }
  html[data-t="bands"] .page-veil[data-veil="shown"] .veil__layers i {
    animation: bands-leave 420ms var(--bands-ease) both;
    animation-delay: calc(var(--i) * 40ms);
  }
}
@keyframes bands-leave { from { transform: translateY(-102%); } to { transform: translateY(0); } }
@keyframes bands-enter { from { transform: translateY(0); } to { transform: translateY(-102%); } }
@keyframes bands-leave-ground {
  0%, 74% { opacity: 0; }
  94%, to { opacity: 1; }
}
/* The ground dissolves early, so the bars are already on their way up when
   they come into view instead of standing there as a flat wall of colour. */
@keyframes bands-enter-ground {
  0%, 8%  { opacity: 1; }
  26%, to { opacity: 0; }
}
@keyframes bands-leave-brand { 0%, 82% { opacity: 0; } 100% { opacity: 1; } }
@keyframes bands-enter-brand { 0% { opacity: 1; } 12%, 100% { opacity: 0; } }

/* ==========================================================================
   11. glass — seen through a prism. Nothing covers the page: it goes out of
   focus behind a soft spectrum sheen, and the next page arrives out of focus
   and sharpens. Only `main` and `.site-footer` are filtered or scaled, never
   the body and never the sticky header.
   ========================================================================== */
html[data-t="glass"] {
  --t-in: 900ms;
  --t-out: 900ms;
  --glass-ease: cubic-bezier(.35, .05, .25, 1);
}
html[data-t="glass"] .page-veil { pointer-events: none; }
html[data-t="glass"] .veil__brand { display: none; }
html[data-t="glass"] .veil__b {
  inset: -25%;
  background-image: var(--spectrum);
  /* one whole spectrum across the screen (the CTA wash uses 200%, which shows
     only half the hues at a time and reads muddy at this size), drifting on
     the shared hairline keyframes; the tile closes on its opening red, so the
     drift has no seam */
  background-size: 100% 100%;
  background-repeat: repeat-x;
  filter: blur(60px);
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  html[data-t="glass"] .page-veil { animation: veil-park var(--t-out) linear both; }
  html[data-t="glass"] .veil__b {
    animation: glass-sheen-out var(--t-out) ease-out both,
               hairline-drift 46s linear infinite;
  }
  html[data-t="glass"] main,
  html[data-t="glass"] .site-footer { animation: glass-enter var(--t-out) var(--glass-ease) both; }
  html[data-t="glass"] .page-veil[data-veil="shown"] { animation: none; visibility: visible; }
  html[data-t="glass"] .page-veil[data-veil="shown"] .veil__b {
    animation: glass-sheen-in var(--t-in) ease-in both,
               hairline-drift 46s linear infinite;
  }
  html[data-t="glass"][data-leaving] main,
  html[data-t="glass"][data-leaving] .site-footer { animation: glass-leave var(--t-in) var(--glass-ease) both; }
}
@keyframes glass-leave {
  from { filter: blur(0px) brightness(1); transform: scale(1); }
  to   { filter: blur(14px) brightness(1.1); transform: scale(1.02); }
}
@keyframes glass-enter {
  from { filter: blur(14px) brightness(1.1); transform: scale(1.02); }
  to   { filter: blur(0px) brightness(1); transform: scale(1); }
}
@keyframes glass-sheen-in { from { opacity: 0; } to { opacity: .28; } }
@keyframes glass-sheen-out { from { opacity: .28; } to { opacity: 0; } }

/* ==========================================================================
   Reduced motion: no overlay, no content animation, in any transition.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .page-veil { display: none !important; }
}
