  /* ---- the phase rail ------------------------------------------------
     Six phases, and the same six at the top of every phase page, so it
     has to be one component in one file. It replaces the five-row bill
     of materials that used to sit here. That table said what each phase
     DELIVERS, which is the phase page's job now; what is left on the
     homepage is the shape of a project and a way into it.

     Built only out of what the page already owns. The rail is one
     hairline, the same weight as every rule in .cells. Each stop is a
     ground-filled box sitting on top of it, so the line reads as
     passing behind the row rather than being segmented by it. The node
     on each stop is the page's dot -- the wordmark's period, the .dots
     sheet -- drawn as a 15px ring so it can hold a mark. No icon set:
     what goes inside is a text arrow, and nothing is round except the
     nodes. 2px is this page's only radius (it is what :focus-visible
     uses) and the stops borrow it rather than introducing a capsule.

     Three states, and they are honest rather than decorative:
       .key   written and reachable. Ink rule, an arrow in the ring, a
              link. The arrow is the affordance: a rail made of
              hairlines reads as a drawing, and a drawing is not
              something you press.
       .soon  not written yet. Dashed rule -- the hidden-line idiom from
              a technical drawing -- an empty ring, and NOT a link,
              because a stop you cannot go to should not behave like one.
       .here  the page you are on. Ink rule doubled, a red core in a red
              ring and no arrow, because an arrow on the stop you are
              standing on points at where you already are.

     --ground and --rule-c are read with fallbacks, so the rail is
     correct inside a .band, inside a .band.alt, or on its own at the
     top of a phase page where no band has declared them.
     -------------------------------------------------------------- */
  /* flow-root, because the ol below carries negative margins that cancel its
     own padding, and with nothing between them they were adjoining .rail's
     margins and collapsing through it instead of pulling the ol up inside.
     .rail kept all 22px of the new padding and the hairline at top:50% sat
     3px under the row it is meant to pass behind -- 8px on a desktop with
     classic scrollbars. A block formatting context resolves the ol's margin
     box inside .rail, which is what the cancelling margins were for. */
  .rail{position:relative;display:flow-root;}
  /* On .rail, not on the ol: at narrow widths the ol becomes the scroll
     container, and an absolutely positioned child of a scroller resolves
     left/right against the visible box and then scrolls away with the
     content. Anchored to the wrapper it always spans exactly what you
     can see, which is what a rail should do. */
  .rail::before{
    content:"";
    position:absolute;
    left:0;
    right:0;
    top:50%;
    border-top:1px solid var(--rule-c,var(--rule));
    z-index:0;
  }
  /* One behaviour at every width, rather than a desktop layout and a phone
     layout that can disagree in the 100px between them. The stops share the
     space while there is enough of it and the rail scrolls once there is
     not: 106px is what the widest stop needs with its node, its rule and
     its padding, measured, not guessed, and 112px leaves it slack. Six of
     those plus the gaps is 732, so the rail is still a fitted row of six
     inside the 820px the homepage section gives it, and a scroller on a
     phone -- same component, no breakpoint deciding which. The 12px gap is
     doing real work: the hairline behind the row only reads as a rail if
     enough of it shows between the stops. */
  .rail ol{
    position:relative;
    z-index:1;
    list-style:none;
    margin:0;
    padding:0;
    display:grid;
    grid-template-columns:repeat(6,minmax(112px,1fr));
    column-gap:12px;
    overflow-x:auto;
    /* An overflow-x of auto computes overflow-y to auto too, so this ol is a
       scroll container in both axes and clips descendant paint to its
       padding box -- which is exactly one stop tall. The hover shadow
       (0 3px 10px) reaches 13px below the box and 7px above it, and every
       pixel of it was being cut off, so the stop rounded but never lifted.
       The padding gives the shadow somewhere to be; the negative margin
       hands the space straight back, so the row does not grow and --railh
       stays honest. */
    padding:8px 0 14px;
    margin:-8px 0 -14px;
    overscroll-behavior-x:contain;
    scroll-snap-type:x proximity;
    /* Only ever seen on a phone, where the rail actually scrolls. Kept at
       hairline weight in the page's own rule colour so it reads as part of
       the drawing rather than as browser furniture. */
    scrollbar-width:thin;
    scrollbar-color:var(--rule-c,var(--rule)) transparent;
  }
  .rail li{min-width:0;scroll-snap-align:center;}
  .rail .pill{
    display:block;
    padding:12px 14px 13px;
    background:var(--ground,var(--paper));
    border:1px solid var(--rule-c,var(--rule));
    /* 8px, where the rest of the page uses 2px. The stops are the one thing
       here you are meant to want to touch, and a softer corner says that
       before any of the type does. The 2px stays everywhere else, including
       :focus-visible, so this reads as one deliberate exception. */
    border-radius:8px;
    color:var(--body);
    /* the global link rule puts a red underline and a padding-bottom on
       every a; the two above take both back for this one */
    text-decoration:none;
    /* background-color is in here because .here MOVES: on /phases the current
       stop hands off to the next one as you scroll, and a ground that snapped
       on would read as a flicker rather than a handover. border-radius and
       box-shadow are in here for the hover, which rounds the box further and
       lifts it. All four are paint. Nothing in this transition can change the
       size of a box, which is what keeps the row of six still. */
    transition:background-color .18s ease,border-color .18s ease,color .18s ease,
               border-radius .18s ease,box-shadow .18s ease;
  }
  .rail .n{
    display:block;
    font-size:.66rem;
    letter-spacing:.14em;
    color:var(--soft);
    font-variant-numeric:tabular-nums;
    transition:color .18s ease;
  }
  .rail .t{
    display:flex;
    align-items:center;
    gap:8px;
    margin-top:5px;
    font-family:"Instrument Serif",Georgia,serif;
    font-size:1.16rem;
    line-height:1.2;
    white-space:nowrap;
  }
  /* The node is a 15px ring rather than the 7px dot it started as, because
     on a stop you can open it now HOLDS something: a right arrow, which is
     the plainest way a row of boxes can say the three you can press. The
     dot was the page's own dot -- the wordmark's period, the .dots sheet --
     and the ring keeps that lineage: it is the same circle, drawn rather
     than filled, and what goes inside it is what the state is.

     One box, 15px, for all three states, so the six labels start on the
     same x whatever each stop is doing. A ring that grew only on the
     stops with an arrow in it would stagger the row.

     15 and not more: .t is a 1.2 line on a 1rem serif in the bar and a
     1.16rem one on the page, so the line box is 19.2px at its smallest.
     Anything up to 19 fits inside it without the row growing a pixel, and
     15 leaves the arrow room to be an arrow while keeping the mark smaller
     than the type beside it. */
  .rail .node{
    flex:none;
    width:15px;
    height:15px;
    border-radius:50%;
    border:1px solid var(--soft);
    background:transparent;
    /* Centring whatever the state puts inside. A flex box rather than
       line-height, because the arrow and the current stop's core are
       different shapes and only one of them is type. */
    display:flex;
    align-items:center;
    justify-content:center;
    transition:background-color .18s ease,border-color .18s ease;
  }
  /* What sits inside, at rest: nothing. A .soon stop is an empty ring, which
     is the same statement its dashed border makes. */
  .rail .node::before{
    content:"";
    line-height:1;
    transition:opacity .14s ease,color .18s ease,background-color .18s ease;
  }
  /* written, reachable */
  /* Both shadows declared at zero, so that every state below is a two-item
     list of the same shape. CSS cannot interpolate between shadow lists whose
     inset keywords differ, and the current stop's rule is inset while the
     hover lift is outset: without this the box-shadow jumps in one frame
     while the colours beside it fade over 180ms. That is the click case
     exactly, where a stop becomes current under a pointer that has not moved.
     A zero-blur zero-spread shadow paints nothing, so the resting look is
     unchanged. Anything added here later needs both items or the snap is
     back. */
  .rail .key .pill{
    color:var(--ink);border-color:var(--ink);
    box-shadow:inset 0 0 0 0 var(--ink),0 0 0 0 rgba(26,26,26,0);
  }
  .rail .key .node{border-color:var(--ink);}
  /* The arrow. It is the affordance, said in one character rather than in a
     shadow or a hover -- a reader on a phone never gets a hover, and the
     rail is deliberately built out of hairlines, which is exactly what
     makes it read as a drawing rather than as something you press.

     A text arrow, in the page's own body face, not an icon: the site owns
     no icon set and this is the only place one would be needed. 9px against
     a 13px inner diameter, and U+2192 is almost all horizontal, so it sits
     inside the ring with air above and below rather than filling it.

     -0.5px, because the glyph's ink is not centred in its advance -- the
     arrowhead pushes the mass right of centre, and optically centred is
     what a mark this small has to be. */
  .rail .key .node::before{
    content:"\2192";
    font-family:"Inter",system-ui,sans-serif;
    font-size:9px;
    color:var(--ink);
    margin-left:-.5px;
  }
  /* Gone the moment the stop is pressed. On /phases the press makes it the
     current stop, and .here draws its own core below, so the arrow is
     already replaced by the time the finger lifts -- this covers the beat
     in between, and it is the whole of the feedback on the homepage, where
     the press is a navigation and there is no current stop to become. */
  .rail .key .pill:active .node::before{opacity:0;}
  /* Hover: the box rounds further and lifts. That is the whole affordance --
     a stop that reacts to the cursor is a stop you can click, and the number
     already says these are steps in a sequence, so no arrow has to say it
     again. The shadow is deliberately weak: 12% ink at 10px blur is a hint
     of a card, and anything heavier turns a drawing into a UI.

     The label stays ink. Red type at this size on paper is about 3:1, so
     the red goes on the number and the node instead, which are marks rather
     than reading matter. */
  /* Guarded, because a tap applies :hover on iOS and Android and leaves it
     applied until the reader taps something else. On /phases a tap is an
     in-page jump, so an unguarded rule leaves the rail showing two current
     stops: the one still painted as hovered, and the one the scrollspy has
     just moved .here to. A rail whose whole job is "where am I" must not
     have two answers. */
  @media (hover:hover) and (pointer:fine){
    .rail .key .pill:hover{
      border-color:var(--red);
      border-radius:14px;
      box-shadow:inset 0 0 0 0 var(--ink),0 3px 10px rgba(26,26,26,.12);
    }
    /* The ring reddens and the arrow inside it goes with it. Not a fill:
       the node is a ring holding a mark now, and filling it would paint out
       the very thing the hover is meant to be pointing at. */
    .rail .key .pill:hover .node{border-color:var(--red);}
    .rail .key .pill:hover .node::before{color:var(--red);}
    /* The mark red, not the text red. This number is 0.66rem, tabular and
       tracked, sitting 14px above a node that is --red and inside a border
       that is --red: it works as a mark, and nothing is read off it. Putting
       it back on --red restores the single-red rail. */
    .rail .key .pill:hover .n{color:var(--red);}
  }
  /* ---- the homepage rail: saying the stops can be opened ---------------
     nav.rail is the homepage's instance and only the homepage's: on a phase
     page the rail is .railband > .rail, so none of this reaches the pinned
     bar, where six stops glinting under a wordmark would be a light show.

     The problem it solves is that on the homepage the rail reads as a
     drawing. It is built out of the page's own hairlines on purpose, and it
     succeeded a little too well -- three of the six are links to a whole
     page each, and nothing said so until a cursor happened to land on one.
     A reader on a phone never gets that cursor at all.

     Two things say it, and they say it in different tenses.

     A standing shadow, so a stop that can be opened sits slightly off the
     paper at rest, and one that cannot is flat on it. That is the state
     that matters: the difference between .key and .soon becomes something
     you can see at a glance rather than a dashed border you have to look
     for. Weaker than the hover -- 1px at 10% against 3px at 12% -- so the
     lift on hover is still a lift and not a nudge.

     And a shine, once, when the rail first comes into view. A single pass
     of the page's own red across the three that open, left to right in
     their reading order, which reads as the row announcing itself rather
     than as three boxes doing something. White would have been the obvious
     sweep and would have been invisible: paper is #FBFAF8, and there is
     nothing brighter than paper to sweep across it.

     Delivery: rail.js adds .lit when the row is actually on screen and sets
     --shine-delay per stop. A plain animation-delay would have run the whole
     thing at load, four screens above the reader. */
  nav.rail .key .pill{
    position:relative;
    /* Both items again -- see the note on the resting box-shadow above. The
       inset stays at zero and the outset is the standing lift. */
    box-shadow:inset 0 0 0 0 var(--ink),0 1px 4px rgba(26,26,26,.10);
  }
  /* Clipping the sweep to the stop. The pill has nothing that overflows it
     -- the current stop's node ring is a spread shadow well inside the
     padding, and :focus-visible is already inset for the scroller -- so
     this costs nothing but the containment it is here for. */
  nav.rail .key .pill{overflow:hidden;}
  nav.rail .key .pill::after{
    content:"";
    position:absolute;
    inset:0;
    pointer-events:none;
    /* 12deg off vertical, so the band crosses the two lines of the stop at
       a slight rake rather than sweeping them as one block. */
    background:linear-gradient(102deg,
      rgba(255,77,77,0) 38%,
      rgba(255,77,77,.20) 50%,
      rgba(255,77,77,0) 62%);
    transform:translateX(-160%);
    opacity:0;
  }
  @media (prefers-reduced-motion:no-preference){
    nav.rail ol.lit .key .pill::after{
      animation:railshine .9s cubic-bezier(.3,0,.35,1) var(--shine-delay,0s) 1 both;
    }
  }
  /* Held at full opacity across the middle rather than faded in and out
     with the travel: the band is already a gradient that fades at both
     edges, and fading the whole layer on top of that made the pass read as
     a blink in the middle of the stop instead of something crossing it. */
  @keyframes railshine{
    from{transform:translateX(-160%);opacity:0;}
    12%{opacity:1;}
    88%{opacity:1;}
    to{transform:translateX(160%);opacity:0;}
  }
  /* Inset, because the ol is a scroll container at narrow widths and an
     outline sitting 3px OUTSIDE the stop is 3px the scroller clips.

     Ink rather than the red used elsewhere, because this ring lands on two
     different grounds: paper on a written stop, tint on the current one.
     Red measures 3.14:1 on the first and 2.85:1 on the second, so it fails
     exactly where the reader is standing. Ink is 16.7 and 15.2. */
  .rail .pill:focus-visible{outline:2px solid var(--ink);outline-offset:-3px;}
  /* not written yet */
  .rail .soon .pill{border-style:dashed;cursor:default;}
  /* the page you are on: the page's own tint under an ink hairline. This
     used to be an ink fill, which put a black brick in the middle of a light
     page and, worse, came back on hover. Tint says the same thing quietly.

     The padding is identical to every other stop, so the six still sit on
     one line with one shared height. A stop that grew when it became current
     would push the row taller and leave the other five hanging off the top
     of it. */
  .rail .here .pill{
    background:var(--tint);
    border-color:var(--ink);
    color:var(--ink);
    /* Tint against paper is 1.10:1, and the border is the same ink the other
       written stops already have, so before this the "you are here" was
       carried almost entirely by the node. An inset hairline doubles the
       rule to 2px without changing the box by a pixel. */
    box-shadow:inset 0 0 0 1px var(--ink),0 0 0 0 rgba(26,26,26,0);
  }
  .rail .here .n{color:var(--body);}
  /* The node picks up a ring, and the ring is box-shadow spread rather than
     a bigger dot or a border: the dot stays 7px, so the label baseline beside
     it cannot move. Tint first, then red, so the gap reads as the chip's own
     ground showing through rather than as a fat two-tone dot. */
  .rail .here .node{border-color:var(--red);}
  /* A red core inside a red ring, and NO arrow: an arrow on the stop you
     are already standing on is an invitation to go where you are. The core
     is the 7px dot the node used to be, which is why this state still reads
     as the page's own dot -- it is the same dot, now with a ring drawn
     round it.
     Below .key's arrow in the file and at the same specificity, so this is
     what a stop carrying both classes ends up with. */
  .rail .here .node::before{
    content:"";
    width:7px;
    height:7px;
    border-radius:50%;
    background:var(--red);
    margin-left:0;
  }
  /* The current stop is a link like the other two, so .key's hover applies to
     it as well and would lift and re-round the stop you are already standing
     on. Hovering where you already are should do nothing, so every property
     that hover touches is restated at its resting value. */
  @media (hover:hover) and (pointer:fine){
    .rail .here .pill:hover{
      background:var(--tint);
      border-color:var(--ink);
      color:var(--ink);
      border-radius:8px;
      /* The resting inset hairline, not none: none would wipe the doubled
         ink rule that marks the current stop. */
      box-shadow:inset 0 0 0 1px var(--ink),0 0 0 0 rgba(26,26,26,0);
    }
    .rail .here .pill:hover .n{color:var(--body);}
    .rail .here .pill:hover .node{border-color:var(--red);}
    .rail .here .pill:hover .node::before{background:var(--red);}
  }

  /* The rail as the top band of a phase page: full bleed, hairline
     under it, and nothing else in the row.

     Sticky, because on /phases the rail is not a signpost you pass once --
     it is the page's position indicator, and it can only say where you are
     if it is still on screen when you get there. It needs an opaque
     background of its own: the band it sticks over scrolls underneath it,
     and --ground alone is a variable, not a paint. z-index clears the .dots
     sheet (0) and the bands that sit above it (1).

     --railh is the offset every anchor target has to clear so a heading
     does not land underneath the rail it was reached from. Declared here,
     next to the padding that decides it, so the two cannot drift. */
  .railband{
    --ground:var(--paper);
    --rule-c:var(--rule);
    position:sticky;
    top:0;
    z-index:5;
    background:var(--paper);
    border-bottom:1px solid var(--rule);
    /* Only the bottom rule changes when it sticks. The obvious idea is to
       shrink the band once it is pinned, and it is wrong: the band is still
       in normal flow, so taking 22px out of it pulls every following section
       up by 22px under a viewport that is not moving. That is a jump, at the
       exact moment the reader is scrolling. Paint, not layout. */
    transition:border-color .18s ease,box-shadow .18s ease;
  }
  /* Set by rail.js the moment the band reaches the top of the screen: the
     hairline goes to ink and picks up the faintest lift, so the rail reads
     as sitting ON the page rather than in it. */
  html.railstuck .railband{
    border-bottom-color:var(--ink);
    box-shadow:0 1px 0 rgba(26,26,26,.06), 0 6px 16px -12px rgba(26,26,26,.5);
  }
  /* Nested in the bar, the band is not what pins -- the header is -- so the
     shadow has to be cast by the thing that has an edge to cast it. */
  html.railstuck .railhead .railband{box-shadow:none;}
  html.railstuck .railhead{
    box-shadow:0 1px 0 rgba(26,26,26,.06), 0 6px 16px -12px rgba(26,26,26,.5);
  }
  /* The bar's rule and its shadow are both the header's now, and both are
     part of the same state change, so both fade on the same clock. Either
     one left out snaps on while the other fades, which is half a state
     change arriving twice. */
  header.railhead{transition:border-color .18s ease,box-shadow .18s ease;}
  /* Everything a stop can jump to clears the rail by its own height plus a
     little air, so you land looking at the heading rather than at the rule
     above it. */
  /* Flush, not 8px clear. The 8px was meant as air between the bar and the
     heading, but what it actually showed on arrival was a 9px stripe of the
     PREVIOUS band's tint above the phase's own ink rule: three horizontal
     lines in 11px, which reads as a rendering seam. The phead's own 60px of
     padding is the air. */
  .phase{scroll-margin-top:var(--railh);}
  /* The last phase is a stub and the band under it is short, so the document
     runs out of scroll before this section's top can reach the line every
     other stop lands on: 157px short at 1280, 101px at 375. The stop lights
     up while the section it names is still under the middle of the screen.
     Reserve the shortfall rather than write filler copy into a stub. svh
     rather than vh so a phone's URL bar cannot make the reserve taller than
     the screen it is reserving against. */
  /* Not :last-of-type. The phases are section elements and section.end sits
     after them, so :last-of-type picks the contact band and this rule would
     silently do nothing. "The phase with no phase after it" is what was
     meant. */
  .phase:not(:has(~ .phase)){min-height:calc(100svh - var(--railh));}
  /* The 70px above is the homepage header. On a phase page the pinned bar is
     the wordmark row plus the rail, so a label pinning at 70px pinned 92px
     up inside the bar, behind it, and was invisible for as long as it was
     stuck. */
  /* The closing band is a sibling of the phases rather than inside one, so
     its label kept pinning at 70px -- the homepage header's height -- while
     the bar above it is 161. Scoped to the page with the tall bar: the
     homepage's own closing band is correct at 70px, because that is its
     header. Latent today, since the band is shorter than a screen. */
  .phase .sec > h2,.phase .sec > h3,
  .railhead ~ .end .sec > h2{top:calc(var(--railh) + 12px);}

  /* ---- the rail as the bar's second row --------------------------------
     From 901px up a stop gets one line rather than two, which is what makes
     the second row of the pinned bar cost about what the wordmark row above
     it costs instead of a screen and a half of it. Everything else about a
     stop is unchanged: same three states, same node, same 8px corner, same
     hover.

     Number BESIDE the label rather than above it. It is the one change that
     buys the whole height back -- two lines of a 15px stack is 41px of type
     before any padding, one line is 21 -- and the number reads the same
     either way, because it is a mark on the stop rather than a heading for
     it. Aligned on the baseline, so the tracked digits sit on the serif's
     line rather than floating in the middle of it.

     The type comes down a little with it: the label from 1.16rem to 1rem and
     the number from .66 to .62. Not for the height -- the padding pays for
     that -- but for the proportion. At the band's size a stop was a card
     with a title; at the bar's it is a tab, and a tab whose label competes
     with the wordmark two inches to its left is a bar with two voices. -- */
  @media (min-width:901px){
    /* 123, not the 112 a two-line stop needs. The min is what decides
       whether the row fits or turns into a scroller inside a pinned bar --
       which it must not, because the whole point of the bar is that all six
       are in front of you. 123 is what the widest stop, Prototype,
       measures with its ring, its padding and its number all on one line;
       the row has the full measure to itself, so the narrowest track in
       this range is about 131 and the min never actually binds. It is the
       floor, not the layout -- and it is here so that a stop growing past
       it turns into a visible scroller rather than a silently clipped
       sixth stop. */
    .railhead .rail ol{grid-template-columns:repeat(6,minmax(123px,1fr));}
    .railhead .rail .pill{
      display:flex;
      align-items:baseline;
      gap:9px;
      padding:7px 12px 8px;
    }
    .railhead .rail .n{font-size:.62rem;}
    .railhead .rail .t{margin-top:0;font-size:1rem;gap:7px;}
  }
