/** Shopify CDN: Minification failed

Line 397:11 Unexpected "2"

**/
/* smart-mill.css — page-only. Everything global lives in tokens/base/shell.
   Composition: THE DECIMAL OUTLINE (Linear, plan §07) — the eight stations numbered
   1.0–1.7 as a technical outline, with a sticky rail, never as eight identical cards.
   Contrast: every colour pair below was computed against its real ground before use.
   (History: --ink-ghost was #4A5A6C = 2.60:1 on Deep Water when this page was written, so
   nothing here uses it for text. tokens.css raised it to #7E99B7 on 2026-08-22 and it now
   clears AA on all four grounds — the avoidance is no longer necessary, only harmless.) */

/* --hdr (the real sticky-header height per breakpoint) is declared ONCE in shell.css.
   This page is a CONSUMER of it in two places — the rail's sticky offset below, and
   the station scroll-margins that shell.css's `[id]` rule now supplies. The values
   measured on this page at 11 widths (65 / 80.19 / 83.8) agreed with Home's to the
   pixel, which is why one declaration serves both. */

/* ─── hero ─────────────────────────────────────────────────────────────── */
.hero{border-top:0;padding-top:clamp(46px,6vw,84px)}
.hero .grid{display:grid;gap:clamp(28px,4vw,58px);grid-template-columns:minmax(0,1fr)}
@media (min-width:980px){.hero .grid{grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);align-items:end}}
.hero h1{font-size:clamp(38px,6.6vw,76px);margin:20px 0 0;max-width:14ch}
.hero .sub{margin:22px 0 0;max-width:52ch;font-size:clamp(16.5px,1.5vw,19px);color:var(--ink-muted)}
.chip{display:inline-flex;align-items:center;gap:12px;margin-top:26px;padding:10px 16px 10px 14px;
  border:1px solid var(--hairline-strong);border-left:2px solid var(--signal-gold);border-radius:2px;
  font-family:var(--f-mono);font-size:13px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink-muted);line-height:1.45;max-width:100%}
.chip b{color:var(--signal-gold);font-weight:600}
/* on a phone the two halves of the chip were squeezed into two narrow columns;
   below 560 they read as two stacked lines, which is what they are */
@media (max-width:560px){.chip{display:flex;flex-direction:column;align-items:flex-start;gap:7px}}
/* shell.css sets `footer a{display:block}`, which pushed the legal row's Privacy link
   onto a line of its own above the city list. Reported to the Home lane; scoped here. */
footer .legal a{display:inline;padding:0;font-size:inherit;font-family:inherit;
  border-bottom:1px solid var(--hairline-strong)}
figure{margin:0}
figure img{width:100%;height:auto;border-radius:2px}
.cap{margin:10px 0 0;font-family:var(--f-mono);font-size:12.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-subtle);line-height:1.6}

/* ─── the decimal outline: rail + stations ─────────────────────────────── */
.stations{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(30px,4vw,64px);align-items:start}
@media (min-width:901px){.stations{grid-template-columns:224px minmax(0,1fr)}}

.rail-h{font-family:var(--f-mono);font-size:12.5px;letter-spacing:.19em;text-transform:uppercase;
  color:var(--ink-subtle);margin:0 0 12px}
.rail ol{list-style:none;margin:0;padding:0;border-top:1px solid var(--hairline-strong)}
.rail li{border-bottom:1px solid var(--hairline)}
.rail a{display:flex;gap:10px;align-items:baseline;padding:10px 8px 10px 10px;
  font-family:var(--f-mono);font-size:12.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-subtle);border-left:2px solid transparent;line-height:1.45;
  transition:color var(--d2) var(--e-quad),border-color var(--d2) var(--e-quad)}
.rail a .n{color:var(--crude-amber);flex:none;font-weight:600}
@media (hover:hover) and (pointer:fine){.rail a:hover{color:var(--ink)}}
.rail a[aria-current="true"]{color:var(--signal-gold);border-left-color:var(--signal-gold)}
.rail a[aria-current="true"] .n{color:var(--signal-gold)}
/* the third rail state. `[aria-current]` is the reader's position, owned by the
   scroll-spy; `.is-peer` is "the drawing above is being pointed at this row" and
   is set by assets/js/fx-plant.js. Amber, not gold, so it can never be mistaken
   for the current chapter. */
.rail a.is-peer{color:var(--ink);border-left-color:var(--crude-amber)}
@media (prefers-reduced-motion:reduce){.rail a{transition:none}}
@media (min-width:901px){.rail{position:sticky;top:calc(var(--hdr) + 22px)}}
@media (max-width:900px){
  .rail ol{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--hairline-strong)}
  .rail li:nth-child(odd){border-right:1px solid var(--hairline)}
  .rail a{padding:11px 8px}
}
@media (max-width:400px){.rail ol{grid-template-columns:1fr}.rail li:nth-child(odd){border-right:0}}

.note{max-width:64ch;font-size:14.5px;color:var(--ink-muted);border-left:1px solid var(--hairline-strong);
  padding-left:16px;margin:22px 0 clamp(30px,4vw,46px)}
.note b{color:var(--ink);font-weight:600}

/* ─── one station block ────────────────────────────────────────────────── */
/* scroll-margin-top removed: shell.css scopes it to `[id]`, which is a strict
   superset here — all 8 .st blocks carry an id (measured), and `[id]` additionally
   fixes #closed-loop, which this narrower rule never reached. */
.st{padding:clamp(38px,5vw,64px) 0;border-top:1px solid var(--hairline)}
.st:first-of-type{border-top:0;padding-top:0}
.st h3{font-size:clamp(25px,3.3vw,40px);max-width:15ch}
.st h3 .n{display:block;font-family:var(--f-mono);font-size:13px;font-weight:600;letter-spacing:.2em;
  color:var(--crude-amber);margin-bottom:12px;letter-spacing:.2em}
.st .lede{margin:16px 0 0;max-width:50ch;color:var(--ink-muted);font-size:16px}
.caps{list-style:none;margin:24px 0 0;padding:0}
.caps li{position:relative;padding:11px 0 11px 22px;border-top:1px solid var(--hairline);
  color:var(--ink-muted);font-size:15.5px;line-height:1.5}
.caps li::before{content:"";position:absolute;left:0;top:20px;width:10px;height:1px;background:var(--crude-amber)}
.verb{margin:16px 0 0;font-family:var(--f-mono);font-size:12px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--ink-subtle)}

/* asymmetry — five different shapes, deliberately not a card grid */
.st--media{display:grid;gap:clamp(24px,3.4vw,48px);grid-template-columns:minmax(0,1fr)}
@media (min-width:820px){
  .st--media{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);align-items:start}
  .st--flip .fig{order:2}
}
.st--inset{padding-left:0}
@media (min-width:820px){.st--inset .body{margin-left:22%;max-width:40rem}}
@media (min-width:820px){.st--split .caps{display:grid;grid-template-columns:1fr 1fr;column-gap:36px}
  .st--split .caps li:nth-child(2){border-top:1px solid var(--hairline)}
  .st--split h3{max-width:22ch}}
@media (min-width:820px){.st--plain .body{max-width:38rem}}
.pair{display:grid;gap:clamp(24px,3.4vw,44px);grid-template-columns:minmax(0,1fr);
  padding:clamp(38px,5vw,64px) 0;border-top:1px solid var(--hairline)}
@media (min-width:820px){
  .pair{grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);align-items:start}
  /* 1.5 is stepped down the page rather than set level with 1.4. Two reasons, and the
     second is not cosmetic: it deepens the asymmetry, AND it gives the two stations
     distinct scroll positions. Set level, they shared one scroll band, so no outline
     rail could ever tell them apart and 1.5 could never become the current chapter. */
  .pair > *:nth-child(2){margin-top:clamp(52px,7vw,104px)}
  /* The value is a straight trade and worth naming: the stagger IS 1.5 Boiler's exclusive
     scroll band in the rail, measured 1:1 (86px stagger -> 75px band, 124px -> ~120px).
     Bigger reads better in the outline and worse as layout, because 1.4 Kernel Plant is
     the shorter column and the difference lands as bare navy beneath it. 104px is the
     middle: ~90-104px of exclusive band at desktop against ~240px of tail whitespace. */
}
.pair .st{padding:0;border-top:0}
.pair .st h3{font-size:clamp(23px,2.6vw,30px)}

/* the two callout stations */
.st--callout .body{background:var(--panel-navy);border:1px solid var(--hairline);border-radius:2px;
  padding:clamp(22px,3vw,34px)}
.st--callout .caps li:first-child{border-top:0}
.hook{margin-top:24px;border-left:2px solid var(--crude-amber);padding:3px 0 3px 16px}
.hook+.hook{margin-top:18px}
.hook h4{font-family:var(--f-body);font-weight:700;font-size:15.5px;letter-spacing:-0.0017em;
  line-height:1.35;color:var(--signal-gold)}
.hook p{margin:7px 0 0;font-size:15px;color:var(--ink-muted);line-height:1.5}

/* ─── the closed loop ──────────────────────────────────────────────────────
   FX-03 replaced the four-across card row with a drawn cycle beside a numbered
   ledger. The ring, the ledger and the return line all live in
   assets/css/fx-plant.css — kept in ONE file so the effect is auditable in one
   place, rather than half here and half there. What stays here is the section's
   own spacing and the verdict beneath it. */
.loopwrap{margin-top:clamp(30px,4vw,52px)}
.verdict{margin:clamp(28px,3.6vw,44px) 0 0;max-width:62ch}
.verdict p{font-family:var(--f-display);font-size:clamp(21px,2.5vw,31px);line-height:1.25;
  letter-spacing:-0.0068em;color:var(--ink)}
.verdict p em{font-style:normal;color:var(--signal-gold)}
.verdict .after{font-family:var(--f-body);font-size:16px;line-height:1.6;color:var(--ink-muted);
  letter-spacing:0;margin-top:16px}

/* ─── the honest-engineer band (paper) ─────────────────────────────────── */
.paper a{color:#10598E}
.paper .btn-secondary{color:#16202B;border-color:rgba(22,32,43,.3)}
@media (hover:hover) and (pointer:fine){
  .paper .btn-secondary:hover{border-color:#8A5E14;color:#8A5E14}}
.paper .cap{color:#4A5A6C}
.honest{display:grid;gap:clamp(28px,4vw,56px);grid-template-columns:minmax(0,1fr)}
@media (min-width:900px){.honest{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);align-items:start}}
.pull{margin:26px 0 0;border:0;padding:0}
.pull .q{font-family:var(--f-display);font-size:clamp(30px,4.6vw,54px);line-height:1.08;
  letter-spacing:-0.0085em;color:#0E1922;max-width:16ch;margin:0}
.pull footer{padding:0;border-top:0;margin-top:20px;color:#3C4C5E;font-size:14.5px;font-family:var(--f-body)}
.pull cite{font-style:normal;color:#16202B;font-weight:600}
.pull .also{font-family:var(--f-body);margin:18px 0 0;font-size:16px;color:#3C4C5E;max-width:46ch}
.answer{border-top:1px solid rgba(22,32,43,.18);padding-top:22px}
.answer h3{font-size:clamp(21px,2.4vw,27px);color:#0E1922;max-width:20ch}
.answer p{margin-top:14px;font-size:16px;color:#3C4C5E;max-width:46ch}
.answer .src{margin-top:16px;font-family:var(--f-mono);font-size:12.5px;letter-spacing:.06em;
  color:#4A5A6C;line-height:1.65}
.answer .src a{border-bottom:1px solid rgba(22,32,43,.28)}

/* ─── close ────────────────────────────────────────────────────────────── */
.close h2{font-size:clamp(32px,4.6vw,56px);max-width:17ch}
.close p{margin-top:20px;max-width:48ch;color:var(--ink-muted);font-size:17px}
.actions{display:flex;flex-wrap:wrap;gap:14px;margin-top:30px}
/* the separators were literal "//" between inline links, so a wrap could start a line
   with a stray "//". They are pseudo-elements on each item now — a separator can only
   ever lead the item it belongs to. */
.contact{margin-top:34px;padding-top:22px;border-top:1px solid var(--hairline);
  display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 16px;
  font-family:var(--f-mono);font-size:13px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink-subtle);line-height:2}
.contact > * + *::before{content:"// ";color:var(--mill-steel)}
.contact a{color:var(--ink-subtle);border-bottom:1px solid var(--hairline-strong)}
@media (hover:hover) and (pointer:fine){.contact a:hover{color:var(--ink)}}

.pull .attrib{font-family:var(--f-body);margin:20px 0 0;color:#3C4C5E;font-size:14.5px;
  line-height:1.6;max-width:44ch}
.answer figure{margin:24px 0 0}

/* ─── SHELL CORRECTIONS — MOVED to shell.css ───────────────────────────────
   The skip-link outline suppression, the footer-column orphan fix and the whole
   masthead-fit block were applied here page-scoped so a parallel lane was not edited
   underneath this one. That was right while the lanes ran. They now live once, in
   shell.css, which is the file that declares the masthead and the footer in the first
   place — with this lane's measurements (390px scrollWidth 504 vs client 390; the
   901-1359 lockup needing ~74px it does not have) carried across intact. */


/* ─── print ────────────────────────────────────────────────────────────────
   base.css puts `break-inside:avoid` on every <section>. That is right for a short
   band and a no-op with side effects for a section taller than a page: this page's
   stations and closed-loop sections both are, and the residue was a print page
   carrying three orphaned lines and nothing else (measured: page 9 of 12, 175 chars).
   The correct granularity is to let the long sections break and protect the ATOMS.

   THE SAME TRAP WAS STILL SET ONE LEVEL DOWN. Measured 2026-08-23 on the A4 render
   (48dpi raster, largest CONTIGUOUS ink-free band per page, final page excluded because
   a short last page is how a document ends):

       as shipped                              11 pages, page 2 of 11 = 75.8% ink-free
       let .st break, protect its atoms        11 pages, page 2 still 75.8%   <- REFUTED
       un-grid .stations only                  11 pages, page 2 still 75.8%   <- REFUTED
       hide .rail only (keep the grid)         12 pages, TWO 100% blank pages <- WORSE
       un-grid + let .st/.pair break (this)    11 pages, worst mid-band 48.4%, none >=60%

   The isolation matters because the first two hypotheses were wrong and the third made
   it worse. `.stations` is a two-column GRID (224px rail | stations) and Chromium
   fragments a grid ROW as a unit, so the rail's row reserved the whole of page 2 for a
   first station that could not fit beside it. Un-gridding alone does not help — the
   station is still an unbreakable ~1010px atom — and hiding the rail alone leaves an
   empty 224px column that pushes TWO pages fully blank. Both halves are needed.

   `.pair` is un-gridded for the same reason and its 104px stagger zeroed: the stagger
   exists to give 1.4 and 1.5 distinct SCROLL bands for the outline rail, and there is
   no scrolling on paper.

   Content is preserved exactly — pdftotext word-multiset diff against the previous
   render shows nothing missing and all 8 stations present. The tighter alternative
   (also hiding the rail) reaches 10 pages / 36.1%, but costs the 28-word "ON THIS PAGE"
   outline, which is a useful table of contents in a document meant to be circulated.
   That is a page-owner judgment, so it is NOT taken here. */
@media print{
  #stations,#closed-loop,.stations,.stack{break-inside:auto;page-break-inside:auto}
  /* un-grid: a grid row is fragmented as a unit and both of these are two-column */
  .stations,.pair{display:block}
  .rail{position:static;break-inside:avoid;margin-bottom:14pt}
  /* the stagger buys 1.5 its own SCROLL band in the rail; paper does not scroll */
  .pair > *:nth-child(2){margin-top:0}
  /* a .st--media with its figure is up to 1010px against ~1085px of A4 content height.
     That is not an atom, it is most of a page, so it must be allowed to break. */
  .st,.pair{break-inside:auto;page-break-inside:auto}
  .st h3,.pair .st h3{break-after:avoid;page-break-after:avoid}
  .st .lede,.caps li,.verb{break-inside:avoid;page-break-inside:avoid}
  .loop li,.verdict,.hook,.answer,.pull,figure{break-inside:avoid;page-break-inside:avoid}
  /* the ring and the ledger are one argument; do not let a page break land
     between them. `.loopgrid` is a two-column grid and Chromium fragments a grid
     ROW as a unit — the same trap this file documents for `.stations` — so it is
     un-gridded for print and the two stack. */
  .loopgrid{display:block}
  .ret-m{display:none}
  .st--callout .body{border:1px solid rgba(16,24,32,.28);background:#fff}
  .contact a{color:#101820}
}

/* ══ STATION RAIL — replaces the axonometric ═══════════════════════════════════
   The drawing needed 409 words to say what it was not (not a flow diagram, not a
   measurement, numbering ours). Photographs of the eight stations need none of that,
   because a photograph of a boiler does not imply a pipework claim. The two real
   disclosures inside those 409 words survive in .rail-foot below; the rest went with
   the thing that required them.
   POSITION indicator, not a flow diagram: it says which of eight you are looking at. */
.srail{position:relative;height:600vh;margin:0 0 var(--d5)}
.srail .stick{position:sticky;top:0;height:100vh;overflow:hidden;display:flex;flex-direction:column;
  background:var(--mill-navy)}
.srail .bar{display:flex;align-items:center;border-bottom:1px solid var(--hairline);flex:none;
  font:600 12px/1 var(--f-mono);letter-spacing:.17em;text-transform:uppercase}
.srail .bar>div{padding:15px 20px;border-right:1px solid var(--hairline);color:var(--ink-ghost);white-space:nowrap}
.srail .bar b{color:var(--airei-blue-lift);font-variant-numeric:tabular-nums;margin-left:10px}
.srail .bar .nm{color:var(--ink);letter-spacing:.11em;min-width:0;flex:1 1 auto;overflow:hidden}
.srail .bar .nm-v{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.srail .track{flex:1;display:flex;align-items:center;padding:26px 0 26px 5vw;will-change:transform}
.scard{flex:0 0 clamp(268px,25vw,410px);margin-right:clamp(18px,2.2vw,34px);display:flex;
  flex-direction:column;opacity:.26;transform:translateY(14px) scale(.97);
  transition:opacity .5s var(--e-quad),transform .5s var(--e-quart)}
.scard.live{opacity:1;transform:none}
.scard .shot{position:relative;aspect-ratio:4/5;overflow:hidden;background:#000;border:1px solid var(--hairline)}
.scard .shot img{width:100%;height:100%;object-fit:cover;display:block;transform:scale(1.06);
  transition:transform 1.3s var(--e-quart);filter:saturate(.88) contrast(1.04)}
.scard.live .shot img{transform:scale(1)}
.scard .shot::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to top,rgba(11,21,32,.94) 0%,rgba(11,21,32,.24) 42%,rgba(11,21,32,0) 66%)}
.scard .cap{position:absolute;left:17px;right:17px;bottom:15px;z-index:2}
.scard .cap .top{display:flex;align-items:center;gap:10px;margin-bottom:7px}
.scard .ic{width:19px;height:19px;color:var(--airei-blue-lift);flex:none}
.scard .num{font:600 12px/1 var(--f-mono);letter-spacing:.14em;color:var(--ink-ghost)}
.scard h3{margin:0;font-size:clamp(17px,1.5vw,23px);line-height:1.08;letter-spacing:-0.0041em;font-weight:600;color:var(--ink)}
.scard .mini{margin-top:14px;min-height:108px}
.scard .mini ul{list-style:none;margin:0;padding:0}
.scard .mini li{position:relative;padding-left:18px;margin-bottom:6px;font-size:13.5px;line-height:1.5;color:var(--ink-muted)}
.scard .mini li::before{content:"";position:absolute;left:0;top:.62em;width:8px;height:1px;background:var(--crude-amber)}
.scard .mon{display:inline-block;font:600 11px/1 var(--f-mono);letter-spacing:.11em;text-transform:uppercase;
  color:var(--airei-blue-lift);border:1px solid rgba(46,155,255,.34);padding:5px 9px;margin-bottom:10px}
.srail .steps{flex:none;border-top:1px solid var(--hairline);background:rgba(11,21,32,.74);
  padding:16px 26px 20px;position:relative}
.srail .steps .line{position:absolute;left:26px;right:26px;top:30px;height:1px;background:var(--hairline)}
/* scaleX, not width: width animates layout on every frame, and the house rule allows
   transform for exactly this reason. transform-origin pins the growth to the left edge
   so the fill still reads as a progress bar rather than a bar that grows both ways. */
.srail .steps .line i{position:absolute;inset:0 auto 0 0;width:100%;transform:scaleX(0);
  transform-origin:left center;background:var(--airei-blue);transition:transform .25s linear}
.srail .nodes{position:relative;display:flex;justify-content:space-between;gap:6px}
.snode{display:flex;flex-direction:column;align-items:center;gap:9px;flex:1;min-width:0;
  background:0;border:0;padding:0;cursor:pointer;color:inherit;font:inherit}
.snode .dot{width:28px;height:28px;border-radius:50%;border:1px solid var(--mill-steel);background:var(--panel-navy);
  display:grid;place-items:center;color:var(--ink-ghost);flex:none;
  transition:border-color .3s var(--e-quart),background .3s var(--e-quart),
             color .3s var(--e-quart),transform .3s var(--e-quart)}
/* box-shadow is deliberately NOT transitioned: the house list allows transform, opacity and
   colour because those composite; box-shadow repaints. The active node's glow ring still
   appears, it just appears at once rather than fading in — which is what a position
   indicator wants anyway. */
.snode .dot svg{width:15px;height:15px}
.snode.done .dot{border-color:rgba(46,155,255,.5);color:var(--airei-blue-lift)}
.snode.on .dot{border-color:var(--airei-blue-lift);color:var(--mill-navy);background:var(--airei-blue-lift);
  transform:scale(1.18);box-shadow:0 0 0 5px rgba(46,155,255,.18)}
.snode>span:last-child{font:600 10.5px/1.25 var(--f-mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-ghost);text-align:center;transition:color .3s;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;max-width:100%}
.snode.on>span:last-child{color:var(--ink)}
.snode:focus-visible{outline:2px solid var(--signal-gold);outline-offset:3px}
.rail-foot{max-width:var(--measure);margin:var(--d3) auto 0}
/* NO-JS + reduced-motion: the rail degrades to a plain scrolling row of the same eight
   cards. A sticky 600vh section with no transform is 6 blank screens, so the height and
   the stick BOTH have to go, not just the animation. */
/* The degraded rail STACKS, it does not scroll sideways.
   The first version kept the horizontal track and gave it overflow-x:auto. The ink-overflow
   test measured 192 text runs sitting outside the viewport at 1100-1920px with JS off, and it
   was right to: a horizontal scroller hides seven of eight stations behind a gesture the page
   never advertises, and with no JavaScript there is no step rail to reveal that they exist.
   A vertical stack has none of that problem — every station is reached by the scroll the
   reader is already doing. */
.srail.noscript,.srail.static{height:auto;margin-bottom:var(--d4)}
.srail.noscript .stick,.srail.static .stick{position:static;height:auto;overflow:visible}
.srail.noscript .track,.srail.static .track{display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(18px,2.4vw,30px);
  overflow:visible;padding:26px 0;transform:none!important}
.srail.noscript .scard,.srail.static .scard{opacity:1;transform:none;flex-basis:auto;
  max-width:none;margin-right:0}
.srail.noscript .steps,.srail.static .steps{display:none}
.srail.noscript .bar,.srail.static .bar{display:none}
.srail.noscript .scard,.srail.static .scard{transition:none}
.srail.noscript .scard .shot img,.srail.static .scard .shot img{transition:none;transform:none}
@media (prefers-reduced-motion:reduce){
  .srail{height:auto;margin-bottom:var(--d4)}
  .srail .stick{position:static;height:auto;overflow:visible}
  .srail .track{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    gap:clamp(18px,2.4vw,30px);overflow:visible;padding:26px 0;transform:none!important}
  .scard{opacity:1;transform:none;flex-basis:auto;max-width:none;margin-right:0;transition:none}
  .scard .shot img{transform:none;transition:none}
  .srail .steps,.srail .bar{display:none}
}
@media (max-width:900px){
  .snode>span:last-child{display:none}
  .srail .steps .line{top:26px}
  .srail .bar>div:last-child{display:none}
  .srail .track{padding-left:4vw}
}

/* FULL-BLEED. The rail sits inside .wrap, whose max-width made .stick 1112px at a
   1440 viewport — while the JS measured against innerWidth (1440). A card at rail-x
   576 actually sat at viewport-x 740, so the "which card is live" test and the travel
   maths were in two different coordinate spaces and station 8 was unreachable. Break
   the rail out of the wrap AND measure in the track's own space (station-rail.js).
   100vw + the negative half-margin is the standard escape; the calc keeps it centred
   whatever --wrap is, and 100vw includes the scrollbar so overflow-x is clipped by
   .stick's own overflow:hidden. */
.srail{width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}
.rail-foot{padding:0 var(--gut)}

/* The rail's own bar (station N / 8 + the station name) was rendering at viewport y=0..43
   — underneath header.site, which is sticky at 0..84 with z-index 100. The counter and the
   name, which are the entire point of the bar, were invisible at every viewport. Park the
   stick below the header using the SAME --hdr ladder shell.css declares (65 / 80 / 84), and
   shorten the stick by the same amount so the step nodes still land on the fold. */
.srail .stick{top:var(--hdr-real,var(--hdr));height:calc(100svh - var(--hdr-real,var(--hdr)))}
/* svh, not vh: on mobile Safari/Chrome vh is the LARGEST viewport (toolbars retracted), so
   100vh minus a header still overflows the visible area while the toolbar is showing and
   the step nodes sit below the fold. svh is the smallest, which is the one that is always
   actually visible. */

/* THE CARD IS SIZED BY HEIGHT, AND ITS WIDTH FOLLOWS.
   Sized off viewport WIDTH, a 4:5 card is 450px tall at 1440 and the column (bar + track
   padding + image + bullets + step nodes) overflowed the stick by 67px — the step rail,
   the one control that says where you are, fell below the fold at the desktop sizes most
   visitors use.

   The first repair was algebra: cap the width at 0.8*stickHeight - 242, the -242 standing
   in for bar + padding + bullets + nodes. Measured across a height ladder it was better but
   still WRONG — at 1440x600 the last capability bullet was still clipped by 22px and at
   1280x560 by 42px, because a bullet block's real height is whatever three lines of wrapped
   text happen to need, which no constant can predict.

   So stop predicting it. Give the bullets their natural height, let the photograph take
   whatever vertical space is left, and let 4:5 compute the WIDTH from that. Nothing is
   estimated, so nothing can be estimated wrong.
   min-height:0 is required twice over — a flex child will not shrink below its content
   without it, and without it on .track the step nodes drop below the fold at every
   viewport height at or under 700px. */
   Attempt 2 (height drives, width follows) killed the clipping but broke the SHAPE: with
   flex:1 the photograph grew past 4:5 wherever the viewport was tall (0.583 at 1920x1080,
   0.413 at 390x844), and the card collapsed to 160px wide on a phone.

   Attempt 3, shipped: keep the width-driven card, keep the two competing budgets — but
   MEASURE the chrome instead of guessing it. station-rail.js publishes --srail-chrome as
   the real summed height of the bar, the track padding, the tallest bullet block and the
   step nodes, re-measured on resize. The constant that could be wrong is gone; the shape
   is exact at every size because 4:5 is never overridden. */
.srail .track{min-height:0}
.scard{flex-basis:min(clamp(268px,25vw,410px),
       calc((100svh - var(--hdr-real,var(--hdr)) - var(--srail-chrome,300px)) * 0.8))}

/* SHORT WINDOWS GET THE STATIC RAIL, AND THAT IS A DECISION, NOT A FALLBACK.
   A full-height sticky rail has to fit a bar, a 4:5 photograph, three capability bullets
   and eight step nodes between the header and the fold. Under about 720px of viewport
   height it does not, and every attempt to make it fit trades away something the reader
   needs — measured: the photograph collapses to 89px wide, or the step nodes land 236px
   below the fold. A short window gets the same eight cards as a plain horizontal row it
   can swipe. Nothing is hidden; only the scroll choreography is dropped.
   Height-only query, so a short desktop window is treated like a short window, which is
   what it is. */
@media (max-height:720px){
  .srail{height:auto;margin-bottom:var(--d4)}
  .srail .stick{position:static;height:auto;overflow:visible}
  .srail .track{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    gap:clamp(18px,2.4vw,30px);overflow:visible;padding:26px 0;transform:none!important}
  .scard{opacity:1;transform:none;flex-basis:auto;max-width:none;margin-right:0;transition:none}
  .scard .shot img{transform:none;transition:none}
  .srail .steps,.srail .bar{display:none}
}

/* The eight step nodes are buttons that jump the rail. At 390px they measured 37x28 —
   under the 32px floor on the short axis, and the axis a thumb actually misses on. The
   label is already hidden below 900px, so the node is just a 28px dot with nothing under
   it; give the button the height back without moving the dot. */
@media (pointer:coarse){
  .snode{min-height:40px;justify-content:center}
  .srail .steps{padding:14px 20px 18px}
}

/* Reduced motion for the rail's own chrome. The rail already falls back to a static row
   under prefers-reduced-motion (see the .static block above), but these three still declared
   transitions that would run inside it. Naming them here is not belt-and-braces: the lint is
   right that a transition with no reduced-motion counterpart is an unanswered question. */
@media (prefers-reduced-motion:reduce){
  .srail .steps .line i{transition:none}
  .snode .dot{transition:none}
  .snode>span:last-child{transition:none}
}

/* On a narrow screen the bar's station name WRAPS rather than truncating.
   Ellipsis looked like the tidy answer and the box fitted — but the ink check measures the
   text run, not the box, and it was right to: "FFB RAMP & WEIGHBRIDGE" was being cut to
   "FFB RAMP & WEIGH…", so the reader lost the second half of the station's name. Two lines
   at 11px costs about 14px of a sticky bar and shows the whole thing. */
@media (max-width:460px){
  .srail .bar>div{white-space:normal;padding:9px 12px}
  .srail .bar .nm-v{white-space:normal;text-overflow:clip;line-height:1.25}
  .srail .bar{align-items:stretch}
}


/* THE STATION GRID DIVIDES EIGHT EVENLY, OR IT DOES NOT SPLIT AT ALL.
   `repeat(auto-fit,minmax(230px,1fr))` fits as many columns as the width allows and has no
   idea there are exactly eight cards. Measured: 7 columns at 1920 (so 7 + 1), 6 at 1680
   (6 + 2), 5 at 1440 (5 + 3) — the operator saw the 5 + 3 and was right. An orphan row of
   one or three reads as a mistake no matter how good the cards are.
   Eight divides by 4, 2 and 1. Those are the only column counts this grid may take. */
@media (min-width:1180px){
  .srail.noscript .track,.srail.static .track{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media (max-width:759px){
  .srail.noscript .track,.srail.static .track{grid-template-columns:minmax(0,1fr)}
}
@media (prefers-reduced-motion:reduce){
  @media (min-width:1180px){ .srail .track{grid-template-columns:repeat(4,minmax(0,1fr))} }
  @media (max-width:759px){ .srail .track{grid-template-columns:minmax(0,1fr)} }
}
@media (max-height:720px){
  @media (min-width:1180px){ .srail .track{grid-template-columns:repeat(4,minmax(0,1fr))} }
  @media (max-width:759px){ .srail .track{grid-template-columns:minmax(0,1fr)} }
}

/* Cards in a row share a top edge. `align-items` was inheriting `center` from the sticky
   track, where it is correct — there the row is one line of cards and centring them on the
   band is the point. In the stacked grid it means eight cards of eight different heights each
   float at their own offset, so a tidy 4 + 4 still reads as ragged. Measured at 1440: tops of
   1302 / 1283 / 1303 / 1307 across one row. */
.srail.noscript .track,.srail.static .track{align-items:start}
@media (prefers-reduced-motion:reduce){ .srail .track{align-items:start} }
@media (max-height:720px){ .srail .track{align-items:start} }
