/* =========================================================================
   Troutwood platform page
   Same field, same tokens, same rhythm as the homepage. Everything here is
   additive: tokens, primitives, nav, footer, hero and close all come from
   global.css. Organised by section, in page order.
   ========================================================================= */

/* -------------------------------------------------------------------------
   01. Hero
   The homepage hero owns a full viewport because nothing follows it but
   type. Here it gives up a little height so the section under it breaks the
   fold and the page reads as having somewhere to go.
   ------------------------------------------------------------------------- */

.hero--platform {
  min-height: 88svh;
}

/* -------------------------------------------------------------------------
   02. The bridge
   Type only: the claim, then a two-item legend naming each direction the
   system runs in.
   ------------------------------------------------------------------------- */

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

/* The two streams are named as well as coloured, because colour on its own is
   never allowed to carry the meaning. */
.bridge__dot {
  display: block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}

.bridge__dot--out {
  background: var(--sage);
  box-shadow: 0 0 12px rgba(103, 188, 124, 0.75);
}

.bridge__dot--back {
  background: var(--sky);
  box-shadow: 0 0 12px rgba(90, 167, 248, 0.75);
}

.bridge__legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 0.875rem clamp(1.5rem, 4vw, 3rem);
  max-width: 58rem;
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
  font-size: var(--step-small);
  line-height: 1.55;
  color: var(--text-tertiary);
}

/* Hanging indent, so a wrapped second line clears the dot instead of running
   back under it. */
.bridge__legend li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.5rem;
}

.bridge__legend .bridge__dot {
  margin-top: 0.55em;
}

.bridge__legend strong {
  color: var(--text-primary);
  font-weight: 600;
}

/* -------------------------------------------------------------------------
   03. The round trip
   A diagram rather than a list: four nodes, a tinted segment and an arrowhead
   in each gap, and a bracket underneath carrying the flow back to the start.
   The four stops used to carry a heading and two or three sentences each,
   which made the page argue its case twice — once here in miniature and again
   in the sections below, where it belongs. A stop now carries a mark, a name,
   and where it happens, and nothing else.

   The segments, the arrowheads and the bracket are all decoration. The
   sequence is an <ol> and reads correctly with none of it.

   The line between the nodes is drawn per gap rather than as one rail behind
   the row. A single rail would have to be hidden where it crosses a node,
   which means painting the node opaque in the band's own tone and keeping the
   two in step by hand. Segments let the node stay a wash the field shows
   through, which is also the better-looking of the two.

   Three custom properties do the arithmetic. --loop-gap has to be the grid
   gap itself, because the segments and the bracket are positioned against
   the space between columns; --loop-node has to be the node's diameter,
   because the segments sit on its centre line and start at its edge; and
   --loop-tint is the node's accent as a bare RGB triple, so one number per
   node covers its ring, its wash, and the segment leaving it.

   A single mark travels the whole circuit, so --loop-arc-h joins them: the
   bracket's height is also its corner radius and also the radius the mark
   turns on, and one number keeps the three in step.
   ------------------------------------------------------------------------- */

.loop {
  --loop-gap: clamp(1.25rem, 2.5vw, 2.25rem);
  --loop-node: clamp(3.5rem, 5.5vw, 4.5rem);
  /* Where the bracket's two ends sit. Declared here rather than on the arc so
     the arrowhead that marks its left end reads the same number: a custom
     property set on the arc would not be visible to its sibling. */
  --loop-arc-inset: calc(12.5% - var(--loop-gap) * 0.375);
  /* The bracket's height, which is also its corner radius, which is also the
     radius the travelling mark turns on. Declared here for the same reason as
     the inset above: the mark is not a child of the arc and cannot read a
     property set on it. */
  --loop-arc-h: clamp(1.5rem, 3vw, 2.25rem);
  --loop-pulse-dur: 15s;
  position: relative;
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
}

.loop__steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--loop-gap);
}

/* The two run-outs: a stub of rail before the first node and after the last,
   each fading to nothing at the edge of the row. The line the four stops sit
   on is longer than the four stops, which is the whole claim — the loop was
   running before this diagram picked it up and it keeps running after.

   They reach to the outer edge of the row, so their length is the distance
   from there to the first node's rim: the node centre sits at --loop-arc-inset
   (the same number the return bracket's ends use) and the rim is one radius
   inside that. */
.loop__steps::before,
.loop__steps::after {
  content: '';
  position: absolute;
  top: calc(var(--loop-node) / 2);
  width: calc(var(--loop-arc-inset) - var(--loop-node) / 2 - 0.75rem);
  height: 1px;
}

.loop__steps::before {
  left: 0;
  background: linear-gradient(to right, transparent, rgba(103, 188, 124, 0.45));
}

.loop__steps::after {
  right: 0;
  background: linear-gradient(to left, transparent, rgba(90, 167, 248, 0.45));
}

.loop__step {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 0.375rem;
  align-content: start;
  text-align: center;
}

/* Sage on the way out, sky on the way back, which is the same pairing the
   bridge legend above this section names in words. The colour turns where
   guidance becomes action, so the segment out of Delivered is the one that
   carries the change and every arrowhead is already the colour of the node
   it points at.

   --sky is a decoration token and never carries text. An aria-hidden glyph
   is decoration; the label under it is --text-primary either way. */
.loop__step:nth-child(-n + 2) {
  --loop-tint: 103, 188, 124;
  --loop-ink: var(--sage-bright);
}

.loop__step:nth-child(n + 3) {
  --loop-tint: 90, 167, 248;
  --loop-ink: var(--sky);
}

.loop__step:nth-child(1) {
  --loop-tint-next: 103, 188, 124;
}

.loop__step:nth-child(2),
.loop__step:nth-child(3) {
  --loop-tint-next: 90, 167, 248;
}

/* The segment, node edge to node edge with a little air at each end. The
   nodes are centred in equal columns, so the span is one column plus one gap
   less one node diameter, whatever those work out to. */
.loop__step:not(:last-child)::before {
  content: '';
  position: absolute;
  top: calc(var(--loop-node) / 2);
  left: calc(50% + var(--loop-node) / 2 + 0.75rem);
  width: calc(100% + var(--loop-gap) - var(--loop-node) - 1.5rem);
  height: 1px;
  background: linear-gradient(
    to right,
    rgba(var(--loop-tint), 0.45),
    rgba(var(--loop-tint-next), 0.6)
  );
}

/* The arrowhead, capping the segment just short of the node it arrives at, so
   the row reads as a direction rather than as four things in a line. */
.loop__step:not(:last-child)::after {
  content: '';
  position: absolute;
  top: calc(var(--loop-node) / 2);
  left: calc(100% + var(--loop-gap) - var(--loop-node) / 2 - 0.75rem);
  width: 7px;
  height: 7px;
  border-top: 1px solid rgba(var(--loop-tint-next), 0.75);
  border-right: 1px solid rgba(var(--loop-tint-next), 0.75);
  transform: translate(-70%, -50%) rotate(45deg);
}

/* The node. Round rather than the 12px tile the trust and Underneath panels
   use, because here the mark is a stop on a path and not the shoulder a
   paragraph starts from. A wash rather than a fill, so the band reads through
   it. The glyphs are the light family, a 300 weight, so they carry a size the
   solid weight would not need. */
.loop__node {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--loop-node);
  height: var(--loop-node);
  margin-bottom: 0.75rem;
  border: 1px solid rgba(var(--loop-tint), 0.34);
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 32%,
    rgba(var(--loop-tint), 0.22),
    rgba(var(--loop-tint), 0.05) 72%
  );
  color: var(--loop-ink);
  font-size: clamp(1.125rem, 1.8vw, 1.375rem);
  line-height: 1;
}

/* Uppercase, which is the eyebrow's device rather than a heading's. These are
   labels on a diagram: they name a stop, and setting them the way the section
   headings are set would give four of them the weight of a heading each. */
.loop__label {
  font-size: var(--step-fine);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-primary);
}

.loop__meta {
  font-size: var(--step-small);
  line-height: 1.45;
  color: var(--text-tertiary);
}

.loop__return {
  position: relative;
  margin-top: clamp(1.75rem, 3.5vw, 2.75rem);
}

/* The arrowhead arriving back at the first node, on the bracket's left end
   and pointing up into it. It lives on the wrapper rather than on the arc
   because the arc is masked, and a mask clips what is inside the element. */
.loop__return::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--loop-arc-inset);
  width: 7px;
  height: 7px;
  border-top: 1px solid rgba(103, 188, 124, 0.7);
  border-left: 1px solid rgba(103, 188, 124, 0.7);
  transform: translate(-50%, -40%) rotate(45deg);
}

/* Three sides of a rounded box: the flow leaving the last node, running back
   under the row, and arriving at the first.

   Painted sky to sage — the direction of travel, reversed — with the standard
   gradient-border mask: the gradient fills the border box, and the mask
   subtracts the padding box, leaving only the ring. Dropping the top border
   makes the padding box reach the top edge, which is what opens the bracket.

   The ends land under the first and last node centres rather than at a round
   percentage. A column is (100% - 3g) / 4 wide, so its centre sits at
   12.5% - 3g/8. */
.loop__return-arc {
  display: block;
  height: var(--loop-arc-h);
  margin-inline: var(--loop-arc-inset);
  border: 1px solid transparent;
  border-top: 0;
  border-radius: 0 0 var(--loop-arc-h) var(--loop-arc-h);
  background: linear-gradient(
      to left,
      rgba(90, 167, 248, 0.55),
      rgba(120, 180, 190, 0.5) 50%,
      rgba(103, 188, 124, 0.55)
    )
    border-box;
  -webkit-mask:
    linear-gradient(#000 0 0) padding-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) padding-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
}

/* The line the section closes on. It reads as a caption to the bracket above
   it rather than as a second statement under the headline, so it sits at body
   size and takes what emphasis it has from the weight instead of the scale.
   The two halves are set apart the way the two directions are everywhere else
   on this page: the trip out is stated, the return is the point. */
.loop__return-label {
  max-width: 34rem;
  margin: 1.125rem auto 0;
  text-align: center;
  font-size: var(--step-body);
  line-height: 1.4;
  color: var(--text-secondary);
}

.loop__return-label strong {
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

/* The travelling marks -------------------------------------------------
   Three signals circulating the diagram a third of a lap apart: out along the
   rail, a beat at each stop, then down and round the bracket and back to
   Guidance. The claim the section makes in words, made once more in motion.

   They are three copies of one animation on one clock, separated only by
   --loop-pulse-offset, so nothing has to be kept in step by hand.

   Each span is a rail, not a dot: it stretches between the first and last
   node centres and carries the visible mark on its own left edge as a pair of
   pseudo-elements. That is what lets the motion run on `translate` alone —
   a percentage there resolves against the span's own width, which is exactly
   the distance to travel, so the mark crosses the row without a single
   animated `left` and without touching layout on any frame.

   Two spans, because the row and the bracket sit in different positioning
   contexts and neither can reach the other. They run one timeline: the rail
   fades out parked on Progress while the bracket's mark fades in directly
   below it, and the reverse under Guidance. The vertical between them is the
   one stretch of the circuit the diagram never draws — the step copy is in
   the way — so the mark crosses it the way the arrowhead does, by implication.

   The rail span sits ahead of the row in source order, so it paints under the
   nodes and goes behind each one. The node is a wash rather than a fill, so
   the mark reads straight through it, and the node answers with a bloom of
   its own on the same beat. The stop lights up as the signal reaches it,
   which is the whole point of drawing the thing as a circuit.

   Timings are path length, not guesswork. The rail is half the circuit and
   takes the first half of the cycle; the bracket's corners are each about 6%
   of the return and take 50-53% and 97-100%, with the straight run between.
   The corners are stepped every 30 degrees, which is under a pixel off a true
   arc at this radius. */

.loop__pulse {
  /* How far behind the leading mark this one runs. Set on the span rather
     than as an animation-delay so the pseudo-elements, which carry a beat of
     their own and inherit nothing from the shorthand, can read the same
     number. */
  --loop-pulse-offset: 0s;
  position: absolute;
  left: var(--loop-arc-inset);
  right: var(--loop-arc-inset);
  height: 0;
  pointer-events: none;
  animation-duration: var(--loop-pulse-dur);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-delay: var(--loop-pulse-offset);
}

/* A third and two thirds of a circuit back. Three marks divide the loop into
   equal stretches: two are on the rail whenever one is under the bracket, so
   the row keeps its rhythm without any of them crowding the next. */
.loop__pulse--lag-1 {
  --loop-pulse-offset: calc(var(--loop-pulse-dur) * -0.33333);
}

.loop__pulse--lag-2 {
  --loop-pulse-offset: calc(var(--loop-pulse-dur) * -0.66667);
}

/* On the node centre line, the same line the segments and run-outs sit on. */
.loop__pulse--rail {
  top: calc(var(--loop-node) / 2);
  animation-name: loop-pulse-tint, loop-pulse-rail;
}

/* The bracket's top edge, which is where its two open ends are. */
.loop__pulse--return {
  top: 0;
  animation-name: loop-pulse-tint, loop-pulse-return;
}

/* The mark itself: a core and a blurred halo, both on currentColor so the one
   colour keyframe on the span drives both. `translate` centres them on the
   span's left edge and `scale` does the beat, which are separate properties,
   so neither has to know about the other. */
.loop__pulse::before,
.loop__pulse::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  translate: -50% -50%;
  animation: loop-pulse-beat var(--loop-pulse-dur) linear infinite;
  animation-delay: var(--loop-pulse-offset);
}

/* The halo is ::before so it paints under the core rather than washing over
   it. Its blur is scaled along with it, so the glow opens up on the beat. */
.loop__pulse::before {
  --beat-rest: 2.2;
  --beat-peak: 4.4;
  opacity: 0.42;
  filter: blur(4px);
}

.loop__pulse::after {
  --beat-rest: 1;
  --beat-peak: 1.9;
}

/* The stop answering the signal. Kept here with the motion rather than on the
   node's own rule above, which stays a description of what a node is.

   One keyframe set for all four, phase-shifted by a negative delay onto each
   stop's arrival time — 0, a sixth, a third, a half. Three marks a third of a
   circuit apart means every stop is passed three times a lap, which is why
   loop-node-lit blooms three times and these four delays still hold: at a
   sixth of a cycle apart, the four stops fall into two alternating phases and
   the marks land on both. The rest window matches the beat exactly, so ring
   and mark swell together. */
.loop__node {
  animation: loop-node-lit var(--loop-pulse-dur) linear infinite;
}

.loop__step:nth-child(2) .loop__node {
  animation-delay: calc(var(--loop-pulse-dur) * -0.83333);
}

.loop__step:nth-child(3) .loop__node {
  animation-delay: calc(var(--loop-pulse-dur) * -0.66667);
}

.loop__step:nth-child(4) .loop__node {
  animation-delay: calc(var(--loop-pulse-dur) * -0.5);
}

/* Sage out of Guidance, the turn to sky where Delivered becomes Action, and
   the bracket's own sky-teal-sage on the way home. The same three colours the
   segments and the arc are painted in, in the same order. */
@keyframes loop-pulse-tint {
  0%,
  16.667% {
    color: rgb(103, 188, 124);
  }

  33.333%,
  50% {
    color: rgb(90, 167, 248);
  }

  75% {
    color: rgb(120, 180, 190);
  }

  100% {
    color: rgb(103, 188, 124);
  }
}

/* Guidance to Progress across the first half, then parked and unlit. The jump
   back to the start is made at 94%, well inside the dark stretch. */
@keyframes loop-pulse-rail {
  0% {
    translate: 0%;
    opacity: 1;
  }

  47% {
    opacity: 1;
  }

  50% {
    translate: 100%;
  }

  52% {
    opacity: 0;
  }

  94% {
    translate: 100%;
    opacity: 0;
  }

  94.001% {
    translate: 0%;
  }

  100% {
    translate: 0%;
    opacity: 1;
  }
}

/* Down the right corner, along the underside, up the left corner into the
   arrowhead. The x figures are percentages of the span, which spans the
   bracket; the y figures are the bracket's own height. */
@keyframes loop-pulse-return {
  0%,
  50% {
    translate: 100% 0;
    opacity: 0;
  }

  51% {
    translate: calc(100% - 0.134 * var(--loop-arc-h)) calc(0.5 * var(--loop-arc-h));
  }

  52% {
    translate: calc(100% - 0.5 * var(--loop-arc-h)) calc(0.866 * var(--loop-arc-h));
  }

  53% {
    translate: calc(100% - var(--loop-arc-h)) var(--loop-arc-h);
  }

  54% {
    opacity: 1;
  }

  96% {
    opacity: 1;
  }

  97% {
    translate: var(--loop-arc-h) var(--loop-arc-h);
  }

  98% {
    translate: calc(0.5 * var(--loop-arc-h)) calc(0.866 * var(--loop-arc-h));
  }

  99% {
    translate: calc(0.134 * var(--loop-arc-h)) calc(0.5 * var(--loop-arc-h));
  }

  100% {
    translate: 0 0;
    opacity: 0;
  }
}

/* The ring brightening and blooming as the mark passes behind it. The lit
   values are the node's own tint pushed up, never a new colour: same accent,
   more of it. The inset shadow is what makes it read as lit from inside
   rather than outlined, which is the difference between a stop responding and
   a stop being circled. */
@keyframes loop-node-lit {
  0%,
  33.333%,
  66.667%,
  100% {
    border-color: rgba(var(--loop-tint), 0.8);
    box-shadow:
      0 0 1.75rem -0.25rem rgba(var(--loop-tint), 0.45),
      inset 0 0 1.5rem -0.25rem rgba(var(--loop-tint), 0.4);
  }

  6%,
  27.333%,
  39.333%,
  60.667%,
  72.667%,
  94% {
    border-color: rgba(var(--loop-tint), 0.34);
    box-shadow:
      0 0 1.75rem -0.25rem rgba(var(--loop-tint), 0),
      inset 0 0 1.5rem -0.25rem rgba(var(--loop-tint), 0);
  }
}

/* A swell into each stop and a decay out of it, resting flat between. The
   peaks land on the four arrival times: Guidance at 0, then every sixth of
   the cycle to Progress at the halfway mark. Nothing beats on the way back,
   because there is nothing to stop at. */
@keyframes loop-pulse-beat {
  0%,
  100% {
    scale: var(--beat-peak);
  }

  6%,
  10.667% {
    scale: var(--beat-rest);
  }

  16.667% {
    scale: var(--beat-peak);
  }

  22.667%,
  27.333% {
    scale: var(--beat-rest);
  }

  33.333% {
    scale: var(--beat-peak);
  }

  39.333%,
  44% {
    scale: var(--beat-rest);
  }

  50% {
    scale: var(--beat-peak);
  }

  56%,
  94% {
    scale: var(--beat-rest);
  }
}

@media (max-width: 900px) {
  .loop {
    --loop-gap: 2rem;
  }

  .loop__steps {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Stacked, a run-out would be a stray mark hanging off the top of the list
     and another off the bottom, with nothing either side to read it against. */
  .loop__steps::before,
  .loop__steps::after {
    display: none;
  }

  /* The node moves to the left of its own copy and a stop reads across
     instead of down, so it keeps the shape it has in the row. */
  .loop__step {
    grid-template-columns: auto minmax(0, 1fr);
    justify-items: start;
    column-gap: 1.125rem;
    row-gap: 0.25rem;
    text-align: left;
  }

  .loop__node {
    grid-row: span 2;
    margin-bottom: 0;
  }

  .loop__label {
    align-self: end;
  }

  /* The segments and their arrowheads turn with the column: down the gap
     below each node rather than across the gap beside it. */
  .loop__step:not(:last-child)::before {
    top: calc(var(--loop-node) + 0.375rem);
    left: calc(var(--loop-node) / 2);
    width: 1px;
    height: calc(100% - var(--loop-node) + var(--loop-gap) - 0.75rem);
    background: linear-gradient(
      to bottom,
      rgba(var(--loop-tint), 0.45),
      rgba(var(--loop-tint-next), 0.6)
    );
  }

  .loop__step:not(:last-child)::after {
    top: calc(100% + var(--loop-gap) - 0.375rem);
    left: calc(var(--loop-node) / 2);
    transform: translate(-50%, -70%) rotate(135deg);
  }

  /* At this width the bracket would be taller than it is wide and stops
     reading as a return path, so the sentence carries it alone. */
  .loop__return-arc,
  .loop__return::before {
    display: none;
  }

  /* With the bracket gone and the rail turned down the column, there is no
     circuit left to travel — and nothing for the nodes to answer. */
  .loop__pulse {
    display: none;
  }

  .loop__node {
    animation: none;
  }

  /* Aligned with the step copy above it rather than with the rail, which is
     decoration the sentence is not part of. */
  .loop__return-label {
    margin-inline: 0;
    text-align: left;
  }
}

/* -------------------------------------------------------------------------
   04. Both ends
   Two panels of equal weight. Not a comparison table: the same system seen
   from either side, so nothing lines up row against row.
   ------------------------------------------------------------------------- */

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

.surface {
  position: relative;
  padding: clamp(1.5rem, 3vw, 2.5rem);
  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, in that surface's stream colour. The label above it
   says which end this is, so the colour is reinforcement rather than the
   only signal. */
.surface::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
}

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

/* Both eyebrows stay sage. --sky is a 3.5:1 colour at the glow's brightest,
   which is decoration only: it can hold a 2px rule and it cannot hold type. */
.surface--app::before {
  background: linear-gradient(to right, var(--sky), rgba(90, 167, 248, 0));
}

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

.surface__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);
}

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

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

/* -------------------------------------------------------------------------
   05. The tours
   Both ends, shown. Section 04 argues the two surfaces in words; these two
   sections are the same claim with the product under it, which is why they sit
   directly after it and why the console one shares its band. A run of one tone
   is one stratum (see section 19 of tw-system.css), so the argument and its
   evidence read as a single layer of the page rather than as a claim followed
   by a separate exhibit.

   One block, two modifiers, because the two tours differ only in what fits in a
   column. The mount itself is `.shot`, in tw-system.css.
   ------------------------------------------------------------------------- */

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

.tour__lead {
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

/* The column gap is set well past what a grid needs to separate two tracks.
   Each of these figures is a mounted object with a shadow under it and, on
   the handsets, a bezel around it, and two of them a rule's width apart read
   as one strip of interface cut into pieces rather than as two things being
   shown. The rows are further apart again, because a row boundary here is
   also a caption ending and the next picture starting. */
.tour__grid {
  display: grid;
  gap: clamp(2.25rem, 4.5vw, 3.5rem) clamp(1.5rem, 3vw, 2.5rem);
  margin-top: clamp(2.5rem, 5vw, 3.75rem);
}

/* Three details across the measure, and the 19rem floor is the legibility
   budget rather than a taste call: these are 2x captures of a 1440px viewport,
   so a column narrower than about 300px puts their body text under 8px. At
   19rem the row breaks to two columns of 440px instead of squeezing three to
   250px, which is the right failure — fewer, readable, over more, illegible.
   Below about 600px it drops to one and each detail gets the full measure. */
.tour--console .tour__grid {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
}

/* Phones take the opposite treatment: three fixed columns, because a phone
   capture is already large-format and survives the reduction a console capture
   does not.

   Held well under the measure and centred, so each track lands on the
   handset's own width. That is what puts the left edge of every caption
   under the left edge of the phone it belongs to; run across the full 76rem
   the tracks would be 100px wider than the phones and every caption would
   start outside the thing it captions.

   Which makes the width a consequence rather than a choice: three 16.5rem
   handsets with a 3rem gutter between them is 55.5rem, and that is the only
   number that holds both the gap and the alignment at once. Change the gap
   and this has to move with it. */
.tour--app .tour__grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  justify-items: center;
  column-gap: 3rem;
  max-width: 55.5rem;
  margin-inline: auto;
}

/* Held at the shot's own pixel width: past 16.5rem the capture is being
   upscaled and starts to soften. The bezel lives inside that number rather
   than around it, so the device is 264px and the screen in it is 248px —
   which is still above what the app's own type needs, and keeps this width
   the one place the row's arithmetic has to agree with. */
.tour__phone {
  width: 100%;
  max-width: 16.5rem;
}

/* The one width where the three-up phone row stops working, and it moved out
   with the gutter. Three tracks and two 3rem gaps need about 895px of
   viewport to leave 247px per handset, which is the width the app's own type
   still reads at; a step below that the tracks fall under 240px and it does
   not. Stacking costs height on a phone and buys back the only thing the
   section is for. */
@media (max-width: 900px) {
  .tour--app .tour__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2rem, 6vw, 3rem);
  }

  .tour__phone {
    max-width: 15rem;
  }

  /* Centred with the handset above it, rather than left-aligned against a
     track edge that no longer exists. */
  .tour--app .shot__caption {
    text-align: center;
    justify-self: center;
  }
}

/* -------------------------------------------------------------------------
   06. Underneath
   The one held panel on the page, so the six commitments read as a set
   rather than as more scrolling copy.
   ------------------------------------------------------------------------- */

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

.spine__panel {
  margin-top: clamp(2.25rem, 4vw, 3rem);
  padding: clamp(2.25rem, 5vw, 3.5rem);
  background: linear-gradient(140deg, rgba(31, 68, 48, 0.55), rgba(11, 26, 17, 0.72));
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) + 8px);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.spine__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: clamp(1.75rem, 3vw, 2.5rem);
}

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

/* The same Font Awesome tile the trust panel runs, for the same reason: a
   2px rule gave each of the six a mark but no shoulder, so the copy started
   against nothing. The tile is a fixed object every column begins from, which
   is most of what stops a six-up panel reading as six floating paragraphs.

   This panel only ever sits on paper, so the accent tokens it leans on are
   the band's: --rule and --sage-bright are already the ink versions inside
   .band--paper, and the wash is set with the rest of the paper overrides in
   tw-system.css beside the trust tile's. */
.spine__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;
}

/* -------------------------------------------------------------------------
   07. Reduced motion and print
   Same treatment as the homepage: black on white, decoration gone, product
   shots kept because they are the page.

   tw-system.css flattens animation, transition and the reveals wholesale
   under the reduced-motion query, which is the right answer for a thing that
   plays once and settles. The circuit never settles: flattened, it would
   strand a lit dot on the rail and hold all four nodes at full bloom, so both
   halves of it are undone outright rather than stopped.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .loop__pulse {
    display: none;
  }

  .loop__node {
    animation: none !important;
  }
}

@media print {
  .loop__pulse,
  .loop__return-arc,
  .loop__return::before,
  .loop__steps::before,
  .loop__steps::after,
  .loop__step::before,
  .loop__step::after {
    display: none !important;
  }

  .hero--platform {
    min-height: 0;
  }

  .surface,
  .spine__panel {
    background: transparent !important;
    border-color: #ccc !important;
  }

  .surface::before {
    display: none;
  }

  .loop__node {
    animation: none !important;
    background: none;
    border-color: #ccc;
    color: #000;
  }
}
