/* ===========================================================================
   ROMOTANA — site stylesheet
   ===========================================================================

   Direction: SPREAD. The site reads as a publication rather than a brochure.
   The association files comments on federal rulemaking and reads registers its
   members cannot; that is a body of record, and bodies of record read like
   publications. Full rationale in docs/design-spread.md.

   The three rules that keep it coherent:

     1. Serif talks, sans signposts. Source Serif 4 carries every word meant to
        be read; Archivo carries navigation, labels, tables, forms and figures.
        A serif label reads as decoration. The one exception is the page
        headline, which went to Archivo 700 on 4 September 2026 — see the note
        above `.hero h1`.
     2. Hairlines, not shadows. Structure comes from rules and space. Nothing
        floats, almost nothing is rounded.
     3. One accent per page. Red marks the action, blue marks reference, and
        the tricolour appears once, as a rule, never as decoration.

   Class names are unchanged from the previous stylesheet so every existing
   template keeps working; what changed is what they look like.
   =========================================================================== */

/* ------------------------------------------------------------------ fonts -- */
/* Self-hosted: the Content-Security-Policy allows fonts from this origin only.
   latin-ext is required, not an optimisation — Romanian's comma-below
   characters (ș U+0219, ț U+021B) live there. */
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 400 800;
  font-stretch: 75% 125%; font-display: swap;
  src: url('/assets/fonts/archivo-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 400 800;
  font-stretch: 75% 125%; font-display: swap;
  src: url('/assets/fonts/archivo-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Source Serif 4'; font-style: normal; font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/sourceserif-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Source Serif 4'; font-style: normal; font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/sourceserif-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ----------------------------------------------------------------- tokens -- */
:root {
  --paper:      #fcfbf8;
  --paper-2:    #f6f4ee;
  --white:      #ffffff;
  --ink:        #131519;
  --ink-2:      #3b3f47;   /* body — 10.2:1 on paper */
  --ink-3:      #61666f;   /* captions — 5.6:1 */
  --rule:       #ddd9d0;
  --rule-firm:  #bdb8ab;

  --blue:       #0b3a8f;
  --blue-deep:  #072a63;
  --yellow:     #fcd116;
  --red:        #c3122a;
  --red-deep:   #a20f21;
  --ok:         #146c3a;

  --ok-bg:      #e6f2ea;  --ok-fg:  #0f5a30;
  --err-bg:     #fbeaec;  --err-fg: #9d1020;
  --warn-bg:    #fdf3d6;  --warn-fg:#7a5c00;
  --info-bg:    #e8eef8;  --info-fg:#0b3a8f;

  /* Shading. Warm, because the ground is warm: a neutral grey shadow reads
     blue against #fcfbf8. Three steps and no more — 1 to lift a box off the
     paper, 2 for the thing being pointed at, 3 for something genuinely
     floating over the page. */
  --shade-1: 0 1px 2px rgb(38 30 20 / 0.06), 0 3px 8px rgb(38 30 20 / 0.05);
  --shade-2: 0 2px 4px rgb(38 30 20 / 0.07), 0 10px 26px rgb(38 30 20 / 0.10);
  --shade-3: 0 4px 12px rgb(19 21 25 / 0.10), 0 20px 56px rgb(19 21 25 / 0.22);

  --serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --sans:  'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font:  var(--serif);
  /* The page headline. Sans since 4 September 2026: a grotesque at 700 states
     the argument, and the serif underneath it still does the reading. Only h1
     takes this — h2 and below stay in the serif, so a section heading reads as
     part of the text rather than as a second headline. */
  --display: var(--sans);

  --step--2: 0.76rem;
  --step--1: clamp(0.86rem, 0.84rem + 0.1vw, 0.92rem);
  --step-0:  clamp(1.06rem, 1.02rem + 0.2vw, 1.14rem);
  --step-1:  clamp(1.24rem, 1.16rem + 0.4vw, 1.46rem);
  --step-2:  clamp(1.5rem, 1.34rem + 0.78vw, 1.95rem);
  --step-3:  clamp(1.85rem, 1.55rem + 1.45vw, 2.6rem);
  --step-4:  clamp(2.3rem, 1.8rem + 2.5vw, 3.6rem);
  --step-5:  clamp(2.9rem, 1.9rem + 4.9vw, 5.6rem);

  --container: 74rem;
  --gutter: clamp(1.15rem, 5vw, 3rem);
  --radius: 3px;
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The paper lives on the root, not on the body, so that the live
     backdrop below can sit behind the body at a negative z-index and
     still have paper underneath it. Every layout keeps its own body
     background, so nothing but the public pages notices. */
  background: var(--paper);
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-2);
  font-family: var(--serif);
  font-size: var(--step-0);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4, h5 {
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.025em;
  text-wrap: balance;
}
h1 {
  font-size: var(--step-4);
  font-family: var(--display);
  font-weight: 700;
  font-stretch: 100%;
  letter-spacing: -0.02em;
}
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-1); }
h4, h5 { font-size: var(--step-0); }

p { margin: 0 0 1.05rem; }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0 0 1.05rem; padding-left: 1.35rem; }
li { margin-bottom: 0.4rem; }

a { color: var(--blue); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--red); }

hr, .rule { border: 0; border-top: 1px solid var(--rule); margin-block: 2rem; }

:focus-visible { outline: 2px solid var(--red); outline-offset: 3px; }

::selection { background: var(--yellow); color: var(--ink); }

/* ----------------------------------------------------------- the backdrop -- */
/*
   A slow wash of colour behind the public pages, added 4 September 2026. The
   brief was light, moving, and never the thing you look at.

   Where it sits. The public layout opens with one .backdrop element; it is
   fixed at z-index -1, which puts it above the root's paper, below every word,
   and — this is the part that matters — below any section carrying its own
   background. A negative-z-index child paints after the root background but
   before block-level backgrounds, so --soft and --navy stay perfectly flat and
   the page does not shimmer all the way down. Only the hero and the plain
   sections let it through, which is the intended amount.

   Why the body goes transparent. A background on the body would be painted on
   top of a negative-z-index child and hide the whole thing. The paper moves up
   to <html> instead (see above); the class is on the body rather than made
   unconditional because the admin and sign-in screens load this stylesheet too
   and must keep their own grounds.

   What it costs. Four layers, animated by transform and opacity only, so the
   compositor handles them and the page never relays out. No blur filters: a
   radial gradient is already soft, and blurring a full-screen layer is what
   makes this kind of effect run a laptop fan. The grain is one 140px tile the
   browser draws once and repeats.

   Why it looks random. The three clouds run on 71, 89 and 107 seconds and the
   turning light on 127 — mutually prime, so the four do not return to the same
   arrangement for the better part of a day. Nobody watches a background for
   two minutes; that is randomness enough, and it costs no script.

   The grain is not decoration. Three per cent of fractal noise, multiplied, is
   what stops a gradient this wide banding into visible steps on an eight-bit
   screen, and it is what makes the result read as uncoated stock rather than
   as a screen. It does not move: moving grain is noticeable, and noticeable is
   the one thing this must not be.
*/
body.has-backdrop { background: transparent; }

.backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  /* Nothing inside here can affect the rest of the page; say so. */
  contain: layout paint;
}
.backdrop i {
  position: absolute;
  display: block;
  /* Oversized, because the layers drift and their edges must stay off screen. */
  inset: -30vmax;
  will-change: transform, opacity;
}

/* The clouds. Stops fade to the same hue at zero alpha rather than to
   `transparent`, which means transparent *black* and would draw a grey halo
   around every one of them. */
.backdrop i:nth-child(1) {
  background: radial-gradient(46vmax 40vmax at 24% 26%,
    rgb(11 58 143 / 0.05), rgb(11 58 143 / 0) 66%);
  animation: backdrop-a 71s ease-in-out infinite alternate;
}
.backdrop i:nth-child(2) {
  background: radial-gradient(52vmax 44vmax at 78% 34%,
    rgb(252 209 22 / 0.09), rgb(252 209 22 / 0) 64%);
  animation: backdrop-b 89s ease-in-out infinite alternate;
}
.backdrop i:nth-child(3) {
  background: radial-gradient(58vmax 48vmax at 52% 88%,
    rgb(163 120 74 / 0.07), rgb(163 120 74 / 0) 62%);
  animation: backdrop-c 107s ease-in-out infinite alternate;
}

/* The turning light: warm against cool, over two minutes. */
.backdrop i:nth-child(4) {
  inset: 0;
  background:
    linear-gradient(152deg, rgb(252 209 22 / 0.07) 0%, rgb(252 209 22 / 0) 55%),
    linear-gradient(28deg, rgb(11 58 143 / 0.05) 0%, rgb(11 58 143 / 0) 60%);
  animation: backdrop-turn 127s ease-in-out infinite alternate;
}

/* The grain. */
.backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.032;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

@keyframes backdrop-a {
  from { transform: translate3d(-4vw, -2vh, 0) scale(1); }
  50%  { transform: translate3d(6vw, 5vh, 0) scale(1.14); }
  to   { transform: translate3d(2vw, -6vh, 0) scale(1.04); }
}
@keyframes backdrop-b {
  from { transform: translate3d(3vw, 3vh, 0) scale(1.06); }
  50%  { transform: translate3d(-7vw, -4vh, 0) scale(0.96); }
  to   { transform: translate3d(-2vw, 6vh, 0) scale(1.12); }
}
@keyframes backdrop-c {
  from { transform: translate3d(2vw, 4vh, 0) scale(1.02); }
  50%  { transform: translate3d(-5vw, -6vh, 0) scale(1.18); }
  to   { transform: translate3d(6vw, 2vh, 0) scale(1); }
}
@keyframes backdrop-turn {
  from { opacity: 1; }
  to   { opacity: 0.25; }
}

/* Reduced motion: stop, at rest, where the gradients are authored. The
   composition is designed to look right standing still — an effect that only
   works while it moves is a trick, not a design.

   `animation: none` and not just a shorter duration: the global reduce rule
   further down collapses every duration to 0.01ms, which would leave these
   four frozen on their *last* keyframe — an arrangement nobody chose. Removing
   the animation outright leaves them at their untransformed positions, which
   is the arrangement that was drawn. `will-change` goes with it, so four
   full-screen layers are not promoted to the compositor to hold still. */
@media (prefers-reduced-motion: reduce) {
  .backdrop i { animation: none !important; will-change: auto; }
}

/* Print: the grain would be an expensive grey wash on paper, and the clouds
   would be nothing at all. */
@media print {
  .backdrop { display: none; }
}

/* -------------------------------------------------------------- structure -- */
.container { max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.container--mid { max-width: 60rem; }
.container--narrow { max-width: 46rem; }
.measure { max-width: 62ch; }
.measure-wide { max-width: 74ch; }
.stack > * + * { margin-top: 1.05rem; }

/* Reachable by a screen reader, absent from the page.
   DO NOT put this class on a <table>. A table will not lay out below its
   min-content width whatever width you give it, so it keeps its full size
   and pushes the page out sideways: the dashboard chart's data table did
   exactly that, 547px of it, and gave the admin 197px of horizontal scroll
   on a phone until 5 September 2026. table-layout: fixed does not help,
   which was the first thing tried. Wrap the table in a div carrying this
   class instead. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: -9999px; z-index: 100;
  background: var(--ink); color: var(--paper); padding: 0.75rem 1rem;
  font-family: var(--sans); font-weight: 600;
}
.skip-link:focus { left: 1rem; top: 1rem; }

.center { text-align: center; }
.text-muted { color: var(--ink-3); }
.text-small { font-size: var(--step--1); }
.mb-0 { margin-bottom: 0 !important; }
.mt-0 { margin-top: 0 !important; }
.mt-2 { margin-top: 1rem !important; }
.mt-3 { margin-top: 1.5rem !important; }
.num, .stat__value, .price-card__amount { font-variant-numeric: tabular-nums lining-nums; }

/* ------------------------------------------------------------------ chrome -- */
/* The topbar is the publication's standing line: contact details and social,
   set small in the sans above the masthead rule. */
.topbar {
  background: var(--ink); color: #b8bcc4;
  font-family: var(--sans); font-size: var(--step--2);
}
.topbar .container { display: flex; flex-wrap: wrap; gap: 0.4rem 1.5rem; align-items: center; justify-content: space-between; padding-block: 0.5rem; }
.topbar__contact { display: flex; flex-wrap: wrap; gap: 0.35rem 1.25rem; }
.topbar a { color: #e4e7eb; text-decoration: none; }
.topbar a:hover { color: #fff; }
.topbar__social { display: flex; gap: 0.75rem; align-items: center; }
.topbar__social a { display: inline-flex; }
.topbar__social svg { width: 1rem; height: 1rem; fill: currentColor; }

.site-header {
  background: rgb(252 251 248 / 0.94);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 40;
}
.site-header .container { display: flex; align-items: center; gap: 1.25rem; padding-block: 0.85rem; }
.site-header .brand { margin-right: auto; }

/* The wordmark is never the thing that gives. When the masthead is a few
   pixels short, flex takes them from whichever item can shrink, and the brand
   was the one paying: its box narrowed while ROMOTANA, which cannot wrap,
   printed straight over the first navigation label.
   The brand still has to shrink — that is how the strapline wraps to two lines
   instead of pushing the navigation off the end — so the floor goes on the text
   column rather than the item. min-content is the widest thing in there that
   cannot be broken, which is the wordmark or the longest word of the strapline,
   whichever is larger; either way it is never narrower than ROMOTANA. */
.brand { display: flex; align-items: center; gap: 0.8rem; text-decoration: none; color: var(--ink); min-width: 0; }
.brand img { height: 2.5rem; width: auto; }
.brand > span { min-width: min-content; }
.brand__name {
  display: block; font-family: var(--sans); font-weight: 800;
  font-size: 1.25rem; letter-spacing: -0.04em; font-stretch: 112%; color: var(--ink);
}
.brand__sub { display: block; font-family: var(--sans); font-size: var(--step--2); color: var(--ink-3); }

/* Between the widest desktop and the menu button there is a band where the
   nine navigation items, Join and the strapline cannot all fit. Something has
   to give, and it is the strapline: the association's full name is in the
   footer, in the page title and in the first line of the About page, whereas a
   navigation label that has wrapped is unreadable in a way nothing recovers. */
@media (max-width: 80rem) { .nav { gap: 1.05rem; } }

/* And in Romanian it goes at every width, because no width helps: the header
   container is capped at 74rem, so a 1920px screen gives the masthead exactly
   as much room as a 1280px one. Romanian labels are longer — nine of them plus
   Join leave the brand a column too narrow for the strapline, which then wraps
   to five lines and makes the masthead 237px tall.
   Shortening two labels by hand recovered a hundred of those pixels; the rest
   has to come from here. The full name is still in the footer, in the page
   title and in the first line of About. */
html[lang="ro"] .brand__sub { display: none; }
/* Hiding the strapline recovers the height but not the width: nine Romanian
   labels plus Join still need about 25px more than the 74rem container has,
   and with the strapline gone the brand has nothing left to give — its floor
   is the wordmark — so the shortfall has to come out of the navigation gap. 1.05rem buys back 45px at every width, which is
   why this rule carries no media query — the container is capped, so 1920px
   is no roomier than 1280px. */
html[lang="ro"] .nav { gap: 1.05rem; }
/* Below this there is no room for the strapline beside the Join panel and the
   menu button: logo, name, Join and Menu already account for most of a 375px
   line. The association's full name is still in the footer and in the page
   title. */

.nav { display: flex; gap: 1.4rem; align-items: center; font-family: var(--sans); }
.nav a {
  color: var(--ink-2); text-decoration: none;
  font-size: var(--step--1); font-weight: 600; padding-block: 0.3rem;
  /* A navigation label is a name, not a paragraph. Without this, flex shrinks
     each link below the width of its own words and "What we do" sets as three
     stacked lines in the middle of the masthead — which is what it did on a
     1440px screen until 7 September. Nothing here may break mid-phrase; if the
     row runs out of room, the strapline goes (below) and then the whole thing
     becomes the menu button. */
  white-space: nowrap;
}
.nav a:hover { color: var(--blue); }
.nav a[aria-current] { color: var(--ink); box-shadow: 0 2px 0 var(--red); }

/* The masthead's subscription flash.
   A publication does not put a solid pill in its masthead; it prints what a
   subscription costs and lets the price do the selling. This is the site's
   one differentiator — almost nobody in this industry publishes a price — so
   the number is the second line rather than being hidden behind a click.
   It sits outside .nav so it stays visible when the menu collapses. */
.join {
  display: inline-grid; gap: 0;
  padding: 0.34rem 0.8rem;
  border-left: 3px solid var(--red);
  background: var(--paper-2);
  font-family: var(--sans); line-height: 1.2;
  text-decoration: none; color: var(--ink);
  transition: background-color .14s ease, color .14s ease;
}
/* Neither line wraps: "Join" and the price are each one phrase, and a masthead
   panel that breaks "$100 a year" across three lines is the thing that made the
   whole row look broken. If it will not fit, the menu button above takes over. */
.join b { font-size: var(--step--1); font-weight: 800; letter-spacing: -0.01em; white-space: nowrap; }
.join span { font-size: var(--step--2); font-weight: 500; color: var(--ink-3); white-space: nowrap; }
.join:hover { background: var(--red); color: #fff; }
.join:hover span { color: #ffdde1; }

.nav-toggle {
  display: none; font: inherit; font-family: var(--sans); font-weight: 700;
  font-size: var(--step--1); align-items: center; gap: 0.5rem;
  background: transparent; border: 1px solid var(--rule-firm); border-radius: var(--radius);
  padding: 0.5rem 0.8rem; color: var(--ink); cursor: pointer;
  /* The bars are 2px and the padding was doing all the work, which left the
     only control on a phone header 40x20 — under the 24px minimum, and smaller
     than a thumb regardless of the minimum. */
  min-height: 44px; min-width: 44px; justify-content: center;
}
.nav-toggle__bars { width: 1rem; height: 2px; background: currentColor; position: relative; display: block; }
.nav-toggle__bars::before, .nav-toggle__bars::after { content: ""; position: absolute; left: 0; width: 100%; height: 2px; background: currentColor; }
.nav-toggle__bars::before { top: -5px; }
.nav-toggle__bars::after { top: 5px; }

/* The menu button takes over at 75rem, not 62rem.
   Measured rather than chosen: nine navigation items need about 710px in
   English and 772px in Romanian, and the brand, the Join panel and the gaps
   need roughly 350 more. Under about 1200px the row was over-full and flex
   did what flex does — it squeezed the brand to thirty pixels and wrapped
   "Join / $100 a year" onto four lines, which is what a full-width masthead
   looked like on a 1024px laptop. */
@media (max-width: 75rem) {
  .nav-toggle { display: inline-flex; }
  .site-header .container { gap: 0.6rem; }
  .nav-toggle { order: 3; }
  .join { order: 2; }
  .nav {
    display: none; position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--paper); border-bottom: 1px solid var(--rule);
    padding: 0.5rem var(--gutter) 1rem;
  }
  /* The class site.js toggles. The Spread rewrite renamed this to
     [data-open] while the script kept on setting .is-open, so the mobile menu
     opened nothing at all from 3 September until this was found on the 4th.
     If the state name is ever changed, change it in site.js in the same
     commit. */
  .nav.is-open { display: flex; }
  .nav a { padding-block: 0.7rem; border-bottom: 1px solid var(--rule); }
  .site-header .container { position: relative; }
}

@media (max-width: 48rem) {
  .brand__sub { display: none; }
  /* At 375px the line has to carry the logo, the wordmark, the Join panel and
     the menu button. The strapline goes, the Join panel drops to its first
     line, and the wordmark comes down a step — together that is the ~60px
     needed to stop ROMOTANA being painted over by the Join panel. */
  .brand__name { font-size: 1.1rem; }
  .join .num { display: none; }
  .join { padding-block: 0.5rem; }
}

/* Below 26rem the wordmark still does not fit: the line needs 356px of content
   and has 336. The word "Menu" is 46px of that, and the button keeps its
   aria-label, so the bars alone carry it. The logo comes down a step too.
   Measured, not guessed — scripts in operations.md §6d. */
@media (max-width: 26rem) {
  .nav-toggle__label { display: none; }
  .nav-toggle { padding-inline: 0.7rem; }
  .brand img { height: 2rem; }
}

/* On the narrowest phones still in use the Join panel has to go as well. The
   membership link is the first thing in the menu behind the button. */
@media (max-width: 21rem) {
  .join { display: none; }
}


/* ---------------------------------------------------------------- sections -- */
.section { padding-block: clamp(2.75rem, 6vw, 4.5rem); }
.section--tight { padding-block: clamp(1.75rem, 4vw, 2.75rem); }
.section--soft { background: var(--paper-2); border-block: 1px solid var(--rule); }

/* The announcement strip under the masthead. This is the one control the
   office has for something urgent — ten logging devices go dark on Monday —
   so it is a component rather than four inline styles on a .section--soft.

   It was the latter until 4 September 2026, and both of the tokens it named
   had been deleted by the Spread rewrite: `var(--line)` on the border and
   `var(--navy-800)` on the label. An undefined custom property makes the whole
   declaration invalid at computed-value time, so the strip had no bottom rule
   at all (`border-bottom-style: none`, measured) and "Announcement:" rendered
   in exactly the same ink as the sentence after it. Nobody had seen it,
   because the setting has never been filled in. Both tokens survive in
   admin.css, which is why nothing else noticed.

   Red on the label, because this is the page's one urgent thing when it is
   showing at all. The rule below it is 2px, like a standing head: the strip
   is apparatus, not a section of the page. */
.announce {
  background: var(--paper-2);
  border-top: 1px solid var(--rule);
  border-bottom: 2px solid var(--ink);
  font-family: var(--sans);
  font-size: var(--step--1);
  color: var(--ink-2);
}
.announce .container { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.6rem; padding-block: 0.8rem; }
.announce b {
  font-weight: 800; color: var(--red);
  font-size: var(--step--2); letter-spacing: 0.06em; text-transform: uppercase;
}
.announce a { color: var(--blue); font-weight: 600; white-space: nowrap; }
.section--navy { background: var(--blue-deep); color: #c3d1ea; }
.section--navy h1, .section--navy h2, .section--navy h3 { color: #fff; }
.section--navy a { color: #fff; }
.section--navy .lede, .section--navy p { color: #b8c9e6; }

/* Those three rules colour the band's own text, and they were reaching into
   anything placed on the band that brings its own ground. A .card inside a
   navy section is still white, so its headings were white on white — the two
   advocacy cards on What we do had invisible titles and body text at 1.68:1
   for as long as the band has existed. A .btn--gold is still yellow, so the
   `a { color: #fff }` rule repainted its label white: "Apply for membership"
   on About was white on yellow, 1.47:1, which is the exact ratio §3 gives as
   the reason yellow is never type.
   Put back, inside those components, the colours the components already had.
   Found 4 September 2026 by the contrast sweep in ui-test.mjs. */
.section--navy .card h1,
.section--navy .card h2,
.section--navy .card h3 { color: var(--ink); }
.section--navy .card p,
.section--navy .card .lede { color: var(--ink-2); }
.section--navy .card a:not(.btn) { color: var(--blue); }
.section--navy .btn--gold, .section--navy .btn--gold:hover { color: var(--ink); }

.section-head { max-width: 48ch; margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.section-head--center { margin-inline: auto; text-align: center; }
.section-head p { margin-top: 0.75rem; }

/* The kicker above a section title. Set in the sans, in red, because it is a
   navigational label and not something to be read as prose. */
.eyebrow {
  font-family: var(--sans); font-size: var(--step--1); font-weight: 700;
  color: var(--red); margin: 0 0 0.35rem; letter-spacing: 0;
}
.eyebrow--light { color: var(--yellow); }

.lede { font-size: var(--step-1); color: var(--ink-2); max-width: 54ch; }

/* A source URL printed in full is one unbreakable word, and on a phone it
   pushed the whole page sideways. Long links wrap mid-string rather than
   setting the width of the article — this applies to anything an editor
   pastes, not only the wire's attribution line. */
.prose a { overflow-wrap: anywhere; }

.prose { max-width: 68ch; }
.prose h2 { margin-top: 2.25rem; }
.prose h3 { margin-top: 1.75rem; }
.prose > *:first-child { margin-top: 0; }
.prose ul, .prose ol { padding-left: 1.4rem; }

/* -------------------------------------------------------------------- hero -- */
/* The cover. On the home page it fills the screen; elsewhere it is a standing
   head, closed by a rule. */
.hero { padding-block: clamp(2.5rem, 7vw, 5.5rem) clamp(2rem, 4vw, 3rem); border-bottom: 1px solid var(--rule); }
.hero--compact { padding-block: clamp(2rem, 4vw, 3rem) clamp(1.25rem, 2.5vw, 2rem); }
.hero h1 { font-size: var(--step-5); font-weight: 700; letter-spacing: -0.02em; line-height: 0.98; max-width: 16ch; }
.hero--compact h1 { font-size: var(--step-4); line-height: 1.02; max-width: 20ch; }
.hero__lead { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: clamp(1.5rem, 5vw, 4rem); align-items: end; }
.hero--compact .hero__lead { grid-template-columns: 1fr; align-items: start; }
.hero .lede { font-size: clamp(1.1rem, 1rem + 0.5vw, 1.4rem); max-width: 46ch; }
.hero__card {
  border: 1px solid var(--ink); background: var(--white);
  padding: clamp(1.1rem, 2.5vw, 1.6rem);
  box-shadow: var(--shade-1);
}

/* The tricolour, used once per page. It hangs off the container rather than
   the section, so it starts on the text's left edge and not the viewport's. */
.hero .container::after,
.cta-band .container::before {
  content: ""; display: block; height: 5px; max-width: 22rem;
  background: linear-gradient(90deg, var(--blue) 0 33.34%, var(--yellow) 33.34% 66.67%, var(--red) 66.67%);
}
.hero .container::after { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.cta-band .container::before { margin-bottom: 1.5rem; }
.hero--compact .container::after { display: none; }

/* The article's category mark sits beside the headline, not above the body.
   A full-width image there reads as the picture that goes with the story; this
   is a label for the subject, and belongs with the other labels. */
.hero__split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(7rem, 16vw, 11rem);
  gap: clamp(1.25rem, 4vw, 3rem);
  align-items: center;
}
.hero__mark {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

@media (max-width: 45rem) {
  .hero__split { grid-template-columns: 1fr; }
  .hero__mark { order: -1; max-width: 6.5rem; margin-bottom: 0.9rem; }
}

@media (max-width: 60rem) {
  .hero__lead { grid-template-columns: 1fr; align-items: start; }
  .hero h1 { max-width: 20ch; }
}

/* ------------------------------------------------------------------- cover -- */
/* The front page's first screen: a daylight photograph of the card at the
   pump, and over its left half a panel of the same paper the rest of the site
   is printed on. The page stays a paper object; the cover is the one place it
   has a window cut in it.

   The photograph is a real <img> (srcset, alt, preload) stretched behind the
   panel with object-fit. The panel is translucent so the truck behind it
   reads as a shape and not a hole, and the card, the hand and the pump's
   price display stay clear to its right. Under 60rem there is no room for
   both, so the picture becomes a band above the panel — cropped towards the
   card, which is the point of the picture. */
.cover {
  position: relative;
  isolation: isolate;
  min-height: clamp(34rem, 78vh, 46rem);
  display: grid;
  align-items: center;
  overflow: hidden;
  border-bottom: 1px solid var(--rule);
  background: var(--paper-2);
}
.cover__photo {
  position: absolute; inset: 0; z-index: -1;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 64% 50%;
}
/* An auto-margin child of a grid is centred and shrunk to its content;
   the container must keep its full measure so the panel sits on its left edge. */
.cover__in { width: 100%; padding-block: clamp(1.75rem, 4vw, 3rem); }
/* Positioned so it paints after the photograph: a replaced element paints
   with inline content, after every block background, and would otherwise
   sit over the panel where the two overlap on a phone. */
.cover__panel {
  position: relative;
  max-width: 35rem;
  padding: clamp(1.25rem, 2.5vw, 1.75rem) clamp(1.25rem, 2.5vw, 2rem);
  background: rgb(252 251 248 / 0.9);
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
  backdrop-filter: blur(14px) saturate(1.1);
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shade-3);
}
.cover__panel .eyebrow { margin-bottom: 0.6rem; }

/* The headline is a figure. The cents sign is set small and high, the way a
   pump prints it, and the words after it drop to their own line in the size
   the rest of the site uses for a section heading. */
.cover__title {
  display: flex; flex-direction: column; gap: 0.15em;
  font-family: var(--display); font-weight: 800; letter-spacing: -0.04em;
  line-height: 0.9; color: var(--blue-deep); text-wrap: initial;
}
.cover__figure { font-size: clamp(4.5rem, 3rem + 6vw, 7rem); font-variant-numeric: tabular-nums lining-nums; }
.cover__cent { font-size: 0.42em; vertical-align: 0.95em; margin-left: 0.04em; letter-spacing: 0; font-weight: 700; }
.cover__after { font-size: var(--step-2); font-weight: 600; letter-spacing: -0.02em; line-height: 1.05; color: var(--ink); max-width: 20ch; text-wrap: balance; }
.cover__lead { margin-top: 0.8rem; font-size: clamp(1rem, 0.95rem + 0.25vw, 1.1rem); line-height: 1.45; max-width: 46ch; }

/* The slider: gallons in, dollars a year out. The result is the second-largest
   thing on the screen because it is the second most important — it is the
   figure, applied to this reader. */
.cover__calc { margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid var(--rule); font-family: var(--sans); }
.cover__calc-row { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; margin-bottom: 0.35rem; }
.cover__calc-row label { font-size: var(--step--1); font-weight: 700; color: var(--ink-2); }
.cover__calc-row output { font-weight: 800; font-size: var(--step-0); color: var(--ink); }
.cover__calc input[type="range"] { display: block; width: 100%; margin: 0; accent-color: var(--red); cursor: pointer; }
.cover__calc input[type="range"]:focus-visible { outline: 2px solid var(--blue); outline-offset: 4px; }
.cover__result { margin: 0.75rem 0 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.6rem; }
.cover__result-figure { font-size: var(--step-3); font-weight: 800; letter-spacing: -0.03em; color: var(--blue-deep); line-height: 1; }
.cover__result-note { font-size: var(--step--1); color: var(--ink-2); }
.cover__actions { margin-top: 1.1rem; }
.cover__source { margin: 0.8rem 0 0; font-family: var(--sans); font-size: var(--step--2); line-height: 1.45; color: var(--ink-3); max-width: 44ch; }

/* The arrival, once a session, keyed off a class site.js adds. The picture
   settles from a slight enlargement while the panel rises into place; the
   figure counts up in the script. Everything is done inside 1.5 seconds, and
   without the class — no script, or a returning visitor — nothing moves. */
.cover--arrive .cover__photo { animation: cover-photo 1.4s cubic-bezier(0.2, 0.6, 0.2, 1) both; }
.cover--arrive .cover__panel { animation: cover-panel 0.7s 0.15s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
@keyframes cover-photo { from { transform: scale(1.05); opacity: 0.6; } to { transform: none; opacity: 1; } }
@keyframes cover-panel { from { transform: translateY(16px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .cover--arrive .cover__photo, .cover--arrive .cover__panel { animation: none; }
}

@media (max-width: 60rem) {
  .cover { min-height: 0; display: block; background: var(--paper); }
  .cover__photo { position: static; display: block; aspect-ratio: 16 / 9; height: auto; object-position: 58% 50%; }
  .cover__in { padding-block: 0 clamp(1.5rem, 4vw, 2.5rem); }
  .cover__panel {
    max-width: none; margin-top: -3rem;
    background: var(--paper); -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .cover__figure { font-size: clamp(4rem, 2rem + 12vw, 6.5rem); }
}

/* The statement, second screen: the old opening, set as an h2 in the h1's
   clothes, because it is no longer the document's title but is still its
   thesis. */
.statement { border-bottom: 1px solid var(--rule); }
.statement__title {
  font-family: var(--display); font-size: var(--step-5); font-weight: 700;
  letter-spacing: -0.02em; line-height: 0.98; max-width: 16ch;
}
@media (max-width: 60rem) { .statement__title { max-width: 20ch; } }

/* A photograph across the measure, used where a section has a picture to
   show and a form or argument to get on with underneath it. */
.photo-band { margin: 0 0 clamp(1.5rem, 3vw, 2.25rem); }
/* On the second screen the photograph closes the statement rather than
   introducing a section, so the space belongs above it. */
.statement .photo-band { margin: clamp(2rem, 5vw, 3.25rem) 0 0; }
.photo-band img {
  display: block; width: 100%; height: auto; aspect-ratio: 21 / 9; object-fit: cover;
  border: 1px solid var(--ink); border-radius: var(--radius); box-shadow: var(--shade-2);
  background: var(--paper-2);
}

/* Scoped to .topbar deliberately: the base .lang-switch rule is declared
   later in this file and sets --ink-3, which is 3.17:1 against the ink bar
   and fails AA. Same specificity, later wins; one extra class settles it
   without moving either block.

   The language switch in the top bar, beside the phone number: a preference,
   not a destination. It began in the masthead and broke it — that row already
   carries the brand, the menu button, nine navigation items and Join, and the
   fifth element folded the brand into a column and lapped the nav over itself.
   Here it costs nothing and reads as what it is. */
.topbar .lang-switch--bar {
  display: inline-flex; gap: 0.25rem; margin-left: 0.5rem;
  font-size: calc(var(--step--2) * 0.9); text-transform: none; letter-spacing: 0;
}
.topbar .lang-switch--bar a {
  padding: 0.1rem 0.4rem; border-color: rgb(252 251 248 / 0.3);
  /* The bar's own link colour, at full strength. It was `inherit` at 0.85
     opacity for one afternoon, which put the language nobody had chosen at
     3.17:1 against the ink — below AA, on every page, and caught by ui-test
     rather than by looking. A control that is hard to read is a control that
     was not offered. */
  color: #e4e7eb;
}
.topbar .lang-switch--bar a:hover { color: #fff; border-color: rgb(252 251 248 / 0.55); }
/* Opaque, not a translucent wash. A 16% paper over the ink bar renders as a
   perfectly readable dark grey, but a contrast checker reads the declared
   colour rather than the composite and calls white-on-paper 1.03:1 — and a
   tool that cannot verify a colour is a tool that will be ignored the next
   time it is right. Solid grey costs nothing and is true at a glance. */
.topbar .lang-switch--bar a[aria-current] { background: #343a42; border-color: #343a42; color: #fff; }
.topbar .lang-switch--bar .flag { width: 1.2em; height: 0.8em; }
@media (max-width: 40rem) { .topbar .lang-switch--bar a span, .topbar .lang-switch--bar a { font-size: var(--step--2); } }

/* ── The dates a reader acts on ──────────────────────────────────────────────
   A comment deadline and an effective date, set as a definition list because
   that is what they are: a label and its date. Rules top and bottom rather
   than a box, so it reads as part of the article's furniture rather than as a
   card dropped into it — the same reason the calculator's rows are ruled. */
.keydates {
  display: flex; flex-wrap: wrap; gap: 0 clamp(1.5rem, 4vw, 2.5rem);
  margin: 0 0 clamp(1.25rem, 3vw, 1.75rem);
  padding: 0.75rem 0;
  border-top: 2px solid var(--ink); border-bottom: 1px solid var(--rule);
}
.keydates dt {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 0.15rem;
}
.keydates dd {
  margin: 0; font-family: var(--sans); font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* How the article was written, at the foot of it. Small, quiet, and not a
   disclaimer: it is a byline note, so it is set like one. */
.byline-note {
  margin: clamp(1.5rem, 3vw, 2rem) 0 0;
  padding-top: 0.75rem; border-top: 1px solid var(--rule);
  font-family: var(--sans); font-size: var(--step--2); color: var(--ink-3);
}

/* ── News subjects ──────────────────────────────────────────────────────────
   Added 6 September 2026, when the subject moved from the wire's own table
   onto the post and the public page could finally use it.

   The filter row borrows .tag-bio's language — uppercase sans, hairline box —
   because both are the same gesture: a small word you can press to narrow what
   you are reading. The count rides inside the link rather than after it, so a
   subject and its number are one target. */
.subject-nav {
  display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin: 0 0 clamp(1.25rem, 3vw, 1.75rem);
}
.subject-nav a {
  display: inline-flex; align-items: baseline; gap: 0.4rem;
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none;
  color: var(--ink-3); background: var(--paper-2);
  border: 1px solid var(--rule-firm);
  padding: 0.34rem 0.66rem; line-height: 1;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.subject-nav a span { font-weight: 600; letter-spacing: 0; color: var(--ink-3); }
.subject-nav a:hover, .subject-nav a:focus-visible {
  color: var(--blue); border-color: var(--blue); background: var(--white);
}
/* The current subject is stated, not merely highlighted: aria-current carries
   it for a screen reader and the ink fill carries it for everyone else. */
.subject-nav a[aria-current="true"] {
  color: var(--white); background: var(--blue-deep); border-color: var(--blue-deep);
}
.subject-nav a[aria-current="true"] span { color: rgb(252 251 248 / 0.72); }

/* On a card the subject is a label, not a link — the whole card is already a
   link to the article. Set in the sans above the headline, at the weight the
   eyebrow uses, so it reads as filing rather than as a second title. */
.card__subject {
  margin: 0 0 0.35rem;
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--red);
}
/* On the article it is a link, because a reader who wants more of this subject
   has nowhere else to ask. */
.article-subject { margin: 0 0 0.5rem; }
.article-subject a {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--red); text-decoration: none;
}
.article-subject a:hover, .article-subject a:focus-visible { text-decoration: underline; }

/* A photograph on the navy band. The 1px --ink rule is all but invisible
   against --blue-deep, so the frame becomes a light one — the band is dark and
   the picture has to look placed on it rather than cut out of it. */
.section--navy .photo-band img { border-color: rgb(252 251 248 / 0.28); }

/* The calculator row that matches the published average in Site settings. */
.calc__average th, .calc__average td { background: var(--paper-2); }
.calc__average th small {
  display: inline-block; margin-left: 0.4rem; padding: 0.1rem 0.4rem;
  font-size: var(--step--2); font-weight: 700; letter-spacing: 0.01em;
  color: var(--paper); background: var(--blue-deep); border-radius: var(--radius); vertical-align: middle;
  white-space: nowrap;
}

/* ------------------------------------------------------------------- grids -- */
.grid { display: grid; gap: clamp(1.25rem, 3vw, 2rem); }
/* Fixed track counts, not auto-fit. `auto-fit` collapses the tracks nothing
   lands in and hands their width to the survivors, so a row holding fewer
   items than it has columns stretches them across the whole measure: one
   published news post became a single card 1088px wide with a two-line
   excerpt in it, on the home page and on /news alike. It also quietly made
   these four columns on a wide screen, which is what the .team-grid note
   below already complains about.

   A card that is alone in its row now sits in a card-width column with empty
   paper beside it, which is what a three-column grid holding one thing should
   look like. */
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 62rem) { .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 46rem) { .grid--2 { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 38rem) { .grid--3 { grid-template-columns: minmax(0, 1fr); } }
.grid--sidebar { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; }
/* minmax(0, 1fr), not 1fr. A grid track's minimum is auto, so a plain 1fr
   cannot shrink below its widest content — one wide table in the sidebar
   column stretched the grid past the viewport and gave the whole page a
   horizontal scrollbar at 320px. Same trap as the bio dialog. */
@media (max-width: 60rem) { .grid--sidebar { grid-template-columns: minmax(0, 1fr); } }

/* ------------------------------------------------------------------- cards -- */
/* Not cards in the SaaS sense. Bordered blocks, square, no shadow: the shape a
   printed page uses for a boxed aside. */
.card {
  background: var(--white);
  border: 1px solid var(--rule);
  border-top: 2px solid var(--ink);
  padding: clamp(1.15rem, 2.5vw, 1.6rem);
  box-shadow: var(--shade-1);
}

/* The category mark on a news card, and beside a headline on the article.
   These are illustrations of a subject, not photographs of the story, so they
   are sized as a label and kept out of the reading path: a picture given the
   width of a press photograph would claim to show something it does not.
   alt is empty for the same reason — a screen reader should reach the headline. */
.card--marked { position: relative; }
.card__mark {
  /* Floated rather than absolutely positioned: the dateline and the first
     lines of the headline wrap beside it, and everything below reclaims the
     full width. Reserving a column with padding-right instead squeezed every
     line of every headline, which cost more than the mark was worth. */
  float: right;
  width: 4rem;
  height: 4rem;
  object-fit: contain;
  margin: 0 0 0.6rem 0.9rem;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.2rem;
  shape-outside: inset(0 round var(--radius));
}
/* The card is a flow root so the float cannot escape its box. */
.card--marked { display: flow-root; }` on line 139 would otherwise cap this
     at the content box and leave the picture short of the card's right edge. */
  max-width: none;
  width: calc(100% + 2 * clamp(1.15rem, 2.5vw, 1.6rem));
  margin: calc(-1 * clamp(1.15rem, 2.5vw, 1.6rem));
  margin-bottom: 1.15rem;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--paper-2);
  border-bottom: 1px solid var(--rule);
}
.card h3 { margin-bottom: 0.5rem; }
.card--link { transition: box-shadow .16s ease, border-color .16s ease; }
.card--link:hover, .card--link:focus-within { box-shadow: var(--shade-2); }
.card p:last-child { margin-bottom: 0; }
.card--accent { border-top-color: var(--blue); }
.card--accent-gold { border-top-color: var(--yellow); }
.card--accent-red { border-top-color: var(--red); }
.card--link { text-decoration: none; color: inherit; display: block; transition: border-color .14s ease; }
.card--link:hover { border-color: var(--rule-firm); border-top-color: var(--red); color: inherit; }
.card--link:hover h3 { color: var(--blue); }

.icon-card__mark {
  display: inline-grid; place-items: center;
  width: 2.4rem; height: 2.4rem; margin-bottom: 0.75rem;
  border: 1px solid var(--rule); background: var(--paper-2); color: var(--blue);
}
.icon-card__mark svg { width: 1.15rem; height: 1.15rem; fill: currentColor; }

/* Deliberately flat. A boxout sits on a tinted ground, which reads as set into
   the page; a shadow would fight that and make it hover instead. Raised things
   are white on paper, inset things are tinted. */
.box { border: 1px solid var(--ink); background: var(--paper-2); padding: clamp(1.1rem, 2.5vw, 1.6rem); }

/* ----------------------------------------------------------------- buttons -- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  font-family: var(--sans); font-weight: 700; font-size: var(--step--1);
  padding: 0.78rem 1.35rem; border: 1px solid transparent; border-radius: var(--radius);
  text-decoration: none; cursor: pointer; text-align: center;
  /* Stated, because the browser does not agree with itself: a <button> takes
     `line-height: normal` as a form control while an <a> inherits the page's
     1.62, so the same padding and the same font made a link 8px taller than a
     button. Every "Save / Cancel" pair in the admin was a step, on five
     screens. Buttons are the same height whatever element they are. */
  line-height: 1.2;
  transition: background-color .14s ease, color .14s ease,
              border-color .14s ease, box-shadow .16s ease;
}
/* Only the solid buttons take shading: an outline button is a hairline by
   definition and a shadow under one looks like a mistake. */
.btn--primary, .btn--navy, .btn--gold, .btn--danger { box-shadow: var(--shade-1); }
.btn--primary:hover, .btn--navy:hover, .btn--gold:hover, .btn--danger:hover { box-shadow: var(--shade-2); }
.btn svg { width: 1rem; height: 1rem; fill: currentColor; }
.btn--primary { background: var(--red); color: #fff; }
.btn--primary:hover { background: var(--red-deep); color: #fff; }
.btn--navy { background: var(--blue-deep); color: #fff; }
.btn--navy:hover { background: var(--blue); color: #fff; }
.btn--gold { background: var(--yellow); color: var(--ink); }
.btn--gold:hover { background: #e8bf0d; color: var(--ink); }
.btn--outline { border-color: var(--ink); color: var(--ink); background: transparent; }
.btn--outline:hover { background: var(--ink); color: var(--paper); }
.btn--ghost-light { border-color: rgb(255 255 255 / 0.55); color: #fff; }
.btn--ghost-light:hover { background: #fff; color: var(--blue-deep); }
.btn--danger { border-color: var(--red); color: var(--red); background: transparent; }
.btn--danger:hover { background: var(--red); color: #fff; }
.btn--sm { padding: 0.55rem 1rem; font-size: var(--step--2); }
.btn--block { display: flex; width: 100%; }
.btn.is-disabled, .btn[disabled] { opacity: 0.45; pointer-events: none; }
.btn-row { display: flex; flex-wrap: wrap; gap: 0.6rem; }

/* ------------------------------------------------------------------- forms -- */
/* A subscription card. Labels above hairline-underlined inputs; nothing
   rounded, because a rounded input breaks the register immediately. */
.form { font-family: var(--sans); }
/* align-items: start, or a field whose neighbour carries a hint stretches to
   match it and its input grows taller than the one beside it. That was
   invisible while inputs were a bare underline. */
.form__row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem 1.25rem; margin-bottom: 1rem; align-items: start;
}
.form__row--3 { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }

/* Fields inside a .form__row are spaced by the grid's gap. Stacked ones — the
   sign-in form, the newsletter box — had nothing between them, which did not
   show while inputs were a bare underline and does now that they have a
   surface. */
.field { display: grid; gap: 0.3rem; min-width: 0; }
.form > .field + .field,
.form > .field + .btn,
.form > .field + .checkbox { margin-top: 1.1rem; }
.form > .checkbox + .btn { margin-top: 1.1rem; }
.field > label { font-size: var(--step--1); font-weight: 700; color: var(--ink); }
.field__req { color: var(--red); }
.field input[type="text"], .field input[type="email"], .field input[type="tel"],
.field input[type="url"], .field input[type="number"], .field input[type="search"],
.field input[type="date"], .field input[type="password"], .field select, .field textarea {
  font: inherit; font-family: var(--sans); font-size: var(--step--1);
  color: var(--ink);
  /* A tinted ground with a shallow inset, so a field reads as somewhere to
     write. It keeps the hairline underline the direction asks for — this is a
     ruled line on a form, not a rounded box — but on a white card a bare
     underline is nearly invisible and the field reads as empty space. */
  background: var(--paper-2);
  box-shadow: inset 0 1px 2px rgb(38 30 20 / 0.06);
  border: 0; border-bottom: 1px solid var(--ink-3); border-radius: 0;
  padding: 0.55rem 0.7rem; width: 100%;
  transition: background-color .14s ease, box-shadow .14s ease;
}
.field input:hover, .field select:hover, .field textarea:hover { background: var(--white); }
.field input:focus, .field select:focus, .field textarea:focus { background: var(--white); }
.field textarea { border: 1px solid var(--ink-3); padding: 0.6rem 0.7rem; min-height: 7rem; resize: vertical; }
.field select { border-bottom: 1px solid var(--ink-3); padding-block: 0.55rem; }
.field input:focus, .field select:focus, .field textarea:focus { outline: 2px solid var(--blue); outline-offset: 3px; }
.field input[aria-invalid="true"], .field select[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: var(--red); border-bottom-width: 2px; }
.field__hint { font-size: var(--step--2); color: var(--ink-3); }
.field__error { font-size: var(--step--2); color: var(--err-fg); font-weight: 600; }

.checkbox { display: flex; gap: 0.6rem; align-items: flex-start; font-family: var(--sans); font-size: var(--step--1); }
.checkbox input { width: 1.05rem; height: 1.05rem; margin-top: 0.2rem; flex: 0 0 auto; }

/* Choosing an option reads as ticking a box on a printed form. */
.radio-cards { display: grid; gap: 0; border-top: 1px solid var(--rule); }
.radio-card {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.85rem;
  align-items: start; padding: 0.9rem 0; border-bottom: 1px solid var(--rule);
  cursor: pointer; font-family: var(--sans);
}
.radio-card input { width: 1.05rem; height: 1.05rem; margin: 0.2rem 0 0; }
.radio-card b, .radio-card > span > b { color: var(--ink); font-weight: 700; }
.radio-card span span { display: block; font-size: var(--step--2); color: var(--ink-3); margin-top: 0.15rem; }
.radio-card { transition: box-shadow .16s ease, background-color .16s ease; }
.radio-card:has(input:checked) {
  background: var(--paper-2);
  box-shadow: inset 3px 0 0 var(--blue), var(--shade-1);
}
.radio-card:has(input:checked) b { color: var(--blue-deep); }

/* The honeypot. Never shown, never announced. */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
fieldset > legend { padding: 0; font-family: var(--sans); font-weight: 700; font-size: var(--step--1); color: var(--ink); margin-bottom: 0.5rem; }

/* ------------------------------------------------------------- price cards -- */
.price-card {
  background: var(--white); border: 1px solid var(--rule); border-top: 2px solid var(--ink);
  padding: clamp(1.15rem, 2.5vw, 1.7rem);
  display: flex; flex-direction: column; gap: 0.85rem;
  box-shadow: var(--shade-1);
}
/* The tier being pointed at sits a step higher than the others. */
.price-card--featured { border-top-color: var(--red); box-shadow: var(--shade-2); }
.price-card__tag {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 800;
  color: var(--red); letter-spacing: 0.02em;
}
.price-card__amount {
  font-family: var(--sans); font-size: var(--step-3); font-weight: 800;
  color: var(--blue-deep); letter-spacing: -0.035em; line-height: 1;
}
.price-card__amount span { font-size: var(--step--1); font-weight: 500; color: var(--ink-3); letter-spacing: 0; }
.price-card ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.45rem; font-family: var(--sans); font-size: var(--step--1); }
.price-card li { position: relative; padding-left: 1.05rem; margin: 0; }
.price-card li::before { content: ""; position: absolute; left: 0; top: 0.62em; width: 0.5rem; height: 2px; background: var(--blue); }

.check-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; font-family: var(--sans); font-size: var(--step--1); }
/* A hanging indent, not a two-column grid. Grid makes every child of the li
   its own grid item, so a single <strong> in the middle of a sentence put the
   words after it into the 1.1rem marker column, one word per line. A list
   item has to survive having emphasis in it. */
.check-list li { position: relative; padding-left: 1.65rem; margin: 0; }
.check-list li::before {
  content: "\2713"; position: absolute; left: 0; top: 0;
  color: var(--ok); font-weight: 800;
}

/* ------------------------------------------------------------ calculator -- */
/* A table set in print rules, like every other table on the site. The figures
   are the point, so they are the only thing set large. */
.calc { width: 100%; border-collapse: collapse; font-family: var(--sans); }
.calc caption {
  text-align: left; font-size: var(--step--2); font-weight: 700;
  color: var(--ink-3); padding-bottom: 0.5rem;
}
.calc th, .calc td { text-align: right; padding: 0.6rem 0 0.6rem 0.75rem; }
.calc thead th {
  font-size: var(--step--2); font-weight: 600; color: var(--ink-3);
  border-bottom: 2px solid var(--ink); white-space: nowrap;
}
.calc thead th:first-child, .calc tbody th { text-align: left; padding-left: 0; }
.calc tbody th {
  font-weight: 700; color: var(--ink); font-size: var(--step-0);
  border-bottom: 1px solid var(--rule); white-space: nowrap;
}
.calc tbody td {
  border-bottom: 1px solid var(--rule);
  font-weight: 700; color: var(--blue-deep);
  font-size: var(--step-1); letter-spacing: -0.02em;
}
.calc tbody tr:last-child th, .calc tbody tr:last-child td { border-bottom: 2px solid var(--ink); }

/* ------------------------------------------------------------------- meta -- */
.meta { display: flex; flex-wrap: wrap; gap: 0.4rem 1.25rem; font-family: var(--sans); font-size: var(--step--1); color: var(--ink-3); }
.meta__item { display: inline-flex; align-items: center; gap: 0.4rem; }
.meta__item svg { width: 0.95rem; height: 0.95rem; fill: currentColor; }

.badge, .pill {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  padding: 0.22rem 0.55rem; border-radius: var(--radius);
  background: var(--info-bg); color: var(--info-fg);
}
.badge--upcoming { background: var(--ok-bg); color: var(--ok-fg); }
.badge--past { background: var(--paper-2); color: var(--ink-3); }
.pill__dot { width: 0.45rem; height: 0.45rem; border-radius: 50%; background: currentColor; }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 0; border-top: 2px solid var(--ink); }
.stat-row > * { padding: 1rem 1.25rem 1rem 0; border-right: 1px solid var(--rule); }
.stat-row > *:last-child { border-right: 0; }
.stat__value {
  display: block; font-family: var(--sans); font-size: var(--step-3); font-weight: 800;
  color: var(--blue-deep); letter-spacing: -0.035em; line-height: 1;
}
.stat__label { display: block; font-family: var(--sans); font-size: var(--step--1); color: var(--ink-3); margin-top: 0.3rem; }

.breadcrumb { font-family: var(--sans); font-size: var(--step--1); color: var(--ink-3); margin-bottom: 0.85rem; }
.breadcrumb a { color: var(--ink-3); }
.breadcrumb a:hover { color: var(--blue); }

.pagination { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; font-family: var(--sans); margin-top: 2rem; }
.pagination a:hover, .pagination a:focus-visible { box-shadow: var(--shade-1); border-color: var(--rule-firm); }
.pagination a, .pagination span {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.4rem; padding: 0.45rem 0.7rem;
  border: 1px solid var(--rule); text-decoration: none; color: var(--ink-2);
  font-size: var(--step--1); font-weight: 600;
  transition: box-shadow .16s ease, border-color .16s ease, color .16s ease;
}
.pagination a:hover { border-color: var(--ink); color: var(--ink); }
.pagination [aria-current] { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.alert {
  display: flex; gap: 0.75rem; align-items: flex-start;
  font-family: var(--sans); font-size: var(--step--1);
  border: 1px solid var(--rule); border-left: 3px solid var(--ink);
  background: var(--white); padding: 0.9rem 1.1rem; margin-bottom: 1.25rem;
}
.alert { box-shadow: var(--shade-1); }
.alert__icon { flex: 0 0 auto; }
.alert__icon svg { width: 1.1rem; height: 1.1rem; fill: currentColor; }
.alert--success { background: var(--ok-bg); border-left-color: var(--ok-fg); color: var(--ok-fg); }
.alert--error { background: var(--err-bg); border-left-color: var(--err-fg); color: var(--err-fg); }
.alert--warning { background: var(--warn-bg); border-left-color: var(--warn-fg); color: var(--warn-fg); }
.alert--info { background: var(--info-bg); border-left-color: var(--info-fg); color: var(--info-fg); }

/* ------------------------------------------------------------------ lists -- */
.contact-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.9rem; font-family: var(--sans); font-size: var(--step--1); }
.contact-list li { display: grid; grid-template-columns: 2.1rem minmax(0, 1fr); gap: 0.8rem; align-items: start; margin: 0; }
.contact-list__mark {
  display: grid; place-items: center; width: 2.1rem; height: 2.1rem;
  border: 1px solid var(--rule); background: var(--paper-2); color: var(--blue);
}
.contact-list__mark svg { width: 1rem; height: 1rem; fill: currentColor; }

.network-list, .chip-list { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0; margin: 0; list-style: none; font-family: var(--sans); }
.network-list li, .chip-list li {
  margin: 0; background: var(--white); border: 1px solid var(--rule);
  padding: 0.35rem 0.75rem; font-size: var(--step--1); font-weight: 600; color: var(--ink-2);
}

/* -------------------------------------------------------------- logo wall -- */
/* The rules are drawn by the items, not by the container.
   This used to be a 1px gap over a --rule background, which is a neat trick
   until the last row is partial: the cells that never get an item have no
   white to paint and the container's rule colour shows through as a solid
   grey slab. Twelve logos in eight columns left four of them, half a row wide,
   under the home page's partner wall. The partner directory has it worse — the
   filter sets `hidden` on a logo, so a slab appears the moment anyone types.

   Each item now carries its own right and bottom hairline and the container
   supplies the top and left, so the wall ends where the logos end at every
   width and every count. */
.logo-wall {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
  border-top: 1px solid var(--rule); border-left: 1px solid var(--rule);
}
.logo-wall__item {
  background: var(--white); display: grid; place-items: center;
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  padding: 1rem; min-height: 5rem; text-decoration: none; color: var(--ink-3);
  font-family: var(--sans); font-size: var(--step--2); text-align: center;
}
/* In colour. These were grayscale at 62% until 4 September 2026, on the usual
   argument that a wall of mixed logos is calmer desaturated. It is also a wall
   of sixty-four companies who paid to be on it, several of whose marks are
   nothing but their colour, and a grey wall says less about the association
   than a bright one does. The hairline grid is what holds it together. */
.logo-wall__item img { max-height: 2.4rem; width: auto; object-fit: contain; transition: transform .16s ease; }
.logo-wall__item:hover img { transform: scale(1.04); }
.logo-wall__item { transition: box-shadow .16s ease; }
.logo-wall__item:hover, .logo-wall__item:focus-visible { box-shadow: var(--shade-2); }

.partner-group + .partner-group { margin-top: clamp(2rem, 4vw, 2.75rem); }
.partner-group h3 {
  font-family: var(--sans); font-size: var(--step--1); font-weight: 800;
  color: var(--red); border-bottom: 2px solid var(--ink);
  padding-bottom: 0.35rem; margin-bottom: 0.85rem;
}

/* Leadership sits at three across rather than using the shared .grid--3, which
   is auto-fit and makes four columns on a wide screen. There are five people:
   four across strands the fifth beside three empty slots, three across gives a
   fuller second row and a larger portrait. */
.team-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 62rem) { .team-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 38rem) { .team-grid { grid-template-columns: 1fr; } }

/* A flex column with the controls pushed down, so the Bio tag and the profile
   icons sit on one line across the row however long a person's role runs.
   Without it a three-line role shunts that card's controls down alone. */
.team-card { display: flex; flex-direction: column; gap: 0.75rem; }
.team-card__foot { margin-top: auto; }
.team-card__photo {
  width: 100%; aspect-ratio: 4 / 5;
  border: 1px solid var(--rule); background: var(--paper-2);
  display: grid; place-items: center; overflow: hidden;
  font-family: var(--sans); font-weight: 800; font-size: 1.6rem; color: var(--ink-3);
}
/* The frame is 4:5 and the photographs are square, so the image has to be told
   to fill and crop. object-fit on the container does nothing — it only applies
   to replaced elements, which a div is not. */
.team-card__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Profile links. Icons only, so each anchor carries its own aria-label. */
.team-card__links { display: flex; gap: 0.5rem; list-style: none; margin: 0; padding: 0; }
.team-card__links a {
  display: grid; place-items: center; width: 2.1rem; height: 2.1rem;
  border: 1px solid var(--rule-firm); color: var(--ink-3);
  background: var(--paper-2);
  transition: color .15s ease, border-color .15s ease,
              background-color .15s ease, box-shadow .15s ease;
}
.team-card__links a:hover, .team-card__links a:focus-visible {
  color: var(--blue); border-color: var(--blue); background: var(--white);
  box-shadow: 0 0 0 3px rgb(11 58 143 / 0.12);
}
.team-card__links svg { width: 1rem; height: 1rem; fill: currentColor; }

/* ------------------------------------------------------------------ tip -- */
/* A small caption that names an icon-only control, in the sans, set as a solid
   block of ink the way a photo credit would be. Shown on hover and on keyboard
   focus, so it is not pointer-only. The text repeats the control's aria-label,
   which already names it for a screen reader, so nothing is lost when this
   cannot be seen. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 0.45rem); left: 50%;
  transform: translateX(-50%) translateY(0.2rem);
  background: var(--ink); color: var(--paper);
  font-family: var(--sans); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.07em; text-transform: uppercase;
  padding: 0.32rem 0.5rem; white-space: nowrap; line-height: 1;
  opacity: 0; pointer-events: none; z-index: 5;
  transition: opacity .14s ease, transform .14s ease;
}
[data-tip]:hover::after, [data-tip]:focus-visible::after {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  [data-tip]::after { transition: none; }
}
/* The close control sits in the top-right corner: above it is outside the
   dialog, which clips its own overflow, and below it is the 2px rule. Its
   caption goes to the left instead, where the masthead has room. */
.bio__close[data-tip]::after {
  bottom: auto; top: 50%; left: auto; right: calc(100% + 0.5rem);
  transform: translateY(-50%) translateX(0.2rem);
}
.bio__close[data-tip]:hover::after,
.bio__close[data-tip]:focus-visible::after { transform: translateY(-50%) translateX(0); }

/* Wide content scrolls inside its own box. The page itself must never scroll
   sideways — a horizontal scrollbar on a phone is the reader losing the
   left-hand edge of every line. */
/* Body text with a source note beside it.
   
   The ELD register borrowed this markup from the Spread specimen — the `.note`
   aside that says where the register was copied from and when — but the rules
   only ever existed in preview-spread.css, which that page does not load. So
   the aside rendered as undifferentiated body text under the article, and the
   provenance of a compliance page read like an afterthought. Carried over here,
   with the same shape and the same 14rem column. */
.withnote {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 14rem;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
.note {
  font-family: var(--sans);
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-3);
  border-top: 2px solid var(--ink);
  padding-top: 0.6rem;
}
.note b { display: block; color: var(--ink); font-weight: 700; margin-bottom: 0.25rem; }
.note p { margin: 0 0 0.6rem; }
.note p:last-child { margin-bottom: 0; }

@media (max-width: 60rem) {
  /* One column, and the note keeps its rule so it still reads as a note rather
     than as more of the article. */
  .withnote { grid-template-columns: minmax(0, 1fr); }
}

.table-scroll { overflow-x: auto; max-width: 100%; }

/* ------------------------------------------------------------ usdot check -- */
/* The answer to "can I give this outfit a load", said before the record it was
   read from. Three tones and no more: it is allowed to operate, it is allowed
   but cannot haul for hire, or it is not allowed. Colour carries the same three
   states the status pills already use elsewhere, so nothing new has to be
   learned to read it. */
.verdict {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  padding: clamp(1rem, 2.5vw, 1.5rem);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--ink-3);
  background: var(--white);
  box-shadow: var(--shade-1);
}
.verdict--good { border-left-color: var(--ok);  background: var(--ok-bg); }
.verdict--warn { border-left-color: #7a5c00;    background: var(--warn-bg); }
.verdict--bad  { border-left-color: var(--red); background: var(--err-bg); }

.verdict__headline {
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  font-size: var(--step-1);
  letter-spacing: -0.01em;
}
.verdict--good .verdict__headline { color: var(--ok-fg); }
.verdict--warn .verdict__headline { color: var(--warn-fg); }
.verdict--bad  .verdict__headline { color: var(--err-fg); }
.verdict__detail { margin: 0.4rem 0 0; max-width: 62ch; }

/* Not an answer: a bad number, a limit reached, FMCSA not responding. Quieter
   than a verdict, because it is about the request rather than the carrier. */
.notice {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  padding: clamp(0.9rem, 2vw, 1.25rem);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--rule-firm);
  background: var(--paper-2);
}
.notice p { margin: 0; max-width: 62ch; }
.notice--warn { border-left-color: #7a5c00; background: var(--warn-bg); }

/* A record table is a list of labelled facts, not a grid of data. Left to
   itself the browser gives the label column half the width and strands each
   value in the middle of the row; the label only needs to be as wide as the
   longest label. The value column takes the rest and the pair reads as one
   line, which is how somebody checks a fact against what they were told. */
/* `.table th` gives every th the 2px head rule, including a row header inside
   tbody — so a label sat under a heavy dark line while the value beside it had
   a hairline, and the row read as two broken segments. A row header is a label,
   not a column head: same hairline as its own row. */
.table--record tbody th[scope="row"],
.table--figures tbody th[scope="row"] { border-bottom: 1px solid var(--rule); }
.table--record tbody tr:last-child th[scope="row"],
.table--figures tbody tr:last-child th[scope="row"] { border-bottom: 2px solid var(--ink); }

.table--record th[scope="row"] {
  width: 1%;
  white-space: nowrap;
  padding-right: 1.75rem;
  vertical-align: baseline;
}
.table--record td { vertical-align: baseline; }

/* Figures line up under each other and never wrap mid-number. */
.table--figures td.num,
.table--figures th[scope="col"] { text-align: right; }
.table--figures th[scope="col"]:first-child,
.table--figures th[scope="row"] { text-align: left; }
.table--figures th[scope="row"] { white-space: nowrap; font-weight: 700; }

/* The lookup form, on this page and on the home page. */
.usdot-search { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: flex-end; }
/* 10rem, not 16: a USDOT number is eight digits, so the box never needs to be
   wide, and a 16rem minimum pushed the button onto its own line on a phone —
   where the pair reads as a search box and its button or it reads as nothing. */
.usdot-search .field { flex: 1 1 10rem; margin: 0; }
.usdot-search input { width: 100%; font-variant-numeric: tabular-nums; }
/* The hint takes a line of its own beneath both. Left inside .field it made the
   field taller than its input, and the button — bottom-aligned to the field —
   came to rest beside the hint rather than beside the box it submits. */
.usdot-search .field__hint { flex: 1 0 100%; margin: 0.1rem 0 0; }
/* Room around the challenge, above and below. It arrives as a 300x65 iframe
   with no margin of its own: unspaced it sat hard against the field above it on
   the sign-in, and then hard against the submit button below. */
.cf-turnstile { margin: 1rem 0; }
.usdot-search + .cf-turnstile { margin-top: 1.1rem; }


/* The table, set in print rules: 2px above the body, hairlines between the
   rows, 2px under the last. No vertical rules, no zebra striping.

   design-spread.md §4 and architecture.md have described this component since
   the Spread rewrite, but it was never carried across from preview-spread.css
   and only existed there. The home page's compliance table therefore carried a
   `class="table"` that styled nothing and reimplemented the whole thing in
   eight inline style attributes — reproducing every rule except the 2px one
   that closes the table, so it ended in a hairline and trailed off. */
.table { width: 100%; border-collapse: collapse; font-family: var(--sans); font-size: var(--step--1); }
.table caption { text-align: left; font-weight: 700; color: var(--ink); font-size: var(--step-0); padding-bottom: 0.6rem; }
.table th {
  text-align: left; font-weight: 700; color: var(--ink-3);
  font-size: var(--step--2); letter-spacing: 0.03em;
  padding: 0.5rem 0.75rem 0.5rem 0; border-bottom: 2px solid var(--ink);
}
.table td { padding: 0.7rem 0.75rem; padding-left: 0; border-bottom: 1px solid var(--rule); color: var(--ink); vertical-align: baseline; }
.table tr:last-child td { border-bottom: 2px solid var(--ink); }
.table th:last-child, .table td:last-child { padding-right: 0; }
/* The first column is the figure the row is looked up by — a date, a bracket,
   a gallon count. Blue marks reference, and tabular figures keep the column
   aligned when the rows are dates of different widths. */
.table td.num { font-weight: 700; color: var(--blue-deep); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The status of a row: normally apparatus, red only when it is the thing the
   reader has to act on this week. Red is the page's one action colour, so a
   table may light up one row and not four. */
.table .state { font-weight: 700; white-space: nowrap; color: var(--ink-3); }
.table .state--now { color: var(--red); }

/* --- a table that stops being one on a phone --------------------------------
   Three or four columns of print rules do not survive a 336px measure. The
   deadline table gave "What happens" a 101px ribbon, so a six-word headline ran
   down four lines and its explanation down eleven, and the status column was
   cut off mid-word; the register table gave the device name 68px and the maker
   40px. Scrolling the box sideways is the fallback the design system already
   has, and it is a poor one here — the reader loses the date while reading the
   consequence.

   So below 44rem each row becomes a block: the thing you look the row up by and
   its status share the top line, and the prose runs the full measure under
   them. The rules stay, because the rules are what make it a register rather
   than a list of cards. */
@media (max-width: 44rem) {
  .table--stack, .table--stack tbody, .table--stack tr, .table--stack td { display: block; width: 100%; }
  /* The headings are the labels for cells that no longer sit under them, so
     they go from the screen but not from the accessibility tree. */
  .table--stack thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  .table--stack tr {
    display: grid; gap: 0.15rem 0.75rem; align-items: baseline;
    padding: 0.95rem 0; border-bottom: 1px solid var(--rule);
  }
  .table--stack tr:last-child { border-bottom: 2px solid var(--ink); }
  /* `.table tr:last-child td` is (0,2,2) and would beat a bare `.table--stack td`,
     so the closing rule reappeared under the last row's first cell, cutting the
     final entry in half. Matched here at the same weight, later in the file. */
  .table--stack td,
  .table--stack tr:last-child td { border: 0; padding: 0; }
  .table--stack td:empty { display: none; }
  .table--stack .state { white-space: normal; text-align: right; }

  /* The deadline table: when | what + detail | status. */
  .table--due tr { grid-template-columns: max-content minmax(0, 1fr); }
  .table--due td:nth-child(1) { grid-column: 1; grid-row: 1; }
  .table--due td:nth-child(3) { grid-column: 2; grid-row: 1; justify-self: end; }
  .table--due td:nth-child(2) { grid-column: 1 / -1; grid-row: 2; margin-top: 0.25rem; }
  .table--due .table__detail { margin-top: 0.35rem; }

  /* The register: status | device + model | identifier | maker. The device
     leads, because that is what a carrier is looking for. */
  .table--eld tr { grid-template-columns: minmax(0, 1fr) max-content; }
  .table--eld td:nth-child(2) { grid-column: 1 / -1; grid-row: 1; }
  .table--eld td:nth-child(1) { grid-column: 1; grid-row: 2; margin-top: 0.3rem; }
  .table--eld td:nth-child(3) { grid-column: 2; grid-row: 2; margin-top: 0.3rem; justify-self: end; }
  .table--eld td:nth-child(4) { grid-column: 1 / -1; grid-row: 3; color: var(--ink-3); font-size: var(--step--2); }
}
/* A row that names a document links to it. Blue marks reference, and the
   underline stays: this is the one place on the page where the reader is being
   invited to go and check the claim, so it should look like it. */
.table__link { color: var(--blue-deep); font-weight: 700; text-underline-offset: 0.15em; }
.table__link:hover { color: var(--red); }
/* One sentence in the reader's terms, under the headline it belongs to. Set
   smaller and in --ink-3 so the column still scans as a list of headlines. */
.table__detail { display: block; margin-top: 0.3rem; color: var(--ink-3); font-weight: 400; max-width: 52ch; }

/* ------------------------------------------------------------ eld register -- */
/* The lookup. A search row that reads as one instrument rather than three
   controls: the field grows, the filter and the button take what they need. */
.eld-search { display: grid; gap: 1rem; align-items: end; }
@media (min-width: 46rem) {
  .eld-search { grid-template-columns: minmax(0, 1fr) minmax(9rem, .5fr) max-content; }
}
.eld-search .field { margin: 0; }
.eld-search .btn { white-space: nowrap; }
.eld-count { font-family: var(--sans); font-size: var(--step--1); color: var(--ink-3); }

/* The status of a listing. Not a badge with a fill — this table is set in print
   rules, and a row of coloured pills would be the loudest thing on the page.
   A rule to the left, the way a register marks a changed line. */
.eld-tag {
  display: inline-block; font-family: var(--sans); font-weight: 800;
  font-size: var(--step--2); letter-spacing: 0.04em; text-transform: uppercase;
  padding-left: 0.5rem; border-left: 3px solid currentColor; white-space: nowrap;
}
.eld-tag--ok        { color: var(--ok); }
.eld-tag--revoked   { color: var(--red); }
.eld-tag--withdrawn { color: var(--ink-3); }
.eld-when { display: block; margin-top: 0.25rem; font-size: var(--step--2); color: var(--ink-3); }
.eld-sub  { display: block; margin-top: 0.15rem; font-size: var(--step--2); color: var(--ink-3); font-weight: 400; }

.eld-siblings { list-style: none; padding: 0; margin: 0.85rem 0 0; display: grid; gap: 0.55rem; }
.eld-siblings li { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0.75rem; align-items: baseline; font-size: var(--step--1); }

/* The home page's entry point: a boxout that happens to contain a form. */
/* Shared by the two front-page lookup tools: the ELD register and the USDOT
   check. Named for the first one that needed it; `.tool-panel` is the name to
   use for anything new, so a third tool does not have to be called an ELD. */
.eld-panel, .tool-panel { display: grid; gap: 1.25rem; }
@media (min-width: 54rem) { .eld-panel, .tool-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; } }
.eld-panel__figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); border-top: 2px solid var(--ink); }
.eld-panel__figures > div { padding: 0.85rem 1rem 0.85rem 0; border-right: 1px solid var(--rule); }
.eld-panel__figures > div:last-child { border-right: 0; }
/* A column after the first starts clear of the rule it sits against, rather
   than with its caption touching the hairline. */
.eld-panel__figures > div + div { padding-left: 1rem; }

/* The results modal. Same construction as the biography dialog: a flex column
   so the body scrolls and the header and footer do not, which is what stopped
   that one clipping its own footer. */
.eld-modal, .tool-modal { border: 0; padding: 0; max-width: min(62rem, 94vw); width: 100%; background: transparent; }
.eld-modal::backdrop, .tool-modal::backdrop { background: rgb(19 21 25 / 0.55); }
.eld-modal__in, .tool-modal__in {
  display: flex; flex-direction: column; max-height: min(84vh, 46rem);
  background: var(--white); border: 1px solid var(--ink); box-shadow: var(--shade-3);
}
.eld-modal__head { padding: 1.1rem 1.35rem; border-bottom: 2px solid var(--ink); display: grid; gap: 0.75rem; }
.eld-modal__head .eld-search { grid-template-columns: minmax(0, 1fr) max-content; gap: 0.6rem; }
.eld-modal__title { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.eld-modal__title h2 { margin: 0; font-size: var(--step-1); }
.eld-modal__body { overflow-y: auto; padding: 0 1.35rem; min-height: 0; }
.eld-modal__foot {
  padding: 0.9rem 1.35rem; border-top: 1px solid var(--rule); background: var(--paper-2);
  display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; align-items: center; justify-content: space-between;
  font-family: var(--sans); font-size: var(--step--2); color: var(--ink-3);
}
.eld-modal__close { border: 0; background: none; cursor: pointer; font-family: var(--sans); font-size: var(--step--1); color: var(--ink-3); padding: 0.35rem; }
.eld-modal__close:hover { color: var(--red); }
.eld-modal .table { font-size: var(--step--2); }
.eld-modal__pager { display: flex; gap: 0.5rem; align-items: center; }
.eld-modal__pager button {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  border: 1px solid var(--rule-firm); background: var(--white); color: var(--ink);
  padding: 0.3rem 0.7rem; cursor: pointer;
}
.eld-modal__pager button[disabled] { color: var(--ink-3); cursor: default; opacity: 0.5; }
.eld-modal__pager button:not([disabled]):hover { border-color: var(--ink); box-shadow: var(--shade-1); }
.eld-busy { padding: 1.5rem 0; font-family: var(--sans); font-size: var(--step--1); color: var(--ink-3); }

/* The type-ahead. A sheet of results laid over the page, so it takes shade 3 —
   the only other thing that does is the biography dialog, and for the same
   reason: it genuinely floats rather than sitting in the flow. */
.eld-combo { position: relative; }
.eld-suggest {
  position: absolute; z-index: 30; top: calc(100% + 2px); left: 0; right: 0;
  margin: 0; padding: 0; list-style: none;
  background: var(--white); border: 1px solid var(--ink); box-shadow: var(--shade-3);
  max-height: 19rem; overflow-y: auto;
}
.eld-suggest__item {
  display: grid; gap: 0.25rem;
  padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--rule); cursor: pointer;
  font-family: var(--sans); font-size: var(--step--1);
}
.eld-suggest__item:last-child { border-bottom: 0; }
.eld-suggest__item[aria-selected="true"], .eld-suggest__item:hover { background: var(--paper-2); }
.eld-suggest__name { font-weight: 700; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
/* Status and identifier share the line under the name. The status labels run to
   "Withdrawn by the maker" — beside the name rather than under it, they left the
   name a column three words wide. */
.eld-suggest__meta {
  display: flex; flex-wrap: wrap; gap: 0.35rem 0.6rem; align-items: baseline;
  font-size: var(--step--2); color: var(--ink-3); min-width: 0; overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- bio dialog -- */
/* A modal <dialog> does not stop the page behind it scrolling — a wheel over
   the backdrop runs the page away underneath the reader. scrollbar-gutter keeps
   the width reserved so nothing shifts sideways when the bar goes. */
html { scrollbar-gutter: stable; }
html:has(dialog.bio[open]), html:has(dialog.eld-modal[open]) { overflow: hidden; }

/* The Bio tag on a leadership card. Sans, letterspaced, hairline box — the
   same register as .eyebrow and the table headings, so it reads as apparatus
   rather than as a call to action. Red is reserved for the page's one action. */
.team-card__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem; padding-top: 0.25rem;
}
/* The Bio tag and the profile icons are controls, and had been drawn as
   marginalia — a hairline box with no fill reads as a label, not as something
   to press. They now sit on a tinted surface, and on hover or keyboard focus
   they take the blue that marks reference throughout the site, with a soft
   ring behind them.
   That ring is the one deliberate exception to "hairlines, not shadows": it is
   interface feedback rather than decoration, it only exists while a pointer or
   focus is on the control, and without it neither control announces itself. */
.tag-bio {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; text-decoration: none;
  color: var(--ink-3); background: var(--paper-2);
  border: 1px solid var(--rule-firm);
  padding: 0.34rem 0.66rem; line-height: 1;
  transition: color .15s ease, border-color .15s ease,
              background-color .15s ease, box-shadow .15s ease;
}
.tag-bio:hover, .tag-bio:focus-visible {
  color: var(--blue); border-color: var(--blue); background: var(--white);
  box-shadow: 0 0 0 3px rgb(11 58 143 / 0.12);
}

/* The dialog itself. A sheet of paper laid over the page: hairline edge, no
   rounded corners, and no drop shadow — the backdrop does the separating. */
.bio {
  padding: 0; border: 1px solid var(--rule-firm); background: var(--paper);
  color: var(--ink-2); max-width: min(68rem, calc(100vw - 2rem));
  width: 100%; max-height: min(92vh, 58rem);
  box-shadow: var(--shade-3);
  /* The dialog itself must never scroll: one scrollbar, on the body, or none. */
  overflow: hidden;
}
.bio::backdrop { background: rgb(19 21 25 / 0.55); }

/* Two things are needed to make the body — and only the body — scroll.
 *
 * The dialog must lay its child out as a flex column. `max-height: 100%` on
 * the sheet does nothing on its own, because a percentage max-height needs a
 * definite height to resolve against and the dialog only has a *max*-height.
 * The sheet was therefore free to grow past the dialog, and `overflow: hidden`
 * then clipped the bottom of the biography off instead of scrolling it.
 *
 * And the scrolling track needs minmax(0, 1fr) rather than 1fr, because a grid
 * track's minimum is auto: a plain 1fr refuses to shrink below its content and
 * pushes the overflow back up the tree.
 *
 * dialog is display:none until it is open, so only [open] may set display. */
.bio[open] { display: flex; flex-direction: column; }
.bio__sheet {
  display: grid; grid-template-rows: auto minmax(0, 1fr);
  flex: 1 1 auto; min-height: 0; width: 100%;
}

.bio__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.85rem clamp(1.1rem, 3vw, 2.1rem);
  border-bottom: 2px solid var(--ink);
}
.bio__logo { width: 88px; height: auto; display: block; }
.bio__close {
  font-family: var(--sans); font-size: 1.5rem; line-height: 1;
  text-decoration: none; color: var(--ink-3);
  width: 2.2rem; height: 2.2rem; display: grid; place-items: center;
  border: 1px solid transparent;
}
.bio__close:hover, .bio__close:focus-visible { color: var(--red); border-color: var(--rule); }

.bio__body {
  display: grid; grid-template-columns: 168px 1fr;
  gap: clamp(1.1rem, 2.2vw, 1.8rem);
  padding: clamp(1.2rem, 2.4vw, 1.8rem) clamp(1.2rem, 2.6vw, 2rem);
  overflow-y: auto; min-height: 0;
}
.bio__portrait {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block;
  border: 1px solid var(--rule); align-self: start;
}
.bio__text h2 { font-size: var(--step-2); margin: 0.2rem 0 0; }
.bio__role {
  font-family: var(--sans); font-size: var(--step--1);
  color: var(--ink-3); margin: 0.35rem 0 0;
}
.bio__text .prose { margin-top: 0.95rem; }
.bio__text .prose p + p { margin-top: 0.85rem; }
.bio__links { margin-top: 1.2rem; padding-top: 1rem; border-top: 1px solid var(--rule); }

@media (max-width: 40rem) {
  .bio__body { grid-template-columns: 1fr; }
  .bio__portrait { width: 132px; }
}

/* Without JavaScript the Bio tag is an ordinary fragment link, so :target has
   to do the showing. Only when the script has not run — once it has, the tag is
   intercepted and the dialog is opened properly with showModal(). */
html:not(.js) .bio:target {
  display: block; position: fixed; z-index: 60;
  inset-block-start: 50%; inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 0 100vmax rgb(19 21 25 / 0.55);
}

/* The masthead is flat against the page until the reader leaves the top of it,
   then it lifts, so it reads as sitting over the article rather than being part
   of it. site.js sets data-stuck. */
.site-header[data-stuck] { box-shadow: var(--shade-1); }

/* --------------------------------------------------------------- cta band -- */
.cta-band {
  background: var(--blue-deep); color: #c3d1ea;
  padding-block: clamp(2.5rem, 6vw, 4rem);
  /* Side padding of its own. It only had padding-block, and leaned on an inner
     .container for the rest — which two of the seven usages have. The other
     five are a <div class="cta-band"> already inside a container, so on a phone
     the heading sat hard on the navy edge with nothing beside it. */
  padding-inline: clamp(1.25rem, 5vw, 2.5rem);
}
/* Where a container IS nested inside, it must not indent again on top of the
   band's own padding; its max-width still does the centring. */
.cta-band > .container { padding-inline: 0; }
.cta-band h2 { color: #fff; max-width: 22ch; }
.cta-band p { color: #b8c9e6; margin-top: 0.75rem; max-width: 54ch; }
.cta-band .btn--outline { border-color: rgb(255 255 255 / 0.6); color: #fff; }
.cta-band .btn--outline:hover { background: #fff; color: var(--blue-deep); }

/* ----------------------------------------------------------------- footer -- */
.site-footer {
  background: var(--ink); color: #9aa1ab;
  font-family: var(--sans); font-size: var(--step--1);
  padding-block: clamp(2.25rem, 5vw, 3.25rem) 2rem;
}
.footer__grid { display: grid; grid-template-columns: minmax(0, 1.5fr) repeat(4, minmax(0, 1fr)); gap: 2rem; }
.site-footer h3 { color: #fff; font-family: var(--sans); font-size: var(--step--1); font-weight: 800; margin-bottom: 0.7rem; letter-spacing: 0; }
.site-footer ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.4rem; }
.site-footer li { margin: 0; }
.site-footer a { color: #d4d8de; text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
/* The mark in colour, on its own plate.
   It was `brightness(0) invert(1)` — every pixel crushed to white, so the truck
   was a silhouette and the tricolour on it was gone. Removing the filter alone
   is not enough: the footer ground is --ink, and the ROMOTANA wordmark under
   the truck is near-black, so the full-colour mark would lose its own name into
   the background. The plate is what lets it be in colour here — the same move
   the logo wall makes, a white cell under a coloured mark. */
.footer__brand img {
  display: block; margin-bottom: 0.9rem;
  background: var(--paper); padding: 0.6rem 0.75rem;
  border: 1px solid rgb(255 255 255 / 0.12);
  width: auto; height: auto; max-width: 9.5rem;
}
.footer__brand p { color: #9aa1ab; max-width: 40ch; }
.footer__bottom {
  border-top: 1px solid #2a2e35; margin-top: 2rem; padding-top: 1.25rem;
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; justify-content: space-between;
  font-size: var(--step--2); color: #7d848e;
}
.footer__bottom a { color: #b6bcc4; }
@media (max-width: 60rem) { .footer__grid { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); } }

/* ------------------------------------------------------------------ print -- */
@media print {
  .topbar, .site-header, .cta-band, .site-footer, .nav-toggle { display: none; }
  body { background: #fff; font-size: 11pt; }
  a { color: inherit; text-decoration: underline; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ===========================================================================
   The fuel card hero
   ===========================================================================
   One orchestrated moment, on the page that brings members in, and nothing
   else on the site moves like this.

   The entrance is a sequence rather than a single fade. The shadow appears
   first and tight, as if something is approaching the surface. The card comes
   in from above and to the right, turned away, and rotates to face the reader
   as it falls. It overshoots very slightly and settles — a card set down by a
   hand, not a div fading in. The moment it lands, light passes across it once.
   Only then does it begin to breathe.

   Timings are deliberate: 1.15s for the arrival, the sheen at 1.05s so the
   glint reads as the light catching it at the instant it stops, and the float
   from 1.15s so nothing overlaps the landing.
   =========================================================================== */

.hero--card__in { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: clamp(1.5rem, 5vw, 4rem); align-items: center; }
.hero--card .lede { max-width: 40ch; }

.stage {
  position: relative;
  width: min(100%, 27rem);
  aspect-ratio: 768 / 746;
  margin-inline: auto;
  perspective: 1400px;
}

/* Kept out of the card's own transform so it can breathe against the float
   rather than with it — a shadow tightens as the object rises. */
.stage__shadow {
  position: absolute; left: 13%; right: 13%; bottom: 2%; height: 9%;
  border-radius: 50%;
  background: radial-gradient(ellipse at center, rgb(19 21 25 / 0.4), rgb(19 21 25 / 0) 70%);
  filter: blur(7px);
  animation:
    card-shadow-arrive 1.15s cubic-bezier(.16,.84,.3,1) both,
    card-shadow-breathe 5s ease-in-out 1.15s infinite;
}

/* The pointer tilt lives on its own wrapper so it composes with the keyframes
   instead of fighting them for the transform property. */
.stage__tilt { position: absolute; inset: 0; transform-style: preserve-3d; transition: transform .2s ease-out; }

.stage__card {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  will-change: transform;
  animation:
    card-arrive 1.15s cubic-bezier(.16,.84,.3,1) both,
    card-float 5s ease-in-out 1.15s infinite;
}
.stage__card img {
  width: 100%; height: 100%; object-fit: contain;
  filter: drop-shadow(0 16px 22px rgb(19 21 25 / 0.22));
  user-select: none; -webkit-user-drag: none;
}

/* The glint, masked to the card's own outline so it cannot spill onto the page. */
.stage__sheen {
  position: absolute; inset: 0; pointer-events: none;
  /* Same URL the <img> uses, handed down by the page, so the artwork is
     fetched once rather than once per cache-busting query string. */
  -webkit-mask: var(--card-art) center / contain no-repeat;
          mask: var(--card-art) center / contain no-repeat;
  background: linear-gradient(115deg,
    rgb(255 255 255 / 0) 40%, rgb(255 255 255 / 0.5) 50%, rgb(255 255 255 / 0) 60%);
  background-size: 250% 100%;
  background-position: 130% 0;
  mix-blend-mode: screen;
  animation: card-sheen 7s ease-in-out 1.05s infinite;
}

@keyframes card-arrive {
  0%   { opacity: 0; transform: translate3d(7%, -20%, 0) rotateX(26deg) rotateY(-18deg) rotateZ(-6deg) scale(.9); }
  55%  { opacity: 1; }
  80%  { transform: translate3d(0, 1.5%, 0) rotateX(-3deg) rotateY(2deg) rotateZ(.7deg) scale(1.015); }
  100% { opacity: 1; transform: translate3d(0, 0, 0) rotateX(0) rotateY(0) rotateZ(0) scale(1); }
}
@keyframes card-float {
  0%, 100% { transform: translateY(0) rotateZ(0); }
  50%      { transform: translateY(-3.5%) rotateZ(.6deg); }
}
@keyframes card-shadow-arrive {
  0%   { opacity: 0; transform: scale(.55, .6); }
  55%  { opacity: .4; }
  80%  { opacity: .95; transform: scale(1.06, 1.05); }
  100% { opacity: .9; transform: scale(1); }
}
@keyframes card-shadow-breathe {
  0%, 100% { transform: scaleX(1);   opacity: .9; }
  50%      { transform: scaleX(.86); opacity: .55; }
}
@keyframes card-sheen {
  0%, 6%    { background-position: 130% 0; }
  26%, 100% { background-position: -30% 0; }
}

@media (max-width: 60rem) {
  .hero--card__in { grid-template-columns: 1fr; }
  .stage { order: -1; width: min(100%, 20rem); margin-bottom: .5rem; }
}

/* Still life: it arrives already landed, and nothing moves after that. */
@media (prefers-reduced-motion: reduce) {
  .stage__card, .stage__shadow, .stage__sheen { animation: none; }
  .stage__sheen { opacity: 0; }
  .stage__shadow { opacity: .9; }
  .stage__tilt { transition: none; }
}

/* ------------------------------------------------------- the loading pump -- */
/* The artwork is the heaviest asset on the page. Rather than spend the
   entrance on a frame that has not decoded yet, the card is held at zero and a
   pump counter runs in its place — the thing a driver actually watches while
   waiting. It counts gallons only; a dollar figure here would read as a
   savings claim, which this page exists to avoid making. */
.pump {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-content: center; justify-items: center; gap: .5rem;
  font-family: var(--sans); text-align: center;
  transition: opacity .45s ease, transform .45s ease;
}
.pump__digits {
  font-size: clamp(2rem, 1.4rem + 2.6vw, 3.1rem);
  font-weight: 800; letter-spacing: 0.06em; color: var(--blue-deep);
  line-height: 1;
}
.pump__unit { font-size: var(--step--2); font-weight: 600; color: var(--ink-3); }
.pump__bar {
  display: block; width: min(58%, 12rem); height: 2px;
  background: var(--rule); overflow: hidden; margin-top: .35rem;
}
.pump__bar i {
  display: block; height: 100%; width: 0;
  background: linear-gradient(90deg, var(--blue) 0 33.34%, var(--yellow) 33.34% 66.67%, var(--red) 66.67%);
  background-size: 12rem 100%;
  transition: width .25s ease-out;
}

/* Held until the image has decoded. */
.stage--awaiting .stage__card,
.stage--awaiting .stage__shadow { opacity: 0; animation: none; }
.stage--awaiting .stage__card { transform: none; }

/* Released: the pump steps aside as the card arrives over it. */
.stage--ready .pump { opacity: 0; transform: scale(.94); pointer-events: none; }
.stage:not(.stage--awaiting) .pump { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .pump { transition: none; }
  .pump__bar i { transition: none; }
}

/* The ELD section's illustration. One of the flat editorial marks, sized to the
   figures under it rather than to the column, so it reads as a plate on the page
   rather than as a banner. Hidden on a phone: it says nothing the heading does
   not, and it would push the lookup — the only thing on this section anyone came
   for — below the fold. */
/* The photograph beside a tool. It replaced a drawing on 6 September, so it
   takes the treatment photographs get here — full column width, a 1px ink rule
   and the second shadow — rather than the hairline a drawing was given. It
   stays on at narrow widths too: a drawing that added nothing was worth
   hiding, a photograph of the thing being looked up is not. */
.tool-panel__photo {
  display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover;
  margin-bottom: 1rem; border: 1px solid var(--ink); border-radius: var(--radius);
  box-shadow: var(--shade-2); background: var(--paper-2);
}

/* ── Member area ────────────────────────────────────────────────────────────
   The signed-in half of the site. It borrows the whole Spread palette and
   both faces; the only new idea here is the identity band, and it is a band
   rather than a card because it is a masthead for the pages under it, not a
   thing on the page.

   Nothing here uses a new colour. A members' area that looked like a
   different product would be a different product. */

/* The sign-in link in the masthead. Set apart from the page links by a
   hairline rather than by weight or colour: it is a door, not a louder page. */
.nav__member {
  padding-left: 1.4rem;
  margin-left: -0.4rem;
  border-left: 1px solid var(--rule);
  color: var(--blue) !important;
}
.nav__member:hover { color: var(--blue-deep) !important; }
@media (max-width: 60rem) {
  .nav__member { border-left: 0; padding-left: 0; margin-left: 0; }
}

.member-bar {
  background: var(--paper-2);
  border-bottom: 1px solid var(--rule);
  font-family: var(--sans);
}
.member-bar__in {
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.4rem;
  align-items: baseline; justify-content: space-between;
  padding-block: 0.7rem;
}
.member-bar__who { display: flex; flex-wrap: wrap; gap: 0.55rem; align-items: baseline; }
/* The account number is the thing people read out on the phone, so it is set
   to be read out: tabular, spaced, and never wrapped mid-number. */
.member-bar__ref {
  font-size: var(--step--2); font-weight: 700;
  letter-spacing: .1em; color: var(--blue);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
.member-bar__name {
  font-family: var(--serif); font-size: var(--step--1); color: var(--ink-2);
}
.member-bar__nav { display: flex; flex-wrap: wrap; gap: 1.1rem; align-items: baseline; }
.member-bar__nav a,
.member-bar__nav button {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 600;
  color: var(--ink-2); text-decoration: none;
  border: 0; background: none; cursor: pointer;
  line-height: 1.4;
  /* Tall enough to hit with a thumb. At padding:0 these were 17px high,
     which is half the 24px a touch target needs and exactly the size of
     the text: fine to read, unpleasant to press, and Sign out is the
     one nobody wants to miss. */
  padding-block: 0.4rem;
  padding-inline: 0;
}
.member-bar__nav a:hover,
.member-bar__nav button:hover { color: var(--blue); }
.member-bar__nav a[aria-current] { color: var(--ink); box-shadow: 0 2px 0 var(--red); }
.member-bar__nav form { display: inline; }

/* Label/value pairs. A two-column grid rather than a table, because these are
   facts about one thing, not rows to compare. Labels in the sans so the values
   in the serif are what the eye lands on. */
.member-facts {
  display: grid; grid-template-columns: minmax(9rem, auto) 1fr;
  gap: 0.5rem 1.2rem; margin: 0;
}
.member-facts dt {
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
  align-self: baseline;
}
.member-facts dd {
  margin: 0; font-size: var(--step--1); color: var(--ink);
  overflow-wrap: anywhere;
}
@media (max-width: 34rem) {
  /* One column below the point where the label column starts squeezing the
     values onto three lines each. */
  .member-facts { grid-template-columns: 1fr; gap: 0.15rem; }
  .member-facts dd { margin-bottom: 0.7rem; }
}

/* The watch button: a warning sign that means "tell me if this changes".
   Its own class rather than a variant, because the sign has to keep its colour
   while the button around it stays an outline — a solid amber button beside a
   USDOT record would read as the verdict rather than as a control. */
.btn--watch { gap: 0.45rem; }
.btn--watch span[aria-hidden] { color: var(--warn-fg); font-size: 1.05em; line-height: 1; }
.btn--watch:hover span[aria-hidden] { color: inherit; }
/* In the register the button is an icon only, so it needs to stay square and
   thumb-sized rather than collapsing to the width of the glyph. */
.eld-watch { width: 1px; white-space: nowrap; }
.eld-watch .btn { min-width: 2.6rem; justify-content: center; }

/* The language switch on a news article. Two links, not a control: it works
   with no script, and each language has an address a member can send on. */
.lang-switch {
  display: flex; gap: 0.5rem; align-items: center;
  font-family: var(--sans); font-size: var(--step--2); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  margin: 0 0 0.75rem;
}
.lang-switch a {
  color: var(--ink-3); text-decoration: none;
  padding: 0.3rem 0.6rem; border: 1px solid var(--rule);
}
.lang-switch a {
  display: inline-flex; align-items: center; gap: 0.45rem;
}
.lang-switch a:hover { color: var(--blue); border-color: var(--blue); }
.lang-switch a[aria-current] { color: var(--white); background: var(--blue); border-color: var(--blue); }

/* The flag rides at the cap height of the word beside it rather than on the
   baseline, and keeps its own proportions: a stretched flag is a wrong flag.
   It is decoration — the word is the control — so it never carries the only
   meaning, and it does not change colour with the link's state. */
.flag {
  display: block; width: 1.35em; height: 0.9em; flex: none;
  border-radius: 1px;
}

/* --------------------------------------------------- membership compare --- */
/* One column per level, one row per module a plan can grant. The rows come
   from the module catalogue, so a module declared tomorrow appears here on its
   own; the table has to hold its shape at any number of columns, which is why
   nothing here names a count.

   It scrolls sideways inside .table-scroll rather than squeezing the columns,
   because a tick that has to be hunted for is worse than a scrollbar. */
.table--compare { width: 100%; border-collapse: collapse; }
.table--compare th,
.table--compare td { padding: .7rem .8rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
.table--compare thead th { text-align: left; white-space: nowrap; }
.table--compare tbody th[scope="row"] { text-align: left; font-weight: 600; min-width: 16rem; }
.table--compare tbody td { text-align: center; font-size: 1.1rem; line-height: 1.4; }
.table--compare tbody tr:last-child th,
.table--compare tbody tr:last-child td { border-bottom: 0; }
.compare__hint { display: block; font-weight: 400; margin-top: .15rem; }

/* Below the measure a comparison stops being a table and becomes one block per
   module, each cell announcing which level it belongs to. Same treatment the
   admin tables get; the reason is the same, that a phone cannot hold five
   columns without shrinking the words to nothing. */
@media (max-width: 44rem) {
  .table--compare thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .table--compare tbody tr { display: block; padding: .8rem 0; border-bottom: 1px solid var(--rule); }
  .table--compare tbody tr:last-child { border-bottom: 0; }
  .table--compare tbody th[scope="row"] { display: block; border: 0; padding: 0 0 .5rem; min-width: 0; }
  .table--compare tbody td { display: flex; justify-content: space-between; gap: 1rem;
    text-align: right; border: 0; padding: .25rem 0; }
  .table--compare tbody td::before { content: attr(data-label); font-weight: 600; text-align: left; }
}

/* ------------------------------------------------ the gated carrier record --- */
/* The one line of insurance a signed-out visitor gets. It is the half of "may
   this outfit haul my freight" that is not authority, so it stays public while
   the filings behind it do not. Three states, and the third — no figure
   published — is deliberately not styled as a failure: since FMCSA's Motus
   changeover a properly covered carrier can be missing from the public record,
   and colouring that red would accuse the innocent. */
.insurance-answer {
  margin: .6rem 0 0;
  padding: .75rem .9rem;
  border-left: 3px solid var(--rule);
  background: color-mix(in oklab, var(--paper) 92%, var(--ink) 8%);
}
.insurance-answer--met     { border-left-color: #1c7c4a; }
.insurance-answer--short   { border-left-color: var(--red, #c3122a); }
.insurance-answer--partial,
.insurance-answer--unknown { border-left-color: #8a6d1f; }

/* The panel where the rest of the record would be. Quiet on purpose: it is an
   explanation of what is behind a membership, not an advertisement shouting
   over the answer the visitor actually came for. */
.panel-note {
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: color-mix(in oklab, var(--paper) 94%, var(--ink) 6%);
}
.panel-note h2 { font-size: var(--step-1); margin-bottom: .4rem; }
.panel-note p { margin: 0 0 .7rem; }
.panel-note .btn-row { margin: .9rem 0 0; }
