
:root{
  color-scheme: light dark;
  --sans: ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;
  --cond:"Arial Narrow","Helvetica Neue Condensed","Segoe UI",ui-sans-serif,system-ui,sans-serif;
  --mono: ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --brand:#0063A8; --brand-deep:#00406e;
  --bg:#f4f6f8; --surface:#fff; --surface2:#e9edf1; --sunk:#e1e6eb;
  --ink:#0c1014; --ink2:#39424c; --ink3:#5f6872;
  --rule:#d2d9e0; --rule2:#aeb8c2; --accent:#00548F; --err:#b3261e; --mark:rgba(0,99,168,.20);
  --stage-hi:#fff; --stage-lo:#e9edf1; --stage-sh1:rgba(18,26,36,.35); --stage-sh2:rgba(18,26,36,.17);
  --logo-tile:#fff;
  --map-water:#dde8f0; --map-land:#fff; --map-other:#eef2f5; --map-border:#c9d2db; --map-coast:#9dabb8;
  --tag-bg:#e6eff6; --tag-ink:#00548F; --tag-edge:#0063A8; --pin-edge:#fff;
  --scroll-sh:rgba(18,26,36,.22);
  --maxw:1200px; --pad:clamp(1rem,4vw,2.5rem);
}
@media (prefers-color-scheme:dark){:root{
  --bg:#08090b; --surface:#0e1216; --surface2:#141820; --sunk:#0b0d10;
  --ink:#f2f5f8; --ink2:#b2bcc6; --ink3:#7f8994;
  --rule:#1e242c; --rule2:#333c46; --accent:#0087E6; --err:#ff8a80; --mark:rgba(0,135,230,.28);
  --stage-hi:#c3cad2; --stage-lo:#9aa4ae; --stage-sh1:rgba(8,9,11,.45); --stage-sh2:rgba(8,9,11,.24);
  --logo-tile:#e4e8ec;
  --map-water:#0d1823; --map-land:#161c23; --map-other:#11161c; --map-border:#2c3642; --map-coast:#415061;
  --tag-bg:#0b2436; --tag-ink:#f2f5f8; --tag-edge:#0087E6; --pin-edge:#d7dee6;
  --scroll-sh:rgba(160,172,186,.32);}}
/* The reader's choice beats the system's: an attribute on <html>, set by the
   toggle in the header (site.js) and, before the page draws, from the saved
   choice (theme.js). color-scheme goes with it, so form controls and
   scrollbars follow the page rather than the system. */
:root[data-theme="dark"]{color-scheme:dark;
  --bg:#08090b; --surface:#0e1216; --surface2:#141820; --sunk:#0b0d10;
  --ink:#f2f5f8; --ink2:#b2bcc6; --ink3:#7f8994;
  --rule:#1e242c; --rule2:#333c46; --accent:#0087E6; --err:#ff8a80; --mark:rgba(0,135,230,.28);
  --stage-hi:#c3cad2; --stage-lo:#9aa4ae; --stage-sh1:rgba(8,9,11,.45); --stage-sh2:rgba(8,9,11,.24);
  --logo-tile:#e4e8ec;
  --map-water:#0d1823; --map-land:#161c23; --map-other:#11161c; --map-border:#2c3642; --map-coast:#415061;
  --tag-bg:#0b2436; --tag-ink:#f2f5f8; --tag-edge:#0087E6; --pin-edge:#d7dee6;
  --scroll-sh:rgba(160,172,186,.32);}
:root[data-theme="light"]{color-scheme:light;
  --bg:#f4f6f8; --surface:#fff; --surface2:#e9edf1; --sunk:#e1e6eb;
  --ink:#0c1014; --ink2:#39424c; --ink3:#5f6872;
  --rule:#d2d9e0; --rule2:#aeb8c2; --accent:#00548F; --err:#b3261e; --mark:rgba(0,99,168,.20);
  --stage-hi:#fff; --stage-lo:#e9edf1; --stage-sh1:rgba(18,26,36,.35); --stage-sh2:rgba(18,26,36,.17);
  --logo-tile:#fff;
  --map-water:#dde8f0; --map-land:#fff; --map-other:#eef2f5; --map-border:#c9d2db; --map-coast:#9dabb8;
  --tag-bg:#e6eff6; --tag-ink:#00548F; --tag-edge:#0063A8; --pin-edge:#fff;
  --scroll-sh:rgba(18,26,36,.22);}

*,*::before,*::after{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);
  font-size:16px;line-height:1.62;overflow-x:hidden;-webkit-text-size-adjust:100%;
  overflow-wrap:break-word}   /* only a word longer than its whole line breaks */
img,svg{max-width:100%}
a{color:inherit}
h1,h2,h3,h4{margin:0;line-height:1.12}
p{margin:0 0 1em}
ul{margin:0;padding:0;list-style:none}
:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.wrap{width:100%;max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}
.skip{position:absolute;left:-9999px;background:var(--brand);color:#fff;padding:.6rem 1rem;font-weight:800;z-index:99}
/* Names a section for a screen reader where the design carries no visible
   heading. Four pages opened a card grid straight after the h1, so anyone
   navigating by heading level met an h1 -> h3 gap. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.skip:focus{left:0;top:0}
/* <main> carries tabindex="-1" so the skip link MOVES focus rather than only
   scrolling -- without it a screen reader hears nothing change and the link
   reads as broken. It can only be focused programmatically, so the visible
   ring is suppressed: a 3px box drawn around 1,700px of page content tells a
   sighted user nothing they did not just do on purpose. The focus move is
   the accessible part; the outline is not. */
main:focus{outline:none}
.kicker{font-family:var(--mono);font-size:.68rem;letter-spacing:.22em;text-transform:uppercase;
  color:var(--ink3);margin:0 0 1rem;display:flex;align-items:center;gap:.75rem}
.kicker::after{content:"";height:1px;flex:1;background:var(--rule)}
.h2{font-family:var(--cond);font-weight:800;text-transform:uppercase;letter-spacing:-.015em;
  font-size:clamp(1.8rem,4.2vw,2.9rem)}
.lede{color:var(--ink2);font-size:clamp(1rem,1.4vw,1.12rem);max-width:64ch}
.dyco-mark{display:block;height:1.65rem;width:auto;filter:invert(1)}

/* ---- motion -----------------------------------------------------------
   One family of easing curves and three durations for the whole site, so
   every hover, press and arrival feels like it belongs to the same object
   rather than six different ones. The owner, 2026-09-29: "for all buttons
   and actions ... make sure there's transitions". No library: CSS carries
   the hovers, presses and entrances, and site.js the few movements CSS
   cannot express, through the browser's own Web Animations API. */
:root{--ease:cubic-bezier(.2,.7,.3,1); --ease-out:cubic-bezier(.16,1,.3,1);
  --t-fast:140ms; --t:220ms; --t-slow:340ms}
a,button,.btn,.cat,.art,.makes a,.site-nav a,.faq summary{
  transition:background-color var(--t) var(--ease),
             border-color var(--t) var(--ease),
             color var(--t-fast) var(--ease),
             box-shadow var(--t) var(--ease),
             transform var(--t) var(--ease)}
input,select,textarea{transition:border-color var(--t-fast) var(--ease),
  box-shadow var(--t-fast) var(--ease),background-color var(--t-fast) var(--ease)}
/* Hover only where something can hover. A touchscreen fakes :hover on a tap
   and keeps it until the next tap somewhere else, so on a phone a card stayed
   lifted, a row stayed shifted and an outlined button stayed filled after it
   was tapped -- on Search or Add another part, which do not leave the page,
   it read as a state the button had been left in. Every :hover rule on the
   site sits inside (hover:hover); check_copy.py holds that. */
@media (hover:hover){
  .btn:hover{transform:translateY(-2px);box-shadow:0 6px 18px rgba(0,99,168,.28)}
  .cat:hover{transform:translateY(-3px);box-shadow:0 10px 26px rgba(0,0,0,.16)}
  .cat:hover:active{transform:translateY(-1px)}
  .art:hover{background:var(--surface2)}
  .makes a:hover{transform:translateX(3px)}
  .cat:hover .cat-go span{transform:translateX(5px)}
}
.btn:active{transform:translateY(0) scale(.98);box-shadow:0 2px 6px rgba(0,99,168,.22);
  transition-duration:60ms}
/* Everything else pressable answers the press, too -- and on a phone, where
   nothing hovers, the press is the only answer a card or a row gets. (iOS
   draws :active only for a page that listens for touches; site.js does.) */
.act:active,.req-add:active,.req-x:active,.loc-tip-x:active,.nav-toggle:active,.fig-zoom:active,
.theme-toggle:active{
  transform:scale(.96);transition-duration:60ms}
.cat:active{transform:scale(.985);transition-duration:60ms}
.makes a:active,.art:active,.doc:active,.route:active{background:var(--surface2);
  transition-duration:60ms}
/* The browser's own tap flash, in the brand's blue -- and none on the things
   that answer a press themselves, which would otherwise flash a grey box
   over their own feedback. */
html{-webkit-tap-highlight-color:rgba(0,99,168,.16)}
.btn,.act,.cat,.art,.doc,.route,.makes a,.req-add,.req-x,.loc-tip-x,.nav-toggle,.fig-zoom,
.theme-toggle,.locmap .loc{-webkit-tap-highlight-color:transparent}
.cat-go span{display:inline-block;transition:transform var(--t) var(--ease)}
.faq summary::after{transition:transform var(--t) var(--ease)}
.faq[open] summary::after{transform:rotate(180deg)}

/* Entrances. Something that appears arrives; nothing already on screen is
   moved. A keyframe animation starts when an element starts being displayed,
   so a [hidden] element that is shown plays its entrance, and a restyle of
   one already showing does not. */
@keyframes rise{from{opacity:0;transform:translateY(8px)}}
@keyframes fade{from{opacity:0}}
@keyframes pop{from{opacity:0;transform:scale(.4)}}
@keyframes halo{from{opacity:0;transform:scale(2.4)}}
@keyframes spin{to{transform:rotate(1turn)}}
.req.is-live .req-line+.req-line,.req.is-live .f-part,
.req.is-live .req-step:not(#req-parts),.req-done{animation:rise var(--t-slow) var(--ease-out)}
.finder-m,.req-m,.filter-empty{animation:fade var(--t) var(--ease)}
.dealer-d{animation:pop var(--t) var(--ease-out)}
.faq[open]>:not(summary){animation:rise var(--t) var(--ease-out)}
/* The answer's height opens and closes with it, where the browser can
   animate to a height of auto; elsewhere it simply rises into place. */
@supports (interpolate-size:allow-keywords){
  .faq{interpolate-size:allow-keywords}
  .faq::details-content{block-size:0;overflow-y:clip;
    transition:block-size var(--t) var(--ease),content-visibility var(--t) allow-discrete}
  .faq[open]::details-content{block-size:auto}
}
/* A button at work (site.js busy()): a turning mark after its label, and no
   second press while it turns. */
.is-busy{pointer-events:none}
.is-busy::after{content:"";flex:none;width:.95em;height:.95em;border-radius:50%;
  border:2px solid currentColor;border-right-color:transparent;animation:spin .7s linear infinite}
/* In-page links glide rather than jump. */
@media (prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}
/* Page to page, a short cross-fade under a header that stays put, where the
   browser supports cross-document view transitions; elsewhere, nothing. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){animation-duration:180ms}
.site-header{view-transition-name:site-header}
/* ...at the same pace as the page: a named layer otherwise takes the
   browser's 250ms default against the page's 180. */
::view-transition-group(site-header),::view-transition-old(site-header),
::view-transition-new(site-header){animation-duration:180ms}
/* The fly-by flows on from page to page (the owner, 2026-10-01: "the current
   icons smoothly flow around to whatever the page contextually is", "the
   icons flash or darken and relighten during the transitions ... I don't
   like that", and then "the fly by looks better with how it went up and
   down"). In any fade -- page to page, or light and dark -- each of the six
   drawings is a layer of its own, on both sides, so its old and new pictures
   blend at full strength instead of fading with the page. Between pages
   theme.js reads each one's old place, finishes the layer's move, and glides
   the page's own drawing from there into its lane. The heads' text is a
   layer of its own above the drawings, and the header above that. */
:root:active-view-transition .flyby i:nth-of-type(1) .part-art{view-transition-name:fly-1}
:root:active-view-transition .flyby i:nth-of-type(2) .part-art{view-transition-name:fly-2}
:root:active-view-transition .flyby i:nth-of-type(3) .part-art{view-transition-name:fly-3}
:root:active-view-transition .flyby i:nth-of-type(4) .part-art{view-transition-name:fly-4}
:root:active-view-transition .flyby i:nth-of-type(5) .part-art{view-transition-name:fly-5}
:root:active-view-transition .flyby i:nth-of-type(6) .part-art{view-transition-name:fly-6}
::view-transition-group(fly-1),::view-transition-group(fly-2),::view-transition-group(fly-3),
::view-transition-group(fly-4),::view-transition-group(fly-5),::view-transition-group(fly-6),
::view-transition-old(fly-1),::view-transition-old(fly-2),::view-transition-old(fly-3),
::view-transition-old(fly-4),::view-transition-old(fly-5),::view-transition-old(fly-6),
::view-transition-new(fly-1),::view-transition-new(fly-2),::view-transition-new(fly-3),
::view-transition-new(fly-4),::view-transition-new(fly-5),::view-transition-new(fly-6){animation-duration:180ms}
/* The glow's lights are in the page's own picture, so the fade carries them;
   their empty twins (.gh) are layers of their own, for theme.js to read the
   old page's lights off and flow the new page's from there. Empty, they draw
   nothing, so they cannot light anything outside the head. */
:root:active-view-transition .gh-1{view-transition-name:glow-1}
:root:active-view-transition .gh-2{view-transition-name:glow-2}
::view-transition-group(glow-1),::view-transition-group(glow-2),::view-transition-old(glow-1),
::view-transition-old(glow-2),::view-transition-new(glow-1),::view-transition-new(glow-2){animation-duration:180ms}
:root:active-view-transition-type(leave) :is(.hero-in,.page-head>.wrap){view-transition-name:head-out}
:root:active-view-transition-type(arrive) :is(.hero-in,.page-head>.wrap){view-transition-name:head-in}
::view-transition-group(head-out),::view-transition-group(head-in){animation-duration:180ms;z-index:2}
::view-transition-group(site-header){z-index:3}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition-duration:.01ms !important;animation-duration:.01ms !important;
    transition-delay:0s !important;animation-delay:0s !important;
    animation-iteration-count:1 !important;scroll-behavior:auto !important}
  .flyby i,.flyby .part-art{animation:none}
  .flyby i{transform:translateX(var(--x))}
  .flyby .part-art{transform:rotate(var(--r0))}
  .btn:hover,.cat:hover,.makes a:hover,.btn:active,.act:active,.cat:active,.req-add:active,
  .req-x:active,.loc-tip-x:active,.nav-toggle:active,.fig-zoom:active,.theme-toggle:active{transform:none}
  @view-transition{navigation:none}
}

/* ---- icons ---- */
.ico{width:3.25rem;height:3.25rem;display:block;color:var(--accent);margin-bottom:.9rem;
  transition:transform var(--t) var(--ease),color var(--t) var(--ease)}
@media (hover:hover){.cat:hover .ico{transform:scale(1.08) rotate(-4deg);color:var(--brand)}}

.site-header{position:sticky;top:0;z-index:40;background:#07080a;color:#f2f5f8;border-bottom:1px solid #1b2028}
.hdr{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap;padding-block:.75rem}
.brand{display:flex;align-items:center;gap:.7rem;text-decoration:none;margin-right:auto;
  padding-block:.25rem;margin-block:-.25rem}      /* the home link, 24px+ tall on a phone */
.site-nav ul{display:flex;flex-wrap:wrap;gap:.1rem}
.site-nav a{display:block;text-decoration:none;font-size:.72rem;font-weight:700;letter-spacing:.035em;
  text-transform:uppercase;color:#c9d2db;padding:.5rem .34rem;border-bottom:2px solid transparent;
  white-space:nowrap}
.site-nav a[aria-current="page"]{color:#fff;border-bottom-color:var(--brand)}
@media (hover:hover){.site-nav a:hover{color:#fff;border-bottom-color:var(--brand)}}
/* The header is near-black in both schemes, and the light scheme's accent
   drew its focus ring at 2.54:1 against it (3:1 is the floor). The dark
   scheme's accent is the header's own. */
.site-header :focus-visible{outline-color:#0087E6}
/* The homepage hero and the page heads are near-black in both schemes too,
   and carry links and buttons: the same ring as the header's. */
.hero :focus-visible,.page-head :focus-visible{outline-color:#0087E6}
/* Light or dark, the reader's choice: the moon offers dark, the sun offers
   light, and which one shows is decided here, by the scheme the page is in,
   so it is right before site.js runs. On the always-dark header, like the
   menu button. It does nothing without scripting, so the <noscript> block
   hides it. */
.theme-toggle{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:36px;height:36px;padding:0;background:none;border:1px solid #2b333d;border-radius:3px;
  color:#c9d2db;cursor:pointer}
.theme-toggle::before{content:"";width:18px;height:18px;background:currentColor;
  -webkit-mask:url(/assets/v/icon-moon.41c5448b.svg) center/contain no-repeat;mask:url(/assets/v/icon-moon.41c5448b.svg) center/contain no-repeat}
:root[data-theme="dark"] .theme-toggle::before{-webkit-mask-image:url(/assets/v/icon-sun.599cee0b.svg);mask-image:url(/assets/v/icon-sun.599cee0b.svg)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .theme-toggle::before{
  -webkit-mask-image:url(/assets/v/icon-sun.599cee0b.svg);mask-image:url(/assets/v/icon-sun.599cee0b.svg)}}
@media (hover:hover){.theme-toggle:hover{color:#fff;border-color:#4a5563}}
@media (forced-colors:active){.theme-toggle::before{forced-color-adjust:none;background:ButtonText}}

.crumbs{background:var(--sunk);border-bottom:1px solid var(--rule);font-size:.82rem}
.crumbs ol{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;margin:0;padding:.7rem 0}
.crumbs li::after{content:"/";margin-left:.4rem;color:var(--ink3)}
.crumbs li:last-child::after{content:""}
.crumbs a{display:inline-block;padding:.3rem 0;margin:-.3rem 0;   /* a 24px target, same strip */
  color:var(--accent);text-decoration:none}
@media (hover:hover){.crumbs a:hover{text-decoration:underline}}
.crumbs [aria-current]{color:var(--ink3)}

section{padding-block:clamp(2.5rem,5.5vw,4.25rem)}
/* A paper's numbered sections are prose, not page bands — the sitewide
   section padding put 132px of nothing above "1. Scope". */
.paper section{padding-block:0}
.paper section+section{margin-top:2.5rem}
.alt{background:var(--surface)}
.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.85fr);
  gap:clamp(1.5rem,4vw,3.25rem);align-items:center}
@media (max-width:880px){.split{grid-template-columns:1fr}}

.page-head{background:linear-gradient(180deg,#0b1015,#08090b);color:#f2f5f8;
  border-bottom:1px solid #171d24;padding-block:clamp(2.25rem,5vw,3.5rem)}
.page-head h1{font-family:var(--cond);font-weight:900;text-transform:uppercase;
  letter-spacing:-.02em;font-size:clamp(1.9rem,5vw,3.4rem);max-width:22ch}
.page-head p{color:#bcc6d1;max-width:62ch;margin-top:1rem}
.page-head .kicker{color:#8794a2}
.page-head .kicker::after{background:#242c36}

.hero{position:relative;background:linear-gradient(180deg,#0b1015 0%,#08090b 72%);
  color:#f2f5f8;border-bottom:1px solid #171d24;overflow:hidden}
.hero::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:repeating-linear-gradient(90deg,rgba(255,255,255,.028) 0 1px,transparent 1px 3px)}
.hero::after{content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background:linear-gradient(90deg,var(--brand) 0%,var(--brand) 38%,transparent 38%)}
.hero-in{position:relative;padding-block:clamp(2.75rem,6.5vw,5rem)}
h1.big{font-family:var(--cond);font-weight:900;text-transform:uppercase;letter-spacing:-.025em;
  font-size:clamp(2.1rem,6.2vw,4.35rem);line-height:.97;max-width:19ch;margin-bottom:1.25rem}
h1.big em{font-style:normal;color:#3AA5F0;display:block}
.hero p{color:#c0cad4;max-width:60ch;font-size:clamp(1rem,1.5vw,1.16rem)}
.hero p strong{color:#fff}
/* The homepage's eyebrow names the six parts, so a visitor knows at once what
   the site sells (the owner, 2026-10-01: it stays there). It is a list, and
   it is set as a label. It was a <p>, so .hero p's size and measure beat
   .kicker's: 18.6px across two lines on a desktop and four on a phone,
   splitting SWING / DRIVES. Now each name stays whole, the dot before each
   hangs in a margin the box clips, so no line starts with one, and a screen
   reader hears a list of six, not "dot". role="list" keeps the list in
   Safari, which drops it from a list with no markers. */
.hero .hero-cats{font-size:.78rem;letter-spacing:.18em;color:#aab6c2;margin:0 0 1.15rem}
.hero .hero-cats::after{background:#242c36}
.hero-cats>div{overflow:hidden;min-width:0}
.hero-cats ul{list-style:none;margin:0 0 0 -1.7em;padding:0;display:flex;flex-wrap:wrap;row-gap:.45rem}
.hero-cats li{padding-left:1.7em;position:relative;white-space:nowrap}
.hero-cats li::before{content:"\00b7";content:"\00b7" / "";position:absolute;left:.6em}
/* Each name is a link to its page. Padding on an inline link widens what a
   finger can hit to 24px without moving a line, and the rows sit far enough
   apart that two names never share a pixel of it. */
.hero-cats a{color:inherit;text-decoration:none;padding-block:.5em}
@media (hover:hover){.hero-cats a:hover{color:#fff;text-decoration:underline;text-underline-offset:.3em}}
.hero-cta{display:flex;flex-wrap:wrap;gap:.7rem;margin-top:1.8rem}
/* ---- the fly-by: the six drawings passing through a dark head ----
   Moving, they cross the whole head, edge to edge of the window, behind the
   text (below). Resting, under reduced motion, they stand where there is
   empty space, which is measured, not guessed. On the homepage that lane
   runs under the headline and right of the lede, whose 60ch measure is its
   left edge, in the lede's font so its ch is theirs. On every page head it
   starts past the wider of the headline's 22ch box and the paragraphs' 62ch:
   two empty spacers carry those measures in those faces, and the first
   column of a grid is as wide as the wider of them. A head is as tall as its
   text, so a page head's drawings are sized from the lane's height (cqh),
   and a resting lane too narrow shows nothing. Each lane runs to the edge of
   the screen. Decorative and aria-hidden, clicks pass through. */
.hero-low{position:relative}
.page-head{position:relative;overflow:hidden}
.page-head>.wrap{position:relative}
.flyby{position:absolute;pointer-events:none;overflow:hidden;color:#3AA5F0;
  right:calc((100% - 100vw) / 2)}
.hero-low .flyby,.fly-lane{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 16%,#000 84%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 16%,#000 84%,transparent)}
.hero-low .flyby{font-size:clamp(1rem,1.5vw,1.16rem);left:calc(60ch + 3rem);
  top:0;bottom:calc(-1 * clamp(2.75rem,6.5vw,5rem))}
.page-head .flyby{left:var(--pad);top:calc(-1 * clamp(2.25rem,5vw,3.5rem));
  bottom:calc(-1 * clamp(2.25rem,5vw,3.5rem));display:grid;
  grid-template-columns:auto minmax(0,1fr);grid-template-rows:100%;column-gap:2rem}
.fly-h1,.fly-p{grid-area:1/1;height:0}
.fly-h1{font:900 clamp(1.9rem,5vw,3.4rem)/1 var(--cond);width:22ch}
.fly-p{width:62ch}
.fly-lane{grid-area:1/2;position:relative;overflow:hidden;container-type:size}
@container (max-width:259px){.fly-lane i{display:none}}
/* The opacity is on the drawing itself, not its box: a page-to-page
   transition carries the drawing alone, and would draw it at full strength. */
.flyby i{position:absolute;left:0;width:100%;top:var(--y)}
.flyby .fg{display:block;width:var(--s);height:var(--s)}
.flyby .part-art{display:block;width:var(--s);height:var(--s);filter:var(--dof)}
.flyby use{opacity:var(--o)}
@keyframes fly{from{transform:translateX(100%)}to{transform:translateX(calc(0% - var(--s)))}}
@keyframes tilt{from{transform:rotate(var(--r0))}to{transform:rotate(var(--r1))}}
/* Moving, the drawings cross the whole head, from one edge of the window to
   the other (the owner, 2026-10-01: "take the full horizontal fly by rather
   than stopping or fading halfway"). So they pass behind the text, and the
   text stays on top: each head is its own stacking context with the fly-by
   at the bottom of it, a light button gets the head's own dark behind its
   label, and every line of text carries a hard ring of the head's dark, so
   where a drawing passes behind a word the letters keep the ground their
   contrast was measured on. Resting, under reduced motion, the drawings keep
   to the lane past the text, as they always did: a drawing that never moves
   would otherwise sit behind a sentence for good. */
.hero,.page-head{isolation:isolate}
/* While a drawing glides from one page's lane into the next (theme.js marks
   the page fly-glide for the glide's length), nothing clips it: leaving the
   hero's low lane for a page head's, it is seen the whole way rather than cut
   off at the edge and brought back. */
.fly-glide .hero,.fly-glide .page-head,.fly-glide .flyby,.fly-glide .fly-lane{overflow:visible}
.flyby{z-index:-1}
.hero-in,.page-head>.wrap{text-shadow:-1px -1px 0 #090b0e,1px -1px 0 #090b0e,-1px 1px 0 #090b0e,
  1px 1px 0 #090b0e,0 -1px 0 #090b0e,0 1px 0 #090b0e,-1px 0 0 #090b0e,1px 0 0 #090b0e,
  0 0 4px #090b0e}
.hero .btn,.page-head .btn{text-shadow:none}
.hero .btn-b,.page-head .btn-b{background:#08090b}
/* Moving, and only for someone who has not asked for less motion: declared
   anywhere else, the animations are made and then stopped by the
   reduced-motion block, and show their first frame -- every drawing at the
   lane's right edge -- before they stop. --fly-clock is the time of day, from
   theme.js, so every page puts each drawing where the page before had it. */
@media (prefers-reduced-motion:no-preference){
  .flyby i{animation:fly var(--t) linear calc(var(--d) - var(--fly-clock,0s)) infinite;
    will-change:transform}
  .flyby .part-art{animation:tilt var(--t) linear calc(var(--d) - var(--fly-clock,0s)) infinite;
    will-change:transform}
  .hero-low .flyby,.page-head .flyby{left:calc((100% - 100vw) / 2)}
  .hero-low .flyby,.fly-lane{-webkit-mask-image:none;mask-image:none}
  .fly-lane{grid-column:1 / -1}
}
@media (max-width:879px){.hero-low .flyby{display:none}}
@media (max-width:1039px){.page-head .flyby{display:none}}
@supports not (height:1cqh){.page-head .flyby{display:none}}
/* !important, as in print: a page head's lane is a grid, and that rule is more
   specific than this one. */
@media (forced-colors:active){.flyby{display:none !important}}
/* ---- the glow: light from the top of every dark head ----
   The owner, 2026-10-01: "you know how the home page has that blue gradient
   glow from the top? can we make that gradient glow dynamic for each page?
   aesthetically pleasing please", then "some depth of field would be nice
   too whether its between icons and the background gradient" and "some
   parallax would be nice to include too". Every head, the homepage's hero
   and every page head, carries two lights shining down from just above it:
   the key in the brand blue and a fainter fill in the accent. Where they
   stand and how far they reach is the page's own (glow_lights() in gen.py),
   so no two pages are lit alike; the homepage keeps its light where it
   always was, top right. Behind everything in the head, under the drawings,
   so the drawings stand out from it, the farther ones softer (--dof, below);
   it is clipped to the head always, even while a drawing glides out of it.
   How bright it may get is arithmetic: check_copy.py stacks both lights at
   their brightest and holds every colour of text a head carries to AA
   against that. Moving, the lights drift on the fly-by's clock, so a page
   opens with them where the page before had them; between pages theme.js
   flows them from the old page's places to the new one's, reading the old
   places off .gh, a small empty twin of each light that is its own layer in
   the fade; and, scrolling, the glow falls behind the page farther than any
   drawing (parallax, below). Decorative and aria-hidden. */
.glow{position:absolute;inset:0;z-index:-2;overflow:hidden;pointer-events:none}
.glow-par{position:absolute;inset:0}
.gl,.gh{position:absolute;left:calc(var(--x) - var(--w) / 2);top:calc(var(--y) - var(--h) / 2);
  width:var(--w);height:var(--h)}
/* A twin is its light's centre and proportions at a hundredth of its size:
   all theme.js reads off it is where the light was and how big, as a ratio
   to this page's twin. Full size, the fade drew both twins, two blank
   pictures as big as the lights, every frame, and frames slowed to 100 ms
   and more in Chromium's software compositor while the drawings glided. */
.gh{left:calc(var(--x) - var(--w) / 200);top:calc(var(--y) - var(--h) / 200);
  width:calc(var(--w) / 100);height:calc(var(--h) / 100)}
.gl-1{background:radial-gradient(closest-side,rgb(0 99 168/.26),rgb(0 99 168/.2) 26%,
  rgb(0 99 168/.12) 52%,rgb(0 99 168/.05) 76%,rgb(0 99 168/0))}
.gl-2{background:radial-gradient(closest-side,rgb(0 135 230/.1),rgb(0 135 230/.062) 34%,
  rgb(0 135 230/.026) 66%,rgb(0 135 230/0))}
@keyframes drift-1{0%,100%{transform:translate(0,0) scale(1)}
  25%{transform:translate(-2.6%,1.8%) scale(1.05)}50%{transform:translate(-.8%,4.2%) scale(.97)}
  75%{transform:translate(2.2%,1.6%) scale(1.03);opacity:.86}}
@keyframes drift-2{0%,100%{transform:translate(0,0) scale(1)}
  30%{transform:translate(3.4%,2.4%) scale(.95);opacity:.8}
  65%{transform:translate(-2.4%,4.6%) scale(1.06)}}
@media (prefers-reduced-motion:no-preference){
  .gl-1{animation:drift-1 96s ease-in-out calc(-31s - var(--fly-clock,0s)) infinite;will-change:transform}
  .gl-2{animation:drift-2 72s ease-in-out calc(-47s - var(--fly-clock,0s)) infinite;will-change:transform}
}
/* Parallax: scrolling, each layer falls behind the page by its own share
   (--pz) of the distance scrolled -- the glow most, then the far drawings,
   the near ones least, the text not at all -- which reads as depth. Driven
   by the scroll itself, on the compositor, where the browser has scroll
   timelines; elsewhere, and for anyone who has asked for less motion,
   everything scrolls together as before. The drawings already run fly, so
   it is restated here with par beside it, on the document's own timeline. */
@keyframes par{to{translate:0 calc(var(--pz) * 640px)}}
@supports (animation-timeline:scroll()){
  @media (prefers-reduced-motion:no-preference){
    .glow-par{--pz:.42;animation:par linear both;animation-timeline:scroll(root);animation-range:0 640px}
    .flyby i{animation:fly var(--t) linear calc(var(--d) - var(--fly-clock,0s)) infinite,par linear both;
      animation-timeline:auto,scroll(root);animation-range:normal,0 640px}
  }
}
@media (forced-colors:active){.glow{display:none}}
.btn{display:inline-flex;align-items:center;gap:.5rem;text-decoration:none;font-weight:800;
  font-size:.92rem;padding:.8rem 1.2rem;border:1px solid transparent}
.btn-a{background:var(--brand);color:#fff}
.btn-ghost{background:transparent;color:#fff;border-color:rgba(255,255,255,.5)}
@media (hover:hover){.btn-ghost:hover{background:rgba(255,255,255,.12)}
  .btn-a:hover{background:#0072c2}}
.btn-b{background:transparent;color:#f2f5f8;border-color:#35404c}
@media (hover:hover){.btn-b:hover{border-color:var(--brand)}}
/* The secondary button on a light or themed surface: outlined, filled on
   hover -- .btn-b is the same thing on the dark hero. It was solid brand blue
   like .btn-a, so "Request a final drive" and "Find a dealer" stood side by
   side as equals and neither said which came first. */
.btn-c{background:transparent;color:var(--accent);border-color:var(--accent)}
@media (hover:hover){.btn-c:hover{background:var(--brand);border-color:var(--brand);color:#fff}}

.stats{background:var(--sunk);border-bottom:1px solid var(--rule)}
.stats ul{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1px;background:var(--rule)}
.stats li{background:var(--bg);padding:1.15rem var(--pad)}
.stat-n{font-family:var(--cond);font-weight:800;font-size:clamp(1.5rem,3vw,2rem);line-height:1;color:var(--accent)}
.stat-l{font-size:.78rem;color:var(--ink3);margin-top:.3rem;display:block}

blockquote{margin:1.5rem 0 0;padding:1rem 0 1rem 1.25rem;border-left:3px solid var(--brand);
  color:var(--ink2);font-size:1.02rem}
blockquote cite{display:block;margin-top:.6rem;font-style:normal;font-family:var(--mono);
  font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent)}
.chart{background:var(--surface2);border:1px solid var(--rule);padding:1.25rem}
.chart h3{font-size:.95rem;margin-bottom:.15rem}
.chart .note{font-size:.76rem;color:var(--ink3);margin:.5rem 0 0}

.pillars,.fails{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:1px;background:var(--rule);border:1px solid var(--rule);margin-top:2rem}
.pillar,.fail{background:var(--bg);padding:1.5rem}
.pillars-3{grid-template-columns:repeat(3,1fr)}
@media (max-width:900px){.pillars-3{grid-template-columns:repeat(2,1fr)}}
@media (max-width:620px){.pillars-3{grid-template-columns:1fr}}
.pillar-n,.fail-n{font-family:var(--mono);font-size:.72rem;color:var(--accent);
  letter-spacing:.14em;display:block;margin-bottom:.7rem}
.pillar h3,.fail h3{font-size:1.08rem;margin-bottom:.55rem}
.pillar p,.fail p{color:var(--ink2);font-size:.94rem;margin:0}
.step p+p,.pillar p+p,.fail p+p,.ans p+p{margin-top:.65rem}
.fails{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
.fail{border-top:3px solid var(--brand)}

.answers{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-top:2rem}
.ans{border-top:2px solid var(--brand);padding-top:1rem}
.ans h3{font-size:1rem;margin-bottom:.5rem;color:var(--accent)}
.ans p{font-size:.93rem;color:var(--ink2);margin:0}

.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(275px,1fr));gap:1rem;margin-top:2rem}
.cat{display:block;text-decoration:none;background:var(--surface);border:1px solid var(--rule);
  padding:1.4rem;transition:border-color .15s}
@media (hover:hover){.cat:hover{border-color:var(--brand)}}
.cat-meta{font-family:var(--mono);font-size:.7rem;letter-spacing:.11em;text-transform:uppercase;color:var(--accent)}
.cat h3{font-family:var(--cond);font-weight:800;text-transform:uppercase;font-size:1.32rem;margin:.45rem 0 .5rem}
.cat p{color:var(--ink2);font-size:.92rem;margin-bottom:.8rem}
.cat-go{font-size:.82rem;font-weight:700;color:var(--accent)}

/* The filter hides items with el.hidden, which works only through the UA
   rule [hidden]{display:none} -- and ANY author display rule beats a UA rule.
   .makes a sets display:flex, so hiding it did nothing: typing "komatsu" on
   /machines/ left all 192 manufacturers on screen while the counter said
   "1 of 192". /research/ filtered correctly only because .art happens to set
   no display. One !important here is worth more than remembering never to
   set display on a filterable item. */
[hidden]{display:none !important}
.makes{display:grid;grid-template-columns:repeat(auto-fill,minmax(185px,1fr));
  gap:1px;margin-top:2rem}
.makes a{display:flex;justify-content:space-between;align-items:baseline;gap:.5rem;
  background:var(--bg);padding:.75rem 1rem;text-decoration:none;font-size:.92rem;
  box-shadow:0 0 0 1px var(--rule)}
@media (hover:hover){.makes a:hover{background:var(--surface2);color:var(--accent)}}

.models{columns:4 165px;column-gap:1.5rem;margin-top:1.5rem}
.models li{padding:.28rem 0;border-bottom:1px solid var(--rule);break-inside:avoid;font-size:.9rem}

.arts{display:grid;grid-template-columns:repeat(auto-fill,minmax(285px,1fr));
  gap:1px;margin-top:2rem}
.art{background:var(--bg);padding:1.25rem;box-shadow:0 0 0 1px var(--rule);position:relative}
.art-cat{font-family:var(--mono);font-size:.65rem;letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.art h3{font-size:1rem;margin:.5rem 0 0}
.art h3 a{text-decoration:none}
/* The whole card is the link. Its title was the only target -- 17px tall on
   a phone wherever the title fitted one line -- inside a card that looked
   pressable from edge to edge. The link's box stretches over the card, and
   the focus ring goes with it. The card holds nothing else to press. */
.art h3 a::after{content:"";position:absolute;inset:0}
.art h3 a:focus-visible{outline:none}
.art h3 a:focus-visible::after{outline:3px solid var(--accent);outline-offset:-3px}
@media (hover:hover){.art:hover h3 a{color:var(--accent)}}

.faqs{margin-top:2rem;border-top:1px solid var(--rule)}
.faq{border-bottom:1px solid var(--rule)}
.faq summary{cursor:pointer;padding:1.05rem 2rem 1.05rem 0;position:relative;list-style:none}
.faq summary::-webkit-details-marker{display:none}
.faq summary h3{display:inline;font-size:1.02rem;font-weight:700}
.faq summary::after{content:"+";position:absolute;right:0;top:.95rem;font-size:1.4rem;
  line-height:1;color:var(--accent);font-weight:400}
.faq[open] summary::after{content:"\2212"}
.faq p{color:var(--ink2);font-size:.96rem;margin:0 0 1.15rem;max-width:80ch}

.tablewrap{overflow-x:auto;margin-top:2rem;border:1px solid var(--rule)}
.cmp{width:100%;border-collapse:collapse;font-size:.92rem;min-width:640px}
.cmp caption{caption-side:bottom;text-align:left;padding:.75rem 1rem;font-size:.8rem;color:var(--ink3)}
.cmp caption a{color:var(--accent)}
.cmp th,.cmp td{border-bottom:1px solid var(--rule);padding:.7rem .9rem;text-align:left;vertical-align:top}
.cmp thead th{background:var(--surface2);font-weight:700;border-bottom:2px solid var(--brand)}
.cmp tbody th{font-weight:700;color:var(--ink);width:26%}
.cmp td:last-child{color:var(--ink2)}
.placeholder{border:1px dashed var(--rule2);padding:1.1rem;background:var(--surface2);
  color:var(--ink3);font-size:.87rem;margin-top:2rem}
.placeholder strong{color:var(--ink2)}

.band{background:var(--brand);color:#fff;padding-block:clamp(2rem,4vw,3rem)}
.band h2{font-family:var(--cond);font-weight:800;text-transform:uppercase;
  font-size:clamp(1.6rem,3.5vw,2.4rem);margin-bottom:.6rem}
.band p{color:#d3e6f5;max-width:56ch}
.band .btn{background:#fff;color:var(--brand-deep);margin-top:.6rem}
.band .btn-ghost{background:transparent;color:#fff;
  border-color:rgba(255,255,255,.55)}
@media (hover:hover){.band .btn-ghost:hover{background:rgba(255,255,255,.14)}}

/* long-form article */
.prose{max-width:74ch;margin-inline:auto}
.prose h2{font-family:var(--cond);font-weight:800;text-transform:uppercase;
  font-size:clamp(1.35rem,3vw,1.9rem);margin:2.25rem 0 .85rem;letter-spacing:-.01em}
.prose h3{font-size:1.12rem;margin:1.75rem 0 .6rem}
.prose p,.prose li{color:var(--ink2);font-size:1.02rem}
.prose ul,.prose ol{margin:0 0 1.15rem 1.15rem;padding:0;list-style:disc}
.prose ol{list-style:decimal}
.prose li{margin-bottom:.45rem}
.prose strong{color:var(--ink)}
.prose a{color:var(--accent)}
.prose table{width:100%;border-collapse:collapse;margin:1.5rem 0;font-size:.92rem;display:block;overflow-x:auto}
.prose th,.prose td{border:1px solid var(--rule);padding:.55rem .7rem;text-align:left}
.prose th{background:var(--surface2);font-weight:700}
.prose .highlight{
  /* inline emphasis only — a band across the lower half of the text, so the
     line box is untouched and nothing overlaps its neighbours */
  background-image:linear-gradient(transparent 52%, var(--mark) 52%, var(--mark) 94%, transparent 94%);
  box-shadow:inset 0 -2px 0 var(--brand);
  padding:0 .14em;font-weight:650;color:var(--ink);
  -webkit-box-decoration-break:clone;box-decoration-break:clone}
.prose .pro-con-box,.prose .highlight-box{background:var(--surface2);
  border-left:3px solid var(--brand);padding:1rem 1.15rem;margin:1.5rem 0}
.prose .pro-con-box .pro,.prose .pro-con-box .con{margin-bottom:.6rem}
/* Fixed light ground in both themes: the archived charts hardcode
   #0f172a text and #e2e8f0 gridlines, so they are only legible on white.
   Theming the container is what broke them, not the SVGs. */
.prose .article-chart{background:#fff;border:1px solid #cbd5e1;border-radius:2px;
  padding:1.1rem;margin:1.75rem 0;color:#0f172a}
.prose figure{margin:1.5rem 0}
.prose .sources-section,.prose .references-section{border-top:1px solid var(--rule);
  margin-top:2.5rem;padding-top:1.25rem}
.prose .sources-list li,
.prose .references-list li{font-size:.88rem;color:var(--ink3);margin-bottom:.4rem}

/* The archived posts carry structural markup from a stylesheet that did not
   come with them, so four components were rendering as undifferentiated text:
   a timeline as orphan year numbers floating above paragraphs, a "Key
   Takeaway" callout as an ordinary sentence, stat figures as plain lines, and
   a manufacturer list as a run of paragraphs. Nothing here changes a word --
   it gives markup that already exists the shape it was written for, using the
   same tokens as .highlight and .pro-con-box beside it. */
.prose .warning-box{background:var(--surface2);border-left:3px solid var(--brand);
  padding:1rem 1.1rem;margin:1.5rem 0;border-radius:2px}
.prose .timeline{margin:1.5rem 0;border-left:2px solid var(--rule);padding-left:1.15rem}
.prose .timeline-item{margin-bottom:1.1rem;position:relative}
.prose .timeline-item::before{content:"";position:absolute;left:-1.45rem;top:.45rem;
  width:7px;height:7px;border-radius:50%;background:var(--brand)}
.prose .timeline-year{font-family:var(--mono);font-size:.78rem;letter-spacing:.06em;
  color:var(--accent);font-weight:700;margin-bottom:.15rem}
.prose .timeline-content p:first-child{margin-top:0}
.prose .stat-box{border:1px solid var(--rule);background:var(--surface);
  padding:1.1rem 1.25rem;margin:1.75rem 0;border-radius:2px}
.prose .stat-box h3{margin-top:0}
.prose .stat-item{margin-top:.9rem}
.prose .stat-number{font-family:var(--cond);font-size:1.75rem;font-weight:700;
  line-height:1.1;color:var(--accent)}
.prose .stat-label{font-size:.85rem;color:var(--ink3);margin-top:.15rem}
.prose .manufacturer-list{display:grid;gap:.9rem;margin:1.5rem 0;
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.prose .manufacturer-card{border:1px solid var(--rule);background:var(--surface);
  padding:.95rem 1.05rem;border-radius:2px}
.prose .manufacturer-card p{font-size:.9rem;margin:.45rem 0 0}
.prose .manufacturer-card p:last-child{margin-bottom:0}

/* ---- technical reference papers ---- */
.paper{max-width:78ch;margin-inline:auto}
.paper-meta{display:flex;flex-wrap:wrap;gap:.5rem 1rem;align-items:center;
  border-top:2px solid var(--brand);border-bottom:1px solid var(--rule);
  padding:.7rem 0;margin-bottom:2rem;font-family:var(--mono);font-size:.7rem;
  letter-spacing:.12em;text-transform:uppercase}
/* ---- dealer locator ----------------------------------------------------
   Desktop: finder and cards on the left, the map on the right and sticky, so
   hovering a card lights its pin without scrolling the map away. Phone: the
   finder, then the map, then the list -- the order a thumb wants. */
.loc-grid{display:grid;gap:1.5rem 2.5rem;grid-template-columns:minmax(0,1fr);
  grid-template-areas:"finder" "map" "list"}
.finder{grid-area:finder}
.locfig{grid-area:map;margin:0;position:relative}   /* the pop-up's frame; sticky below keeps that */
.loc-list{grid-area:list}
.loc-list:focus{outline:none}          /* where "Skip the map" lands, as <main> is the skip link's */
@media (min-width:1061px){
  .loc-grid{grid-template-columns:minmax(0,5fr) minmax(0,7fr);
    grid-template-areas:"finder map" "list map";grid-template-rows:auto 1fr}
  .locfig{position:sticky;top:5.5rem;align-self:start}
}
.finder{background:var(--surface);border:1px solid var(--rule);border-radius:12px;padding:1.1rem 1.2rem}
.finder-l{display:block;font-weight:800;font-size:1.05rem;margin-bottom:.6rem}
.finder-r{display:flex;gap:.5rem}
.finder-i{flex:1;min-width:0;font:inherit;font-size:1rem;padding:.7rem .8rem;
  border:1px solid var(--rule2);border-radius:6px;background:var(--bg);color:var(--ink)}
.finder-i:focus{outline:3px solid var(--accent);outline-offset:1px}
.finder-i[aria-invalid="true"]{border-color:var(--err);box-shadow:0 0 0 1px var(--err)}
.finder-go{padding-block:.7rem}
.finder-geo{margin:.6rem 1.25rem 0 0}
.finder-m{margin:.6rem 0 0;font-size:.9rem;color:var(--ink2);min-height:1.2em}
.finder-m:empty{display:none}
/* An error reads as one -- the error colour and a mark, and the field
   flagged -- where it looked exactly like a result (the design review,
   2026-09-29). */
.finder-m.is-err{display:flex;gap:.45rem;align-items:flex-start;color:var(--err);font-weight:600}
.finder-m.is-err::before{content:"!";flex:none;display:grid;place-items:center;width:1.15rem;
  height:1.15rem;margin-top:.1rem;border-radius:50%;background:var(--err);color:var(--surface);
  font-size:.75rem;font-weight:800;line-height:1}
.locmap{display:block;width:100%;height:auto;border:1px solid var(--rule);border-radius:12px;
  overflow:hidden}
.locmap .water{fill:var(--map-water)}
.locmap .land{fill:var(--map-land)}
.locmap .land-other{fill:var(--map-other)}
.locmap .border{fill:none;stroke:var(--map-border);stroke-width:.7;stroke-linejoin:round}
.locmap .coast{fill:none;stroke:var(--map-coast);stroke-width:.9;stroke-linejoin:round}
/* A pin separates from the land by its fill, or -- on the dark map, where
   brand blue on the land is 2.74:1 -- by its light edge. */
.locmap .pin{fill:var(--brand);stroke:var(--pin-edge);stroke-width:2;
  transition:fill var(--t) var(--ease),stroke var(--t-fast) var(--ease),stroke-width var(--t-fast) var(--ease)}
.locmap .pin-hit{fill:transparent;stroke:none}   /* the pin's tap target, locator._hit_region */
.locmap .pin-hq{fill:var(--ink)}
.locmap .loc-hq .pin-n{fill:var(--bg)}
/* The stocking ring, and a receding pin's outline below, are --accent: the
   brand blue was 2.74:1 on the dark land, under the 3:1 a mark needs. */
.locmap .pin-ring{fill:none;stroke:var(--accent);stroke-width:2}
.locmap .pin-open{fill:none;stroke:var(--brand);stroke-dasharray:3 3}
.locmap .pin-dot{fill:var(--brand)}
.locmap .leader{stroke:var(--brand);stroke-width:1.3}
.locmap .pin-n{fill:#fff;font:800 12.5px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
  text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill var(--t) var(--ease)}
.locmap .pin-n-hq{font-size:9px;letter-spacing:.04em}
.locmap .loc{cursor:pointer;outline:none}
.locmap .loc.is-hover .pin,.locmap .loc.is-on .pin{stroke:var(--ink);stroke-width:3}
@media (hover:hover){.locmap .loc:hover .pin{stroke:var(--ink);stroke-width:3}}
/* Keyboard focus: a ring outside the pin, in the page's ink -- drawn with
   every pin by locator.py, shown on focus. The pin's own stroke in the
   accent was 1.26:1 against its fill. */
.locmap .pin-focus{fill:none;stroke:var(--ink);stroke-width:3;opacity:0;
  transition:opacity var(--t-fast) var(--ease)}
.locmap .loc:focus-visible .pin-focus{opacity:1}
/* After a search the pins beyond the nearest three recede by outline, not by
   fading: at 45% opacity their numbers were 2.1:1. */
.locmap .loc.is-far .pin{fill:var(--map-land);stroke:var(--accent);stroke-width:1.5}
.locmap .loc.is-far .pin-n{fill:var(--ink2)}
.locmap .you{pointer-events:none}
.locmap .you circle{transform-box:fill-box;transform-origin:center}
.locmap .you .you-dot{fill:var(--accent);stroke:var(--map-land);stroke-width:2.5;
  animation:pop var(--t-slow) var(--ease-out)}
.locmap .you .you-halo{fill:var(--accent);stroke:none;opacity:.2;animation:halo .6s var(--ease-out)}
/* The pop-up: a non-modal dialog (site.js). Beside its pin, on whichever side
   covers the fewest other pins, or, on a map too narrow for that, in the
   page's flow under the legend. It fades and rises in, and closes on its own
   button, on Escape, and on a click or tap anywhere that is not a pin. */
.loc-tip{position:absolute;z-index:3;width:min(19rem,calc(100% - 12px));background:var(--surface);
  border:1px solid var(--rule2);border-radius:10px;padding:.75rem .9rem .85rem;
  box-shadow:0 10px 30px rgba(8,12,18,.22);font-size:.88rem;line-height:1.4;
  opacity:0;transform:translateY(6px) scale(.97);transform-origin:50% 100%;
  transition:opacity var(--t-fast) var(--ease),transform var(--t) var(--ease-out)}
.loc-tip.is-in{opacity:1;transform:none}
.loc-tip.is-docked{position:static;width:auto;max-width:none;margin-top:.9rem;
  transform:translateY(-4px);transform-origin:50% 0;box-shadow:0 8px 24px rgba(8,12,18,.16)}
.loc-tip.is-docked.is-in{transform:none}
.loc-tip[hidden]{display:none}
.loc-tip-t{margin:0;padding-right:2rem;font-weight:800;font-size:.95rem;line-height:1.3}
.loc-tip-w{margin:.1rem 0 0;color:var(--ink2);font-size:.85rem}
.loc-tip-x{position:absolute;top:.4rem;right:.4rem;display:grid;place-items:center;
  width:2rem;height:2rem;padding:0;border:0;border-radius:6px;background:transparent;
  color:var(--ink2);cursor:pointer}
.loc-tip-x::before{content:"";width:1rem;height:1rem;background:currentColor;
  -webkit-mask:url(/assets/v/icon-close.0c96db60.svg) center/contain no-repeat;mask:url(/assets/v/icon-close.0c96db60.svg) center/contain no-repeat}
@media (hover:hover){.loc-tip-x:hover{background:var(--surface2);color:var(--ink)}}
.loc-tip .dealer-x{gap:0 1rem;margin-top:.4rem}
.loc-tip .dealer-cta{margin-top:.6rem}
.loc-tip .act-req{width:100%;justify-content:center}
.locfig figcaption{display:flex;flex-wrap:wrap;gap:.5rem 1.3rem;margin-top:.8rem;
  font-size:.8rem;color:var(--ink3)}
.key{display:inline-flex;align-items:center;gap:.45rem}
.k{width:12px;height:12px;display:inline-block;border-radius:50%;background:var(--brand)}
.k-node{box-shadow:0 0 0 2px var(--surface),0 0 0 3.5px var(--accent)}
.k-hq{background:var(--ink);border-radius:3px}
.loc-n{font-size:.85rem;color:var(--ink3);margin:0 0 .75rem}
.dealers{list-style:none;margin:0;padding:0;display:grid;gap:.75rem}
.dealer{display:grid;grid-template-columns:2.1rem minmax(0,1fr);gap:.9rem;
  background:var(--surface);border:1px solid var(--rule);border-radius:12px;padding:1rem 1.1rem;
  scroll-margin-top:6rem;transition:border-color var(--t) var(--ease),box-shadow var(--t) var(--ease)}
/* One selection, one ring (site.js paint()): the dealer whose pop-up is open,
   the nearest after a search, or where a pin's link landed. Hover and
   keyboard focus colour the border only. The ring was shared by all of them
   and by :target, which never cleared, so two cards could wear it at once. */
.dealer.is-hover,.dealer.is-near{border-color:var(--brand)}
.dealer.is-on{border-color:var(--brand);box-shadow:0 0 0 2px var(--brand)}
.dealer-hq{margin-top:.75rem;background:transparent}
.dealer-open{margin-top:.75rem;border-style:dashed}
.dealer-open .dealer-n{background:transparent;color:var(--brand);border:2px dashed var(--brand)}
.dealer-n{display:grid;place-items:center;width:2.1rem;height:2.1rem;border-radius:50%;
  background:var(--brand);color:#fff;font-weight:800;font-size:.9rem}
.dealer-hq .dealer-n{border-radius:6px;background:var(--ink);color:var(--bg);font-size:.66rem;letter-spacing:.04em}
.dealer-t{font-size:1.05rem;margin:0;line-height:1.3}
/* The dealer's own logo, from the owner (build/make_dealer_logos.py), on a
   tile. The tile stays light in dark mode, dimmed: most of the logos are dark
   ink, which a dark card swallows. The two drawn for a dark ground -- white
   and yellow lettering -- carry .is-dark. The image is decorative (alt=""):
   the heading beside it already says whose logo it is. */
.dealer-logo{float:right;display:grid;place-items:center;width:8.5rem;height:3.75rem;
  margin:0 0 .4rem .8rem;border:1px solid var(--rule);border-radius:8px;background:var(--logo-tile)}
.dealer-logo.is-dark{background:#0b0d10;border-color:#2b333d}
.dealer-logo img{display:block;max-width:100%;max-height:100%;object-fit:contain}
/* In the narrow list column of the two-column layout the full tile left the
   name 179px at 1080, and it broke onto two lines (the design review). */
@media (min-width:1061px) and (max-width:1280px){
  .dealer-logo{width:6.75rem;height:3.1rem;margin-left:.6rem}}
@media (forced-colors:active){.dealer-logo{forced-color-adjust:none}}
.dealer-w{display:flex;flex-wrap:wrap;align-items:center;gap:.2rem .5rem;
  margin:.15rem 0 0;color:var(--ink2);font-size:.92rem}
/* The distance after a search: information, so a neutral tag rather than
   link-blue text, and never split across lines ("about 8" / "mi"). */
.dealer-d{display:inline-block;padding:0 .45rem;border-radius:999px;
  background:var(--surface2);color:var(--ink);font-size:.8rem;font-weight:700;
  line-height:1.6;white-space:nowrap}
/* Three kinds of thing on a card, drawn as three kinds. The stock line is a
   status, drawn as a tag -- a tinted pill with the map legend's ring mark.
   It was link-blue capitals among the links (the owner, 2026-09-29), then,
   for an hour, a filled label that looked more like a button than the
   button did (the design review). The contacts are links, each with its
   icon. The request, DYCO's own route to the dealer, is the card's one
   filled button. */
.dealer-s{display:inline-flex;align-items:center;gap:.5rem;margin:.5rem 0 0;
  padding:.14rem .65rem .14rem .45rem;border:1px solid var(--tag-edge);border-radius:999px;
  background:var(--tag-bg);color:var(--tag-ink);font-size:.7rem;font-weight:800;
  letter-spacing:.06em;text-transform:uppercase;line-height:1.5}
.dealer-s::before{content:"";flex:none;width:.45rem;height:.45rem;border-radius:50%;
  background:var(--tag-edge);box-shadow:0 0 0 1.5px var(--tag-bg),0 0 0 3px var(--tag-edge)}
.dealer-a{margin:.5rem 0 0;font-size:.92rem}
.dealer-x{margin:.45rem 0 0;display:flex;flex-wrap:wrap;gap:.1rem 1.1rem}
.dealer-cta{margin:.75rem 0 0;display:flex;flex-wrap:wrap;align-items:center;gap:.5rem 1.25rem}
.dealer-note{margin:.4rem 0 0;font-size:.85rem;color:var(--ink3)}
/* A text action -- a link, or a button that acts on the page: icon, label,
   and an underline that fades in on hover. One style for all of them; the
   finder's two buttons were underlined bold text like links in a sentence. */
.act{display:inline-flex;align-items:center;gap:.4rem;min-height:1.75rem;font-weight:600;
  font-size:.9rem;color:var(--accent);text-decoration:underline;text-decoration-color:transparent;
  text-decoration-thickness:1px;text-underline-offset:3px;
  transition:color var(--t-fast) var(--ease),text-decoration-color var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease)}
@media (hover:hover){.act:hover{text-decoration-color:currentColor}}
button.act{padding:0;border:0;background:none;font:inherit;font-weight:600;font-size:.9rem;
  color:var(--accent);cursor:pointer}
.act-tel{font-variant-numeric:tabular-nums}
.act-tel::before,.act-web::before,.act-dir::before,.act-map::before,.act-mail::before,
.act-loc::before,.act-clear::before,.act-req::after{content:"";flex:none;width:1rem;height:1rem;
  background:currentColor;-webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.act-tel::before{-webkit-mask-image:url(/assets/v/icon-phone.46b63383.svg);mask-image:url(/assets/v/icon-phone.46b63383.svg)}
.act-web::before{-webkit-mask-image:url(/assets/v/icon-globe.54e16a0e.svg);mask-image:url(/assets/v/icon-globe.54e16a0e.svg)}
.act-dir::before{-webkit-mask-image:url(/assets/v/icon-directions.a13215b1.svg);mask-image:url(/assets/v/icon-directions.a13215b1.svg)}
.act-map::before{-webkit-mask-image:url(/assets/v/icon-pin.f4d8672f.svg);mask-image:url(/assets/v/icon-pin.f4d8672f.svg)}
.act-mail::before{-webkit-mask-image:url(/assets/v/icon-mail.2bcd51be.svg);mask-image:url(/assets/v/icon-mail.2bcd51be.svg)}
.act-loc::before{-webkit-mask-image:url(/assets/v/icon-locate.3e0d1f10.svg);mask-image:url(/assets/v/icon-locate.3e0d1f10.svg)}
.act-clear::before{-webkit-mask-image:url(/assets/v/icon-close.0c96db60.svg);mask-image:url(/assets/v/icon-close.0c96db60.svg)}
.btn-s{padding:.55rem .95rem;font-size:.88rem}
.act-req::after{-webkit-mask-image:url(/assets/v/icon-arrow.b2ed57de.svg);mask-image:url(/assets/v/icon-arrow.b2ed57de.svg);
  transition:transform var(--t) var(--ease)}
@media (hover:hover){.act-req:hover::after{transform:translateX(3px)}}
@media (forced-colors:active){
  .act-tel::before,.act-web::before,.act-dir::before,.act-map::before,.act-mail::before,
  .act-loc::before,.act-clear::before,.act-req::after,.loc-tip-x::before{forced-color-adjust:none}
  /* The legend's keys are CSS boxes, which forced colours erase: keep them,
     and the map they explain, as drawn. */
  .k,.locmap{forced-color-adjust:none}
}
.loc-more{margin-top:1.25rem;font-size:.92rem;color:var(--ink2);max-width:60ch}
.cat-art{display:block;margin-bottom:1.1rem;color:var(--ink2)}
.cat-art .part-art{display:block;width:100%;height:150px}
@media (hover:hover){.cat-shot:hover .cat-art{color:var(--accent)}}
.cat-shot h3{margin-top:.35rem}
.cat-hero{margin:0;color:var(--ink2)}
/* The category photographs are cut-outs (build/make_cutouts.py): the part
   alone, on transparency, trimmed to its own outline. The stage supplies the
   ground and the shadow, so one treatment serves every photograph and a sixth
   can be added without touching this rule.

   Chosen by a three-judge panel over two alternatives -- a floor-and-horizon
   "stage" and a graph-paper "drawing sheet" -- and both lost for reasons worth
   keeping. The floor line crossed behind every part at an arbitrary height
   and its ellipse shadow sat under the frame rather than the part, so each
   part hovered above its own shadow. The graph paper implied a measurement
   scale the photograph does not have, on a site whose drawings are actually
   computed. Do not graft either back.

   The ground stays LIGHT in dark mode, dimmed, and that is deliberate. The
   parts are black and dark-grey paint, median luminance ~50/255; on the dark
   card they vanish, and the only thing that separated them there was a white
   rim-light that showed exactly where the mask ran. check_copy.py holds the
   ground between two limits: the dark paint must still separate from it, and
   it must not outshine the page's own headline ink. --stage-* is tokenised in
   all four palette blocks so a data-theme override follows it.

   It is still never `cover`. max-width and max-height inside a fixed 4:3 box
   show the whole part whatever its orientation -- the swing drive is 1:1.9,
   the pump 2:1 -- which is what the old object-fit:contain rule was for. It
   used to be `cover`, and /swing-drives/ showed the middle 58% of the part.
   Centred, not bottom-aligned: there is no floor to stand on, and a wide part
   parked at the bottom left its lit centre empty. */
.cat-stage{display:flex;align-items:center;justify-content:center;
  aspect-ratio:4/3;padding:6% 8%;box-sizing:border-box;overflow:hidden;
  border:1px solid var(--rule);border-radius:12px;
  background:radial-gradient(ellipse 80% 75% at 50% 45%,var(--stage-hi) 0%,var(--stage-lo) 100%)}
.cat-photo{display:block;max-width:100%;max-height:100%;width:auto;height:auto;
  filter:drop-shadow(0 1px 1.5px var(--stage-sh1)) drop-shadow(0 10px 12px var(--stage-sh2))}
/* A transparent image of a black part on a system Canvas disappears in a
   dark forced-colours theme, so the stage keeps its own ground there. */
@media (forced-colors:active){.cat-stage{forced-color-adjust:none;
  background:#e9edf1;border-color:CanvasText}}
/* Same frame as the photograph, so moving between category pages does not
   change the hero's height -- it was 330px fixed against the photo's 4:3. */
.cat-hero .part-art{display:block;width:100%;height:auto;aspect-ratio:4/3;border-radius:12px;
  border:1px solid var(--rule);background:var(--surface);padding:8% 10%;
  box-sizing:border-box}
/* Measuring a swing bearing (the owner's sheet, redrawn): the drawing on a
   figure panel, the nine numbers listed beside it in the same blue discs. */
.split.dims{grid-template-columns:minmax(0,1.45fr) minmax(0,.55fr);align-items:start}
@media (max-width:880px){.split.dims{grid-template-columns:1fr}}
.dims-fig{margin:1.5rem 0 0}
.sb-sec{stroke:currentColor;stroke-width:1.5;stroke-linejoin:round}
.sb-hatch{stroke:currentColor;stroke-width:1;opacity:.45}
.sb-far{fill:none;stroke:currentColor;stroke-width:1;opacity:.5}
.sb-dim{fill:none;stroke:currentColor;stroke-width:1}
.sb-tip{fill:currentColor}
.sb-co circle{fill:var(--brand)}
.sb-co text{fill:#fff;font:700 15px var(--sans);text-anchor:middle}
.dims-list h3{font-family:var(--cond);font-weight:800;text-transform:uppercase;
  font-size:1.05rem;letter-spacing:.02em;margin:1.5rem 0 .6rem}
.dims-list ul{display:grid;gap:.45rem}
.dims-list li{display:flex;align-items:center;gap:.7rem;line-height:1.35}
.dn{flex:none;display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;
  border-radius:50%;background:var(--brand);color:#fff;font-weight:700;font-size:.9rem}
.cat-hero figcaption{margin-top:.7rem;font-size:.85rem;color:var(--ink3);line-height:1.5}
.paper-type{color:var(--accent);font-weight:700}
.byline{font-family:var(--cond);font-weight:700;letter-spacing:.02em;
  color:var(--accent);margin:.15rem 0 .9rem}

/* ---- publication apparatus --------------------------------------------
   Citations, numbered equations and front matter. Generated by
   papers/apparatus.py and docinfo(); see those for why. */
.docinfo{display:grid;grid-template-columns:1fr;gap:0;margin:1.5rem 0 2.25rem;
  border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
.docinfo>div{display:grid;grid-template-columns:8.5rem 1fr;gap:1rem;
  padding:.55rem 0;border-bottom:1px solid var(--rule)}
.docinfo>div:last-child{border-bottom:0}
.docinfo dt{font-family:var(--mono);font-size:.68rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink3);padding-top:.15rem}
.docinfo dd{margin:0;font-size:.88rem;color:var(--ink2)}
a.cite{color:inherit;text-decoration:none;border-bottom:1px dotted var(--accent)}
@media (hover:hover){a.cite:hover{color:var(--accent)}}
a.cite sup{font-family:var(--mono);font-size:.62em;color:var(--accent);
  margin-left:.12em;font-weight:600}
.eq{display:grid;grid-template-columns:1fr auto;align-items:center;gap:1rem;
  margin:1.35rem 0;padding:.85rem 1rem;background:var(--surface);
  border-left:2px solid var(--brand);border-radius:0 4px 4px 0}
.eq-b{font-size:1.02rem;color:var(--ink);text-align:center}
.eq-n{font-family:var(--mono);font-size:.8rem;color:var(--ink3)}
.paper-refs li{scroll-margin-top:5rem}
.paper-refs li:target{background:var(--surface2);outline:2px solid var(--brand);
  outline-offset:3px;border-radius:3px}
.cite-as{max-width:78ch;margin:2.5rem auto 0;padding:1rem 1.15rem;
  border:1px solid var(--rule);border-radius:6px;font-size:.85rem;
  color:var(--ink2);line-height:1.6;overflow-wrap:anywhere}   /* its address is one long word */
.cite-as strong{font-family:var(--mono);font-size:.68rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink3)}

/* ---- print ------------------------------------------------------------
   A technical publication has to survive being printed or saved as a PDF,
   which is how anyone actually files one. Chrome and screen furniture go;
   the document, its figures and its references stay, and every hyperlink in
   the reference list prints its URL so a paper copy is still followable. */
/* ---- /request/ ---------------------------------------------------------
   Inputs are bordered in --ink3, not --rule2: a form control's boundary is
   held to WCAG 1.4.11's 3:1, and --rule2 is under 2:1 on every surface.
   Errors use --err, which check_copy.py holds to 4.5:1 like the other text
   tokens. Nothing here is hidden by CSS waiting for a script: the steps a
   script collapses are collapsed with the hidden attribute, by the script. */
.req{display:grid;gap:1.4rem;max-width:980px}
.req-step{border:1px solid var(--rule);border-radius:12px;background:var(--surface);
  padding:1.1rem 1.25rem 1.3rem;margin:0;min-width:0}
.req-step legend{display:flex;align-items:center;gap:.6rem;padding:0 .35rem;
  font-weight:800;font-size:1.12rem}
.req-n{display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;border-radius:50%;
  background:var(--brand);color:#fff;font-size:.9rem;font-weight:800}
.req-help,.req-privacy,.req-nojs{color:var(--ink2);font-size:.92rem;max-width:68ch;margin:.4rem 0 .9rem}
.req-line{display:grid;gap:.65rem;align-items:end;padding:.8rem 0;border-top:1px solid var(--rule);
  grid-template-columns:minmax(0,1.3fr) repeat(2,minmax(0,1fr)) minmax(0,1.25fr) 5.5rem auto}
.req-line:first-child{border-top:0;padding-top:.2rem}
.f-part{grid-row:2;grid-column:1}
.f{display:flex;flex-direction:column;gap:.3rem;min-width:0}
.f label,.f-l{font-size:.85rem;font-weight:700;color:var(--ink2)}
.req-must{font-weight:600;color:var(--ink3);font-size:.78rem;margin-left:.3rem}
.req input:not([type=radio]),.req select,.req textarea{width:100%;min-width:0;font:inherit;
  font-size:1rem;padding:.62rem .7rem;border:1px solid var(--ink3);border-radius:8px;
  background:var(--bg);color:var(--ink)}
.req input:not([type=radio]),.req select{min-height:2.9rem}
.req textarea{resize:vertical;min-height:6.5rem}
.req input:focus,.req select:focus,.req textarea:focus{outline:3px solid var(--accent);outline-offset:1px}
.req [aria-invalid="true"]{border-color:var(--err);box-shadow:0 0 0 1px var(--err)}
.req-x{background:none;border:0;padding:.55rem .2rem;font:inherit;font-size:.88rem;
  font-weight:700;color:var(--accent);cursor:pointer;text-align:left}
.req-acts{display:flex;flex-wrap:wrap;align-items:center;gap:.65rem;margin-top:.7rem}
.req-add{background:none;border:1px dashed var(--ink3);border-radius:8px;min-height:2.9rem;
  padding:.55rem .95rem;font:inherit;font-weight:700;color:var(--accent);cursor:pointer}
.req-ds{display:grid;grid-template-columns:repeat(auto-fill,minmax(15.5rem,1fr));gap:.6rem;margin-top:.4rem}
.req-d{display:flex;gap:.65rem;align-items:flex-start;padding:.7rem .8rem;border:1px solid var(--ink3);
  border-radius:10px;background:var(--bg);cursor:pointer}
.req-d:has(input:checked){border-color:var(--brand);box-shadow:inset 0 0 0 2px var(--brand);
  background:var(--surface)}
.req-d input{margin:.25rem 0 0;accent-color:var(--brand);flex:none}
.req-d-b{display:flex;flex-direction:column;min-width:0}
.req-d-w,.req-d-k{color:var(--ink2);font-size:.88rem}
.req-d.is-near .req-d-k{color:var(--accent);font-weight:700}
.req-near{max-width:30rem;margin-bottom:1rem}
.req-you{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.8rem 1rem}
.f-wide{grid-column:1/-1}
.hp{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden}
.req-go{margin:1rem 0 0}
.req-m{margin:.8rem 0 0;color:var(--err);font-weight:700}
.req-m:empty{display:none}
.req-done{max-width:64ch;border:1px solid var(--brand);border-radius:12px;background:var(--surface);
  padding:1.3rem 1.4rem}
.req-done h2{font-size:1.45rem;margin-bottom:.6rem}
/* Both take focus from the script; without this the sticky header covers
   the heading the focus was meant to show. */
.req-done,.req-step{scroll-margin-top:5.5rem}
.req-why li{padding:.55rem .8rem;border-left:3px solid var(--rule);margin-bottom:.4rem;color:var(--ink2)}
.req-why li:target{border-left-color:var(--err);background:var(--surface);color:var(--ink);font-weight:700}
.head-acts{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1.4rem}
.req-privacy a{color:var(--accent);font-weight:700}
.privacy-who{margin-top:2rem;max-width:72ch;color:var(--ink2);font-size:.92rem}
.band-acts{display:flex;flex-wrap:wrap;gap:.6rem;margin:0}
@media (max-width:760px){
  .req-line{grid-template-columns:repeat(2,minmax(0,1fr))}
  .f-type,.f-pn,.f-part{grid-column:1/-1}
  .f-part{grid-row:auto}
  .req-you{grid-template-columns:1fr}
}

@media print{
  :root{--bg:#fff;--surface:#fff;--surface2:#fff;--ink:#000;--ink2:#1a1a1a;
    --ink3:#444;--rule:#bbb;--accent:#000;--brand:#000}
  body{background:#fff;color:#000;font-size:10.5pt}
  .site-header,.site-nav,.crumbs,.band,.site-footer,.nav-toggle,.flyby,.glow,
  .alt .docs,.alt .btn,section.alt{display:none !important}
  .page-head{background:#fff !important;color:#000;padding:0 0 1rem}
  /* The heads' dark ring round every letter would print as a smudge. */
  .hero-in,.page-head>.wrap{text-shadow:none}
  .page-head h1{font-size:20pt;color:#000}
  /* The byline, the apparatus line and the DYCO-TP chip inherit their
     dark-hero colours and print invisible on white otherwise. */
  .page-head p,.page-head .kicker,.page-head .lib-tag,.byline{color:#000}
  .page-head .lib-tag{border-color:#000}
  .wrap{max-width:none;padding:0}
  .paper{max-width:none}
  a{color:#000;text-decoration:none}
  .paper-refs a[href^="http"]::after{content:" <" attr(href) ">";
    font-family:monospace;font-size:8pt;word-break:break-all}
  a.cite{border:0}
  .paper-fig,.paper-table,.eq{break-inside:avoid;page-break-inside:avoid}
  .paper h2,.paper h3{break-after:avoid;page-break-after:avoid}
  .paper-fig svg{max-height:9cm}
  /* An enlarged figure prints at the page's width like the rest, without
     the button that enlarged it; a table prints without its scroll shadows. */
  .fig-bar{display:none !important}
  .paper-fig svg{width:100% !important;max-width:100% !important}
  .paper-table,.prose table{background:none}
  .docinfo{break-inside:avoid}
  @page{margin:18mm 16mm}
}
/* Charts are computed from the published relations named in their captions,
   not from measurements. currentColor keeps them readable in both themes. */
.chart-plot{stroke:currentColor;fill:none}
.chart-grid{stroke:currentColor;opacity:.18;stroke-width:1}
.chart-axis{stroke:currentColor;opacity:.55;stroke-width:1.4}
.chart-lbl{fill:currentColor;font-family:var(--mono);font-size:12px}
.chart-key{fill:currentColor;font-family:var(--mono);font-size:12.5px}
.paper-id{color:var(--ink2)}
.paper-rev{color:var(--ink3);margin-left:auto}
.paper h2{font-family:var(--cond);font-weight:800;text-transform:uppercase;
  font-size:clamp(1.2rem,2.6vw,1.6rem);margin:2.5rem 0 .8rem;letter-spacing:-.01em;
  padding-bottom:.4rem;border-bottom:1px solid var(--rule)}
.paper h3{font-size:1.05rem;margin:1.75rem 0 .55rem;color:var(--ink)}
.paper h4{font-size:.96rem;margin:1.2rem 0 .4rem;color:var(--ink2)}
.paper p,.paper li{color:var(--ink2);font-size:1rem;line-height:1.72}
.paper ul,.paper ol{margin:0 0 1.15rem 1.3rem;padding:0;list-style:disc}
.paper ol{list-style:decimal}
.paper li{margin-bottom:.5rem}
.paper strong{color:var(--ink)}
.paper sub,.paper sup{font-size:.72em}
.paper-abstract{background:var(--surface2);border-left:3px solid var(--brand);
  padding:1.25rem 1.4rem;margin-bottom:1.5rem}
.paper-abstract h2{margin-top:0;border:0;padding:0;font-size:.78rem;letter-spacing:.16em;
  font-family:var(--mono);color:var(--accent)}
.paper-abstract p{margin:0;font-size:.97rem}
.paper-table{width:100%;border-collapse:collapse;margin:1.75rem 0;font-size:.9rem;
  display:block;overflow-x:auto}
.paper-table caption{caption-side:top;text-align:left;font-family:var(--mono);
  font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);
  padding-bottom:.55rem}
.paper-table th,.paper-table td{border:1px solid var(--rule);padding:.5rem .65rem;
  text-align:left;vertical-align:top}
.paper-table thead th{background:var(--surface2);font-weight:700;color:var(--ink)}
.paper-fig{margin:2rem 0;padding:1.15rem;background:var(--surface2);border:1px solid var(--rule)}
.paper-fig svg{width:100%;height:auto;display:block;color:var(--ink2)}
.paper-fig figcaption{margin-top:.85rem;font-size:.82rem;color:var(--ink3);
  padding-top:.6rem;border-top:1px solid var(--rule)}
/* On a phone a figure drawn 640-720 units wide is shown at half that, and
   its labels come out at five pixels. site.js wraps the drawing in .fig-pan
   and adds Enlarge wherever it is drawn below 80% of its size: the drawing
   goes to full size and the reader pans across it. Without scripting the
   page still pinch-zooms, as it always did. */
.fig-pan{overflow-x:auto;overscroll-behavior-x:contain}
.fig-pan:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.paper-fig.is-zoomed svg{max-width:none}
.fig-bar{display:flex;justify-content:flex-end;margin-top:.6rem}
.fig-zoom{display:inline-flex;align-items:center;gap:.45rem;min-height:40px;padding:.4rem .85rem;
  font:inherit;font-size:.82rem;font-weight:700;color:var(--accent);cursor:pointer;
  background:var(--surface);border:1px solid var(--rule2);border-radius:999px}
.fig-zoom::before{content:"";flex:none;width:1.05em;height:1.05em;background:currentColor;
  -webkit-mask:url(/assets/v/icon-zoom-in.158d586f.svg) center/contain no-repeat;mask:url(/assets/v/icon-zoom-in.158d586f.svg) center/contain no-repeat}
.is-zoomed .fig-zoom::before{-webkit-mask-image:url(/assets/v/icon-zoom-out.d94fca49.svg);mask-image:url(/assets/v/icon-zoom-out.d94fca49.svg)}
@media (hover:hover){.fig-zoom:hover{border-color:var(--accent)}}
@media (forced-colors:active){.fig-zoom::before{background:ButtonText}}
/* A table wider than a phone scrolls sideways (site.js makes it focusable
   when it does). A shadow on the side that has more says so: two
   background layers scroll with the content and cover the two that stay
   put at the edges, so a shadow shows only where there is more to see. */
.paper-table,.prose table{background:
  linear-gradient(to right,var(--bg) 30%,transparent) left/2.5rem 100% no-repeat local,
  linear-gradient(to left,var(--bg) 30%,transparent) right/2.5rem 100% no-repeat local,
  radial-gradient(farthest-side at 0 50%,var(--scroll-sh),transparent) left/.9rem 100% no-repeat scroll,
  radial-gradient(farthest-side at 100% 50%,var(--scroll-sh),transparent) right/.9rem 100% no-repeat scroll}
/* ...and its caption wraps to the width that shows, and stays put while the
   rows scroll under it: it was as wide as the table, so on a phone "Table 2 --
   Filtration ratio and the correspond" ran off the screen mid-word. */
.paper-table caption,.prose table caption{max-width:calc(100vw - 2*var(--pad));position:sticky;left:0}
.paper-refs{margin-top:3rem;border-top:2px solid var(--brand);padding-top:1rem}
.paper-refs ol{list-style:decimal;margin-left:1.4rem}
.paper-refs li{font-size:.86rem;color:var(--ink3);margin-bottom:.5rem}
.paper-refs cite{font-style:italic;color:var(--ink2)}
/* Papers are presented as a document index, not as cards. The articles use
   .arts/.art — a card grid — and when the papers used the same treatment the
   two libraries read as one. A numbered list of records with a document ID in
   a fixed left column is what a technical reference series looks like, and it
   separates the two on sight rather than by reading the heading above. */
.docs{border-top:2px solid var(--ink);margin-top:1.75rem}
.doc{display:grid;grid-template-columns:8.5rem 1fr auto;gap:0 1.25rem;align-items:baseline;
  padding:1.15rem .5rem 1.15rem .25rem;border-bottom:1px solid var(--rule);
  text-decoration:none;position:relative;transition:background .15s,padding .15s}
.doc::before{content:"";position:absolute;left:0;top:-1px;bottom:-1px;width:2px;
  background:var(--brand);transform:scaleY(0);transition:transform .15s}
@media (hover:hover){.doc:hover{background:var(--surface2);padding-left:.9rem}
  .doc:hover::before{transform:scaleY(1)}}
.doc-id{font-family:var(--mono);font-size:.74rem;letter-spacing:.1em;color:var(--accent);
  font-weight:600;white-space:nowrap}
.doc-t{font-size:1.02rem;font-weight:700;color:var(--ink);line-height:1.35}
.doc-m{display:block;font-family:var(--mono);font-size:.7rem;color:var(--ink3);
  margin-top:.3rem;letter-spacing:.02em}
.doc-go{font-family:var(--mono);font-size:.72rem;color:var(--ink3);white-space:nowrap}
@media (hover:hover){.doc:hover .doc-go{color:var(--accent)}}
@media (max-width:640px){
  .doc{grid-template-columns:1fr;gap:.35rem}
  .doc-go{display:none}
}
/* Tells the reader which library they are in before they read a word. */
.lib-tag{display:inline-flex;align-items:center;gap:.5rem;font-family:var(--mono);
  font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;font-weight:700;
  padding:.4rem .7rem;border:1px solid var(--rule);border-radius:999px;color:var(--ink2)}
.lib-tag::before{content:"";width:.5rem;height:.5rem;border-radius:50%;background:var(--brand)}
.lib-tag.is-doc::before{border-radius:1px}
.page-head .lib-tag{border-color:rgba(255,255,255,.22);color:#e8eaee}

/* ---- mobile navigation -------------------------------------------------
   The wrapped nav was 205px tall on a 390px screen — a quarter of the
   viewport spent on chrome. Below 900px it collapses behind a button. */
.nav-toggle{display:none;background:none;border:1px solid #2b333d;border-radius:3px;
  width:44px;height:44px;padding:0;cursor:pointer;align-items:center;justify-content:center;
  order:3}
.nav-bars,.nav-bars::before,.nav-bars::after{display:block;width:20px;height:2px;
  background:#e4eaf0;transition:transform var(--t) var(--ease),opacity var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease)}
.nav-bars::before,.nav-bars::after{content:"";position:absolute}
.nav-bars::before{transform:translateY(-6px)}
.nav-bars::after{transform:translateY(6px)}
.nav-open .nav-bars{background:transparent}
.nav-open .nav-bars::before{transform:rotate(45deg)}
.nav-open .nav-bars::after{transform:rotate(-45deg)}
.nav-scrim{display:none}

/* 1060px. With nine destinations -- /become-a-dealer/ was added 2026-09-23 and
   replaced the same day by /request/ ("Request a part", one character shorter) --
   and the phone button (removed 2026-09-29, when the site stopped publishing
   a phone number), the one-line desktop header broke at every width with
   the old link spacing: the wrap is capped at 1200px, so above that the brand
   sub-label (removed 2026-09-29) always showed and the ninth link pushed the
   phone button onto a second line, nearly doubling the header. Link spacing was tightened (.34rem
   padding, .035em tracking, .72rem type) until a sweep at 2px steps from 1001
   to 1440 showed it breaking only at 1001-1047; the hamburger takes over at
   1060, before the wrap rather than after it. It was 1000px for eight links,
   and before that 900, which was wrong the same way. Re-sweep when the nav
   changes. */
@media (max-width:1060px){
  .nav-toggle{display:inline-flex}
  .theme-toggle{width:44px;height:44px;order:3}
  /* The collapsed menu is a second flex line of no height, which still took
     a row gap: ten pixels more header on every screen of every phone. */
  .hdr{row-gap:0}
  /* The panel's box is clipped at the nav, and the list keeps its own
     height inside it: stretched to the animating track, the list and the
     page below moved on different curves, with an empty band of up to
     116px between the header's links and the page. */
  .site-nav{order:4;flex-basis:100%;
    display:grid;grid-template-rows:0fr;overflow:hidden;
    transition:grid-template-rows var(--t) var(--ease),box-shadow var(--t) var(--ease)}
  /* With the button there (site.js puts it in), the panel lies over the
     page instead of pushing it down. In the header's flow, every line of
     the page slid 464px down and back each time the menu opened and closed
     -- the owner's "glitches some text around" (2026-09-29) -- and Chrome's
     scroll anchoring, which Safari does not have, could leave the page
     hundreds of pixels from where it was. Without scripting there is no
     button and no overlay: the <noscript> block shows the menu in the
     header's flow, and the header scrolls away with it. */
  .nav-toggle+.site-nav{position:absolute;top:calc(100% + 1px);left:0;right:0;z-index:1;
    padding-inline:var(--pad);background:#07080a}
  .nav-open .nav-toggle+.site-nav{box-shadow:0 22px 32px -18px rgba(0,0,0,.7)}
  /* The page behind the open menu dims, and a tap on it closes the menu
     without following the link that lay under the finger. site.js puts it
     straight after the header. */
  .nav-scrim{display:block;position:fixed;inset:0;z-index:39;background:rgba(4,6,9,.55);
    opacity:0;visibility:hidden;touch-action:none;
    transition:opacity var(--t) var(--ease),visibility 0s linear var(--t)}
  .nav-open+.nav-scrim{opacity:1;visibility:visible;transition-delay:0s}
  /* Collapsed, the links were clipped to nothing and still in the tab order:
     a keyboard at phone or tablet width tabbed through nine links it could
     not see. visibility takes them out of it, and its delay keeps them drawn
     while the panel closes over them. The <noscript> block reopens both.
     One column that never wraps: the list keeps the desktop row's
     flex-wrap otherwise, and a wrapping column takes new columns whenever
     it is shorter than its links -- which, while the panel's height
     animates, is every frame: nine columns, then three, then two, then
     one, on the way open, and the same in reverse.
     The panel is never taller than the screen under the header, open or
     closing: on a phone held sideways its last links sat past the bottom
     of a header that does not scroll, and a cap applied only while open
     let a scrolled list jump taller and back to its top as it closed. The
     links fade as the panel moves; they used to rise one by one, and a
     second tap mid-cascade made the rest pop in. */
  .site-nav>ul{overflow:hidden;min-height:0;align-self:start;
    flex-direction:column;flex-wrap:nowrap;gap:0;
    max-height:calc(100vh - 4.5rem);max-height:calc(100dvh - 4.5rem);
    visibility:hidden;opacity:0;
    transition:visibility 0s linear var(--t),opacity var(--t-fast) var(--ease)}
  .nav-open .site-nav{grid-template-rows:1fr}
  .nav-open .site-nav>ul{visibility:visible;opacity:1;transition-delay:0s;
    overflow-y:auto;overscroll-behavior:contain}
  /* The logo's row is as tall as the button site.js adds to it, so the
     header does not grow 22px -- and every line of the page drop -- when
     the button arrives after the first paint. Only where scripting is on:
     without it the button never comes. */
  @media (scripting:enabled){.brand{min-height:calc(44px + .5rem)}}
  .site-nav a{padding:.85rem .25rem;font-size:.9rem;border-bottom:1px solid #171d24}
  /* Inside the link, where the list's clipping cannot cut it: on the first
     link only the bottom edge was left, which read as the current-page
     underline. */
  .site-nav a:focus-visible{outline-offset:-3px}
  /* A phone held sideways gets two columns, from a grid: its tracks do not
     depend on the panel's height, so nothing moves while it opens. One
     column needs about 536px of screen; two need about 480px of width. Its
     rows are a little shorter, still 44px targets, so all nine links fit a
     568x320 screen without scrolling. */
  @media (max-height:540px) and (min-width:480px){
    .site-nav>ul{display:grid;grid-template-columns:1fr 1fr;column-gap:1.25rem;align-content:start}
    .site-nav a{padding-block:.7rem}
  }
  .site-nav a[aria-current="page"]{border-bottom-color:var(--brand)}
  @media (hover:hover){.site-nav a:hover{border-bottom-color:var(--brand)}}
  /* 44px minimum touch target everywhere it matters. A model is not a link,
     so its row is not a target: 44px each put Komatsu's 427 models in one
     column 19,000px long on a phone -- 28 screens of scrolling. */
  .makes a,.faq summary{min-height:44px;display:flex;align-items:center}
  .models{columns:4 120px}
}

/* ---- reveal on scroll ---- */
.has-reveal .reveal{opacity:0;transform:translateY(14px);
  transition:opacity 520ms var(--ease),transform 520ms var(--ease)}
.has-reveal .reveal.is-in{opacity:1;transform:none}

/* ---- live filter ---- */
.filter{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem 1rem;margin-top:2rem}
.filter-l{font-family:var(--mono);font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink3)}
.filter-i{flex:1 1 220px;min-height:44px;background:var(--surface);color:var(--ink);
  border:1px solid var(--rule);border-radius:3px;padding:.55rem .8rem;font:inherit;
  font-size:1rem;transition:border-color var(--t) var(--ease),box-shadow var(--t) var(--ease)}
.filter-i:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(0,99,168,.25)}
.filter-c{font-family:var(--mono);font-size:.74rem;color:var(--ink3);white-space:nowrap}
.filter-empty{margin-top:1.25rem;padding:1rem 1.15rem;background:var(--surface2);
  border-left:3px solid var(--brand);color:var(--ink2);font-size:.93rem}


/* ---- disclosure sheet + pre-call checklist ---- */

.steps{counter-reset:step;margin-top:2rem;display:grid;gap:1px;
  background:var(--rule);border:1px solid var(--rule)}
.step{background:var(--bg);padding:1.3rem 1.4rem 1.3rem 3.6rem;position:relative}
.step::before{counter-increment:step;content:counter(step);position:absolute;
  left:1.2rem;top:1.25rem;width:1.6rem;height:1.6rem;border-radius:50%;
  background:var(--brand);color:#fff;font-family:var(--mono);font-size:.78rem;
  font-weight:700;display:flex;align-items:center;justify-content:center}
.step h3{font-size:1rem;margin-bottom:.4rem}
.guides{margin-top:2rem;padding-top:1.25rem;border-top:1px solid var(--rule)}
.guides ul{list-style:none;margin:.75rem 0 0;padding:0;display:grid;gap:.55rem}
.guides a{color:var(--accent);font-weight:600}
.step p{margin:0;color:var(--ink2);font-size:.93rem}
.step p+p,.pillar p+p,.fail p+p,.ans p+p{margin-top:.65rem}

.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:1rem;margin-top:2rem}
.route{border:1px solid var(--rule);background:var(--surface);padding:1.25rem;
  text-decoration:none;display:block}
@media (hover:hover){.route:hover{border-color:var(--brand);transform:translateY(-3px);
  box-shadow:0 10px 26px rgba(0,0,0,.16)}}
.route-k{font-family:var(--mono);font-size:.66rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--accent);display:block;margin-bottom:.5rem}
.route b{display:block;font-size:1.05rem;margin-bottom:.3rem;color:var(--ink)}
.route span.r-d{color:var(--ink3);font-size:.87rem}

.site-footer{background:#07080a;color:#c0cad4;border-top:1px solid #1b2028;padding-block:2.5rem 1.75rem}
.site-footer a{color:#e4eaf0}
.fgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:1.75rem}
.site-footer .fh{font-family:var(--mono);font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;
  color:#8792a0;margin-bottom:.8rem}
/* Each footer link's box is the full row, 25.6px -- over WCAG 2.5.8's 24px
   minimum -- by padding it takes back in margin, so no text moves. They were
   16px, which a thumb on a phone finds between rows rather than on one. */
.site-footer li{padding:.3rem 0;font-size:.9rem}
.site-footer li a{display:inline-block;padding:.3rem 0;margin:-.3rem 0}
.legal{border-top:1px solid #171c23;margin-top:2rem;padding-top:1.25rem;font-size:.76rem;color:#78828e}

/* ---- phones ------------------------------------------------------------
   The owner, 2026-09-29: "make the entire thing mobile friendly". Measured
   at true phone widths (320-414 px) before a line of this was written: no
   page scrolled sideways, and what a phone got wrong was length and size --
   single columns of short things, a quarter of the footer per link, labels
   under 11px. Last in the stylesheet on purpose: each rule adjusts a
   component defined above, and source order is what lets it win. */
@media (max-width:520px){
  .hdr{column-gap:.6rem;padding-block:.55rem}
  .dyco-mark{height:1.4rem}
  .stats li{padding:.9rem var(--pad)}
  /* Two stats to a row and the third across the whole of its row: auto-fit
     gave it half a row and an empty grey cell beside it. */
  .stats ul{grid-template-columns:1fr 1fr}
  .stats li:last-child:nth-child(odd){grid-column:1/-1}
  .pillars,.fails{grid-template-columns:1fr}
  /* A model number is six characters at the median, so three columns hold
     them: Komatsu's list went from 28 screens to about six. */
  .models{columns:3 88px;column-gap:1rem}
  /* Two makes to a row, each still a 44px target. */
  .makes{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
  .makes a{padding:.7rem .8rem}
  /* The footer's two link lists side by side, the mark and the contact
     details across the width. It was one column of seventeen links. */
  .fgrid{grid-template-columns:1fr 1fr;gap:1.75rem 1rem}
  .fgrid>:first-child,.fgrid>:last-child{grid-column:1/-1}
  /* Six stacked categories: less air in each, so the grid is not the page. */
  .cat{padding:1.15rem}
  .cat .ico{margin-bottom:.6rem}
  /* The hero's list of parts, two lines at 390px. */
  .hero .hero-cats{font-size:.72rem;letter-spacing:.12em}
  .hero .hero-cats::after{display:none}
  /* The smallest labels, up from 10.4-10.9px. */
  .kicker,.site-footer .fh,.docinfo dt,.filter-l,.art-cat,.lib-tag,.route-k{font-size:.72rem}
  /* A paper's front matter reads label over value; beside a 136px label
     column the value had half the screen. */
  .docinfo>div{grid-template-columns:1fr;gap:.1rem}
  .paper-fig{padding:.75rem}
  /* A dealer card gives its number badge's column back to the text: the
     badge moves to the top corner, where a map pin's number is read. */
  .dealer{grid-template-columns:minmax(0,1fr);position:relative}
  .dealer-n{position:absolute;top:1rem;right:1.1rem}
  .dealer-logo{float:none;margin:0 0 .6rem}
  .dealer-b>.dealer-t:first-child{padding-right:2.75rem}
}
/* The narrowest phones (320px): a page title's longest word -- TRANSFORMATION
   -- was wider than the screen at the title's smallest size, and the
   homepage's MANUFACTURER. was 312px in a 288px column in the widest
   fallback face, DejaVu Sans Bold, so it broke after the E. */
@media (max-width:360px){.page-head h1{font-size:1.72rem}h1.big{font-size:1.9rem}}
