/* ═══════════════════════════════════════════════════════════════════════════
   T-SkiLL · Broadsheet · the shared layer.

   Sits on top of shared.css (the live site's own tokens). Three things live
   here because every page in the direction needs them identically:

     1. The masthead: one hairline rule, the name left, tracked caps right,
        and a very faint glass behind it so the page can scroll under it.
     2. The handover: how a page leaves and how the next one arrives. A
        cousin of the live site's "Applying to university?" exit, cut from
        3.5s to about a second and a quarter, same order, same curves: the
        edges withdraw, the middle melts, the ground washes to the colour of
        wherever you are going, the masthead glass follows it, and the new
        page rises into that colour. Never a cut.
     3. Page furniture the sub-pages share: the corner back link, the
        column measure, the meta row, the marker pen.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. Masthead ─────────────────────────────────────────────────────────── */
.masthead {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 15px clamp(16px, 3vw, 30px) 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  /* The glass. Barely there on purpose: the page ground at about half
     strength, blurred just enough that scrolling text softens under the rule
     instead of colliding with it. If you can see it, it is too much. */
  background: color-mix(in srgb, var(--mast, var(--page)) var(--mast-mix, 58%), transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.03);
  animation: mast-in 0.8s var(--ease) 0.4s both;
  /* The glass follows the wash to the next page's colour (nav.js moves
     --mast), on the wash's own schedule, so the bar never jumps colour on
     arrival. */
  transition: background-color 0.5s ease 0.15s;
}
@keyframes mast-in { from { opacity: 0; } to { opacity: 1; } }
/* Arrived from another page of this site: the masthead was on screen a
   moment ago, above the wash, so it must not fade in again. And the page's
   own opening starts 380ms into its score (shared.css, --lead): the fresh
   load's pre-roll exists so the masthead can fade in first, and here the
   masthead is already there and the wash has been holding an empty ground.
   Nothing may sit still after the handover lands. */
html.is-handover .masthead { animation: none; }
html.is-handover { --lead: 380ms; }
/* No double taps: a link mid-exit is inert, and a tap never zooms. */
a, button { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
html.bs-leaving .stage { pointer-events: none; }

.mast-left { display: inline-flex; align-items: baseline; gap: 8px; font-size: 13px; color: var(--ink-faint); }
/* Neither side of the masthead may wrap: the wordmark broke into "T-" and
   "SkiLL" on a 320px phone before this. */
.mast-left, .mast-nav, .chrome-mark { flex-shrink: 0; white-space: nowrap; }
/* The same break happens in running copy; wrap the wordmark in this. */
.wm { white-space: nowrap; }
.mast-nav { display: flex; align-items: center; gap: clamp(14px, 2.4vw, 30px); }
.mast-nav a {
  font-family: var(--display);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-decoration: none;
  transition: color 0.2s var(--ease);
}
.mast-nav a:hover, .mast-nav a[aria-current] { color: var(--ink); }
.mast-nav .uni {
  font-family: var(--text);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-size: 13px;
}
@media (max-width: 720px) {
  .mast-left .note-txt, .mast-left .chrome-sep { display: none; }
  .mast-nav .uni { display: none; }
  .mast-nav { gap: 14px; }
}
/* The smallest phones (320 wide): the four links need 241px and there are
   215 beside the wordmark. Closer tracking and a smaller gap, nothing dropped. */
@media (max-width: 359px) {
  .masthead { padding-left: 14px; padding-right: 14px; gap: 12px; }
  .mast-nav { gap: 10px; }
  .mast-nav a { font-size: 10px; letter-spacing: 0.09em; }
}

/* ── 2. The handover ─────────────────────────────────────────────────────── */

/* The wash: a solid layer in the colour of the destination. Opacity, not an
   animated background colour, for the reason the live site gives: the ground
   carries a tiled grain, and repainting it every frame drops frames. Sits
   under the masthead (30) so the masthead is the one thing that survives. */
.wash {
  position: fixed;
  inset: 0;
  z-index: 25;
  background: var(--wash-to, var(--page));
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
/* The same grain as the ground, so the texture is there before the next
   page paints and nothing changes at the moment of navigation. */
.wash::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: var(--grain-opacity);
  background-image: var(--grain);
  background-size: 200px 200px;
}
/* The score, in the live site's order and with its curves, at a quarter of
   its length. Nothing waits for the thing before it to finish, and the next
   page's opening (--lead, above) starts the moment it paints, so from the
   click to the first word of the next page is about a second of continuous
   movement with no still frame in it:
     0.00s  the phones start leaving, the bar starts dropping, the middle
            starts to melt
     0.15s  the wash begins to rise, and the masthead glass starts to take
            the new colour on the same clock
     0.65s  the wash is solid, the middle is gone
     0.70s  navigate, into a page whose ground is already this colour
   (It was 1.25s and 1s, and the last half second was an empty screen.) */
.stage.bs-leaving .wash {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.5s ease 0.15s;
}

/* The middle melts. Blur on the type, not on the column holding it, so the
   words lose their edges before they lose their presence. */
.stage.bs-leaving .leave-melt {
  opacity: 0;
  transform: translateY(14px) scale(0.985);
  pointer-events: none;
  transition: opacity 0.7s var(--ease-cine), transform 0.75s var(--ease-cine);
}
.stage.bs-leaving .leave-melt .hero-type,
.stage.bs-leaving .leave-melt .lede,
.stage.bs-leaving .leave-blur {
  filter: blur(9px);
  transition: filter 0.75s var(--ease-cine);
}

/* The phones withdraw sideways, each to its own edge, flat. The base state
   is redeclared here so the entrance animation can be dropped at exit time
   without the phone snapping to opacity 0 first. */
.field-phone { opacity: 1; transform: translateY(0) rotate(var(--tilt, 0deg)) scale(1); }
.stage.bs-leaving .field-phone {
  animation: none;
  opacity: 0;
  transition: transform 0.75s cubic-bezier(0.36, 0, 0.2, 1), opacity 0.5s var(--ease) 0.1s;
}
.stage.bs-leaving .field-phone.is-left  { transform: translateX(-88vw) rotate(0deg) scale(0.98); }
.stage.bs-leaving .field-phone.is-right { transform: translateX(88vw)  rotate(0deg) scale(0.98); }

/* The bar drops. It sits at the bottom and the shortest way off is down. */
.stage.bs-leaving .bar,
.stage.bs-leaving .bar-privacy {
  animation: none;
  opacity: 0;
  transform: translate(-50%, 120px);
  pointer-events: none;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.7, 1), opacity 0.45s var(--ease) 0.06s;
}
.stage.bs-leaving .bar-privacy { transition-delay: 0.04s, 0.1s; }
.stage.bs-leaving .chrome-social,
.stage.bs-leaving .back-link,
html.bs-leaving .rail,
html.bs-leaving .nudge {
  animation: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
/* Arriving on the front page through a handover, the bar and the social
   links would otherwise be simply there on the first frame, the one thing
   in the whole sequence that appears without moving. They fade up while the
   first words rise. A fresh load is unchanged. (The exit rules above drop
   this animation first, as they do for the phones, so its filled end state
   cannot hold the bar at full opacity while it is meant to be leaving.) */
html.is-handover .bar,
html.is-handover .bar-privacy,
html.is-handover .chrome-social { animation: settle-in 0.7s var(--ease) 0.2s both; }

/* ── The settled front page ──────────────────────────────────────────────
   The front page performs its entrance once per visit: on a fresh load or a
   refresh. Coming back to it from another page (handover.js decides) lands
   on the finished composition, and only the arrival fade plays, so the
   phones are simply there rather than climbing in again. */
html.is-settled .rv-w,
html.is-settled .swap-old, html.is-settled .swap-old::after,
html.is-settled .swap-new, html.is-settled .swap-new-i,
html.is-settled .rise, html.is-settled .hint { animation: none; }
html.is-settled .swap-old { opacity: 0.5; }
html.is-settled .swap-old::after { transform: scaleX(1); }
html.is-settled .field-phone {
  animation: settle-in 0.7s var(--ease) 0.05s both;
}
html.is-settled .stage > main { animation: settle-in 0.6s var(--ease) both; }
@keyframes settle-in { from { opacity: 0; } to { opacity: 1; } }

/* ── The aurora ────────────────────────────────────────────────────────────
   A ground that is one flat colour reads as a fill. This is what is behind
   the type on the pages that carry a long read: three enormous, heavily
   blurred bands in three hues off that page's own ground, screen-blended so
   they only ever add light, never a shape.

   --au is the page's own progress, 0 at the top and 1 at the end, written by
   the page's script. Everything scales off it, so the field gathers as you
   read rather than announcing itself on arrival.

   It drifts, slowly (54 to 78 seconds a cycle) and over a few percent of its
   own size. An aurora that is perfectly still is just a gradient; anything
   you can actually watch moving is a screensaver. This sits between the two
   on purpose, and holds still entirely under reduced motion.

   Each page sets its own three hues as space-separated RGB triples. The
   blur is 60px on top of gradients that are already soft, because the one
   thing that would make this look cheap is a visible edge anywhere. */
.aurora {
  position: fixed;
  inset: -12%;
  z-index: 0;
  pointer-events: none;
  opacity: calc(var(--au-base, 0.24) + var(--au, 0) * var(--au-gain, 0.76));
  filter: blur(60px);
  transition: opacity 1.1s linear;
}
.aurora span { position: absolute; display: block; border-radius: 50%; mix-blend-mode: screen; }
.au1 {
  left: -6%; right: 34%; top: 32%; bottom: -18%;
  background: radial-gradient(closest-side, rgb(var(--au-1, 150 110 255) / calc(0.20 + var(--au, 0) * 0.30)), transparent 72%);
  animation: au-a 54s ease-in-out infinite;
}
.au2 {
  left: 26%; right: 4%; top: 46%; bottom: -26%;
  background: radial-gradient(closest-side, rgb(var(--au-2, 88 214 200) / calc(0.10 + var(--au, 0) * 0.20)), transparent 70%);
  animation: au-b 67s ease-in-out infinite;
}
.au3 {
  left: 14%; right: 18%; top: 62%; bottom: -34%;
  background: radial-gradient(closest-side, rgb(var(--au-3, 255 150 210) / calc(0.07 + var(--au, 0) * 0.16)), transparent 68%);
  animation: au-c 78s ease-in-out infinite;
}
@keyframes au-a { 0%, 100% { transform: translate3d(0, 0, 0) scale(1); }    50% { transform: translate3d(3%, -4%, 0) scale(1.08); } }
@keyframes au-b { 0%, 100% { transform: translate3d(0, 0, 0) scale(1.04); } 50% { transform: translate3d(-4%, -3%, 0) scale(0.96); } }
@keyframes au-c { 0%, 100% { transform: translate3d(0, 0, 0) scale(0.98); } 50% { transform: translate3d(2%, -5%, 0) scale(1.1); } }
@media (prefers-reduced-motion: reduce) { .aurora span { animation: none; } }

/* ── 3. Furniture ────────────────────────────────────────────────────────── */

/* Top right corner, where the university link lives on the front page. */
.back-link {
  position: fixed;
  right: calc(clamp(16px, 3vw, 30px) + env(safe-area-inset-right, 0px));
  top: calc(clamp(18px, 2.6vw, 28px) + env(safe-area-inset-top, 0px));
  z-index: 30;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--ink-faint);
  text-decoration: none;
  transition: color 0.2s var(--ease);
  animation: mast-in 0.6s var(--ease) 0.5s both;
}
.back-link:hover { color: var(--ink); }
.back-link svg { width: 11px; height: 11px; }

.column {
  width: 100%;
  max-width: 980px;
  margin: 0 auto;
  padding: 140px 24px 150px;
  position: relative;
  z-index: 2;
}

.meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
  padding: 14px 0;
  border-top: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border);
  font-family: var(--display);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.meta-row .hot { color: var(--success-lift); }

/* Inner-page headings are Stack Sans Notch at weight 500. The front page's
   Notch 200 is hard to read at length, and Text Bold reads like any other
   grotesque; the middle of the variable axis keeps the notches and stays
   legible. Every heading on the inner pages sets these two lines. */
.page-title {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(40px, 6.8vw, 88px);
  line-height: 1.04;
  letter-spacing: -0.028em;
  margin: 40px 0 56px;
  text-wrap: balance;
  max-width: 11em;
}

/* The marker pen: solid green, dark ink, the verdict card's own pairing. */
.mark {
  display: inline;
  background: var(--success-fill);
  color: #06301A;
  font-weight: inherit;
  padding: 0.04em 0.18em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* ── One way to reach a person, at the foot of every inner page ────────────
   Letter, mission, team and teachers all end with this and the front page
   does not: the front page's job is the waitlist, and a second address under
   it would give a visitor two things to do at the moment they should have
   one. On the inner pages someone has just read several hundred words and
   the reasonable next thought is "who do I say that to".

   It sits ABOVE the foot rule, so it belongs to the page that has just ended
   rather than to the navigation that follows it. */
/* It sits IN the foot row now, between the two page links, at the owner's
   instruction: one line across the bottom of the page rather than a block
   above it. Centred and allowed to shrink, so at narrow widths it drops to
   its own line under the two links rather than squeezing them. */
.foot-say {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-family: var(--text);
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
  color: var(--ink-faint);
  padding: 0 16px;
}
@media (max-width: 780px) {
  .foot-say { order: 3; flex-basis: 100%; padding: 6px 0 0; text-align: left; }
}
.foot-say a, .say-hello a {
  color: var(--ink-muted);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color 0.24s var(--ease);
}
.foot-say a:hover, .foot-say a:focus-visible { color: var(--ink); }

.foot-nav {
  margin-top: 90px;
  padding-top: 22px;
  border-top: 1px solid var(--border-strong);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
/* The rule under a foot link draws out from the edge it points at: the back
   link's from the left, the forward link's from the right, so the underline
   travels the way the link does. A colour change alone is what a link does
   when nobody has thought about it. */
.foot-nav a {
  position: relative;
  color: var(--ink-faint);
  text-decoration: none;
  transition: color 0.28s var(--ease);
  padding-bottom: 3px;
}
.foot-nav a::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s var(--ease-cine);
}
.foot-nav a:last-child::after { transform-origin: right center; }
.foot-nav a:hover, .foot-nav a:focus-visible { color: var(--ink); }
.foot-nav a:hover::after, .foot-nav a:focus-visible::after { transform: none; }

/* The masthead links get the same treatment, tight to the caps. */
.mast-nav a { position: relative; }
.mast-nav a::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -5px; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s var(--ease-cine);
}
.mast-nav a:hover::after, .mast-nav a:focus-visible::after { transform: none; }
/* The page you are on keeps its rule permanently, so the bar says where you
   are without a second colour doing the work. */
.mast-nav a[aria-current="page"]::after { transform: none; opacity: 0.42; }

@media (prefers-reduced-motion: reduce) {
  .foot-nav a::after, .mast-nav a::after { transition-duration: 0.2s; }
}

/* ── The rail: a scrollbar drawn by the page ─────────────────────────────
   The native one is hidden on pages that scroll, and this stands in: a 2px
   hairline track, a soft thumb, and nothing else. rail.js keeps it honest. */
.has-rail { scrollbar-width: none; }
.has-rail::-webkit-scrollbar { display: none; }
.rail {
  position: fixed;
  top: 76px;
  right: 10px;
  bottom: 20px;
  width: 14px;
  z-index: 31;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.rail.is-on { opacity: 1; }
.rail::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 1px;
  margin-left: -0.5px;
  background: rgba(255, 255, 255, 0.14);
}
.rail-thumb {
  position: absolute;
  left: 50%;
  width: 3px;
  margin-left: -1.5px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.55);
  pointer-events: auto;
  cursor: grab;
  transition: width 0.2s var(--ease), margin-left 0.2s var(--ease), background 0.2s var(--ease);
}
.rail:hover .rail-thumb, .rail.is-dragging .rail-thumb {
  width: 5px; margin-left: -2.5px; background: rgba(255, 255, 255, 0.85);
}
.rail.is-dragging .rail-thumb { cursor: grabbing; }
@media (max-width: 780px), (pointer: coarse) { .rail { display: none; } .has-rail { scrollbar-width: auto; } .has-rail::-webkit-scrollbar { display: initial; } }

/* ── The nudge: a down arrow for a page that has not been scrolled ────────
   scroll.js shows it once, after a few seconds of nothing happening, and
   takes it away on the first scroll. It never comes back. */
.nudge {
  position: fixed;
  left: 50%;
  bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%) translateY(6px);
  z-index: 28;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.nudge.is-on { opacity: 1; transform: translateX(-50%) translateY(0); }
.nudge svg { width: 16px; height: 16px; animation: nudge-bob 1.9s var(--ease) infinite; }
@keyframes nudge-bob { 0%, 100% { transform: translateY(-3px); } 50% { transform: translateY(3px); } }
/* A phone on its side: the fold is so short the arrow lands on the copy. */
@media (max-height: 520px) { .nudge { display: none; } }

/* Lenis's own required rules (scroll.js). Without the height:auto pair a
   page with a 100svh stage can end up unscrollable under it. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

/* ── Reading speed ─────────────────────────────────────────────────────────
   scroll.js watches how fast the page is actually moving and stamps
   html.is-brisk while it is fast. A reader going through a long page at a
   flick's pace should find every heading and paragraph already sitting at
   full strength as it arrives, not still trailing in behind them — a float
   built for someone lingering reads as a page that cannot keep up when it
   is not given the time it was designed for. Someone reading slowly, or
   paused, still gets the fuller, more considered float these tokens
   default to. Sub-pages key their own .reveal/.ws-w durations off these
   two custom properties instead of a literal number, so both speeds are
   one edit away. */
:root { --rv-o: 0.85s; --rv-t: 1.15s; --rv-ws: 0.95s; }
/* THE SNAP IS GONE, and this is a reversal of the 2026-09-06 decision above.
   The reasoning there was sound in isolation — a fast reader should not find
   text still trailing in behind them — but the cure was to cut the reveal to
   a quarter of a second, and a quarter of a second is a CUT. Scrolling fast
   was the one time the site stopped being soft, which is the opposite of the
   way round it should be: fast scrolling is exactly when a page needs to
   feel like it has weight. One speed now, the considered one, everywhere.
   scroll.js still stamps is-brisk and nothing keys off it; left in place
   because the measurement is right even though the response was wrong. */

/* ── How things arrive ─────────────────────────────────────────────────────
   Every element on this site used to arrive the same way: up thirty-odd
   pixels, fading. One move, applied to headings, paragraphs, rules, numbers
   and faces alike. It is the reason the pages read as plain even once the
   mechanics were fixed — a page where everything moves identically has no
   hierarchy in its motion, and a long travel distance is what makes a reveal
   look cheap, because nothing in the physical world slides that far to settle.

   So: a vocabulary instead. Small travel, long tail, and a different arrival
   for each kind of thing, chosen to suit what the thing is.

     type          rises through a mask, because a line of type has a baseline
     body copy     barely moves; it is the reading, not the event
     a rule        draws itself from the left, the way a rule is made
     an eyebrow    settles its tracking, the way metal type is locked up
     a face        comes up in scale, because a circle has no baseline to rise
                   from and sliding one looks like a card, not a person

   --rv-y is the travel, once, for everything that travels. 12px, not 34. */
:root { --rv-y: 12px; }

/* A rule that draws itself. Replaces a border, so the element it is on keeps
   its own box: position:relative and no border, and the hairline is drawn by
   the pseudo-element instead. */
.rv-rule { position: relative; border-top-width: 0 !important; }
.rv-rule::before {
  content: "";
  position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: var(--rv-rule-colour, var(--border));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1.15s var(--ease-cine) var(--d, 0s);
}
.rv-rule.in::before { transform: none; }

/* An eyebrow locking up: a shade wider than its resting tracking, settling
   in. 0.22em and not more, because letter-spacing reflows: at 0.34em the
   longest label on the site ("WHAT THAT MEANS, IN PRACTICE") wrapped to two
   lines on a 320px phone and un-wrapped as it settled, and a reveal that
   changes the line count halfway through is worse than no reveal at all. */
.eyebrow.reveal { letter-spacing: 0.22em; }
.eyebrow.reveal.in {
  letter-spacing: 0.14em;
  transition: opacity var(--rv-o, 0.85s) var(--ease-float, var(--ease-cine)),
              transform var(--rv-t, 1.15s) var(--ease-float, var(--ease-cine)),
              letter-spacing 1.25s var(--ease-cine);
  transition-delay: var(--d, 0s);
}

/* A face, or anything else round: scale, not slide. */
.rv-pop { opacity: 0; transform: scale(0.94); transition: opacity 0.5s var(--ease), transform 0.6s var(--ease); }
.rv-pop.in {
  opacity: 1; transform: none;
  transition: opacity var(--rv-o, 0.85s) var(--ease-cine), transform var(--rv-t, 1.15s) cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--d, 0s);
}

@media (prefers-reduced-motion: reduce) {
  .rv-rule::before { transition-duration: 0.35s; }
  .eyebrow.reveal { letter-spacing: 0.14em; }
  .eyebrow.reveal.in { transition-duration: 0.35s, 0.35s, 0.35s; }
  .rv-pop { transform: scale(0.98); }
}

/* Reduced, not removed: the owner wants the site to still visibly move even
   for a visitor who has asked for less motion, so nothing here goes to
   "none". Instead every arrival keeps its stagger but travels a shorter
   distance in less time, and only the one purely ambient, looping bit (the
   idle nudge arrow, which carries no information) drops out. .masthead and
   .back-link already animate with a plain opacity fade (mast-in) and need
   no override at all. */
@media (prefers-reduced-motion: reduce) {
  .rv-w { animation: rv-word-soft 0.4s var(--ease) both; animation-delay: max(0ms, calc(var(--d, 0ms) - var(--lead, 0ms))); }
  .rise { animation-duration: 0.4s; }
  .swap-old { animation-duration: 0.3s; }
  .swap-old::after { animation-duration: 0.3s; }
  .swap-new { animation: swap-fade 0.3s var(--ease) both; animation-delay: max(0ms, calc(var(--strike, 2980ms) + 60ms - var(--lead, 0ms))); }
  .swap-new-i { animation: rv-word-soft 0.3s var(--ease) both; animation-delay: max(0ms, calc(var(--strike, 2980ms) + 60ms - var(--lead, 0ms))); }
  .nudge svg { animation: none; }
}
@keyframes rv-word-soft { from { opacity: 0; transform: translateY(30%); } to { opacity: 1; transform: none; } }
@keyframes swap-fade {
  from { opacity: 0; transform: translateX(-50%) rotate(-1.2deg) translateY(6px); }
  to   { opacity: 1; transform: translateX(-50%) rotate(-1.2deg); }
}
/* Under reduced motion nav.js uses this class instead of bs-leaving: only
   the wash, a plain fade to the next ground, so a page change is still
   never a cut while nothing on the page travels. */
.stage.bs-leaving-quiet .wash {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.4s linear;
}
@media (prefers-reduced-motion: reduce) {
  .masthead { transition: background-color 0.4s linear; }
}
