/* ALT D - "Split". Loaded AFTER world.css by a second <link>, not by @import: an @import
   inside a stylesheet serialises two round trips before any rule applies.

   ALT D

   The page is two halves for its whole length. Left is graphics: the film, and the six-globe
   index. Right is everything you read, and at the navigation stop it is also where the
   evidence goes - the six recorded walks, each with the agent that produced it.

   The reason to split rather than stack: the case grid is six cards, and six cards under a
   film leaves no room for either. Side by side, the picture can stay up while the evidence
   scrolls past it, which is the point - you are meant to be looking at the world the numbers
   describe while you read them.

   The navigation stop unfolds in four steps as you scroll through its band, driven by
   `data-step` on the root. One case, then two, then three, then all six with the failures
   underneath. No extra stops: the film would need a frame for each and there is nothing new
   to show, so the picture holds while the right column does the work. */

:root {
  --left: 52vw;
  --gut: clamp(24px, 3vw, 56px);
  /* THE HORIZON. One line that both halves hang from.
     Before this the film was vertically centred (top:50%, translateY(-50%)) and the copy
     panel was independently vertically centred inside its column. Two centred blocks of
     different heights can never share a top edge, and they did not: measured at 1920 the
     first painted element on the left and the first on the right differed by +231, +69,
     +99, -85, +46, -33, +80, +42 across eight stops - eight deltas, no repeats - and the
     eyebrow, which appears on nearly every stop, was never twice in the same place.
     That is what "the shapes and tables do not flow" describes. */
  --horizon: clamp(132px, 20vh, 232px);
}
/* Past this width the left half has room to spare and the film does not, so give the split
   itself over: measured at 1920 the film held 18 per cent of the screen with a thousand
   pixels of empty floor beside it. */
@media (min-width: 1700px) { :root { --left: 56vw; } }

.fw-shell-split .fw-video {
  top: var(--horizon);
  left: calc(var(--left) / 2 + 46px);
  transform: translateX(-50%) scale(1.14);
  transform-origin: top center;
  /* The multiplier is set by the two things the film must clear: the globe index in the
     left gutter, and the copy column's left edge. At 2.4 gutters the painted film stops
     about ten pixels short of the index at every width from 1366 up. */
  width: min(calc((var(--left) - var(--gut) * 2.4) / 1.14), calc((100vh - 130px) * 16 / 9 / 1.14));
  height: auto;
  aspect-ratio: 16 / 9;
}

/* The COMPUTED backdrop lives in the same box as the film.
   It only ever shows in the single-file export, where the video config is stripped - and
   there it had no rule at all: sized and positioned by flight.js's draw() against the whole
   viewport, which is a stacked-shell assumption that predates this shell. Measured in the
   export: a 1351x1386 photograph behind every stop, with the abstract, the charts, the six
   map tiles and all seven results tables printed on top of it, and no scrim left to rescue
   them because the scrim was retired when the film arrived. That is the whole reason the
   artifact looked worse than the served page. */
.fw-shell-split .fw-hero {
  top: var(--horizon);
  left: calc(var(--left) / 2 + 46px);
  transform: translateX(-50%) !important;
  transform-origin: top center;
  width: min(calc(var(--left) - var(--gut) * 2.4), calc((100vh - 130px) * 16 / 9)) !important;
  height: auto !important;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  /* draw() blurs and desaturates it to sell the push-in against a full-bleed backdrop.
     Inside a frame it is the subject, not the wallpaper. */
  filter: none !important;
  --ramp-t: clamp(26px, calc(var(--film-h2, 300px) * 0.095), 44px);
  --ramp-b: clamp(34px, calc(var(--film-h2, 300px) * 0.125), 92px);
  --ramp-x: clamp(64px, calc(var(--film-w2, 500px) * 0.14), 150px);
  -webkit-mask-image:
    linear-gradient(to bottom, transparent 0, #000 var(--ramp-t), #000 calc(100% - var(--ramp-b)), transparent 100%),
    linear-gradient(to right, transparent 0, #000 var(--ramp-x), #000 calc(100% - var(--ramp-x)), transparent 100%);
  -webkit-mask-composite: source-in;
  -webkit-mask-repeat: no-repeat;
  mask-image:
    linear-gradient(to bottom, transparent 0, #000 var(--ramp-t), #000 calc(100% - var(--ramp-b)), transparent 100%),
    linear-gradient(to right, transparent 0, #000 var(--ramp-x), #000 calc(100% - var(--ramp-x)), transparent 100%);
  mask-composite: intersect;
  mask-repeat: no-repeat;
}
/* The sky is a full-stage wash; behind a framed backdrop it is a grey haze over the copy. */
.fw-shell-split .fw-sky { display: none; }

.fw-shell-split .fw-copy {
  max-width: calc(var(--vw) - var(--left) - var(--gut));
  left: var(--left);
  right: var(--gut);
  top: 0;
  bottom: 0;
  transform: none;
  width: auto;
  align-items: start;
  overflow-y: auto;
  /* `contain` here is a trap, not a nicety. If the column ever does overflow, containment
     stops the leftover wheel reaching the page, so the flight freezes and the wheel reads
     as broken. Chaining degrades instead: the column takes up its slack, then the page
     carries on. The rules below exist so this path is reached as rarely as possible. */
  overscroll-behavior: auto;
  padding: var(--horizon) 0 32px;
  /* The same clock as the film beside it. --left steps from 52vw to 34vw when the block
     changes from prose to a table, and with no transition the copy column teleported 281px
     left and grew 281px wider in a single frame while the film next to it eased its width
     over 550ms. Two halves of one screen changing shape on two different clocks is the
     thing that reads as not flowing. */
  transition: left .55s cubic-bezier(.3,.7,.3,1),
              right .55s cubic-bezier(.3,.7,.3,1),
              max-width .55s cubic-bezier(.3,.7,.3,1);
}
.fw-shell-split .fw-panel { align-self: start; text-align: left; }
/* Six cards plus two prose columns plus a call to action is the tallest panel on the page,
   and on a 768px-tall laptop it is the one that decides whether the column scrolls inside
   itself - which on a scroll-driven page reads as the wheel having stopped working. These
   are the millimetres that keep it out of that state. */
.fw-shell-split .fw-cases { gap: 8px; }
.fw-shell-split .fw-case { padding: 8px 8px 8px; }
.fw-shell-split .fw-case-model { margin-bottom: 8px; }
.fw-shell-split .fw-case-note { margin-top: 8px; padding-top: 8px; }
.fw-shell-split .fw-panel-hero { text-align: left; max-width: none; margin-inline: 0; }
.fw-shell-split .fw-panel-hero .fw-body { margin-inline: 0; }
.fw-shell-split .fw-panel-hero .fw-tags,
.fw-shell-split .fw-panel-hero .fw-cta { justify-content: flex-start; }
.fw-shell-split .fw-title { font-size: clamp(20px, 2.1vw, 32px); }
/* Not `none`: uncapped this ran to 103 characters a line at 1920. */
.fw-shell-split .fw-body { max-width: 64ch; }
.fw-shell-split .fw-pair { grid-template-columns: 1fr 1fr; gap: clamp(18px, 2vw, 32px); }

/* The index sits in the left gutter, so it is left-aligned rather than centred: a label like
   "Pathfinding" is wider than the 52px thumbnail above it, and centring meant the overhang
   ran off the left edge of the window. Aligned left, the label grows into the gutter
   instead, which is empty. */
.fw-shell-split .fw-globes {
  right: auto;
  left: clamp(10px, 1.4vw, 20px);
  gap: clamp(2px, 0.7vh, 8px);
  align-items: flex-start;
}
.fw-shell-split .fw-globe { align-items: flex-start; padding-inline: 0; }
/* Capped and allowed to wrap. At 14px a single-line "Pathfinding" is wider than the gutter
   and ran under the film; wrapped inside the thumbnail's own width it stays in the gutter
   at every laptop width, which is what the index is for. */
.fw-shell-split .fw-globe-label {
  text-align: left;
  max-width: 108px;
  white-space: normal;
  line-height: 1.18;
  overflow-wrap: normal;
  word-break: keep-all;
  hyphens: none;
}

/* Inside the right margin, never past it. A negative inset here put the dots half off the
   edge of the window. */
.fw-shell-split .fw-rail {
  left: auto;
  right: clamp(4px, 0.6vw, 12px);
  top: 50%;
  transform: translateY(-50%);
  max-width: none;
}
/* Unlabelled, these dots were a 9x21 hit target - under the 24x24 WCAG 2.2 minimum, and at
   1920 they read as dust in the gutter rather than as navigation. The padding is the target;
   the mark stays the same size. */
.fw-shell-split .fw-dot { padding: 8px 8px; }
.fw-shell-split .fw-dot-mark { width: 10px; height: 10px; }
.fw-shell-split .fw-dot-label { display: none; }
/* The autoplay control is gone: this is a page that is read by scrolling. */
.fw-shell-split .fw-auto { display: none; }
.fw-shell-split .fw-hint { display: none; }



/* Short-viewport compaction. On a 1366x768 laptop the final reveal - two prose columns,
   six cards and a caption - overran the column by 256px and started scrolling inside
   itself, which on a scroll-driven page reads as the wheel having stopped working. Nothing
   is removed here; the type and the gaps give up the millimetres instead. */
@media (max-height: 850px) and (min-width: 1041px) {
  .fw-shell-split { --horizon: 104px; }
  .fw-shell-split .fw-copy { padding-bottom: 16px; }
  .fw-shell-split .fw-title { margin-bottom: 8px; }
  .fw-shell-split .fw-pair { margin-top: 8px; }
  .fw-shell-split .fw-col-body { font-size: 14px; line-height: 1.5; }
  .fw-shell-split .fw-cases { gap: 8px; }
  .fw-shell-split .fw-case { padding: 8px 8px 8px; }
  .fw-shell-split .fw-case-nums { gap: 4px 8px; }
  .fw-shell-split .fw-case-num strong { font-size: 14px; }
  .fw-shell-split .fw-case-note { font-size: 14px; line-height: 1.38; margin-top: 8px; padding-top: 4px; }
  .fw-shell-split .fw-cases-label { margin-top: 8px; }
  .fw-shell-split .fw-cases-label + .fw-cases { margin-top: 8px; }
}

/* ===========================================================================
   The paper layout
   ---------------------------------------------------------------------------
   The split is not the same shape at every stop any more. Three cases:

     stage="logo" at the opening   the page is ONE centred column, front matter
                                   only, and the mark sits above the title. The
                                   split has not engaged yet, which is what makes
                                   the mark's move to the left read as a move.
     stage="film"                  the split as it was: film left, evidence right.
     stage="mark"                  a world name left, its whole results table
                                   right, so the right column takes more width -
                                   which is what pays for eleven columns and
                                   their half-widths.
   ========================================================================= */

/* The way out. The film takes CENTRE stage and the copy steps aside: it is the last thing
   on the page and there is nothing left to read beside it. */
.fw-shell-split[data-block="out"] { --left: 100vw; }
.fw-shell-split[data-block="out"] .fw-video,
.fw-shell-split[data-block="out"] .fw-hero {
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(1.06) !important;
  transform-origin: center;
  /* 100vh - 210px so the frame clears the topbar above it and the autoplay control below;
     centred and full-bleed it reached under both. */
  width: min(calc(var(--vw) - 14vw), calc((100vh - 210px) * 16 / 9)) !important;
}
.fw-shell-split[data-block="out"] .fw-copy,
.fw-shell-split[data-block="out"] .fw-globes {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.fw-shell-split[data-block="out"] .fw-mark { display: none; }

/* Front matter: the SPLIT, not a centred column.
   The mark and the title used to be a single centred column with the abstract under it, and
   nothing else. It is now the same two halves as every other stop - the film on the left at
   the size it keeps for the whole page, so nothing jumps when the flight starts - with the
   title, the abstract and the three figures the abstract claims stacked on the right.
   The film holds on its opening frame here; it only begins to move at stop 2. */
.fw-shell-split[data-stop="0"] .fw-panel-hero,
.fw-shell-split[data-stop="0"] .fw-panel-hero .fw-body { text-align: left; margin-inline: 0; }
.fw-shell-split[data-stop="0"] .fw-panel-hero .fw-tags,
.fw-shell-split[data-stop="0"] .fw-panel-hero .fw-cta { justify-content: flex-start; }
/* The title is the largest thing on the page and it shares a screen with an abstract and
   three plots, so it gives up a step. */
.fw-shell-split[data-stop="0"] .fw-title {
  font-size: clamp(20px, 1.72vw, 32px);
  margin-bottom: 2.0px;
}
/* The opening keeps the same split as every other stop: the mark on the left at the size
   it holds for the whole page, the tiles in the right column. The tiles scroll up and out;
   the mark does not move. An earlier cut made this stop full-bleed with the mark as the
   middle cell of the matrix, which meant the mark had to fly out to the left at the next
   stop - a move the page was never asked to make. */
/* The opening carries more than any other stop - an abstract, four tiles and three plots -
   so it starts higher than the shared horizon. It is the one stop where the two halves do
   not begin on the same line, and it is worth it: the alternative is a column that scrolls
   inside itself, which on a scroll-driven page reads as the wheel having stopped. */
/* The walks take three quarters of the page. One map alone was a fifth of the screen in a
   half-width column - the evidence given less room than the picture of the world it is
   evidence about. The world graphic keeps a quarter, which is all a name plate needs. */
.fw-shell-split[data-block="maps"] { --left: 24vw; }
.fw-shell-split[data-block="maps"] .fw-video,
.fw-shell-split[data-block="maps"] .fw-hero {
  width: min(calc((var(--left) - var(--gut) * 1.1) / 1.14), calc((100vh - 260px) * 16 / 9 / 1.14));
}
.fw-shell-split[data-block="maps"] .fw-maps {
  --map-tall: clamp(260px, 50vh, 560px);
  --map-h: calc((var(--map-tall) - 9px) / 2);
}
/* The index would print through the matrix, and the "six worlds" tile already names them.
   `visibility` as well as opacity: at opacity alone the six labels stay in the accessibility
   tree and in a page search, under a copy column that now spans the whole width. */
.fw-shell-split[data-block="summary"] .fw-globes {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* The mark travels between these two. --left is where it ends up; the interpolation itself
   is --logo-t, written per frame by flight.js.

   Its vertical travel ends ON THE HORIZON, not at the middle of the window. The base sheet
   centres it, which is right for the front matter where it sits above the title and wrong
   everywhere else: measured at 1920, the two stops that show the mark were the only two
   left of the twelve whose left half did not start on the same line as the right. The
   1.0262 is the hero's own aspect (1254/1222), halved, which converts its width into the
   distance from its top edge to its centre - the element is translate(-50%, -50%). */
.fw-shell-split .fw-logo {
  --left: 52vw;
  top: calc(15% + (var(--horizon) + var(--logo-w) * 0.5131 - 15%) * var(--logo-t));
}

/* The name plate belongs to the LEFT half, over the film. The base sheet spans the stage
   because on the stacked shell the stage IS the whole page; left spanning here it centred
   itself over the middle of the window, which is the middle of the results table - measured,
   the word "Banking" printed straight through six rows of numbers.
   The left inset clears the world index, which lives in the same gutter. */
/* Under the film, aligned to its left edge. --film-x/--film-w2/--film-bot are the film's
   MEASURED box, published by flight.js after every layout and stop change: the picture is
   not a fixed size (each stop gets its own) or a fixed place (each shell puts it somewhere
   else), so there is nothing in the sheet alone to anchor to. Anchored to the stage instead,
   the plate sat at the bottom of the window while the film floated in the middle of it. */
.fw-shell-split .fw-mark {
  right: auto;
  bottom: auto;
  /* max() against the index's own right edge, not just the film's left. At 1366 the film
     is wide enough that its left edge sits INSIDE the index, and the plate printed
     "Pathfinding" straight through the index's labels. The index is a 52px thumbnail at
     left: clamp(10px, 1.4vw, 20px) with a label under it, so 96px clears the widest of
     them at every width the split is used at. */
  left: max(calc(var(--film-x, 100px) + clamp(8px, 1vw, 22px)), 96px);
  width: calc(min(var(--film-w2, 420px), var(--left) - 96px) - clamp(16px, 2vw, 44px));
  top: calc(var(--film-bot, 60%) - clamp(6px, 1.2vh, 14px));
  padding-inline: 0;
}

/* A table stop: the evidence is eleven columns wide, so the split moves to meet it. Keyed
   to what the RIGHT column carries, not to what is on the left: the suite stop is a film
   stop whose content is the same eleven-column table, and left at the film's 52vw its
   Naturalness pillar was cut off entirely. */
/* Five recorded runs need room to be read, so this stop takes the table stops' split.
   Its artwork is TOP-ALIGNED with the tiles beside it rather than floating at the film's
   own vertical centre: the two halves of this stop are one figure and its caption. */
.fw-shell-split[data-block="runs"] { --left: 32vw; }
.fw-shell-split .fw-art {
  top: var(--horizon);
  left: calc(var(--left) / 2 + 46px);
  transform: translateX(-50%);
  transform-origin: top center;
  width: min(calc(var(--left) - var(--gut) * 1.6), calc((100vh - 260px) * 3 / 2));
  height: auto;
}

.fw-shell-split[data-block="table"] { --left: 34vw; }

/* A world stop is a chart AND a table of the same numbers, STACKED, both at the column's
   full width. Side by side was tried and is wrong here: it left the table about 540px, and
   eleven columns will not fit 540px at any size worth reading, so the Naturalness pillar
   was cut off at every world. Stacked, the table gets the whole column and the pair still
   fills the height that one figure alone left three quarters empty. */
.fw-shell-split .fw-panel:has(.fw-bars):has(.fw-pillars) .fw-bars { margin-top: 16px; }
.fw-shell-split .fw-panel:has(.fw-bars):has(.fw-pillars) .fw-pillars-wrap { margin-top: 16px; }
/* The chart is the summary and the table is the detail, so the chart is set slightly
   tighter: it should read at a glance rather than compete with the table below it. */
.fw-shell-split .fw-panel:has(.fw-bars):has(.fw-pillars) .fw-bar-track { height: 17px; }
.fw-shell-split .fw-panel:has(.fw-bars):has(.fw-pillars) .fw-bars-list { gap: 4px; }

/* The film is the left half's tenant, not its owner: at a table stop it takes what is left
   rather than setting the width. */
.fw-shell-split[data-block="table"] .fw-video {
  width: min(calc((var(--left) - var(--gut) * 1.6) / 1.14), calc((100vh - 190px) * 16 / 9 / 1.14));
}

/* The copy must clear the rail, not merely avoid its centre. The rail is 26px wide inside a
   12px inset, so anything less than that and a right-aligned number sits under a dot. */
.fw-shell-split .fw-copy { right: calc(var(--gut) + 22px); }

/* A stop whose evidence is a table is a different shape from one whose evidence is prose:
   the prose is an introduction to the table rather than the point of the stop, so it gives
   up the room. Measured at 1560x900 the six world panels ran 74 to 122px past the column
   and started scrolling inside themselves, which on a scroll-driven page reads as the wheel
   having stopped working. */
.fw-shell-split .fw-pillars-wrap + .fw-note,
.fw-shell-split .fw-panel:has(.fw-pillars) .fw-body { max-width: 74ch; }
.fw-shell-split .fw-panel:has(.fw-pillars) .fw-title { margin-bottom: 4px; }
.fw-shell-split .fw-pillars-wrap { margin-top: 12px; }
.fw-shell-split .fw-pillars td,
.fw-shell-split .fw-pillars .fw-p-metrics th { padding: 4px 4px; }
.fw-shell-split .fw-pillars th.fw-p-sys { padding: 4px 8px 4px 0; }

/* Eleven columns of numbers want to be read as rows, so the system column stays put while
   the metrics scroll under it on a narrow screen. */
.fw-shell-split .fw-pillars th.fw-p-sys {
  position: sticky;
  left: 0;
  background: var(--bg);
  z-index: 1;
}

/* Six live renderers plus their captions is the tallest block on the page, and the copy
   column is the only thing holding it. The row height is what decides whether it fits, and
   the map has to win it: the head and the note cost about 88px of every row, so a row under
   about 180px leaves a strip of map rather than a map. */
/* The walks stop gives its whole band to the walks: one map alone takes the height six
   will share, and only the last step splits it. */
.fw-shell-split .fw-maps {
  --map-tall: clamp(250px, 50vh, 520px);
  --map-h: calc((var(--map-tall) - 9px) / 2);
}

/* Under this width there is no second column worth having, so it stacks like the base page. */
@media (max-width: 1040px) {
  .fw-shell-split .fw-video {
    top: 0; left: 50%;
    transform: translateX(-50%);
    width: min(var(--vw), calc(44vh * 16 / 9));
  }
  .fw-shell-split .fw-copy {
    /* max-width was copy-pasted in from the two-column rule and evaluated to 48vw - gut
       here, clamping a column that had just been told to be the full viewport width to
       under half of it. */
    max-width: none;
    left: 50%; right: auto;
    transform: translateX(-50%);
    top: 44vh; bottom: 0;
    width: calc(var(--vw) - 28px);
    align-items: start;
    padding: 16px 0 24px;
  }
  .fw-shell-split .fw-panel { align-self: start; }
  .fw-shell-split .fw-cases { grid-template-columns: repeat(2, 1fr); }
  .fw-shell-split[data-step="0"] .fw-case.is-ok[data-at="0"] { grid-column: span 2; }
  .fw-shell-split .fw-pair { grid-template-columns: 1fr; }
  /* Once the copy is centred and full width there is no left gutter left to stand in, and
     the index sits at z-index 8 over copy at z-index 6. Both index and rail become
     horizontal strips in the gap between the film and the copy instead. */
  .fw-shell-split .fw-globes {
    left: 50%; right: auto; top: calc(44vh - 52px);
    transform: translateX(-50%);
    flex-direction: row;
    align-items: flex-end;
    gap: 8px;
  }
  .fw-shell-split .fw-globe { align-items: center; }
  .fw-shell-split .fw-globe img { width: 34px; }
  .fw-shell-split .fw-globe-label { display: none; }
  /* On the same strip as the index, not below it: at 44vh + 6px the dots landed inside the
     copy column, which starts at 44vh. */
  .fw-shell-split .fw-rail {
    left: auto; right: 10px; top: calc(44vh - 46px);
    transform: none;
    flex-direction: row;
    gap: 8px;
  }
  /* Autoplay is a leave-it-running-in-a-room control, which is a desk or a kiosk, not a
     phone. Pinned to the bottom-left of the stage it sat ON the copy - measured, over a map
     caption and over the "Nova 2 Sonic" bar label - and reserving a strip for it did not
     help, because the column scrolls inside itself here so there is always something
     passing under it. */
  .fw-shell-split .fw-auto { display: none; }

  /* PHONE. Everything that is a row of tiles on a laptop becomes a column, and the stops
     that carry the most give the picture back most of its band. The copy column still
     scrolls inside itself on the densest stops - an abstract plus six tiles cannot be made
     to fit 504px - but it CHAINS, so the wheel keeps working and the page carries on. */
  .fw-shell-split .fw-sum { grid-template-columns: 1fr; }
  .fw-shell-split .fw-sum-abstract { grid-column: 1; column-count: 1; }
  .fw-shell-split .fw-runs { grid-template-columns: 1fr; height: auto; }
  .fw-shell-split .fw-run { grid-column: 1 !important; min-height: 210px; }
  .fw-shell-split .fw-cards { grid-template-columns: 1fr; }
  .fw-shell-split .fw-work-out { grid-template-columns: repeat(3, 1fr); }
  .fw-shell-split .fw-work-row { grid-template-columns: 4.5ch 1fr 10ch; gap: 8px; }
  .fw-shell-split .fw-grp-name { font-size: 14px; }
  .fw-shell-split[data-block="summary"] .fw-copy,
  .fw-shell-split[data-block="runs"] .fw-copy { top: 26vh; }
  .fw-shell-split[data-block="summary"] .fw-video,
  .fw-shell-split[data-block="summary"] .fw-hero,
  .fw-shell-split[data-block="runs"] .fw-art {
    width: min(var(--vw), calc(25vh * 16 / 9));
  }
  .fw-shell-split[data-block="runs"] .fw-art { left: 50%; top: 0; }
  /* The section bar is the page's navigation on a phone, so it stays - wrapped, scrolling
     sideways if it must, rather than hidden behind a menu the reader has to find. */
  .topbar { flex-wrap: wrap; row-gap: 4px; padding-bottom: 8px; }
  .fw-nav { margin-left: 0; width: 100%; justify-content: flex-start; overflow-x: auto;
    flex-wrap: nowrap; scrollbar-width: none; }
  .fw-nav::-webkit-scrollbar { display: none; }
  .fw-nav-item { font-size: 14px; padding: 4px 8px; }
  /* The name plate shares the top strip with the world index on a phone, so it starts
     after it rather than under it. */
  .fw-shell-split[data-block="table"] .fw-mark {
    left: 0;
    width: 100%;
    padding-left: calc(clamp(52px, 15vw, 76px) + 14px);
  }

  /* The stage earns its 44vh only when there is something in it worth 44vh. On a phone
     the front matter and the world stops are text, and giving nearly half the screen to a
     decorative mark left the abstract 304px past the bottom of its column. So the picture
     yields to the reading on exactly the stops where the reading is the point. */
  .fw-shell-split[data-stage="logo"] .fw-copy { top: clamp(132px, 19vh, 190px); }
  .fw-shell-split[data-stage="mark"] .fw-copy { top: clamp(150px, 22vh, 220px); }
  .fw-shell-split[data-stage="logo"] .fw-logo,
  .fw-shell-split[data-stage="mark"] .fw-mark { --logo-lg: 30vw; }
  .fw-shell-split[data-stage="mark"] .fw-mark {
    inset: 0 0 auto 0;
    width: auto;
    height: clamp(140px, 21vh, 210px);
    flex-direction: row;
    gap: 12px;
    text-align: left;
    align-items: center;
  }
  .fw-shell-split[data-stage="mark"] .fw-mark-globe { width: clamp(52px, 15vw, 76px); }
  .fw-shell-split[data-stage="mark"] .fw-mark-name { font-size: clamp(20px, 7vw, 32px); }
  .fw-shell-split[data-stage="mark"] .fw-mark-line { font-size: 14px; max-width: none; }
  /* The index and the rail belong to the film's strip. On a text stop the film is not
     there, so they would print over the copy. */
  .fw-shell-split[data-stage="logo"] .fw-globes,
  .fw-shell-split[data-stage="logo"] .fw-rail,
  .fw-shell-split[data-stage="mark"] .fw-globes { opacity: 0; pointer-events: none; }
  .fw-shell-split[data-stage="mark"] .fw-rail { top: 8px; }
  /* The walks stop is the one place on a phone where the picture on the left is the LEAST
     valuable thing on screen: the maps are also pictures, they are the evidence, and there
     are six of them. So the film gives up most of its band here and nowhere else. */
  .fw-shell-split[data-block="maps"] .fw-video { width: min(var(--vw), calc(26vh * 16 / 9)); }
  .fw-shell-split[data-block="maps"] .fw-copy { top: 27vh; }
  .fw-shell-split[data-block="maps"] .fw-globes { top: calc(26vh - 44px); }
  .fw-shell-split[data-block="maps"] .fw-rail { top: calc(26vh - 40px); }
  .fw-shell-split[data-block="maps"] .fw-body { font-size: 14px; line-height: 1.5; }
  /* A five-row chart plus its caption is taller than a paragraph, by about the height of
     the chart. Same trade, smaller. */
  .fw-shell-split[data-block="bars"] .fw-video { width: min(var(--vw), calc(36vh * 16 / 9)); }
  .fw-shell-split[data-block="bars"] .fw-copy { top: 37vh; }
  .fw-shell-split[data-block="bars"] .fw-globes { top: calc(36vh - 44px); }
  .fw-shell-split[data-block="bars"] .fw-rail { top: calc(36vh - 40px); }
  /* Nineteen characters of label is four of the twenty-six available at 430. */
  .fw-shell-split .fw-bar-row { grid-template-columns: minmax(0, 11ch) 1fr auto; gap: 8px; }
  .fw-shell-split .fw-bars-axis { margin-left: calc(11ch + 8px); }
  /* The abstract is the longest single block on the page and the front matter is the one
     stop with nothing else competing, so it takes the smaller size rather than the scroll. */
  .fw-shell-split[data-stop="0"] .fw-abstract-p { font-size: 14px; line-height: 1.55; }

  /* Two across rather than three: at 430 a third of the width is 130px of map. */
  .fw-shell-split .fw-maps {
    --map-tall: clamp(200px, 34vh, 300px);
    --map-h: calc((var(--map-tall) - 9px) / 2);
  }
  .fw-shell-split[data-step="2"] .fw-maps,
  .fw-shell-split[data-step="3"] .fw-maps { grid-template-columns: 1fr 1fr; }
  .fw-shell-split[data-step="2"] .fw-maps { grid-template-rows: var(--map-h) var(--map-h); }
  .fw-shell-split[data-step="3"] .fw-maps {
    grid-template-rows: var(--map-h) var(--map-h) var(--map-h);
  }
}

/* The nine conversation types. The RIGHT panel is unchanged from the text stops - nine cards
   need that width and losing it costs a card per row - so the left graphic is what shrinks. */
.fw-shell-split[data-block="cards"] { --left: 52vw; }
.fw-shell-split[data-block="cards"] .fw-video,
.fw-shell-split[data-block="cards"] .fw-art {
  width: min(calc((var(--left) - var(--gut) * 2.4) / 1.62), calc((100vh - 130px) * 16 / 9 / 1.62));
  left: calc(var(--left) / 2 + 20px);
}

/* Small laptops: the left gutter has to hold a 52px thumbnail AND a wrapped 14px label, so
   the stage centre moves right by exactly that gutter rather than the film and the index
   sharing pixels. Measured at 1366 and 1440, where they overlapped by 11% of the index. */
@media (max-width: 1500px) {
  .fw-shell-split .fw-video,
  .fw-shell-split .fw-art {
    left: calc(var(--left) / 2 + 46px + 44px);
  }
  /* The name plate is TEXT, so it does not move with the stage: shifted right by the same
     44px its longest line ran under the table beside it. It stays put and is capped to the
     column it lives in. */
  .fw-shell-split .fw-mark {
    max-width: calc(var(--left) - var(--gut) * 1.5 - 60px);
  }
  /* The two stops whose evidence is WIDE (six live maps, a five-row chart) take the left
     column down to 24vw, which is not enough for a name plate as well. On those the plate
     rides in the gutter above the index instead of beside the evidence. */
  .fw-shell-split[data-block="maps"] .fw-mark,
  .fw-shell-split[data-block="bars"] .fw-mark {
    left: clamp(10px, 1.4vw, 20px);
    transform: none;
    text-align: left;
    max-width: 200px;
  }
  /* One thing in the gutter at a time. The plate names the stop the reader is on, which the
     index cannot do, and the header bar above already carries the navigation the index
     duplicates - so on these two stops the index stands down. */
  .fw-shell-split[data-block="maps"] .fw-globes,
  .fw-shell-split[data-block="bars"] .fw-globes { opacity: 0; pointer-events: none; }
  /* The table stops keep both, so the plate is capped clear of the first table column. */
  .fw-shell-split[data-block="table"] .fw-mark { max-width: 190px; }
  .fw-shell-split .fw-video,
  .fw-shell-split .fw-art {
    width: min(calc((var(--left) - var(--gut) * 2.4 - 88px) / 1.14),
               calc((100vh - 130px) * 16 / 9 / 1.14));
  }
}

/* ============================================================================
   PHONES
   ----------------------------------------------------------------------------
   Everything above this point assumes two columns and a viewport wide enough to
   stand a film beside a table. A handset has neither, so the page changes shape
   rather than shrinking: the opening becomes a scroll instead of one fixed screen,
   the split becomes a stack, and the wide evidence (an eleven column table, six
   maps) gets its own horizontal scroll instead of being squeezed to illegibility.
   ========================================================================== */
@media (max-width: 700px) {
  /* On a phone the opening scrolls, so nothing there needs clipping: the tile grows
     to hold its own paragraph. */
  .fw-open-cell { overflow: visible; }
  /* The header. The nav ran off the right edge with no way to reach the last item;
     it now scrolls sideways under the thumb, which is what a phone expects. */
  .topbar { padding: 8px 0 8px; }
  .wordmark { padding-left: 12px; font-size: 17px; }
  .wordmark .sub { display: none; }
  /* The phone header hides BENCHMARK, which shortens the line box, so the desktop
     correction overshoots by exactly the amount it corrects. Measured at 390: centred on
     the line is already centred on the words here. */
  .wordmark-logo { transform: none; }
  .fw-nav {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 0 12px;
    justify-content: flex-start;
  }
  .fw-nav::-webkit-scrollbar { display: none; }
  .fw-nav-item { flex: none; font-size: 14px; padding: 8px 8px; }

  /* The opening is a SCROLL on a phone, not one screen. Nine tiles inside 844px is
     nine unreadable tiles; stacked, each one is legible and the reader swipes. */
  .fw-open {
    height: auto;
    max-height: none;
    overflow: visible;
    padding: 84px 16px 24px;
  }
  .fw-open-grid {
    grid-template-columns: 1fr;
    grid-auto-rows: auto;
    gap: 8px;
  }
  /* Higher specificity than the base rule's overflow:hidden, which was still clipping the
     abstract by four pixels even though the phone opening scrolls. */
  .fw-open .fw-open-cell { min-height: 0; overflow: visible; }
  /* The abstract carries its own overflow:hidden for the fixed-height desktop tile. On a
     phone the opening scrolls, so it has nothing to be clipped by and nothing to hide. */
  .fw-open .fw-open-text { overflow: visible; }
  .fw-open-title { font-size: clamp(20px, 7.6vw, 32px); }
  .fw-open-authors { font-size: 14px; }
  /* The mark is the middle cell of a three by three that no longer exists. Stacked, it
     is just the biggest tile, so it keeps its own aspect rather than a grid row. */
  .fw-open-cell.is-mark { padding: 0; }
  .fw-open-mark { max-height: 46vh; width: auto; margin: 0 auto; }
  .fw-open-worlds { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The three plots. Rotated names need vertical room a stacked tile does not have to
     spare, so on a phone the plot lies down: name and value on one line, bar beside. */
  .fw-open-cols, .fw-open-names { grid-template-columns: 1fr; }
  .fw-open-cols { gap: 8px; align-items: stretch; }
  .fw-open-cb {
    grid-template-rows: none;
    grid-template-columns: 22px 4.2em 1fr;
    align-items: center;
    gap: 8px;
    height: auto;
  }
  .fw-open-mk { margin: 0; }
  .fw-open-cv { margin: 0; text-align: left; }
  .fw-open-bar {
    width: var(--wpc, 100%);
    max-width: none;
    height: 13px;
    min-height: 13px;
    align-self: center;
    border-radius: 3px;
    margin: 0;
  }
  .fw-open-names { display: none; }

  /* The split becomes a stack. The stage sits above the evidence at its own size and
     the copy runs full width underneath, rather than the two fighting over 390px. */
  .fw-shell-split { --left: 100vw; --gut: 14px; }
  .fw-shell-split .fw-panel {
    position: relative;
    left: 0;
    width: 100%;
    max-width: 100%;
    padding: 0 16px;
    align-self: start;
  }
  .fw-shell-split .fw-video,
  .fw-shell-split .fw-art {
    left: 50%;
    /* Clear of the header, including the mask's own feather: at 74px the film's top ramp
       grazed the topbar box. */
    top: 92px;
    width: min(94vw, 520px) !important;
  }
  /* The name plate cleared the header by riding at the horizon, which on a phone is
     under the header itself. Below the stage, left aligned, in the flow of the read. */
  /* Under the stage, not at the horizon: the horizon on a phone is behind the header,
     so the world's name was printed over the wordmark. The stage keeps its aspect, so
     its foot is a computable distance down and the plate sits just below it. */
  .fw-shell-split .fw-mark {
    left: 14px;
    top: calc(78px + min(94vw, 520px) * 0.62);
    transform: none;
    text-align: left;
    max-width: calc(100vw - 28px);
  }
  /* The panel clears the stage and the plate rather than starting at the fold. */
  /* Measured, not guessed: at 390x844 the stage runs 74-280, the plate 305-453 and the
     panel box begins at 387, so 80px of padding starts the evidence at 467 - just clear
     of the plate. The old expression assumed the panel began at the top of the screen and
     opened a 249px hole between the world's name and its numbers. */
  .fw-shell-split .fw-panel { padding-top: calc(86px + min(94vw, 520px) * 0.60); }
  .fw-shell-split[data-block="out"] .fw-panel { padding-top: 24px; }
  /* A BAND has no stage to clear, so it starts under the header. It was still reserving
     306px for a film that is not there - a screen of empty page above every world. */
  .fw-shell-split[data-block="band"] .fw-panel { padding-top: 76px; }
  /* The copy column hangs at --film-h, which the flight computes from the stage. A band
     has no stage, but the variable keeps its last value, so every world opened with half
     a screen of nothing above its name. */
  .fw-shell-split[data-block="band"] .fw-copy {
    padding-top: 0;
    /* Still absolute - going static threw the column out of its own left offset and cut
       the content off the left edge - just no longer hung off the stage's height. */
    top: 0;
  }
  /* The film's opacity is written INLINE every frame, so a stylesheet rule cannot switch
     it off; the width rule above then gave it a size again and the dive reappeared at the
     top of every world. Zero width is the one thing the inline style does not set. */
  .fw-shell-split[data-block="band"] .fw-video,
  .fw-shell-split[data-block="band"] .fw-art {
    width: 0 !important; height: 0 !important; opacity: 0 !important;
  }
  /* The world's name is on the plate. The screen-reader heading is unhidden on phones for
     sections that have no plate; where there is one it printed the name twice. */
  .fw-shell-split[data-block="band"] .fw-title.fw-sr { display: none; }
  /* Specific enough to beat the desktop grid that fixes the name column at 8.2em and
     truncates it. On a phone the name gets the full width and the bar sits under it. */
  .fw-shell-split .fw-bar-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "name val" "track track";
    row-gap: 4px;
  }
  .fw-shell-split .fw-bar-name {
    grid-area: name;
    max-width: none; width: auto; min-width: 0;
    white-space: normal; overflow: visible; text-overflow: clip;
  }
  .fw-shell-split .fw-bar-val { grid-area: val; }
  .fw-shell-split .fw-bar-track { grid-area: track; width: 100%; }
  .fw-shell-split .fw-globes { display: none; }
  /* The plate cannot be placed over a stacked panel without covering it - measured at
     100% coverage on all six world stops - so on a phone it stands down and the panel
     carries the same two lines in the flow. */
  .fw-shell-split .fw-mark { display: none; }
  .fw-shell-split .fw-title.fw-sr {
    position: static; width: auto; height: auto; clip: auto; clip-path: none;
    margin: 0 0 8px; overflow: visible; white-space: normal;
  }
  .fw-phone-only { display: block; }

  /* Wide evidence scrolls in its own box rather than off the page. */
  .fw-pillars-wrap, .fw-bars, .fw-runs, .fw-maps {
    max-width: 100%;
  }
  .fw-pillars-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* Written at the SAME specificity as the band rules further down that they have to
     beat. `!important` does not settle a fight between two important declarations -
     specificity does, and (0,1,0) never beats (0,3,0). So these read as one column in the
     sheet and rendered as three, which on a 390px screen is nine conversation-type cards
     at 87px each with "disambiguation" clipped mid-word, six live walk renderers at 93px,
     and an audio scrubber squeezed to 13 by 6 pixels. */
  .fw-shell-split[data-block="band"] .fw-runs,
  .fw-shell-split[data-block="band"] .fw-maps,
  .fw-runs, .fw-maps { grid-template-columns: 1fr !important; }
  .fw-shell-split[data-block="band"] .fw-cards,
  .fw-cards { grid-template-columns: 1fr !important; }
  .fw-shell-split[data-block="band"] .fw-maps { --map-h: clamp(210px, 34vh, 300px); }
  /* The copy column's top padding is set twice at the same specificity, 71 lines apart,
     and the later one wins - which put back the 96px gap the phone rule exists to close. */
  .fw-shell-split[data-block="band"] .fw-copy { padding-top: 0 !important; }
  /* A five row chart with a truncated name is a chart of five unknowns. */
  .fw-bar-name { min-width: 0; max-width: none; white-space: normal; line-height: 1.2; }
  .fw-bar-row { grid-template-columns: minmax(0, 1fr); gap: 3.0px; }
  .fw-bar-track { width: 100%; }
}


/* The composition matrix is a full-width BAND, not a card beside a film: eleven columns
   plus a world label and a marginal need the whole page. The stage stands down here. */
.fw-shell-split[data-block="band"] { --left: 0vw; }
.fw-shell-split[data-block="band"] .fw-video,
.fw-shell-split[data-block="band"] .fw-art,
.fw-shell-split[data-block="band"] .fw-mark { opacity: 0; pointer-events: none; }
/* The index names the same six worlds the matrix's own first column names, one gutter
   away from it, and at this width its labels wrap mid-word. Two lists of six worlds on
   one screen is one too many. */
.fw-shell-split[data-block="band"] .fw-globes { display: none; }
/* The world plate is drawn on the stage, which on a full-width band is not there: it was
   printing under the copy that now spans the page. The band's own eyebrow names the stop. */
.fw-shell-split[data-block="band"] .fw-mark { display: none; }
/* A band has no film to align to, so it does not need the horizon the split layout hangs
   from - that was leaving a fifth of the screen empty above every world's own name. The
   copy column's own top padding is what sets it. */
/* `--band-pad` is written by the flight on every stop change: 96 for a panel that has to
   travel, and whatever centres it for one that fits. See the note beside it in flight.js. */
.fw-shell-split[data-block="band"] .fw-copy { padding-top: var(--band-pad, 96px); }
/* SYMMETRY. The copy column is the FILM'S neighbour in the split layout, so it is pinned to
   the film's right edge and to the page's right gutter - `left: var(--left); right: var(--gut)`.
   On a band --left drops to 0 but the right gutter stayed, so the whole column was the width
   of the page minus one gutter, hard against the left edge. Measured at 1600: 24px of margin
   on the left and 94 on the right, and the reader reads that as the page being off centre
   rather than as a gutter. The band spans the page and its own panel does the centring.

   ONLY where the layout is actually two columns. Below 1041 the split has already become a
   stack and the copy is centred the other way, with `left: 50%` and a translate of half its
   own width. Setting left to 0 there and leaving the translate alone put the panel at
   x = -488 on a 1024px screen - half the page off the left edge, with no sideways scroll to
   reach it. Measured, after this rule was first written without the query. */
@media (min-width: 1041px) {
  .fw-shell-split[data-block="band"] .fw-copy {
    left: 0;
    right: 0;
    max-width: none;
  }
}
/* Below that the split is a stack: the film takes the top 44vh and the copy starts under it.
   A band has no film - the stage is explicitly stood down - so those 44 vertical percent
   were 338px of empty page above the maps on a 768px screen. The copy starts under the
   header instead, and the band's own fitted length shortens to match. */
@media (max-width: 1040px) {
  .fw-shell-split[data-block="band"] .fw-copy { top: 72px; }
}
.fw-shell-split[data-block="band"] .fw-panel {
  will-change: transform;
  left: 0;
  width: 100%;
  max-width: min(1200px, calc(100vw - 48px));
  margin: 0 auto;
  padding-inline: 24px;
}


/* The two bands whose content is a GRID rather than prose need their own column counts:
   six live walks read as three across, and the recordings as two, which keeps a transcript
   wide enough to be read rather than skimmed. */
.fw-shell-split[data-block="band"] .fw-maps {
  grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  /* 190px of ROW is 104px of map once the card's own heading and caption are taken out, and
     a 487x104 window on a five-by-five street grid is where the walker's label and the
     copilot's label print on top of each other. The row height was small because the band
     was a fixed number of viewports and a taller grid fell off the bottom of it - now that
     the band is fitted to its content, a taller grid simply gets more band to travel
     through, so the maps can have the size the labels need. */
  --map-h: clamp(300px, 40vh, 430px);
  /* The ONE-row height, used by the stop that shows only the three solved walks. It gets
     the whole band to itself, so it is not the two-row height and should not be derived
     from it. */
  --map-tall: clamp(320px, 56vh, 560px);
}
/* SIX TRACKS, so the two groups land on their own rows.
   Two equal columns put the third Pathfinding walk beside the first banking call, which
   reads as one undifferentiated list. On six tracks the sheet's own span rules apply -
   the first three tiles take two tracks each and the rest take three - so the walks fill
   the top row three across and the recorded calls run two across underneath, which is the
   order the page claims: Pathfinding first, then the enterprise types.
   The rows stay auto so the band's travel carries the reader down them; the tiles get
   their height from the rule below rather than from the row. */
.fw-shell-split[data-block="band"] .fw-runs {
  grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
  grid-auto-rows: auto !important;
  height: auto !important;
  max-height: none !important;
}
.fw-shell-split[data-block="band"] .fw-run { min-height: 320px; }

/* THE STAGED RUNS GRID. One sample, then two, then three.
   Same idea as the walks on the setup page and the same reason: three recordings dropped
   on screen at once is a contact sheet, and the reader has no way to know which one the
   page is talking about. Revealed one at a time, each arrival is the subject.
   Column count rather than opacity does the work - a tile that is not up yet has no track
   to sit in - so the first sample uses the whole width instead of a third of it. Opacity
   is carried alongside so the arrival is a fade rather than a snap, and visibility is
   taken away too: a transparent tile is still in the hit-testing tree, and a click near
   the left edge of the grid opened a recording nobody could see. */
.fw-shell-split[data-block="band"] .fw-runs.is-staged {
  /* NO width transition on the columns. The walk renderer re-frames itself 140ms after its
     box stops changing, so an eased column is 550ms of the map being redrawn against a
     width it is about to lose - photographed mid-reveal as a street grid with no route,
     no closures and no walker on it. The columns snap and the tile fades in instead: the
     renderer measures once, at the size it is going to keep. */
  grid-template-columns: 1fr 0fr 0fr !important;
}
.fw-shell-split[data-block="band"][data-step="1"] .fw-runs.is-staged {
  grid-template-columns: 1fr 1fr 0fr !important;
}
.fw-shell-split[data-block="band"][data-step="2"] .fw-runs.is-staged,
.fw-shell-split[data-block="band"][data-step="3"] .fw-runs.is-staged {
  grid-template-columns: 1fr 1fr 1fr !important;
}
.fw-runs.is-staged .fw-run {
  /* The sheet spans the first three tiles across two tracks each and the rest across three,
     which is the six-track layout the un-staged grid uses. A staged grid has three tracks
     and one tile per track, so the spans have to go or the second tile lands off the end. */
  grid-column: auto !important;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(10px);
  /* Transform, not height or width: a tile that arrives by GROWING makes the walk renderer
     inside it re-measure and re-frame the map for every frame of the arrival. A transform
     is composited and changes no box, so the map is drawn once, at the size it keeps. */
  transition: opacity .42s ease, transform .42s cubic-bezier(.2,.7,.3,1);
}
[data-step="0"] .fw-runs.is-staged .fw-run[data-at="0"],
[data-step="1"] .fw-runs.is-staged .fw-run[data-at="0"],
[data-step="1"] .fw-runs.is-staged .fw-run[data-at="1"],
[data-step="2"] .fw-runs.is-staged .fw-run,
[data-step="3"] .fw-runs.is-staged .fw-run {
  opacity: 1; visibility: visible; pointer-events: auto; transform: none;
}
/* ONE height for every step, and a tall one.
   Two reasons, and the second is the important one. A tile that is 52vh alone and 40vh in
   company changes size on every step, and each change makes the walk renderer inside it
   re-measure and redraw a map that did not need redrawing. And with the panel now centred
   in its band, a 40vh tile left a third of the screen empty under it: at 58vh the three
   walks fill two thirds of the viewport, which is what the stop is for. */
.fw-shell-split[data-block="band"] .fw-runs.is-staged .fw-run {
  /* 66vh, up from 58. With the panel centred in its band, the tiles are the only thing on
     the screen and the rest is ground: at 58vh they used 63% of the viewport and left 146px
     of nothing under them. At 66 the panel is 535 of 757 - a heading, three tiles, and a
     margin either side that is a margin rather than a gap. Any taller and a three-across
     map goes portrait, which the street grid inside it does not want. */
  height: clamp(320px, 66vh, 620px);
}
@media (prefers-reduced-motion: reduce) {
  .fw-runs.is-staged .fw-run { transition: none; transform: none; }
}
/* A DEFINITE height, not just a floor.
   `grid-auto-rows: auto` lets a row be as tall as its content, and the content of a
   Pathfinding tile is a renderer that measures its host and lays itself out to fill it -
   so with an auto row there is nothing to fill and nothing to stop it. Measured on the
   samples page: two walk tiles over 690px tall each, one street grid per screen, and the
   five call tiles pushed a page and a half down. Same failure the `--map-h` comment
   describes for the maps grid, and the same fix: state the height.
   `min-height` still wins where it is larger, so a tile that has been played - which opens
   the speech activity strip and the transcript rail under the map - still grows to 620. */
.fw-shell-split[data-block="band"] .fw-run { height: clamp(300px, 40vh, 440px); }


/* At 1366 the opening's three plot tiles are ~30px shorter than at 1560, which is exactly
   the fifth system's row. The rows tighten rather than the chart losing a system. */
@media (max-width: 1440px) {
  .fw-open-hrows { gap: 2.0px; margin-top: 2.0px; }
  .fw-open-htrack { height: 7px; }
  .fw-open-cell.is-plot .fw-open-lab { margin-bottom: 0; }
  .fw-open-pnote { margin-top: 4px; }
}


/* The opening is exactly one viewport. On a short screen (a 768-tall laptop) the three
   plot tiles cannot hold a headline AND five systems, and a chart missing a system is a
   worse trade than a missing headline - the chart states the same number on its top row.
   The shared caption below the row still carries the thesis. */
/* Guarded on width. These compact a SHORT LAPTOP; on a phone the opening is
   height:auto and scrolls, so there is nothing to compact - and the 820px rule was
   deleting the three headline results from every phone that shows its URL bar. */
@media (max-height: 820px) and (min-width: 701px) {
  .fw-open-head { display: none; }
}


/* At 390 the cap rule sits at the right edge of a ~200px plot, so its label cannot fit
   beside it in either direction. On a phone the caption states the cap instead. */
@media (max-width: 700px) {
  .fw-vio-capline b { display: none; }
}


/* THE OPENING ON A SHORT SCREEN
   ---------------------------------------------------------------------------------
   The opening is exactly one viewport and it clips, so a tile that cannot hold its
   content does not push the page - it silently cuts the text. Measured: the abstract lost
   29px (a line and a half of a sentence) at 1366x768 and 17px at 1280x800, and the DNSMOS
   chart lost its fifth system at every height below 1080.

   Rather than cut the words, the type and the spacing step down with the viewport. Two
   thresholds, because a 982px MacBook needs a little and a 768px laptop needs a lot. */
@media (max-height: 1000px) and (min-width: 701px) {
  .fw-open { padding-top: clamp(52px, 6.2vh, 80px); }
  /* The three byline rows are a row of type each; on a short screen they set at the micro
     size and lose their gaps, which is the last few pixels the tiles need. */
  .fw-open-affrow { font-size: 14px; }
  .fw-open-by { gap: 0; line-height: 1.28; }
  /* The byline is five stacked lines - two affiliations, an email and two notes - and on
     a short screen those lines come straight out of the charts below. Flowed inline they
     are one or two lines and say exactly the same thing. This only exists in the public
     build; the anonymous one has no byline at all. */
  .fw-open-note, .fw-open-mail, .fw-open-aff { font-size: 14px; }
  /* The middle row holds the mark and takes 1.45 of the three shares. On a short screen
     that is exactly the height the bottom row needs for its fifth system, and a slightly
     smaller globe costs nothing next to a chart that shows four systems out of five. */
  .fw-open-grid { grid-template-rows: minmax(0, 1fr) minmax(0, 1.16fr) minmax(0, 1.04fr); }
  .fw-open-cell { padding: 8px 12px; }
  .fw-open-lab { margin-bottom: 4px; letter-spacing: 0.07em; }
  .fw-open-text { line-height: 1.42; }
  .fw-open-hrows { gap: 2.0px; margin-top: 2.0px; }
  .fw-open-htrack { height: 7px; }
  .fw-open-hrow { row-gap: 1.0px; }
  .fw-open-i { line-height: 1.3; }
}
@media (max-height: 860px) and (min-width: 701px) {
  .fw-open { padding-top: 56px; padding-bottom: 8px; }
  /* The ring, its six counts and the three figures above them are a few pixels over at
     1366x768: the last row, Pathfinding, was losing its baseline. */
  .fw-ring { max-height: 96px; }
  /* The note keeps all of its words: clamping it truncated an explanation, and nothing on
     this page is truncated. It gets the room instead - the label above it gives up its
     letter-spacing row and the rows tighten by a pixel. */
  .fw-open-pnote { margin-top: 0; line-height: 1.3; }
  .fw-open-cell.is-plot .fw-open-lab { margin-bottom: 0; letter-spacing: .04em; }
  .fw-ring-wrap { gap: 8px; margin-top: 0; align-items: flex-start; }
  .fw-ring-krow { line-height: 1.25; }
  .fw-ring-khead { margin-bottom: 0; grid-column: 1 / -1; }
  /* Six rows in a column is one row taller than this tile on a short screen, so the
     counts run in two columns. Same six numbers, half the height. */
  .fw-ring-key {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 12px; row-gap: 1.0px;
  }
  /* The last few pixels at 1366x768, which are the fifth system's track. */
  .fw-open:has(.fw-open-by) .fw-open-grid {
    grid-template-rows: minmax(0, 0.94fr) minmax(0, 1.02fr) minmax(0, 1.12fr);
  }
  .fw-open-title { margin-bottom: 8px; }
  .fw-open-cell { padding: 8px 8px; border-radius: 12px; }
  .fw-open-text { font-size: 14px; line-height: 1.38; }
  .fw-open-lab { font-size: 14px; margin-bottom: 2.0px; }
  .fw-open-grid { gap: 8px; }
  .fw-open-hrows { gap: 1.0px; margin-top: 0; }
  .fw-open-htrack { height: 6px; }
  .fw-open-chip { padding: 3.0px 8px; }
  .fw-open-w span { font-size: 14px; }
  .fw-open-wimg { width: clamp(20px, 1.7vw, 26px); }
}


/* A byline is two more lines above the matrix, so the rows rebalance when one is present.
   Only the public build has one; the anonymous build keeps the original shares. */
.fw-open:has(.fw-open-by) .fw-open-grid {
  grid-template-rows: minmax(0, 0.98fr) minmax(0, 1.08fr) minmax(0, 1.10fr);
}
/* Grouping the byline into three rows costs about a line of height. It comes out of the
   title's own margin, not out of the charts, which have none to give. */
.fw-open:has(.fw-open-by) .fw-open-title { margin-bottom: 4px; }


/* A Pathfinding tile in voice mode has three things to show, not one: the map, the two
   lane speech activity strip and the transcript rail. The tile grows to fit them, because
   the timeline is the reason the play button exists. */
.fw-shell-split[data-block="band"] .fw-run[data-kind="map"]:has(.wm-voice) {
  grid-column: 1 / -1;
  min-height: 560px;
}
.fw-run .wm-root.wm-voice .wm-tl { min-height: 84px; flex: none; }
.fw-run .wm-root.wm-voice .wm-rail { max-height: 200px; overflow-y: auto; flex: none; }
/* The box that holds the map has to give the strip and the rail their own room, or the
   map keeps all of it and the timeline opens at zero height below the fold. */
.fw-run-box:has(.wm-voice) { min-height: 460px; }
.fw-run-box:has(.wm-voice) .wm-body { flex: 1 1 auto; min-height: 180px; }
.fw-run:has(.wm-voice) { min-height: 620px; }




/* THE EXPERIENCE PAGE
   ---------------------------------------------------------------------------------
   A cinema. The film fills the screen and one line sits on it; there is no second column
   because there is nothing to put in it. Everything else on this site earns its place by
   carrying a number - here the picture is the argument. */
.fw-shell-split[data-block="cinema"] { --left: 100vw; }
.fw-shell-split[data-block="cinema"] .fw-video,
.fw-shell-split[data-block="cinema"] .fw-art {
  left: 50%;
  top: 0;
  width: 100vw !important;
  height: 100vh;
  object-fit: cover;
  /* No feather: a full-bleed frame has no edge to hide. */
  -webkit-mask-image: none; mask-image: none;
  border-radius: 0;
}
.fw-shell-split[data-block="cinema"] .fw-globes,
.fw-shell-split[data-block="cinema"] .fw-mark { display: none; }
.fw-shell-split[data-block="cinema"] .fw-panel {
  left: 0; width: 100%; max-width: 100%;
  /* Centred on the screen rather than sitting on its floor: the film fills the frame, so
     a line at the bottom edge reads as a subtitle under a picture. In the middle it reads
     as the point of it. */
  display: grid; place-items: center;
  min-height: 100vh;
  padding: 0 clamp(24px, 6vw, 96px);
  pointer-events: none;
  text-align: center;
}
/* The copy column is positioned at --left on every other block, which on a cinema is the
   full width of the window - so the words were laid out 1526px into a 1440px viewport,
   present and perfectly styled and entirely off the screen. */
/* The stage is fixed and the copy is not, so on every other block the two never meet.
   With the film at full bleed they occupy the same pixels, and the words were painting
   underneath it - present, correctly placed, invisible. */
.fw-shell-split[data-block="cinema"] .fw-panel { position: relative; z-index: 12; }
.fw-shell-split[data-block="cinema"] .fw-copy {
  position: static;
  z-index: 12;
  left: 0; right: auto;
  width: 100%; max-width: 100%;
  padding-top: 0;
}
.fw-cine {
  max-width: min(920px, 84vw);
  pointer-events: auto;
  position: relative;
  text-align: center;
}
/* A scrim, because the film underneath is bright, detailed and different at every stop:
   dark type on a snow globe is legible in one frame and gone in the next. The gradient is
   the page's own ground, so the words sit on the same surface the rest of the site uses
   rather than on a black bar. */
.fw-shell-split[data-block="cinema"] .fw-panel::before {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at center,
    color-mix(in srgb, var(--bg-base) 88%, transparent) 0%,
    color-mix(in srgb, var(--bg-base) 72%, transparent) 42%,
    transparent 72%);
  pointer-events: none;
  z-index: -1;
}
.fw-cine-big {
  margin: 0;
  font-size: clamp(32px, 5.6vw, 64px);
  font-weight: 800; letter-spacing: -0.025em; line-height: 1.02;
  color: var(--ink);
  text-wrap: balance;
}
.fw-cine-say {
  margin: 16px auto 0;
  font-size: clamp(17px, 1.7vw, 24px);
  line-height: 1.4; color: var(--sub);
  max-width: 52ch;
}
.fw-cine-go {
  display: inline-block; margin: 24px 16px 0 0;
  font-size: 17px; font-weight: 700; text-decoration: none;
  color: #fff; background: var(--ink);
  padding: 12px 24px; border-radius: 999px;
}
.fw-cine-go:hover { background: var(--pillar-agentic); }
.fw-cine-go:focus-visible { outline: 2px solid var(--pillar-agentic); outline-offset: 3px; }
