/* GENERATED by site/tools/build-critical-css.mjs — DO NOT EDIT BY HAND.
   Render-blocking sheet for history.html ONLY, carrying geometry that has to exist at first paint.
   These rules are also in the async tpl.css, which lands later with identical declarations and
   wins the cascade, so nothing moves when it arrives. Edit tpl.css, re-run this builder. */

/* ---- tpl.css 7524-7894: the history rail's own geometry. Without it the pre-JS vertical ledger paints at the inherited body type and then collapses when tpl.css lands: measured 2132->1726px on desktop, and that one collapse dragged the footer far enough to score CLS 0.517 desktop / 1.453 tablet / 0.699 mobile. Serving this range render-blocking on this one page takes the same measurements to 0.097 / 0.55 / 0.001 ---- */
/* ————— History route (history.html) — .hx ——————————————————————————
   DEFAULT = vertical ledger (no-JS / reduced-motion / ≤720px all get this).
   tpl.js adds .hx--go on desktop when motion is allowed → pinned horizontal
   scrub (GSAP ScrollTrigger): the page pins and vertical scroll drives the
   track right-to-left. Supersedes the old about.html .rt route (deleted). */
.hx{padding:0;position:relative;z-index:0}
/* the footer curtains up over the pinned timeline as it finishes — same technique as the
   D2C .fit-curtain: an opaque higher layer, pulled up, overlays the pinned scene during the
   handoff instead of the footer simply appearing below it. Only when the scrub is active
   (≥721px per the breakpoint contract at the top of this file, plus motion on) — otherwise
   normal flow. The width here MUST match tpl.js's
   `mqNarrow` for .hx--go: the curtain's -100vh pull only makes sense over a pinned scene,
   so a curtain live where the scrub is not would eat a viewport of the page.
   min-height:100vh is load-bearing (see the .fit-curtain note above the same trick):
   without it the pinned scene's paper background can show through as a gap above the
   footer on tall viewports. */
body.hx-page footer{position:relative;z-index:2}
@media(min-width:721px) and (prefers-reduced-motion:no-preference){html:not(.a11y-no-motion) body.hx-page footer{margin-top:-100vh;min-height:100vh}}
/* red lead-in — a gradient cap living OUTSIDE the footer's own box (bottom:100% sits it
   directly above the footer, not inside), same var(--red) as the footer itself. It's a
   plain part of the footer's own (position:relative) box, so it isn't scroll/JS-driven at
   all — it moves as one rigid piece WITH the footer as the curtain rises, arriving a beat
   ahead of the solid colour instead of the footer's hard edge cutting in.

   GATED TO THE CURTAIN, added 2026-08-05, owner: "on mobile it's completely broken — the
   gradient from the footer doesn't work." The cap used to be declared unconditionally while
   the curtain above it is gated to (min-width:721px + motion). Wherever the curtain is OFF
   there is no pinned scene under the cap and no -100vh pull, so the 28vh of transparent→red
   landed instead on the .share-band — the share strip tpl.js builds as the footer's previous
   sibling on EVERY page (~1645). Measured (CDP, exact CSS px, file at HEAD):
     390×844  cap 236.3px vs a 197.2px share band -> 100% covered, red alpha 0.17→1.00;
              "Copy link" sat under alpha 0.73→0.92, the 44px icon buttons under 0.45→0.64
     1440×900 + prefers-reduced-motion:reduce -> also 100% covered, and WORSE: the
              SHARE THIS PAGE label under alpha 0.80→0.88
     1440×900 + motion -> cap lands on the pinned scene, share band 0% covered (correct)
   So this was never mobile-only: it broke at <721px, under prefers-reduced-motion at any
   width, and under the site's own .a11y-no-motion pause. The footer (z-index:2) outranks
   .share-band (position:relative, z-index:auto), so the wash always won.

   Not "fixed" by retuning the stops or shortening the cap: the share band is an opaque paper
   surface with its own hairline borders and 44px controls, so ANY red fading across it costs
   legibility, and moving the cap above the band would read red → paper → red. The cap only
   ever meant something as the leading edge of the rising curtain, so it now lives and dies
   with it — one gate, same terms as the margin-top rule above. Where the curtain is off the
   footer keeps its own hard red edge, exactly like every other page on the site. */
@media(min-width:721px) and (prefers-reduced-motion:no-preference){
  html:not(.a11y-no-motion) body.hx-page footer::before{content:"";position:absolute;left:0;right:0;bottom:100%;height:28vh;background:linear-gradient(180deg,transparent 0%,var(--red) 96%);pointer-events:none}
}
/* Noise-grain backdrop behind the timeline, in the site's own grain language rather than a
   photograph. Two photo candidates were tried and rejected by the owner on 2026-08-19: the DNA
   helix first (it is DNA Filters' motif, so TPL's story ran behind a partner's branding), then
   story-meeting.webp. A photograph was the wrong class of surface for this band — the timeline's
   own cards are the subject, and a scene behind them competes with them.
   This reuses .band-noise's exact recipe (tpl.css, search "noise-textured band"): the same
   noise-bg.jpg / noisy-dark.webp pair, the same 75% wash and multiply blend, so the band matches
   the one on branches.html instead of inventing a third treatment. No new asset; noise-bg.jpg is
   116 KB against story-meeting.webp's 44 KB, but it is already fetched by any visitor who has
   seen branches.html and it tiles far better under text than a scene does.
   UNLIKE the photo versions this has a real dark treatment rather than falling back to flat
   paper, because the grain reads in both themes. story-meeting.webp and dna-image.webp both
   stay on disk, unreferenced, so either rollback is one line. */
.hx-scene{position:relative;background:linear-gradient(rgba(240,240,240,.75),rgba(240,240,240,.75)),#f0f0f0 url(https://img.tpl.one/websites/tplgr/v2/assets/images/backgrounds/noise-bg.jpg) center/cover no-repeat;background-blend-mode:normal,multiply}
html[data-theme="dark"] .hx-scene{background:linear-gradient(rgba(13,13,14,.75),rgba(13,13,14,.75)),#0d0d0e url(https://img.tpl.one/websites/tplgr/v2/assets/images/backgrounds/noisy-dark.webp) center/cover no-repeat;background-blend-mode:normal,multiply}
.hx-head{padding:clamp(40px,6vw,64px) clamp(20px,4vw,48px) 6px;max-width:var(--max);margin:0 auto}
.hx-head .eyebrow{margin-bottom:8px}
.hx-h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.02em;line-height:1.05;color:var(--ink);font-size:clamp(30px,5vw,52px)}
.hx-cue{display:none}
.hx-nav{display:none}
/* vertical ledger (default) — NOT phone-only: .hx--go needs ≥721px AND motion AND GSAP, so
   the ledger is also what every reduced-motion / a11y-pause / no-GSAP visitor gets at any
   width. Its static state therefore has to read as a timeline on its own, before any JS.
   Two variables carry the whole geometry, and dot, cube, leg and stem all derive from them:
     --hx-rail  the axis, from the stop's own left edge (so it tracks the track's clamp()
                padding automatically and stays width-independent)
     --hx-node  the node centre, down from the stop's top — set to the optical centre of the
                year line, so the marker points at the date it marks rather than at the
                card's top corner.
   Everything below that touches .hx-seg / .hx-dot / .hx-cube / .hx-card::before is scoped
   :not(.hx--go): the pinned scrub owns those same four with completely different geometry
   (absolute cards, a horizontal axis, stems off the card's near edge) and the two must not
   bleed into each other. .hx--go lives on the .hx section — tpl.js: sec = scene.closest('.hx'). */
.hx-track{--hx-rail:9px;--hx-node:28px;list-style:none;margin:0 auto;padding:56px clamp(20px,4vw,48px) 72px;position:relative;max-width:var(--max)}
.hx-stop{position:relative;padding:0 0 32px 44px;max-width:620px}
/* the rail is per-leg, never one full-height line: each stop draws its own leg from its own
   node down to the NEXT stop's node with .hx-seg — the element the pinned scrub already uses,
   and which has sat inert in the ledger since this page shipped. `bottom` is pulled a whole
   --hx-node PAST the stop's own box, which lands exactly on the next node. Three things fall
   out of that. The route starts on 2006's node and ends on 2027+'s, instead of the old
   full-height rail floating 12px above the first and ~100px below the last. Legs paint in one
   at a time as stops reveal, so scrolling reads as travel rather than as cards appearing. And
   each leg can carry its own meaning, which the past/future split below needs. The scrub works
   the same way — it has no full-length rail either, only segs. */
.hx:not(.hx--go) .hx-seg{position:absolute;left:calc(var(--hx-rail) - 1px);width:2px;top:var(--hx-node);bottom:calc(var(--hx-node) * -1);background:var(--red);z-index:0}
.hx:not(.hx--go) .hx-stop:last-child .hx-seg{display:none}
/* ---- REMOVED 2026-08-05, owner: "the gradient on mobile doesn't work — make it work or remove
   it." The rail is now two solid colours and nothing else. Do not reintroduce a gradient or a
   scroll-driven fill here without a real-device test in hand.

   What was here, and why it is gone. The leg was a grey TRACK carrying a
   repeating-linear-gradient of dashes from Today onward, with a separate red fill layer on top
   whose length followed the scroll — first written per-frame from JS, then (same day) moved onto
   a view-progress timeline to stop it stepping during touch-momentum scrolling. Both versions
   measured perfect in headless Chrome: the fill tracked a 55% reading line to within 0.03% over
   48 samples, legs met their nodes at 0.0px, red dashes landed in phase over grey ones. It still
   did not work on the owner's phone, and none of the three symptoms I could reproduce explained
   it — so the honest reading is that the mechanism, not its tuning, was the problem, and a
   mechanism that cannot be verified on the device it targets does not belong on that device.

   THE RULE THIS ENCODES: mobile matches desktop. The desktop scrub's rail has no gradient at all
   (`.hx--go .hx-seg{background:var(--hairline)}`, going solid red on .is-passed), which is what
   "not working like desktop" meant. Colour alone now carries the whole reading — red for the
   route already travelled, grey for what is still ahead — so the rail states the same thing in
   both modes with no texture channel and no moving parts.

   What this deliberately gives up: the red no longer tracks the reader down the page, and the
   projected legs are no longer distinguishable from the travelled ones by texture. Today is
   still marked by its cube, its red card border and the grey rail below it. Nothing here needs
   JS, so the rail cannot break on any device or in any motion mode. */
.hx:not(.hx--go) :is(.hx-stop--now,.hx-stop--now ~ .hx-stop) .hx-seg{background:var(--paper-500)}
.hx-dot{position:absolute;left:0;top:3px;width:18px;height:18px;border-radius:50%;background:var(--paper-0);border:2px solid var(--red);z-index:1}
/* red for the route already travelled, --paper-500 once past Today — the ledger's equivalent of
   the scrub's idle/.is-passed pair, and the same grey as the legs ahead so the projected part of
   the route reads as one material. Both rules outrank the @supports masked defaults below on
   specificity, so source order here doesn't matter.
   The :not(.hx--follow) qualifier and the two .hx--follow rules that used to sit below were
   dropped with the scroll-driven fill (see the .hx-seg note above): there is only one mode now,
   so the nodes are coloured by chronology unconditionally and never wait on a class from JS.
   border:0 on the projected dots is not decoration: .hx-stop--future .hx-dot sets
   `border:2px dashed` at (0,2,0), which outranks the masked block's own `border:0` at (0,1,0)
   and survives into masked mode — where a border is meaningless anyway, since the mask doesn't
   clip it. Left alone it renders as a hollow dashed square floating around the shape.
   --paper-300 was tried first and is invisible on --paper-100: #E8E6E0 on #F2F1ED. */
.hx:not(.hx--go) .hx-dot{top:calc(var(--hx-node) - 9px)}
.hx:not(.hx--go) .hx-stop--future .hx-dot{border:0}
.hx:not(.hx--go) .hx-dot{background:var(--red)}
.hx:not(.hx--go) .hx-stop--now ~ .hx-stop .hx-dot{background:var(--paper-500)}
.hx-stop--future .hx-dot{border:2px dashed var(--paper-500);background:var(--paper-100)}
.hx-stop--now .hx-dot{display:none}
/* each bullet takes one of the 4 TPL brand shapes (assets/shapes/), cycling by position —
   CSS mask so colour still tracks state (idle/future/active) the way the plain dot did.
   Border makes no sense on an irregular masked shape (it isn't clipped by the mask), so
   masked mode drops it and uses filter:drop-shadow for the active glow instead, which
   hugs the shape outline rather than the dot's rectangular box. @supports keeps the
   plain circle above as the fallback where mask isn't supported. */
@supports ((mask-size:100% 100%) or (-webkit-mask-size:100% 100%)){
  .hx-dot{border:0;border-radius:0;background:var(--paper-500);
    -webkit-mask-image:url(../shapes/shape-1.svg);mask-image:url(../shapes/shape-1.svg);
    -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
    -webkit-mask-position:center;mask-position:center;
    -webkit-mask-size:100% 100%;mask-size:100% 100%}
  .hx-stop--future .hx-dot{background:var(--paper-300)}
  .hx-stop:nth-child(4n+2) .hx-dot{-webkit-mask-image:url(../shapes/shape-2.svg);mask-image:url(../shapes/shape-2.svg)}
  .hx-stop:nth-child(4n+3) .hx-dot{-webkit-mask-image:url(../shapes/shape-3.svg);mask-image:url(../shapes/shape-3.svg)}
  .hx-stop:nth-child(4n) .hx-dot{-webkit-mask-image:url(../shapes/shape-5.svg);mask-image:url(../shapes/shape-5.svg)}
}
.hx-cube{position:absolute;left:-6px;top:-1px;width:27px;height:27px;z-index:2;filter:drop-shadow(0 3px 6px rgba(0,0,0,.18))}
.hx-cube svg{display:block;width:100%;height:100%}
.hx-card{background:var(--paper-50);border:1px solid var(--hairline);border-radius:var(--radius-card);padding:20px 22px}
.hx-y{display:block;font-family:var(--font-mono);font-size:14px;font-weight:500;letter-spacing:.06em;color:var(--ink)}
/* .hx-k is an h2 (2026-09-08): each stop on the rail IS a section of the story, and the page
   had no headings at all — a flat outline for screen readers and crawlers alike. Safe to
   promote because this rule already sets every property a heading would otherwise inherit
   differently: display, family, size, weight, spacing, case, colour and margin. Change one
   of those away and the year rail will start moving. */
.hx-k{display:block;font-family:var(--font-mono);font-size:10.5px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-muted);margin:3px 0 8px}
.hx-k--award{color:var(--red-ink)}
.hx-k--us{color:var(--americas-ink)}
.hx-k--in{color:var(--india-ink)}
.hx-card p{margin:0;font-size:14.5px;line-height:1.55;color:var(--ink-soft)}
.hx-fig{margin-top:14px;display:flex;align-items:center;gap:14px}
.hx-fig img{height:34px;width:auto}
.hx-fig img.hx-photo{height:64px;border-radius:6px;border:1px solid var(--hairline)}
.hx-stats{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:12px;font-family:var(--font-mono);font-size:12px;letter-spacing:.04em;color:var(--ink)}
.hx-stop--future .hx-card{opacity:.62}
/* the stem — a hairline branch from the node out to the card's own edge. This is what turns a
   dashed line sitting BESIDE a card stack into cards hanging OFF an axis. 28px reaches ~1px
   back into the dot so the two always touch; the -1.5px on top is the card's own 1px border
   (an absolute child's containing block is the padding box, not the border box) plus half the
   1px line. */
.hx:not(.hx--go) .hx-card{position:relative}
.hx:not(.hx--go) .hx-card::before{content:"";position:absolute;right:100%;top:calc(var(--hx-node) - 1.5px);width:28px;height:1px;background:var(--paper-500)}
/* Today is the page's anchor: extra rail before it so it lands as an arrival, a red hairline on
   its card, and a bigger cube. The extra room is padding-bottom on the PRECEDING stop, not
   margin-top on Today — the preceding leg's `bottom` is measured from that stop's own box, so
   padding stretches the leg with it while margin would leave the leg short of the next node.
   Deliberately no --red-wash background here: that token REPLACES --paper-50 rather than
   tinting it, which would let the DNA backdrop through the one card that most needs to read
   as solid. */
.hx:not(.hx--go) .hx-stop:has(+ .hx-stop--now){padding-bottom:64px}
.hx:not(.hx--go) .hx-stop--now .hx-card{border-color:var(--red)}
.hx:not(.hx--go) .hx-cube{left:calc(var(--hx-rail) - 16px);top:calc(var(--hx-node) - 16px);width:32px;height:32px}
/* mobile scroll reveal — the pinned horizontal scrub runs from 721px up (breakpoint contract);
   at ≤720px the ledger's stops draw themselves in as they enter view (.m-in via tpl.js). Same
   width as tpl.js's `mqNarrow`, inverted.
   The parent carries OPACITY ONLY and the children carry TRANSFORM ONLY. That split is
   load-bearing twice over: it's why .hx-stop--future keeps its own .62 card dim (the reason the
   original code put the whole reveal on the parent), and it's why nothing here ever animates two
   properties against each other. The node grows out of the rail and the card follows its own stem
   outward, so a stop assembles from its axis rather than fading up as a block.
   The leg itself is NOT revealed here — it is simply present, in its own colour, from first paint.
   That was true when the red on it followed the scroll and stays true now that the fill is gone
   (see the .hx-seg note above): the rail is never animated, only the stop's arrival is.
   The hidden states stay inside this ≤720px query on purpose — the ledger at ≥721px
   (reduced motion, no GSAP) then renders complete and unanimated, which is the safest
   degradation available and is unchanged from before. */
@media(max-width:720px){
  .hx-track .hx-stop{opacity:0;transition:opacity .3s var(--ease-out)}
  .hx-track .hx-stop.m-in{opacity:1}
  .hx:not(.hx--go) .hx-stop :is(.hx-dot,.hx-cube){transform:scale(.62);transition:transform .26s var(--ease-out)}
  .hx:not(.hx--go) .hx-stop .hx-card{transform:translate3d(-8px,8px,0);transition:transform .36s var(--ease-out) .08s}
  .hx:not(.hx--go) .hx-stop.m-in :is(.hx-dot,.hx-cube,.hx-card){transform:none}
}
/* the three snap-in guards. Each is prefixed .hx:not(.hx--go) to reach (0,5,0): the child rules
   above are (0,4,0) and would otherwise win, leaving stops permanently offset for exactly the
   visitors who most need them settled. The legs need no guard at all any more: the rail is two
   static colours with nothing animating on it, so all three cases already render it complete. */
@media(max-width:720px) and (prefers-reduced-motion:reduce){
  .hx-track .hx-stop{opacity:1;transform:none;transition:none}
  .hx:not(.hx--go) .hx-track .hx-stop :is(.hx-dot,.hx-cube,.hx-card){transform:none;transition:none}
}
html.a11y-no-motion .hx-track .hx-stop{opacity:1;transform:none;transition:none}
html.a11y-no-motion .hx:not(.hx--go) .hx-track .hx-stop :is(.hx-dot,.hx-cube,.hx-card){transform:none;transition:none}
/* no-JS safety net: .m-in comes from tpl.js, so with scripting off every stop would sit at
   opacity:0 and the whole ledger — the page's only content — would be invisible on a phone.
   Engines without the `scripting` feature drop this query as invalid, i.e. no-op / status quo. */
@media(max-width:720px) and (scripting:none){
  .hx-track .hx-stop{opacity:1;transform:none;transition:none}
  .hx:not(.hx--go) .hx-track .hx-stop :is(.hx-dot,.hx-cube,.hx-card){transform:none;transition:none}
}
/* pinned horizontal mode (tpl.js adds .hx--go) — a scattered "exhibit wall": cards float
   above/below a year-ruler axis at one of 3 fixed distances (tier), each connected back to
   the axis by a thin stem drawn from the CARD's own near edge (.hx-card::before), so the
   stem length stays exact regardless of the card's own (content-driven) height. There is
   no dimming/"active" state — every card reads at full strength once it has entered, same
   as a wall of prints; the horizontal pan is what reveals new ones, not an opacity change. */
.hx--go .hx-scene{height:calc(100vh - var(--hx-top,64px));min-height:700px;display:flex;flex-direction:column;justify-content:center;overflow:hidden}
.hx--go .hx-head{position:absolute;top:clamp(16px,3.5vh,36px);left:0;right:0;padding:0 clamp(20px,4vw,48px);max-width:none;margin:0;z-index:4}
.hx--go .hx-title{max-width:520px}
.hx--go .hx-head .eyebrow{margin-bottom:6px}
.hx--go .hx-h1{font-size:clamp(21px,2.3vw,31px)}
/* --ink-soft, not the original --ink-muted: this cue sits directly on the .hx-scene surface, and
   11px --ink-muted has never passed there on ANY backdrop — 1.77:1 over the old DNA helix,
   2.12:1 over the meeting-room photo, 4.10:1 over the noise grain now (all worst glyph pixel).
   That is the documented --ink-muted-off-a-tinted-ground problem, same as .map-fall p and
   .manifest dt, and no backdrop fixes it. --ink-soft measures 6.90:1 on the grain and is the
   quietest token that clears 4.5:1, so it keeps the cue subordinate as designed rather than
   shouting it in full --ink (13.83:1). Token, so dark still flips: 17.50:1 there. */
.hx--go .hx-cue{display:block;position:absolute;bottom:clamp(22px,4.5vh,38px);left:0;right:0;text-align:center;font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft);z-index:4;transition:opacity .4s var(--ease)}

.hx--go .hx-track{display:flex;align-items:stretch;height:min(72vh,660px);padding:0 clamp(20px,4vw,48px);max-width:none;margin:0}
/* (the old full-height .hx-track::before rail this mode used to suppress is gone: both modes
   now draw their axis from per-stop .hx-seg legs, so there was nothing left to hide) */
/* axis drawn as discrete per-stop segments (a real DOM element per gap, plain colour
   toggle) rather than one continuous scaled overlay — each segment runs from THIS stop's
   centre to the NEXT one's (spanning 100% of one stop's own width lands exactly on the
   next stop's centre, since all stops share the same flex-basis), turning red once its
   left-hand stop has been reached. Simpler to verify than a transform-driven fill and
   reads as discrete "milestones reached", matching the reference's tick-marked ruler. */
.hx--go .hx-seg{position:absolute;left:50%;width:100%;top:50%;height:1px;margin-top:0;background:var(--hairline);z-index:0;transition:background .25s var(--ease)}
.hx--go .hx-stop:last-child .hx-seg{display:none}
.hx--go .hx-stop.is-passed .hx-seg{background:var(--red)}

.hx--go .hx-stop{position:relative;flex:0 0 clamp(220px,19vw,280px)}
/* full-height gridline at every stop's year position, behind everything — turns red with
   the rest of the route once its stop is reached, so the whole vertical reads as "travelled" */
.hx--go .hx-stop::before{content:"";position:absolute;left:50%;top:0;bottom:0;width:1px;background:var(--hairline);opacity:.35;z-index:0;transition:background .25s var(--ease),opacity .25s var(--ease)}
.hx--go .hx-stop.is-passed::before{background:var(--red);opacity:1}
/* year labels — single-sourced from data-year so they can never drift from the card's own date */
.hx--go .hx-stop::after{content:attr(data-year);position:absolute;left:50%;top:calc(50% + 15px);transform:translateX(-50%);font-family:var(--font-mono);font-size:11px;font-weight:500;letter-spacing:.06em;color:var(--ink-muted);white-space:nowrap;z-index:1}
.hx--go .hx-stop--below::after{top:auto;bottom:calc(50% + 15px)}
/* tick — small ring sitting exactly on the axis */
.hx--go .hx-tick{position:absolute;left:50%;top:50%;width:9px;height:9px;margin:-4.5px 0 0 -4.5px;border-radius:50%;border:1.5px solid var(--paper-500);background:var(--paper-0);z-index:1;transition:background .25s var(--ease),border-color .25s var(--ease)}
.hx--go .hx-stop.is-passed .hx-tick{background:var(--red);border-color:var(--red)}

/* entrance only: a stop fades in once scrolled into view and then STAYS at full strength —
   no re-dimming, matching the reference's uniform, all-visible wall (overrides the plain
   .hx-stop--future .hx-card dimming above, which is deliberately vertical-ledger-only) */
.hx--go .hx-stop .hx-card,.hx--go .hx-stop .hx-dot,.hx--go .hx-stop .hx-cube{opacity:0;transition:opacity .5s var(--ease)}
.hx--go .hx-stop.is-in .hx-card,.hx--go .hx-stop.is-in .hx-dot,.hx--go .hx-stop.is-in .hx-cube{opacity:1}

/* card — anchored by its near edge (bottom for above-cards, top for below-cards) so the
   stem length stays exact regardless of the card's own height; --stem set per tier below */
.hx--go .hx-card{position:absolute;left:50%;transform:translateX(-50%);width:clamp(210px,85%,270px);max-width:270px;z-index:2}
/* stem — carries the red fill from the axis up/down to the dot once the stop is passed, so
   the coloured route visibly reaches (touches) the dot instead of stopping at the tick */
.hx--go .hx-card::before{content:"";position:absolute;left:50%;width:1px;background:var(--hairline);transition:background .25s var(--ease)}
.hx--go .hx-stop--above .hx-card{bottom:calc(50% + var(--stem))}
.hx--go .hx-stop--above .hx-card::before{top:100%;height:var(--stem)}
.hx--go .hx-stop--below .hx-card{top:calc(50% + var(--stem))}
.hx--go .hx-stop--below .hx-card::before{bottom:100%;height:var(--stem)}
.hx--go .hx-stop.is-passed .hx-card::before{background:var(--red)}
.hx--go .hx-stop--near{--stem:64px}
.hx--go .hx-stop--mid{--stem:100px}
.hx--go .hx-stop--far{--stem:150px}

/* dot / cube — sit where the stem meets the card; turns red once the stem's stop is passed,
   so the dot itself reads as the route's leading edge rather than a static marker */
.hx--go .hx-dot{left:50%;width:20px;height:20px;margin-left:-10px;background:var(--paper-500);z-index:3;transition:background .25s var(--ease)}
.hx--go .hx-stop.is-passed .hx-dot{background:var(--red)}
.hx--go .hx-cube{left:50%;width:27px;height:27px;margin-left:-13.5px;top:auto;z-index:3}
.hx--go .hx-stop--above .hx-dot,.hx--go .hx-stop--above .hx-cube{bottom:calc(50% + var(--stem) - 10px)}
.hx--go .hx-stop--below .hx-dot,.hx--go .hx-stop--below .hx-cube{top:calc(50% + var(--stem) - 10px)}

/* THE ROUTE ENDS AT TODAY, in the scrub exactly as in the ledger. .is-passed is a reading
   state — "the scrub has carried you past this stop" — and travel now centres the LAST stop
   (see the dist note in tpl.js), so without these the red would run on through the projected
   part of the route and paint 2027+ as travelled. Same statement as the ledger's rule at
   :7222, expressed against .is-passed instead of chronology: the --now stop's OWN outgoing
   segment is grey, everything after it is grey, and a --future stop keeps its grey node and
   gridline however far the reader has scrubbed. Red therefore reaches the Today node and
   stops there — which is the whole point of the marker. */
.hx--go :is(.hx-stop--now,.hx-stop--now ~ .hx-stop).is-passed .hx-seg{background:var(--hairline)}
.hx--go .hx-stop--now ~ .hx-stop.is-passed::before{background:var(--hairline);opacity:.35}
.hx--go .hx-stop--now ~ .hx-stop.is-passed .hx-tick{background:var(--paper-0);border-color:var(--paper-500)}
.hx--go .hx-stop--now ~ .hx-stop.is-passed .hx-dot{background:var(--paper-500)}
.hx--go .hx-stop--now ~ .hx-stop.is-passed .hx-card::before{background:var(--hairline)}
@supports ((mask-size:100% 100%) or (-webkit-mask-size:100% 100%)){.hx--go .hx-dot{border:0}}

/* prev/next — discrete step navigation alongside the continuous scroll-scrub */
.hx-nav{position:absolute;bottom:clamp(18px,3.5vh,32px);left:50%;transform:translateX(-50%);display:none;gap:8px;z-index:5}
.hx--go .hx-nav{display:flex}
.hx-nav-btn{width:44px;height:44px;border-radius:50%;border:1px solid var(--hairline);background:var(--paper-0);color:var(--ink);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s var(--ease),color .15s var(--ease),border-color .15s var(--ease)}
.hx-nav-btn svg{width:18px;height:18px}
.hx-nav-btn:hover,.hx-nav-btn:focus-visible{background:var(--ink);color:var(--paper-0);border-color:var(--ink)}
.hx-nav-next{background:var(--ink);color:var(--paper-0);border-color:var(--ink)}
.hx-nav-next:hover,.hx-nav-next:focus-visible{background:var(--red);border-color:var(--red)}
/* ---- the same prev/next on the vertical ledger (2026-08-05, owner) ----
   The ledger had no navigation affordance at all: both .hx-nav and .hx-cue were gated to
   .hx--go, so every phone, reduced-motion and no-GSAP visitor got free-scroll only.

   POSITION IS MEASURED, NOT PICKED. The bottom edge is already occupied: .espa-bar spans the
   full width at the very bottom (z 85) and .a11y / button.to-top sit in the two corners (z 80),
   leaving a free centred strip between them in the same band (278px at 390). So the pair sits
   centred, in that band, at the same z-index — no overlap with any of the three, and it reads as
   one row of controls with them rather than a fourth floating thing. Fixed, not absolute: the
   ledger is a long document, so a stage-anchored bar would scroll away from what it drives.

   THE OFFSET IS THE SITE'S OWN, NOT A NUMBER. It reuses the exact rules .a11y and .to-top use
   (:2897, :2902) — 20px, or 12px on phones, lifted by --espa-h while the co-funding bar is up.
   A hardcoded 69px was written first and was wrong: 69px is only what that formula RESOLVES to
   at 390, where --espa-h is floored at 56+1. --espa-h scales with the viewport (it is derived
   from 100vw ÷ 8.4675), so at 1440 the bar is ~161px tall and the nav rendered inside it. Caught
   at 1440 with reduced motion, which is a real case: that combination renders this ledger, not
   the scrub. The bottom transition matches theirs too, so all three lift together when the bar
   is dismissed instead of the nav detaching from the pair.

   Only visible while the track is actually on screen — tpl.js adds .hx-nav--on — otherwise two
   buttons would hover over the hero and the footer, where they navigate nothing. */
.hx:not(.hx--go) .hx-nav{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:80;
  display:none;gap:8px;transition:bottom 520ms var(--ease)}
.hx:not(.hx--go) .hx-nav.hx-nav--on{display:flex}
html.espa-on .hx:not(.hx--go) .hx-nav{bottom:calc(20px + var(--espa-h))}
@media(max-width:640px){
  .hx:not(.hx--go) .hx-nav{bottom:12px}
  html.espa-on .hx:not(.hx--go) .hx-nav{bottom:calc(12px + var(--espa-h))}
}
/* a spent direction is disabled rather than hidden, so the pair never changes width mid-scroll */
.hx-nav-btn[disabled]{opacity:.4;cursor:default}
.hx-nav-btn[disabled]:hover{background:var(--paper-0);color:var(--ink);border-color:var(--hairline)}
.hx-nav-next[disabled]:hover{background:var(--ink);color:var(--paper-0);border-color:var(--ink)}
/* The cue is static on the ledger — it sits above the track in source order already, so it only
   needs un-absoluting. It must NOT join the bottom band: that band is full, and the cue is a
   read-once instruction rather than a control. Its arrow is rewritten by tpl.js, because the
   markup ships the horizontal "travels →" for the scrub and the ledger travels down. */
.hx:not(.hx--go) .hx-cue{display:block;position:static;margin:0 0 clamp(18px,3vw,26px);
  font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  /* --ink-soft for the same reason as the scrub variant above: 11px --ink-muted fails on the
     worst glyph pixel of this surface on every backdrop tried */
  color:var(--ink-soft)}


