  header{
    position:sticky;
    top:0;
    z-index:6;
    background:var(--paper);
    border-bottom:1px solid var(--ink);
  }
  /* On a phase page the wordmark and the rail are one bar, not two.
     Two sticky bars stacked was wrong twice over: the rail pinned UNDER the
     header and you saw a sliver of it, and even stacked correctly it was
     195px of pinned furniture on a phone. Nested, the header does the
     sticking for both. */
  /* The bar's bottom edge is the HEADER's, in the rail's hairline weight
     rather than the ink the homepage header uses. It used to be the band's
     own border, which worked while the band was a full-width sibling; the
     band is a child of the measure now, so its border would stop under the
     last stop and leave the wordmark's half of the bar with no edge. */
  header.railhead{border-bottom-color:var(--rule);}
  html.railstuck header.railhead{border-bottom-color:var(--ink);}
  .railhead .railband{position:static;background:none;border-bottom:none;}
  /* Same 22/12 as the homepage header, so the wordmark row on a phase page
     is the same block -- and the row-gap repeats the 12 under it, so the
     rail is separated from the wordmark by exactly what the homepage puts
     under its own. The band carries no padding of its own: one box decides
     the spacing, and it is the box the wordmark is in. */
  header.railhead > .in{row-gap:12px;}
  /* The band gets a line of its own, under the wordmark row, at every
     width. flex-basis 100% is what puts it there, since it is a flex child
     of that row rather than a block after it.

     The stops did sit BESIDE the wordmark for a while, which made the bar
     the same 66px the homepage header is. The site nav took that row: a
     list of where you can go and an indicator of where you are are two
     different statements, and a row holding both says neither clearly --
     at 1024 it could not physically hold both anyway. So the rail is a
     second row again, but a slim one: one line per stop rather than two,
     which is what the row beside the wordmark bought and is worth keeping
     on its own. The bar is about 115px, against 161 before and 66 for the
     wordmark row alone.

     The bottom rule is the header's rather than the band's, because the
     band is a child of the measure now and its border would stop at the
     text margin instead of running the width of the screen. Same for the
     stuck state's ink. */
  /* min-width:0, because a flex item's automatic minimum is its max-content
     size and the stops are a grid of six nowrap labels: without it the band
     sized itself to 796px inside a 375px screen and took the whole document
     sideways with it. The ol never clipped, because the ol WAS the thing
     that was too wide -- a scroller can only scroll if it is allowed to be
     narrower than what is in it. */
  .railhead .railband{flex:1 0 100%;min-width:0;}
  /* ---- the site nav ---------------------------------------------------
     Three links, in the slot the address line used to have, in the same
     grey gloss it was set in: this is furniture, and the page spends its
     serif on the things you read. Nothing here introduces a colour, a
     weight or a size the header did not already use.

     The underline is the page's own link rule, held at transparent until
     the link is hovered or is the page you are on. Held rather than
     removed, so the box never changes size and the row cannot shift by a
     pixel when the red arrives. */
  .sitenav{
    display:flex;
    flex-wrap:wrap;
    gap:6px 22px;
    font-size:.75rem;
    letter-spacing:.06em;
    text-transform:uppercase;
    white-space:nowrap;
  }
  .sitenav a{
    color:var(--soft);
    border-bottom-color:transparent;
    transition:color .18s ease,border-color .18s ease;
  }
  /* Ink and a red rule for both, because "where you are" and "where the
     cursor is" are the same statement made about different things, and the
     bar has one way of making it. aria-current is what carries it on the
     page you are on, so the markup and the paint cannot disagree. */
  .sitenav a:hover,
  .sitenav a[aria-current]{color:var(--ink);border-bottom-color:var(--red);}
  /* Two of the three links are in-page jumps, and the header they jump
     under is sticky, so without this the section they land on arrives
     behind it. 66, measured, for the same reason and by the same rule as
     --railh: under the box rather than equal to it, so the landing is a
     pixel inside the bar rather than a pixel clear of it. */
  #work,#contact{scroll-margin-top:66px;}
  /* On a phase page the bar is the wordmark row plus the stops, so Contact
     has a taller thing to clear -- and --railh is exactly the measurement
     of it. :has, so the rule reads off the bar the page actually has
     rather than off a class someone has to remember to put on <body>. */
  html:has(.railhead) #contact{scroll-margin-top:var(--railh);}
  /* Direct child. It used to matter because the rail carried a second .in
     inside this one and picked up the row's flex and its 28px of padding;
     that .in is gone, and the combinator stays because the shape it
     describes -- this rule is the header's own row, not any measure that
     happens to be under it -- is still what is meant. */
  header > .in{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    gap:16px;
    flex-wrap:wrap;
    padding-top:22px;
    padding-bottom:12px;
  }
  .mark{
    /* Baskerville: the self-hosted cut first, then the system faces
       (Baskerville on macOS, Baskerville Old Face on Windows). */
    font-family:"Libre Baskerville",Baskerville,"Baskerville Old Face","Times New Roman",serif;
    font-size:1.32rem;
    letter-spacing:-.005em;
    position:relative;
    white-space:nowrap;
    /* .mark itself never transforms in either choreography, so nothing reflows
       and the meta text to its right never moves. */
  }
  /* Unscoped, because these have to hold whether or not a variant ever
     initialises -- JS off, reduced motion, a module bailing out. The disc
     stays hidden and the plain red period is what you see. */
  .mark .disc{display:none;}
  .mark .stop{color:var(--red);font-size:1.366em;line-height:0;}
  /* zero-width strut: its top edge is the wordmark's baseline */
  .mark .base{display:inline-block;width:0;height:0;vertical-align:baseline;}
  /* The choreography's own transitions, scoped to .vb so a second one could
     ship beside it without the two ever seeing each other; MORPH.mount puts
     the variant class on .mark before it builds anything.

     Timing lives HERE, in the transitions, and only here -- the script sets
     the two end states and lets CSS interpolate between them. The few delays
     the script does compute (the per-letter cascades) are solved against the
     curves below and say so, with the duration they must match named in the
     variant that owns them.

     dev/morph-b.html is the standalone sandbox this grew up in. It predates
     the shared core and is no longer the source of truth: tune here, and
     treat it as history. It sits outside public/ so it is never deployed --
     see dev/README.md. */
  /* ===== variant B: `anker.` ===== */
  /* Beat 2 slides `anker` right into the waiting dot, on `left` off its
     relative position rather than a transform: `left` on a relatively
     positioned box is a paint offset, so nothing reflows, and the letter run
     can stay plain inline and keep its shaping.
     The A additionally needs beat 3's scale on its own clock, and scale is
     transform-level, so the A alone becomes an inline-block -- it is already
     an element boundary (the letter spans start after it), so nothing is
     re-shaped across it and the resting width is unchanged; the run inside
     `.rest` is what must not be broken, and that is left alone.
     JS points its transform-origin at the final disc centre, so the A grows
     about the same fixed point the disc does: the disc centre keeps the same
     fractional place inside the A's box throughout, which holds CX and CY
     exact at every frame and leaves only the diameter briefly out of step.
     The A rides above the disc, so it can land knocked out on top of it. */
  .mark.vb .a{
    display:inline-block;
    position:relative;
    z-index:2;
    transition:color .22s ease .10s,
               left .6s cubic-bezier(.4,0,.2,1) .3s,
               scale .52s cubic-bezier(.4,0,.2,1);
  }
  .mark.vb.on .a{
    transition:color .20s ease 1.34s,
               left .60s cubic-bezier(.8,0,.6,1) .86s,
               scale .55s cubic-bezier(.44,.01,.68,1) 1.19s;
  }
  /* letters sit below the A, so it slides over them rather than under them
     and whatever is left of a fading letter is hidden by it */
  .mark.vb .rest{
    position:relative;
    z-index:1;
    transition:left .6s cubic-bezier(.4,0,.2,1) .3s;
  }
  .mark.vb.on .rest{transition:left .60s cubic-bezier(.8,0,.6,1) .86s;}
  /* One span per letter, built in JS. They stay display:inline so the run
     keeps its kerning and letter-spacing; only the delay differs per letter,
     and JS writes that, because it is solved from where the letter's own ink
     edge meets the edge of whatever is about to pass over it. */
  .mark.vb .ltr{display:inline;transition:opacity .26s ease;}
  .mark.vb.on .ltr{transition:opacity .26s cubic-bezier(.75,0,.75,.9);}
  /* Five nested layers -- x, y, beat 1's lift, beat 3's scale, then beat 2's
     scale -- so each can carry its own duration and curve. The scales are
     separate layers rather than one property run twice: beat 2 takes .disc-d
     down to the lockup diameter and stops, and beat 3 multiplies that by GROW
     on .disc-g, so they compose instead of doubling up. The dot's whole
     horizontal life is beat 1: it is final in x from the moment it lands on
     the p and never moves sideways again, so .disc is idle for the rest of
     the sequence. In beat 2 the arc is no longer inside the dot's own path
     but between the dot and the A -- the dot rises while the A runs right, on
     separate clocks, and the y layer being ~19% slower than the A's slide is
     what bows it. */
  .mark.vb .disc,.mark.vb .disc-y,.mark.vb .disc-l,.mark.vb .disc-g,.mark.vb .disc-p{position:absolute;left:0;top:0;width:0;height:0;}
  /* a 0x0 box sitting on the disc's centre, so scaling it is scaling the
     disc about that centre -- the same fixed point the A grows about */
  .mark.vb .disc-g,.mark.vb .disc-p{transform-origin:0 0;}
  .mark.vb .disc-d{
    position:absolute;
    left:0;
    top:0;
    border-radius:50%;
    background:var(--red);
    transform-origin:50% 50%;
    will-change:transform;
  }
  /* plain rule = the way back, .on rule = the way out. Both directions run
     over the same 1.76s and every layer's outbound delay is 1.76s minus its
     inbound (delay + duration), so the two schedules line up beat for beat
     and the two ends are identical to 0.0000px on the disc's diameter and
     centre and on the A's box.
     
     What is NOT mirrored is the curves, and in most cases the durations
     with them. Only .disc-y and the .a/.rest slide are true reversals
     (cubic-bezier(x1,y1,x2,y2) reversed is (1-x2,1-y2,1-x1,1-y1)); the
     flight, the three scales and the A's colour all run their own outbound
     shape. One reason underlies all of it: reversing a curve turns a soft
     landing into a soft launch, and a soft launch is what a stall feels
     like.
       .disc -- cubic-bezier(.8,0,.6,1) behind an 80ms delay left the dot
     motionless for the first 200ms after the trigger, 1.1px of 47 covered.
     It launches on cubic-bezier(.25,.65,.45,.98) with no delay instead:
     first pixel at 10ms, 31px by 200ms, so it is already travelling when
     the scroll starts.
       .disc-p/.disc-l, .disc-d and .disc-g -- three scales that multiply
     into one apparent growth, so they have to be cut from one arc rather
     than mirrored one at a time. Mirrored, .disc-d ended and .disc-g began
     on zero slope and the dot's growth came to a dead standstill at .80s
     between them, mid-morph, with the overlap doing nothing to hide it.
     Re-drawn, growth never drops below 13px/s once it starts and changes by
     at most 7.8px/s per 20ms, against a 0px/s standstill and 17.7px/s of
     jerk before.
       .a colour and .a scale -- the knockout follows the disc across
     rather than fading on its own clock, and the A's growth shares
     .disc-g's curve instead of a mirrored one, which is what holds the two
     in register (see beat 3 below).
     The per-letter cascades are not mirrored either: both directions are
     solved from the geometry, out and back separately, in MORPH.B.delays.
     
     The sequence out: the dot flies flat and horizontal from 0 to .62s,
     eating `punt` letter by letter, while .disc-p swells it to a prominent
     dot from .18 to 1.02s -- the swell covers the flight's slow tail and
     carries on past it, so the hold has something moving in it. The letter
     delays below are solved, not chosen (see MORPH.B.delays), so they move
     with the type: at 1280px the p is gone at .30s, the dot lands at .62s
     and the r does not start to go until .86s, which leaves `anker.` fully
     legible with only the swell in motion for about 240ms. Then everything
     overlaps rather than queueing: the rise runs .68-1.36, the slide
     .86-1.46, the second scale .65-1.33 and the grow 1.16-1.76, so no layer
     waits on another's landing. Consequence of the mirror, and it is a real
     change of intent: the dot crosses the lockup diameter at 1.23s but the
     A does not land until 1.46s. The A slides into a dot that is already
     growing instead of arriving on a dot that has stopped. Peak growth is
     59px/s at 1.51s.
     
     Way back: beat 2 reverses first (the mark ungrows, `anker` retreats
     left, the dot sinks and shrinks, y dropping late), and only once the dot
     is alone over the p does beat 1 run it home and bring `punt` back. */
  .mark.vb .disc{transition:transform .62s cubic-bezier(.4,0,.2,1) 1.06s;}
  .mark.vb.on .disc{transition:transform .62s cubic-bezier(.25,.65,.45,.98);}
  .mark.vb .disc-l{transition:transform .62s cubic-bezier(.4,0,.2,1) .96s;}
  .mark.vb.on .disc-l{transition:transform .84s cubic-bezier(.56,0,.29,.83) .18s;}
  .mark.vb .disc-p{transition:transform .62s cubic-bezier(.4,0,.2,1) .96s;}
  .mark.vb.on .disc-p{transition:transform .84s cubic-bezier(.56,0,.29,.83) .18s;}
  .mark.vb .disc-y{transition:transform .68s cubic-bezier(.4,0,.24,1) .4s;}
  .mark.vb.on .disc-y{transition:transform .68s cubic-bezier(.76,0,.6,1) .68s;}
  .mark.vb .disc-d{transition:transform .62s cubic-bezier(.5,0,.25,1) .34s;}
  .mark.vb.on .disc-d{transition:transform .68s cubic-bezier(.72,.32,.65,1) .65s;}
  /* Beat 3, the disc's half: 1.16s to 1.76s, overlapping the last 170ms of
     .disc-d rather than abutting it, so the two scales are never handed off
     -- there is no seam to smooth. The A's own half runs 1.19-1.74 on the
     same curve, 30ms behind and 20ms ahead, so the disc leads through the
     middle and the A settles first. Sharing the curve is what keeps the two
     in register: the disc runs proud of the diameter the A's scale implies
     by at most 3.0%, around 1.33s, where the old mismatched pairing was
     10.3% out at 1.54s. And it is concentric with its lockup point
     throughout, so what is left reads as the dot swallowing the letter
     rather than as a registration error. */
  .mark.vb .disc-g{transition:transform .60s cubic-bezier(.4,0,.2,1);}
  .mark.vb.on .disc-g{transition:transform .60s cubic-bezier(.44,.01,.68,1) 1.16s;}
  /* while JS is re-measuring, snap rather than animate */
  .mark.vb.still,.mark.vb.still .a,.mark.vb.still .rest,.mark.vb.still .ltr,
  .mark.vb.still .disc,.mark.vb.still .disc-y,
  .mark.vb.still .disc-l,.mark.vb.still .disc-g,.mark.vb.still .disc-p,.mark.vb.still .disc-d{transition:none;}
  /* ===== end variant B ===== */
  /* The mark is the way back to the top, so it has to look like it can be
     pressed. Everything about how it is BUILT stays where it is above -- this
     only adds the affordance. No underline: the wordmark carries a red period
     already and the global link rule would put a second red line under it. */
  .mark{cursor:pointer;-webkit-tap-highlight-color:transparent;}
  /* On the phase pages the mark is a real <a href="/"> rather than the
     scroll-home button the homepage uses, so the global link rule would
     hang a red hairline under the wordmark, directly beneath the red
     period. Both properties the rule sets are handed back. */
  a.mark{border-bottom:none;padding-bottom:0;color:var(--ink);}
  a.mark:hover{color:var(--ink);}
  .mark:focus-visible{outline:2px solid var(--red);outline-offset:6px;border-radius:2px;}
  .meta{font-size:.75rem;color:var(--soft);letter-spacing:.06em;text-transform:uppercase;}

