/* =========================================================================
   Troutwood financial institutions page
   Same field, same tokens, same rhythm as the homepage. Everything here is
   additive: tokens, primitives, nav, footer, hero, the trust panel and the
   close all come from tw-system.css. Organised by section, in page order.

   Three components here are the employers page's, near enough to be read as
   the same thing by anyone who visits both: the rollout rail, the two-chair
   split, and the fit grid. They are restated rather than shared because that
   is how the redesigned pages are built (see tw-employers.css and
   tw-advisors.css, which each carry their own copy) and because a shared
   component across three pages is a component nobody can change for one page
   without checking the other two.
   ========================================================================= */

/* -------------------------------------------------------------------------
   01. The gap
   The sibling pages put a single researched figure in this slot and let the
   list beside it carry the consequences. This page has no number it can stand
   behind in front of a regulated buyer, and inventing one for a bank is a
   worse idea here than on any other page we run.

   So the figure column holds a statement instead. Same box, same weight, same
   two-column relationship to the list: the panel is the argument and the list
   is what it costs. They are not peers and are not drawn as peers.
   ------------------------------------------------------------------------- */

.gap__body {
  margin-top: 1.75rem;
  font-size: var(--step-lead);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* 20rem for the figure column, not a fraction: the statement is set in
   vw-scaled type and a fractional column lets it collide with the list a long
   way before the layout is narrow enough to want stacking. */
.gap__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(2rem, 5vw, 4rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
  align-items: start;
}

.gap__figure {
  padding: clamp(1.75rem, 3vw, 2.5rem);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* Display type, a step under the section title so the panel never outranks
   the heading it sits below. The second sentence takes the accent, which is
   the same device the thesis uses: the turn in the sentence gets the colour
   rather than a weight change.

   Sage rather than sage-bright is safe here for the same reason it is on the
   employers page's number: this is well past the 24px large-text threshold,
   where sage clears contrast at 3.8:1 on the glow's brightest. */
.gap__lede {
  font-size: clamp(1.5rem, 1.05rem + 2vw, 2.375rem);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.gap__lede em {
  font-style: normal;
  color: var(--sage);
}

.gap__caption {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* Same device as the item closers below: white and the weight, on the two
   words the sentence turns on. */
.gap__caption strong {
  font-weight: 700;
  color: var(--text-primary);
}

.gap__items {
  display: grid;
  gap: 1.75rem;
}

.gap__item {
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
}

.gap__item:first-child {
  border-top: 0;
  padding-top: 0;
}

.gap__item p {
  margin-top: 0.5rem;
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* The closing sentence of each item is the consequence, and it is the part a
   skimming reader needs off the page. It takes white and the weight rather
   than the accent, so it reads as the sentence landing rather than a third
   colour competing with the lede in the panel beside it. */
.gap__item strong {
  font-weight: 700;
  color: var(--text-primary);
}

/* -------------------------------------------------------------------------
   02. Where we sit
   The page's showpiece, and the only place the argument is shown rather than
   stated. Both panels hold the same three parts. What changes between them is
   where the third one is standing: in the line, or under it.

   That is the whole reason this is a drawing and not a sentence. "We sit
   inside the relationship, not between you and it" is a spatial claim, and a
   spatial claim is cheap in prose and expensive to argue. Two diagrams settle
   it before the caption underneath is read.

   The left panel's lower half is empty on purpose. The right panel has
   something under the relationship and the left one has nothing, which is the
   point being made; filling it to balance the pair would cost the section its
   argument in exchange for symmetry.

   Both diagrams are aria-hidden. The captions state the claim in words
   directly underneath, so nothing here is the only carrier of meaning, and
   the labels inside are legibility for the drawing rather than content.
   ------------------------------------------------------------------------- */

.orbit__body {
  margin-top: 1.75rem;
  font-size: var(--step-lead);
  line-height: 1.55;
  color: var(--text-secondary);
}

.orbit__stage {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: 1rem;
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.orbit__panel {
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* A well rather than a transparent box. The panel is already a light surface
   on a dark band, and a diagram drawn straight onto it has no ground of its
   own: the rail and the nodes end up floating in the same plane as the copy
   underneath. Sinking the drawing back below the panel gives it one. */
.orbit__diagram {
  position: relative;
  aspect-ratio: 3 / 2;
  border-radius: calc(var(--radius) - 6px);
  background: rgba(4, 12, 7, 0.55);
  overflow: hidden;
}

/* The relationship. It runs the full span between the two nodes rather than
   stopping short of them, and the nodes are opaque and painted after it, so
   each end is covered by the thing it arrives at. One length to maintain
   instead of two insets that have to keep agreeing with a circle's radius at
   every panel width. */
.orbit__rail {
  position: absolute;
  top: 36%;
  left: 18%;
  right: 18%;
  height: 1px;
  background: var(--rule-strong);
}

/* Dead in the middle. The block already covers the centre of the rail, so
   this changes nothing a reader could point at and everything about how the
   panel reads: the line is strong where each party is holding it and has
   nothing left by the time it reaches whatever is standing between them. */
.orbit__panel--between .orbit__rail {
  background: linear-gradient(
    90deg,
    var(--rule-strong),
    rgba(255, 255, 255, 0.05) 40%,
    rgba(255, 255, 255, 0.05) 60%,
    var(--rule-strong)
  );
}

/* Lit, and lit from the middle: the relationship is the live thing in this
   panel, and the ends fade because what matters is that nothing interrupts
   the span. */
.orbit__panel--inside .orbit__rail {
  /* Two pixels heavier than the left panel's. It is the only line in this
     diagram now that the second one is gone, it is carrying traffic in both
     directions, and it is the thing the panel is claiming stays unbroken —
     a hairline says none of that. Pulled up by a pixel so the extra weight
     grows around 36% rather than hanging off the underside of it, which is
     where the nodes and the labels are centred. */
  height: 3px;
  transform: translateY(-1px);
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    rgba(103, 188, 124, 0.3),
    var(--sage) 50%,
    rgba(103, 188, 124, 0.3)
  );
}

/* Both nodes are one grid box holding the disc, with the caption hung off it
   absolutely. The node itself is centred on the rail by the translate, and
   because a transformed element is a containing block, the label positions
   against the disc rather than against the diagram. */
.orbit__node {
  position: absolute;
  top: 36%;
  transform: translate(-50%, -50%);
  display: grid;
  justify-items: center;
}

/* 18 and 82 rather than anything wider, and the number is set by the label
   rather than by the disc. The narrowest this diagram is ever drawn is the
   moment the stage takes its second column, where each panel is 19rem and the
   well inside it is about 264px; "Your customer" centred under a node any
   further out than 82% runs past the edge and the well clips it. */
.orbit__node--you {
  left: 18%;
}

.orbit__node--them {
  left: 82%;
}

/* Opaque, which is load-bearing: this is what covers the end of the rail. */
.orbit__dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--rule-strong);
  border-radius: 50%;
  background: #091810;
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1;
}

.orbit__panel--inside .orbit__dot {
  border-color: rgba(103, 188, 124, 0.45);
  color: var(--sage-bright);
}

.orbit__label {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-tertiary);
}

/* The interception. It is centred on the rail and painted over it, so the
   line does not pass through: whatever the institution sends stops here, and
   whatever the customer needs is answered here.

   Deliberately neutral rather than any colour in the palette. Every green and
   the one blue on this page belong to Troutwood, and the whole claim of the
   panel is that this box is somebody else. */
.orbit__block {
  position: absolute;
  /* On the rail, not in the middle of the well. The nodes, their labels and
     the line all sit at 36%, and a slab centred on the well's own midpoint
     hangs below every one of them. The min-height is the narrow two-column
     case, where 54% of a 176px well is shorter than the label standing up
     inside it. */
  top: 36%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 54%;
  min-height: 6rem;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  /* Three layers, and the bottom one is opaque on purpose: the two bounces
     run their last few percent behind the slab, and a translucent wall would
     show the pulse through the thing it is supposed to be stopped by. The
     value is the well's own colour resolved flat (#040c07 band, the panel's
     3.5% white, the well's 55% black), so an opaque wall still reads as the
     same wash it was when it lay across the rail.

     The courses are what makes it masonry rather than a tall pill. Neutral,
     like everything else about this box: every green on the page is
     Troutwood's, and the point of the slab is that it belongs to somebody
     else. */
  background-image:
    repeating-linear-gradient(
      0deg,
      rgba(255, 255, 255, 0.07) 0 1px,
      transparent 1px 10px
    ),
    linear-gradient(rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09));
  background-color: #080f0b;
  box-shadow: 0 14px 26px -16px rgba(0, 0, 0, 0.85);
  writing-mode: vertical-rl;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  color: var(--text-secondary);
}

/* What the institution sends and what the customer asks, each turned back at
   the slab. Two segments rather than one line through the middle, because a
   line through the middle is the arrangement this panel is arguing does not
   exist.

   The dot is a background rather than an element of its own, so the travel is
   a background-position sweep across the segment's own width: the segment
   scales with the panel and the throw scales with it, with no length written
   down twice. `alternate` is the bounce, and `ease-in` is what makes it one:
   the pulse accelerates into the slab and, with the easing mirrored on the
   return, springs off it and slows coming home.

   Both segments run a few percent past the slab's inner edge at every width
   this panel is drawn at, so the turn happens behind it rather than in front
   of it. The mask fades each end, which keeps the dot from appearing out of
   nothing at the node it left. */
.orbit__bounce {
  position: absolute;
  top: 36%;
  height: 5px;
  transform: translateY(-50%);
  background-image: radial-gradient(
    circle closest-side,
    rgba(236, 247, 240, 0.95),
    rgba(236, 247, 240, 0.35) 55%,
    transparent 100%
  );
  background-size: 5px 5px;
  background-repeat: no-repeat;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  animation: orbit-bounce 2.4s ease-in infinite alternate;
}

.orbit__bounce--you {
  left: 18%;
  right: 52%;
}

/* Mirrored: this one starts at the customer, so it runs the sweep backwards
   and out of step with the other. Nothing about the claim depends on the two
   being synchronised, and two pulses hitting the slab together reads as one
   event rather than two parties each getting the same answer. */
.orbit__bounce--them {
  left: 52%;
  right: 18%;
  animation-direction: alternate-reverse;
  animation-duration: 2.9s;
}

@keyframes orbit-bounce {
  from {
    background-position: 0% 50%;
  }
  to {
    background-position: 100% 50%;
  }
}

/* The ground. Under both nodes, under the rail, and touching neither: what
   the relationship stands on rather than a station along it. Its whole
   argument is positional, so it needs no box drawn around it. A panel of its
   own would have made it a third object in the diagram, which is the one
   thing this panel is arguing Troutwood is not.

   The wordmark itself rather than the name set in Inter. Every other label in
   these two diagrams is a description of a party ("You", "Their app") and is
   typed; this is the mark, and setting it in body type beside the real one in
   the nav reads as a near-miss of the brand rather than as the brand.

   The paths are the nav's, with `fill="white"` swapped for `currentColor` so
   the colour lives here. White, which is the mark's own colour everywhere
   else on the site: the accent is what the diagram uses to say a thing is
   live, and the wordmark is not one of the moving parts. Leaving it in the
   brand's white also keeps it identical to the one in the nav a screen
   above. */
.orbit__mark {
  position: absolute;
  left: 50%;
  top: 78%;
  transform: translate(-50%, -50%);
  display: block;
  width: 58%;
  height: auto;
  color: var(--text-primary);
}

/* A light travelling the rail. It exists to say the right-hand panel is live
   rather than merely greener: the difference between the two arrangements is
   the whole section, and a static contrast reads as a colour choice.

   Same construction as the employers page's sweep, for the same reason: one
   masked element rather than a moving box, so nothing here is animating a
   property that costs layout. Switched off under reduced motion at the foot
   of this file. */
.orbit__flow {
  position: absolute;
  top: 36%;
  left: 18%;
  right: 18%;
  height: 3px;
  transform: translateY(-1px);
  border-radius: 2px;
  background: #d8f6e2;
  box-shadow: 0 0 8px rgba(216, 246, 226, 0.75);
  -webkit-mask-image: linear-gradient(90deg, transparent 36%, #000 50%, transparent 64%);
  mask-image: linear-gradient(90deg, transparent 36%, #000 50%, transparent 64%);
  -webkit-mask-size: 300% 100%;
  mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  animation: orbit-flow 5.5s linear infinite;
}

@keyframes orbit-flow {
  from {
    -webkit-mask-position: 0% 0;
    mask-position: 0% 0;
  }
  to {
    -webkit-mask-position: 100% 0;
    mask-position: 100% 0;
  }
}

/* The other half of the caption, on the same line the rail already draws.
   Guidance goes out and progress comes back, so the traffic is two-way: two
   dots leaving the institution and two leaving the customer, staggered so
   the line reads as a run of traffic rather than a pair of headlights.

   No second line to carry it. A lane of its own put a stray rule under the
   diagram, and a lane joined to the nodes closed three sides of a box — and
   a box is an object, which is the one thing this panel is arguing Troutwood
   is not. Both directions on the one line is the claim: nothing in the middle
   to stop them, which is exactly what the slab does on the other panel.

   Same construction as the bounce over there, and the likeness is the
   argument: identical dots, one pair turned back at a wall and these four
   running the full span. Linear and unalternating, because this is traffic
   rather than a rebound. The mask fades both ends, so each dot arrives out of
   the node it left and the loop restarts out of sight instead of popping back
   to where it started. */
.orbit__particle {
  position: absolute;
  top: 36%;
  left: 18%;
  right: 18%;
  height: 5px;
  transform: translateY(-50%);
  background-image: radial-gradient(
    circle closest-side,
    rgba(216, 246, 226, 0.95),
    rgba(143, 217, 163, 0.4) 55%,
    transparent 100%
  );
  background-size: 5px 5px;
  background-repeat: no-repeat;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  animation: orbit-bounce 4.2s linear infinite;
}

/* Prime numbers of a sort: durations that do not divide into each other, so
   the four never fall back into step and the line never looks metered. */
.orbit__particle--out-late {
  animation-duration: 5.6s;
  animation-delay: -2.3s;
}

.orbit__particle--back {
  animation-duration: 5.1s;
  animation-direction: reverse;
}

.orbit__particle--back-late {
  animation-duration: 3.7s;
  animation-delay: -1.4s;
  animation-direction: reverse;
}

.orbit__caption {
  margin-top: 1.125rem;
}

.orbit__caption h3 {
  font-size: var(--step-body);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.015em;
}

.orbit__caption p {
  margin-top: 0.375rem;
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* -------------------------------------------------------------------------
   03. The engine
   Four claims in one grid, two by two, because the section's argument is that
   they turn together. Four cards in a row would read as a feature list and a
   feature list is exactly what this is not: each item is the same claim seen
   from a different side of the institution.
   ------------------------------------------------------------------------- */

.engine__body {
  margin-top: 1.5rem;
  font-size: var(--step-lead);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* 22rem, so this lands at two columns and stays there. At the 14rem the fit
   grid uses it would go to four across on a wide viewport, and these
   paragraphs are twice the length that column can carry. */
.engine__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: clamp(2rem, 3.5vw, 3rem);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
}

/* The trust panel's tile, restated. Same construction and the same reason for
   it: a glyph in a fixed box gives every item a shoulder to start from, which
   is most of what stops a grid of paragraphs reading as loose copy. The light
   family is a 300 weight, so it carries a larger size than a solid glyph
   would need or it thins out to nothing against the field. */
.engine__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-bottom: 1.25rem;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: rgba(103, 188, 124, 0.12);
  color: var(--sage-bright);
  font-size: 1.25rem;
  line-height: 1;
}

.engine__item p {
  margin-top: 0.625rem;
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* -------------------------------------------------------------------------
   04. Rollout
   Four steps. The rule and its lit node are drawn per step rather than as one
   rail element behind the row: side by side they line up into a single rail,
   and when the grid wraps each step keeps its own marker instead of trailing
   a line to nowhere.

   The node runs sage to sky across the four, which is the same direction the
   platform page gives the round trip: expertise out, signal back.
   ------------------------------------------------------------------------- */

.rollout__body {
  margin-top: 1.5rem;
  font-size: var(--step-lead);
  color: var(--text-secondary);
}

.rollout__steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: clamp(1.75rem, 3vw, 2.5rem);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
}

.rollout__step {
  position: relative;
  padding-top: 2rem;
}

.rollout__step::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--rule);
}

.rollout__step::after {
  content: '';
  position: absolute;
  top: -3px;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--node, var(--sage));
  box-shadow: 0 0 12px var(--node, var(--sage));
}

.rollout__step:nth-child(2) {
  --node: #6ab894;
}

.rollout__step:nth-child(3) {
  --node: #63b0c6;
}

.rollout__step:nth-child(4) {
  --node: var(--sky);
}

.rollout__num {
  display: block;
  font-size: var(--step-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--text-tertiary);
}

.rollout__step h3 {
  margin-top: 0.625rem;
}

.rollout__step p {
  margin-top: 0.5rem;
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* -------------------------------------------------------------------------
   05. Both sides
   Two panels of equal weight. Not a comparison table: the same programme seen
   from the two people who have to live with it.
   ------------------------------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
  gap: 1rem;
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.split__panel {
  /* A token rather than a literal because the cover below has to cancel it
     exactly on three sides, and two clamps that have to agree are one clamp
     waiting to disagree. */
  --panel-pad: clamp(1.5rem, 3vw, 2.5rem);
  position: relative;
  padding: var(--panel-pad);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  overflow: hidden;
}

/* One lit edge per panel. The eyebrow above it names which side this is, so
   the colour reinforces a label rather than carrying one on its own. */
.split__panel::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
}

.split__panel--customer::before {
  background: linear-gradient(to right, var(--sage), rgba(103, 188, 124, 0));
}

/* --sky is a 3.5:1 colour at the glow's brightest: it can hold a 2px rule and
   it cannot hold type, so the eyebrow above it stays sage. */
.split__panel--team::before {
  background: linear-gradient(to right, var(--sky), rgba(90, 167, 248, 0));
}

/* A crop of the real thing across the head of each panel, cut tight enough to
   read at panel width. Section 06 then shows both screens whole, so the page
   runs detail, then object. That order is deliberate: the detail is small
   enough to take in while reading the list beside it, and by the time the
   whole screen arrives the reader already knows what to look for on it.

   No radius and no negative bottom margin: the panel is already clipped and
   already rounded, so the cover inherits both by being inside it, and the two
   sides only have to agree about one number.

   No mount either, and that is the reason these are the one place on the page
   a capture is not framed as a device. A cover bleeds to three edges of the
   panel it is in, which is what makes it read as the head of the panel rather
   than as a picture inside it; a bezel drawn around it would be a second
   frame inside a frame that is already there. What it does get is more air
   under it than the panel's own padding, because the eyebrow directly below
   is the first line of a different thought. */
.split__cover {
  margin: calc(var(--panel-pad) * -1) calc(var(--panel-pad) * -1) calc(var(--panel-pad) + 0.75rem);
  border-bottom: 1px solid var(--rule);
}

.split__cover img {
  display: block;
  width: 100%;
  height: auto;
}

.split__title {
  margin-top: 1rem;
  font-size: var(--step-h2);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.025em;
}

.split__list {
  display: grid;
  gap: 1.5rem;
  margin-top: clamp(1.75rem, 3vw, 2.25rem);
  padding-top: clamp(1.75rem, 3vw, 2.25rem);
  border-top: 1px solid var(--rule);
}

.split__list h4 {
  margin: 0;
  font-size: var(--step-body);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.015em;
}

.split__list p {
  margin-top: 0.375rem;
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* -------------------------------------------------------------------------
   06. Both screens
   The section above argues the two chairs in words and the panel covers give
   each one a detail; this gives both the whole screen. It is the only place
   on the page the product is shown at size, and it is the last band before
   the page comes back onto the open field.

   The mount is `.shot`, in tw-system.css. Everything here is the arrangement.
   ------------------------------------------------------------------------- */

.screens__body {
  margin-top: 1.5rem;
  font-size: var(--step-lead);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* Full measure, and it has to be. A console capture is a 2x shot of a 1440px
   viewport, so its body text lands near 11px across 1136px of layout and
   under 6px at a third of it. Half a console is not half as convincing; it is
   a picture of a console. */
.screens__lead {
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

/* Two handsets, held to 36rem so each track lands on the phone's own width.
   That is what puts the left edge of a caption under the left edge of the
   device it belongs to; run across the full measure the tracks would be twice
   the handset and every caption would start out in space beside it.

   The 3rem gutter is the same one the platform page's phone row uses, and it
   is doing the same job: two mounted devices a rule's width apart read as one
   object split down the middle. */
.screens__pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  justify-items: center;
  gap: clamp(2.25rem, 4.5vw, 3.5rem) 3rem;
  max-width: 36rem;
  margin: clamp(2.75rem, 5vw, 4rem) auto 0;
}

.screens__phone {
  width: 100%;
  max-width: 16.5rem;
}

/* Below this the pair is two 200px handsets, which is under what the app's
   own type survives. One column, a little smaller, and the captions come back
   under the middle. */
@media (max-width: 640px) {
  .screens__pair {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2rem, 6vw, 3rem);
  }

  .screens__phone {
    max-width: 15rem;
  }

  .screens__pair .shot__caption {
    text-align: center;
    justify-self: center;
  }
}

/* -------------------------------------------------------------------------
   07. The rotator
   The console shot cycles through four views rather than standing on one. A
   bank's objection to a screenshot is never "I don't believe you", it is "that
   is one screen and I have to run a book on it", and four views answer that in
   the only way a still image can.

   The mechanics that matter are in tw-rota.js. What lives here is the stack,
   the crossfade, and the control row underneath. The timing is declared here
   too, as custom properties the script reads, so a change of pace is a change
   in one file rather than two.
   ------------------------------------------------------------------------- */

/* On the <figure>, not on the stack: the control row and the caption's
   changing half both sit outside the mount, so the element the script is
   handed has to be the one that contains all three. It therefore carries no
   layout of its own — `.shot` is already the grid these rows sit in. */
.rota {
  --rota-dwell: 5200ms;
  --rota-fade: 700ms;
}

.rota__stack {
  display: grid;
}

/* Every frame in the same cell, so the stack is exactly as tall as the images
   and there is no aspect-ratio to keep in agreement with the files. All four
   are cut to one rectangle in the pipeline, so nothing here can reflow between
   slides even before an image has arrived: the width and height attributes
   are identical across the set. */
.rota__slide {
  grid-area: 1 / 1;
  position: relative;
  z-index: 0;
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  transition: opacity var(--rota-fade) var(--ease-out);
}

.rota__slide.is-current {
  z-index: 2;
  opacity: 1;
}

/* The outgoing frame, held at full opacity underneath the one fading in over
   it. Without this both frames are half-transparent at the midpoint of the
   dissolve and the frame's own ground shows through, which is a grey dip in
   the middle of every transition. The script drops the class once the new
   frame is opaque. */
.rota__slide.is-under {
  z-index: 1;
  opacity: 1;
  transition: none;
}

/* ---- The control row ---------------------------------------------------
   Four labelled buttons rather than a row of anonymous dots. A dot says only
   that there is more; a name says what the next view is, which is the whole
   reason a reader would wait for it.

   It sits outside the mount rather than over the capture. A control strip
   floated on top of a screenshot covers the product to advertise itself, and
   on a shot this size it would land on live interface. */
.rota__bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  margin-top: clamp(1.25rem, 2vw, 1.75rem);
}

.rota__tab {
  flex: 1 1 7rem;
  max-width: 12rem;
  display: grid;
  gap: 0.625rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--text-tertiary);
  text-align: left;
  cursor: pointer;
  transition: color 220ms var(--ease);
}

.rota__tab:hover,
.rota__tab.is-current {
  color: var(--text-primary);
}

.rota__tab:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 4px;
  border-radius: 4px;
}

/* The track is the tab's whole hit area at the top edge, which is what lets a
   2px rule be a 44px touch target without a 44px rule. */
.rota__track {
  position: relative;
  height: 2px;
  background: var(--rule);
  border-radius: 2px;
}

.rota__track::before {
  content: '';
  position: absolute;
  inset: -1rem 0 -0.5rem;
}

/* The dwell, drawn. This is the part that makes the rotation read as a
   deliberate sequence rather than as something that keeps changing while you
   are trying to look at it: the reader can see how long the frame has left,
   so the change is expected rather than arriving at them.

   It is also the clock. The script advances on this animation's `animationend`
   rather than on a timer of its own, which is why the bar can never disagree
   with the frame it is measuring: there is only one thing counting. Pausing is
   then `animation-play-state` and nothing else, and it resumes from where it
   stopped instead of starting the frame over.

   Because the animation is declared on `.is-current`, moving that class from
   one tab to the next restarts the fill for free. No reflow trick, and no way
   for a stale animation to survive a frame change. */
.rota__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  border-radius: 2px;
  background: var(--sage);
  box-shadow: 0 0 10px var(--accent-glow);
  transform: scaleX(0);
  transform-origin: left;
}

.rota.is-live .rota__tab.is-current .rota__fill {
  animation: rota-fill var(--rota-dwell) linear forwards;
}

/* Held: the pointer is over the shot, something inside it has focus, or the
   tab is in the background. The sweep stops where it is. */
.rota.is-live.is-held .rota__tab.is-current .rota__fill {
  animation-play-state: paused;
}

/* A rotator that is not running at all still marks which frame is showing, at
   the full width of the track: this is the state a reader arrives in under
   reduced motion, and a bar sitting at zero would read as one that has not
   started rather than as a marker. */
.rota:not(.is-live) .rota__tab.is-current .rota__fill {
  transform: scaleX(1);
}

@keyframes rota-fill {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

.rota__name {
  font-size: var(--step-eyebrow);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* ---- The caption's changing half ---------------------------------------
   All four lines share one grid cell, so the box is as tall as the longest of
   them and swapping the text moves nothing under it. Reserving the height any
   other way would mean measuring at runtime and getting it wrong on the first
   paint. */
.rota__lines {
  display: grid;
  margin-top: 0.25rem;
}

.rota__line {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity 380ms var(--ease-out);
}

.rota__line.is-current {
  opacity: 1;
}

/* -------------------------------------------------------------------------
   08. Fit
   ------------------------------------------------------------------------- */

.fit__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: clamp(1.75rem, 3vw, 2.5rem);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
}

.fit__rule {
  width: 2rem;
  height: 2px;
  margin-bottom: 1.125rem;
  background: var(--sage);
  box-shadow: 0 0 14px var(--accent-glow);
}

.fit__item p {
  margin-top: 0.625rem;
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* -------------------------------------------------------------------------
   09. Partners
   Education and research partners, not customers, and the heading says so.
   A row of logos on a page pitched at a bank is read as a client list by
   default, and these are not one.

   The artwork is white on transparent, so it needs no filter to sit on the
   field; the opacity step is there to keep four wordmarks from outranking the
   copy around them.
   ------------------------------------------------------------------------- */

.partners__body {
  margin-top: 1.5rem;
  font-size: var(--step-lead);
  color: var(--text-secondary);
}

.partners__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(2rem, 5vw, 3.75rem);
  margin-top: clamp(2.25rem, 4vw, 3rem);
  padding-top: clamp(2.25rem, 4vw, 3rem);
  border-top: 1px solid var(--rule);
}

/* Height-normalised, width auto. The four files run from 1.7:1 to 5.2:1, so a
   shared width would set the squarest one three times the optical size of the
   widest. */
.partners__row img {
  height: clamp(28px, 4.5vw, 38px);
  width: auto;
  opacity: 0.72;
  transition: opacity 220ms var(--ease);
}

.partners__row img:hover {
  opacity: 1;
}

/* -------------------------------------------------------------------------
   10. Reduced motion
   The two diagrams are the only things on this page that move of their own
   accord. What each one is drawing attention to — a line stopped at a wall,
   and an unbroken one carrying traffic both ways — is still there with the
   animation gone, so the section makes its point either way.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .orbit__flow {
    animation: none;
    -webkit-mask-image: none;
    mask-image: none;
    opacity: 0.3;
  }

  /* Parked rather than hidden, and parked short of the ends: a dot stopped
     at 0% or 100% is either sitting under a node or hidden behind the slab,
     and both read as a rendering fault rather than a still frame. These sit
     where the drawing still says what it meant — the two on the left panel
     bearing down on a slab they do not get past, the two on the right out on
     the path in opposite directions. */
  .orbit__bounce,
  .orbit__particle {
    animation: none;
  }

  .orbit__bounce--you {
    background-position: 84% 50%;
  }

  .orbit__bounce--them {
    background-position: 16% 50%;
  }

  .orbit__particle--out {
    background-position: 72% 50%;
  }

  .orbit__particle--out-late {
    background-position: 44% 50%;
  }

  .orbit__particle--back {
    background-position: 28% 50%;
  }

  .orbit__particle--back-late {
    background-position: 58% 50%;
  }

  /* The script already refuses to auto-advance here, so this is belt and
     braces for the fill rather than the mechanism. The frames still cross-fade
     when a reader presses a control, because that is a response to their own
     action rather than something happening at them. */
  .rota__fill {
    animation: none;
  }
}

/* -------------------------------------------------------------------------
   11. Print
   ------------------------------------------------------------------------- */

@media print {
  .orbit__diagram {
    display: none;
  }

  /* One frame on paper, and no controls: the rest of the stack is the same
     shot four times and a row of buttons nobody can press. */
  .rota__slide:not(.is-current),
  .rota__bar {
    display: none;
  }

  .rota__line:not(.is-current) {
    display: none;
  }

  .gap__figure,
  .orbit__panel,
  .split__panel {
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
