@charset "utf-8";
/* ==========================================================================
   LFG Freight Solutions — v2, photographic direction.

   Palette sampled directly from the client's flyer PNG, not eyeballed:
     navy  #001F4C   orange #FD5A01   paper #F6F6F6   white #FFFFFF

   Display face is Bebas Neue at weight 400 only. There is no bold cut; a
   heavier weight triggers faux-bold synthesis and breaks the mark. Every rule
   that sets the display face also sets font-weight: 400 — including headings
   and th, which default to bold.

   Contrast pairs in use, all measured, all AA:
     white  on navy   #001F4C   16.2 : 1
     orange on navy             5.11 : 1
     navy   on orange           5.11 : 1
     navy   on paper  #F6F6F6   14.9 : 1
     navy   on white            16.7 : 1
   White on orange is only 3.16 : 1 and is therefore NOT used for text
   anywhere — the orange band and the orange button both take navy type.
   ========================================================================== */

/* --------------------------------------------------------------- fonts -- */
/* All three faces are self-hosted from /assets/fonts/. There is never a Google
   Fonts call on this site: a stylesheet link there hands every visitor's IP
   address to Google on page load, for a file we already ship. */
@font-face {
  font-family: 'Bebas Neue';
  src: url('../fonts/bebas-neue-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Space Mono, for the studio credit in the footer rail only. */
@font-face {
  font-family: 'Space Mono';
  src: url('../fonts/space-mono/space-mono-400-normal-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Mono';
  src: url('../fonts/space-mono/space-mono-700-normal-latin.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Montserrat, for the endorsement line in the footer only — the one string on
   this site set in it.

   VARIABLE FONT, weight axis deliberately clipped to 500-600 and the charset to
   printable ASCII. The endorsement needs two real weights: 500 for "Powered by"
   and a heavier cut for the client's name. Declaring a heavier weight against a
   single static 500 file would make the browser SYNTHESISE the bold — the same
   faked weight the Bebas rule exists to prevent, and it looks just as broken.

   The axis range is what keeps this cheap. Full 100-900 unsubsetted is 34.7 KB;
   clipped to 500-600 over ASCII it is 16.2 KB, which is also smaller than the
   two static instances (16.6 KB) it replaces the need for — and unlike them it
   can be tuned to any value in the range without shipping another file. */
@font-face {
  font-family: 'Montserrat';
  src: url('../fonts/montserrat/montserrat-var-500-600-latin.woff2') format('woff2');
  font-weight: 500 600;          /* a RANGE — both ends are real instances */
  font-style: normal;
  font-display: swap;
}

/* -------------------------------------------------------------- tokens -- */
:root {
  --navy: #001F4C;
  --navy-deep: #00132F;
  --orange: #FD5A01;
  --orange-soft: #FF7A33;
  --paper: #F6F6F6;
  --white: #FFFFFF;
  --ink: #10192B;
  --muted: #4A5568;
  --go: #24D07A;

  /* The link colour for orange type on a light ground. --orange #FD5A01 is only
     2.6 : 1 on white and cannot carry text; this is the darkened cut that can.
     Measured 4.88 : 1 on #FFFFFF — AA for body text. It was referenced by three
     rules for weeks without ever being declared, so all three silently ran on
     their inline fallback. Declared here now; the fallbacks stay as belt and
     braces. */
  --orange-text: #C74600;

  --wrap: 1180px;
  /* The hero is a composition over a photograph, not a column of reading text.
     1180px centred works down the page but on an ultrawide frame it drags the
     headline into the middle of the trailer, when the artwork's clean area is
     an L: the top third full width plus the left third full height. Letting the
     hero container track the frame keeps the headline inside that left third at
     3440 while staying symmetric — the padding is equal on both sides, the
     container is simply wider. */
  --wrap-hero: clamp(1180px, 88vw, 1680px);
  --pad: clamp(20px, 5vw, 48px);
  --rule: 3px;

  /* Height of the sticky tap-to-call bar. Zero above the phone breakpoint so
     every dependent calculation collapses to nothing on desktop. */
  --callbar-h: 0px;

  /* Equipment ticker, phones only. The height is the one lever — every emblem
     takes its natural width from it, so the whole row scales from this number.
     It has to stay at or above roughly 5.9vw: below that the five marks plus
     their gaps stop being wider than the screen, the strip no longer runs off
     either edge, and the second copy drifts into view alongside the first with
     two of each on screen at once. 9.2vw holds that margin across the whole
     ticker range (it caps at 56px from 609px up, and 56px still clears the
     5.9vw floor out to 962px, past the 900px cutoff), and yields about 36px on
     a 390px phone against the 19.4px of ink the old letterboxed layout was
     actually drawing. The cap also stops a portrait tablet from rendering
     emblems larger than the desktop site does. */
  --equip-emb-h: clamp(32px, 9.2vw, 56px);
  --equip-gap:   clamp(14px, 4.2vw, 44px);

  --display: 'Bebas Neue', 'Haettenschweiler', 'Arial Narrow', Impact, sans-serif;
  --text: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
          Arial, 'Noto Sans', sans-serif;
}

@media (max-width: 767px) {
  :root { --callbar-h: 58px; }
}

/* ---------------------------------------------------------------- base -- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--white);
  color: var(--ink);
  font-family: var(--text);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* Keep the sticky call bar off the last of the footer. The bar grows by the
     safe-area inset on a notched phone, so the reservation has to grow with it
     or the last footer line ends up underneath. */
  padding-bottom: calc(var(--callbar-h) + env(safe-area-inset-bottom, 0px));
}

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

h1, h2, h3, h4, h5, h6 {
  font-family: var(--display);
  font-weight: 400;              /* Bebas Neue has no bold cut. Never raise. */
  margin: 0;
  line-height: 1.02;
  letter-spacing: 0.012em;
  /* The client's flyer is set in uniform caps. The markup stays sentence case
     so the copy reads and copies correctly; the caps are presentational. */
  text-transform: uppercase;
}

p { margin: 0 0 1em; }
ul, ol { margin: 0; padding: 0; list-style: none; }

:focus-visible {
  outline: 3px solid var(--orange);
  outline-offset: 3px;
  border-radius: 2px;
}
.hero :focus-visible,
.band--navy :focus-visible,
.band--deep :focus-visible { outline-color: var(--white); }
.band--orange :focus-visible { outline-color: var(--navy); }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--navy);
  color: var(--white);
  border-bottom: 4px solid var(--orange);
  padding: 14px 20px;
  font: 400 20px/1 var(--display);
  letter-spacing: 0.08em;
}
.skip:focus { left: 12px; top: 12px; }

.vh {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* --------------------------------------------------------- scaffolding -- */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad);   /* symmetric left / right, always */
}

.band { padding-block: clamp(66px, 9vw, 120px); }
.band--navy   { background: var(--navy); color: var(--white); }
.band--deep   { background: var(--navy-deep); color: var(--white); }
.band--paper  { background: var(--paper); color: var(--ink); }
.band--white  { background: var(--white); color: var(--ink); }
.band--orange { background: var(--orange); color: var(--navy); }

.h2 {
  font-size: clamp(40px, 6.4vw, 72px);
  max-width: 20ch;
}
.rule {
  width: 104px;
  height: var(--rule);
  background: var(--orange);
  border: 0;
  margin: 24px 0 0;
}
.band--navy .rule, .band--deep .rule { background: var(--orange); }

/* ------------------------------------------------------------ masthead -- */
/* A frosted band the client's full-colour lockup can sit on. Fixed rather than
   sticky so the hero artwork still runs edge to edge underneath and reads
   through the blur — the band is over the photograph, not above it.

   Height is a custom property because the hero's top padding is derived from
   it. Change it here and the hero follows; there is no second number to keep
   in sync. */
/* THE LOGO IS THE LEVER, and the band is derived from it — "slightly taller
   than the logo", which is how this was specified.

   --logo-w is the client's original masthead sizing, unchanged. Everything else
   in the band is a fraction of the logo's rendered HEIGHT, so the whole header
   still condenses as one piece on scroll.

   Driving this from the band's height instead is what shrank the mark from
   236px to 161px wide without anyone asking for it: a rule change that silently
   resized the logo, because the lockup's aspect ratio is part of the sum. */
:root {
  --logo-w: clamp(150px, 17vw, 236px);
  --logo-ar: 2.0339;                      /* 720 x 354, the colour lockup in use */
  --logo-h: calc(var(--logo-w) / var(--logo-ar));
  --mast-air: 1.34;                       /* band = logo height x this */
}

.masthead {
  --mast-h: calc(var(--logo-h) * var(--mast-air));
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  height: var(--mast-h);
  display: flex;
  align-items: center;

  /* Opacity carries the contrast, the blur is texture. Over a dark hero frame
     a lightly-tinted band samples down to grey and the navy LF loses the
     legibility this whole change exists to buy. Measured against the top strip
     of the hero art, not guessed.

     White rather than paper, at .85, which is the transparency floor.

     TRANSPARENCY AND WHITENESS ARE THE SAME DIAL. Every point of alpha removed
     lets more of the sky above the truck through, and that blue is exactly what
     made the band read grey before. Whiteness runs out long before the logo
     does. Measured over the hero at 390px:

       .90  rgb(230,234,238)  navy 13.4:1
       .85  rgb(217,224,230)  navy 12.1:1   <- floor
       .80  rgb(204,213,221)  navy 10.9:1   the rejected grey, see below
       .70  rgb(179,192,204)  navy  8.7:1
       .45  rgb(115,139,162)  navy  4.6:1   still legible, a solid blue slab

     .80 is where it lands back on the state that was rejected: the original
     paper #F6F6F6 at .82 composited to rgb(202,211,218), and white at .80 gives
     rgb(204,213,221), within two points of it. So the usable range is .90 to
     .85 and nothing below.

     Legibility is never the constraint here — the navy holds 4.6:1 even at .45.
     Do not "fix" a too-grey band by raising alpha past .90 or a not-transparent-
     enough one by dropping below .85. The lever that actually has room is the
     blur radius, or the artwork behind the band.

     Note the orange G in the lockup is 3.16:1 against pure white at best and
     never meets a 3:1 bar on any lighter surface. That is a property of the
     supplied mark, not of this band, and transparency cannot move it. */
  background: rgba(255, 255, 255, .85);
  -webkit-backdrop-filter: blur(16px) saturate(1.18);
  backdrop-filter: blur(16px) saturate(1.18);
  border-bottom: 1px solid rgba(0, 31, 76, .10);
  transition: height .28s ease, box-shadow .28s ease;
}

/* Only the lever moves. Everything inside follows because it is expressed as a
   fraction of it, so the logo keeps the same share of the band and the air
   above and below it stays in the same proportion.

   The frost is NOT touched here. It is the same translucency and the same blur
   the whole way down the page — the content passing underneath is meant to be
   visible through it, and swapping to an opaque bar after the hero would throw
   that away exactly where most of the scrolling happens. */
.masthead.is-stuck {
  --logo-w: calc(clamp(150px, 17vw, 236px) * .69);
  box-shadow: 0 2px 18px rgba(0, 31, 76, .14);
}

/* Where backdrop-filter is unavailable the band is simply opaque. Nothing is
   lost but the blur, and legibility never depended on it. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .masthead { background: rgba(255, 255, 255, .97); }
}

.masthead__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--mast-h) * .16);
  width: 100%;
  max-width: var(--wrap-hero);
}
.masthead__mark { display: block; line-height: 0; }

/* Sized by HEIGHT, as a share of the band. This is the rule that keeps the
   proportions honest: the lockup always occupies the same share of the band, so
   the air above and below stays constant at every size. Sizing it by width
   instead lets that air change every time the band moves.

   The lockup keeps the client's original masthead width. It is not a share of
   the band — the band is a share of it. */
.masthead img {
  width: var(--logo-w);
  height: auto;
  transition: width .28s ease;
}

/* Hidden until the hero's own two buttons have left the screen, so the page
   never shows the same call to action twice at once. `visibility` rather than
   `display` keeps the transition and takes them out of the a11y tree while
   they are not on screen. */
.masthead__actions {
  display: none;
  align-items: center;
  gap: calc(var(--mast-h) * .18);
  visibility: hidden;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity .28s ease, transform .28s ease, visibility .28s;
}
.masthead.is-stuck .masthead__actions {
  visibility: visible;
  opacity: 1;
  transform: none;
}
.masthead__tel {
  font: 400 calc(var(--mast-h) * .22)/1 var(--display);
  letter-spacing: .02em;
  color: var(--navy);
  text-decoration: none;
  white-space: nowrap;
  transition: font-size .28s ease;
}
.masthead__tel:hover { color: var(--orange-text, #C74600); }

/* .btn is sized for the hero, where it is a primary target sharing a row with
   the phone and free to grow. Inside the band it has to sit WITHIN the strip
   with air above and below, so its growth and its minimums are cancelled and
   its height, type and padding all become fractions of the band. Left on the
   hero's sizing it fills the band edge to edge and reads as a coloured block
   rather than a button. */
/* Specificity is deliberate, not decorative. `.btn` is declared LATER in this
   stylesheet, so a single-class selector here loses to it and the button keeps
   the hero's min-height and padding — which is what pinned it at 60px and made
   it grow from 52% of the band to 68% as the band condensed. */
.masthead .masthead__cta {
  flex: 0 0 auto;
  align-self: center;
  min-width: 0;
  min-height: 0;
  height: calc(var(--mast-h) * .52);
  padding: 0 calc(var(--mast-h) * .26);
  font-size: calc(var(--mast-h) * .19);
  white-space: nowrap;
  transition: height .28s ease, font-size .28s ease, padding .28s ease,
              background-color .2s ease, color .2s ease, border-color .2s ease;
}

/* Phones and small tablets: logo only. The sticky orange call bar at the foot
   of the page already carries the number and the client asked for it to stay
   untouched, so putting it up here as well would show it twice. */
@media (min-width: 900px) {
  .masthead__actions { display: flex; }
}

@media (prefers-reduced-motion: reduce) {
  .masthead,
  .masthead img,
  .masthead__actions { transition: none; }
}

/* ==========================================================================
   1 — HERO
   ========================================================================== */
.hero {
  position: relative;
  height: 100vh;
  min-height: 600px;
  isolation: isolate;
  background: var(--navy-deep);
  color: var(--white);
  overflow: hidden;
}
@supports (height: 100svh) { .hero { height: 100svh; } }

/* Phones only: the hero is pinned to a height measured ONCE, at load, and after
   an orientation change — never on an ordinary resize.

   A phone's URL bar collapses as you scroll, which changes the viewport height,
   which resizes anything sized to the viewport. The hero image is cropped to
   fill, so a taller box crops differently and the truck visibly grows and
   shrinks while you are scrolling. It reads as the picture zooming. svh is
   supposed to prevent this by pinning to the small viewport, and where it is
   honoured it does; where it is not, this does.
   --hero-lock is written by site.js. Without JS the svh/vh value above stands,
   which is the current behaviour, so nothing regresses. */
@media (max-width: 900px) {
  .hero { height: var(--hero-lock, 100svh); }
}

/* 112% tall so the parallax has somewhere to travel without ever exposing an
   edge. With the script blocked the offset is zero, which is exactly the
   intended first view — the parallax only ever moves the art *up*. */
.hero__art {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 112%;
  z-index: 0;
  will-change: transform;
}
/* The 12% of travel above only exists to feed the parallax, and the parallax is
   off on phones. Without this the extra height is dead crop, pushing the truck
   down out of the frame for no gain. */
@media (max-width: 900px) {
  .hero__art { height: 100%; }
}
.hero__art picture { display: block; width: 100%; height: 100%; }
.hero__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  /* The portrait frame is measurably darker than the landscape one through the
     zone the copy sits in: mean luminance of the top third is 51.9 against
     58.8 on the desktop frame (8-bit grey, measured off the sources). A small
     lift keeps it from going muddy on a phone in daylight. Removed again at
     the desktop-art breakpoint below, where it is not needed. */
  filter: brightness(1.08) saturate(1.04);
}

/* Two scrims. Both are load-bearing, and both are pinned to measurements taken
   off the source artwork rather than guessed:

   VERTICAL. The foreground road carries a bright orange reflection across the
   bottom quarter of both frames — specular highlights clipping to 255 in rows
   70–90% of the landscape frame and rows 60–80% of the portrait one. That is
   exactly where the sticky call bar, the equipment strip and the actions sit,
   so it is darkened here rather than by regenerating the art.

   HORIZONTAL. The headline sits upper-left, in the artwork's clean L. The left
   third of the landscape frame peaks at luminance 165 where the sunset meets
   the horizon; white type over that alone is 2.5:1. The left scrim takes the
   worst case to 8.2:1 and leaves the truck, which is right of centre, clean. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
/* Portrait frame. Its top third is already dark (mean luminance 42–49), so the
   headline only needs a light touch up there — .34 takes the worst pixel in
   that zone to 11.7 : 1 for white and 4.5 : 1 for the orange accent, and keeps
   the sky reading as sky. The weight goes where it is actually needed: the road
   reflection across the bottom quarter, under the strip and the call bar. */
.hero__scrim--v {
  background: linear-gradient(to bottom,
    rgba(0, 19, 47, .34) 0%,
    rgba(0, 19, 47, .12) 26%,
    rgba(0, 19, 47, .00) 46%,
    rgba(0, 19, 47, .16) 62%,
    rgba(0, 19, 47, .58) 78%,
    rgba(0, 19, 47, .90) 100%);
}
.hero__scrim--h { display: none; }

/* Must stay identical to the media attribute on the hero <source> elements and
   to the desktopArt matchMedia string in site.js. All three swap together. */
@media (min-width: 1024px) and (min-aspect-ratio: 1/1) {
  .hero__art img { filter: none; }
  .hero__scrim--v {
    background: linear-gradient(to bottom,
      rgba(0, 19, 47, .40) 0%,
      rgba(0, 19, 47, .08) 22%,
      rgba(0, 19, 47, .06) 52%,
      rgba(0, 19, 47, .34) 68%,
      rgba(0, 19, 47, .74) 84%,
      rgba(0, 19, 47, .92) 100%);
  }
  .hero__scrim--h {
    display: block;
    background: linear-gradient(to right,
      rgba(0, 19, 47, .62) 0%,
      rgba(0, 19, 47, .40) 30%,
      rgba(0, 19, 47, .00) 58%);
  }
}

.hero__inner {
  position: relative;
  z-index: 3;
  height: 100%;
  display: flex;
  flex-direction: column;
  /* Derived from the band's own height so the two can never drift apart, plus
     air so the headline never crowds the logo it sits under. Deliberately
     tighter on phones, where every pixel the copy gives back is truck. */
  padding-top: calc((var(--logo-w) / var(--logo-ar) * var(--mast-air)) + clamp(20px, 3.4vh, 44px));
  padding-bottom: calc(var(--callbar-h) + env(safe-area-inset-bottom, 0px) + 12px);
}

.hero__copy, .hero__actions, .equip__row { max-width: var(--wrap-hero); }

.hero h1 {
  font-size: clamp(42px, 8.4vw, 92px);
  line-height: .96;
  max-width: 18ch;
  text-wrap: balance;
}
.hero h1 .accent { color: var(--orange-soft); }

.hero__sub {
  margin: 20px 0 0;
  font-size: clamp(17px, 1.6vw, 21px);
  line-height: 1.55;
  max-width: 40ch;
  color: #E4EDF9;
  font-weight: 600;
}

/* Two actions, equal weight. The form and the phone. Drivers call — the number
   is never buried behind a link. */
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: auto;             /* phones: sit low, over the scrimmed road */
}
/* Desktop is the height-constrained case: the hero is locked to the viewport,
   and the headline, the actions and the strip all have to fit inside it. The
   band keeps its full 1.34 of air on phones, where there is no such ceiling,
   and tightens to 1.18 here. Measured at 1440x900: band 155.5px -> 137.1px,
   hero top padding 227.5px -> 200.5px, which is what puts the whole strip back
   inside the frame at 1440, 1512, 1600 and 1920. */
@media (min-width: 1024px) and (min-aspect-ratio: 1/1) {
  :root { --mast-air: 1.18; }
  .hero__inner { padding-top: calc((var(--logo-w) / var(--logo-ar) * var(--mast-air)) + clamp(24px, 5vh, 64px)); }
  .hero__actions { margin-top: clamp(30px, 4vh, 48px); gap: 16px; }
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex: 1 1 auto;
  min-width: 230px;
  min-height: 54px;
  padding: 13px 28px;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(24px, 2.6vw, 31px);
  letter-spacing: .05em;
  text-transform: uppercase;
  text-decoration: none;
  border: var(--rule) solid transparent;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}
@media (min-width: 620px) { .btn { flex: 0 0 auto; min-height: 60px; padding: 15px 30px; } }

/* --ink, not --navy. Navy sits 165.7 degrees from the brand orange on the hue
   wheel and both are fully saturated, which is the worst pairing for edge
   vibration. --ink is 62.8% saturated instead of 100%, so it attacks the
   mechanism directly: vibration scales with the saturation of BOTH colours,
   not only the hue angle between them. Contrast improves 5.11:1 to 5.55:1 as
   a side effect. Pure black would remove hue opposition entirely but is not in
   the palette and reads harder than the logo's own navy. */
.btn--go   { background: var(--orange); color: var(--ink); border-color: var(--orange); }
.btn--call { background: var(--white);  color: var(--navy); border-color: var(--white); }
.btn--go:hover, .btn--go:focus-visible {
  background: transparent; color: var(--orange); border-color: var(--orange);
}
.btn--call:hover, .btn--call:focus-visible {
  background: transparent; color: var(--white); border-color: var(--white);
}
.btn__ico { width: 24px; height: 24px; fill: currentColor; flex: 0 0 auto; }

/* Equipment strip — thin, along the bottom edge of the frame. */
.equip {
  border-top: 1px solid rgba(255, 255, 255, .22);
  padding-top: 12px;
  margin-top: clamp(16px, 2.4vh, 30px);
}
/* The heading gets its own line rather than sitting inline as a sixth flex item,
   where it read as another equipment type. On its own line the five below it
   read as a set. */
.equip__row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: clamp(8px, 1.4vh, 14px);
}
/* The ticker wrappers are inert by default: display:contents makes the track and
   the marquee disappear from the box tree, so .equip__list stays a direct grid
   child of .equip__row exactly as it was, and the desktop layout is untouched.
   The phone block below turns them back into real boxes. */
.equip__track,
.equip__marquee { display: contents; }
/* Doubled class deliberately. The dupe carries both classes, so a single-class
   rule ties with .equip__list on specificity and loses on source order — which
   put a second five-across grid on desktop and clipped the hero by 64px. */
.equip__list.equip__list--dupe { display: none; }
.equip__label {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(14px, 1.2vw, 16px);   /* a label, not reading text — floor is 11px */
  letter-spacing: .24em;
  text-transform: uppercase;
  color: #B7C9E0;
}
/* Five equal columns, emblem STACKED over its word.

   The client asked for this to be bigger, especially on phones, and side-by-side
   is what made that impossible: an item laid out [emblem][gap][word] is as wide
   as both together, so five of them at any usable size overflow 390px and wrap
   into ragged rows. Stacked, a column is only as wide as the WIDER of the two,
   which is what buys the size increase. No carousel, no scrolling, no motion —
   all five stay on screen at once, which is the whole point of the strip. */
.equip__list {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  align-items: start;
  gap: clamp(6px, 1.6vw, 20px);
}
.equip__list li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(5px, .9vw, 9px);
  text-align: center;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(17px, 1.9vw, 23px);
  /* Tracking is width spent on air. At five-across it is the difference between
     "POWER ONLY" fitting its column and not, so it tightens as the screen
     narrows rather than being dropped outright. */
  letter-spacing: clamp(.02em, .5vw, .1em);
  line-height: 1.05;
  text-transform: uppercase;
  color: var(--white);
  /* "POWER ONLY" is the longest label and the only one that needs two lines on
     a phone. Balanced so it breaks POWER / ONLY rather than orphaning a word. */
  text-wrap: balance;
}
/* The studio's emblems are not square, and they are not one shared ratio either.
   Measured off the live viewBoxes: flatbed 1319x451 (2.92), hotshot 868x385
   (2.25), power only 765x456 (1.68). An earlier note here claimed a uniform
   "8:5, viewBox 0 0 256 160" — that was never true of these files. A square box
   letterboxes them, so the box is height-driven with width auto and each mark
   keeps its own ratio, which is how the client drew them.

   Because every mark's artwork fills its own viewBox top to bottom (measured
   slack below the ink: 0.04–0.12px at 44px), equal box height already puts every
   ground line on the same row. Measured spread across all five: 0.21px. No
   bottom-alignment shim is needed and none should be added.

   Size is a judgement, made against the studio's own specimen renders rather
   than a rule. At 21px dry van and reefer collapse into the same smudge and the
   hotshot is a smear; the set starts resolving at 26px; it is clean at 62px,
   the studio's designed minimum. 44px sits well clear of the point where all
   five separate while keeping this a strip along the hero's edge rather than a
   band competing with the headline. Below 40px the hotshot is the first to go. */
.equip__emb {
  /* Height-driven, width auto. The client's flyer draws these at differing
     widths on a shared height (power only is short, flatbed is long) and each
     wrapper carries its own viewBox, so letting width follow height keeps that
     relationship exactly as he drew it. */
  /* 22px was the side-by-side size and it was the thing the client called too
     small. Stacking freed the width to raise it: 32px on a phone up to 44px at
     desktop. max-width keeps the widest emblems (flatbed, reefer) inside their
     column instead of forcing the grid apart. */
  height: clamp(32px, 3.2vw, 44px);
  width: auto;
  max-width: 100%;
  flex: 0 0 auto;

  /* The studio's emblems are themed through five custom properties. Nothing was
     setting them, so every part fell back to navy and the whole set vanished
     into the navy strip. This is the dark-ground pairing from the studio's own
     specimen: body, ground, hub and tyre in paper, the single accent in orange.
     Note the naming trap the studio flagged: --emblem-ground is the GROUND BAR
     under the vehicle, and it follows the body colour. It is not a background. */
  --emblem-body:   var(--paper);
  --emblem-ground: var(--paper);
  --emblem-hub:    var(--paper);
  --emblem-tyre:   var(--paper);
  --emblem-accent: var(--orange);
}

/* Desktop pins the strip to the bottom edge of the frame. This block has to sit
   *after* the base .equip rule — same specificity, so source order decides. */
@media (min-width: 1024px) and (min-aspect-ratio: 1/1) {
  .equip { margin-top: auto; padding-top: 14px; }
  /* No emblem override here. A fixed 28px used to sit in this block, left over
     from the side-by-side strip, and it silently capped desktop at 28px while
     the base rule read clamp(32px, 3.2vw, 44px). The client's "make it bigger"
     was therefore unmet on desktop — 22px to 28px, not to 44px. The base rule
     governs at every width now. */
}
/* The interpunct separator that used to live here has been removed, not moved.
   It was written for the single flex ROW the strip used to be. Now that each
   item is a stacked column, `li + li::before` became the first item IN that
   column — a 33.2px block sitting above the emblem on four of the five items.
   That was both the bulk of the desktop overflow and the entire reason dry van
   appeared to sit higher than the rest: dry van is the first li and had no
   ::before. With it gone, ground-line spread across all five is 0.21px.
   A separator cannot be reintroduced this way; the grid gap carries it. */

/* ==========================================================================
   1b — EQUIPMENT TICKER (phones and portrait tablets only)
   ==========================================================================
   The exact complement of the desktop query above, so the two can never both
   apply. Desktop keeps the static five-across grid; this is phones only.

   WHY THE SIZE JUMPS SO MUCH. The emblems were never rendering at the size the
   CSS asked for on a phone. `max-width: 100%` capped each one at its 65px grid
   column, and because the artwork is up to 3.37:1, the drawing shrank to fit
   that width inside its box: dry van and reefer were laying down 19.4px of
   actual ink inside a 32px box, flatbed 22.2px, while power only (1.68:1) was
   the only one reaching its full 32px. That is why the set looked both small
   and uneven, and why raising the box height alone changed nothing.

   The fix is width, not height. Freed from the column, each mark draws at its
   natural width for a shared height, so all five finally match and the widest
   ones nearly double. Height is the single lever: --equip-emb-h.

   The row is deliberately wider than the screen. Items run off both edges,
   which is the signal that there are more than fit, and the drift means every
   driver sees his own equipment type without touching anything. */
@media (max-width: 900px) {
  .equip__track {
    display: block;
    /* Full-bleed. The items have to run off the actual screen edge, not off the
       inside of the wrap's padding, or the half-cut item reads as a mistake. */
    width: 100vw;
    margin-left: calc(50% - 50vw);
    overflow: hidden;
    /* The row is wider than the screen, so both edges always slice through
       something. A sliced TRUCK reads as "there is more"; a sliced WORD reads as
       broken — the left edge was showing "SHOT" off the end of HOTSHOT. Fading
       the last few percent to transparent makes items dissolve out of frame
       instead of being cut, so no fragment ever looks like a typo. */
    -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 9%, #000 91%, transparent 100%);
    mask-image: linear-gradient(to right,
      transparent 0, #000 9%, #000 91%, transparent 100%);
  }
  .equip__marquee {
    display: flex;
    width: max-content;
    /* One pass of the five takes a minute. Linear and unattended: no easing, no
       pause, nothing that draws the eye to the motion itself. You should notice
       that it changed, not watch it move. */
    animation: equip-drift 60s linear infinite;
    will-change: transform;
  }
  .equip__list.equip__list--dupe { display: flex; }
  .equip__list {
    display: flex;
    gap: var(--equip-gap);
    /* Matches the gap so the seam between the two copies is indistinguishable
       from any other join in the row. */
    padding-right: var(--equip-gap);
  }
  .equip__list li { flex: 0 0 auto; }
  .equip__emb {
    height: var(--equip-emb-h);
    width: auto;
    /* The letterbox trap. Must stay off here — reinstating it re-caps the wide
       emblems to their column and silently shrinks the artwork again. */
    max-width: none;
  }

  /* Both hero buttons come off the phone. The number is already carried by the
     sticky orange call bar directly below, and the two full-width slabs were
     covering 151px of the truck through the cab. */
  .hero__actions { display: none; }
  /* .hero__actions used to be the flex item holding margin-top:auto, which is
     what pushed the strip to the bottom of the frame. With it gone the strip
     has to take that job or the whole hero collapses upward. */
  .equip { margin-top: auto; }
}
/* Travels exactly one copy's width, so the frame it lands on is identical to
   the frame it left and the wrap cannot be seen. Rightward, per the direction
   asked for: translate goes from negative to zero. */
@keyframes equip-drift {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Motion is opt-out, and the opt-out cannot leave a dead end: a stopped ticker
   with items cut off at both edges gives no way to reach the rest. So reduced
   motion falls all the way back to the static five-across grid — the layout
   that shipped before the ticker, unchanged. */
@media (prefers-reduced-motion: reduce) and (max-width: 900px) {
  .equip__track {
    width: auto;
    margin-left: 0;
    overflow: visible;
  }
  .equip__marquee { animation: none; transform: none; display: block; }
  .equip__list.equip__list--dupe { display: none; }
  .equip__list {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    padding-right: 0;
    gap: clamp(6px, 1.6vw, 20px);
  }
  .equip__emb { height: clamp(32px, 3.2vw, 44px); max-width: 100%; }
  /* The two hero buttons stay gone here. Their removal is a layout decision, not
     a motion one — a reduced-motion preference is not a request to have the
     truck covered again. Only the ticker falls back; .equip keeps margin-top:
     auto from the block above so the strip still sits on the frame's edge. */
}

/* Placeholder frames only. The real emblems arrive as a studio sprite and are
   inlined by tools/build-assets.py, at which point this rule stops matching. */
.emb-ph {
  fill: none;
  stroke: currentColor;
  stroke-width: 3;
  stroke-dasharray: 5 5;
  opacity: .6;
}

/* ==========================================================================
   2 — PROMISE BAND
   ========================================================================== */
.promise__grid {
  display: grid;
  gap: clamp(30px, 4vw, 60px);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 900px) {
  .promise__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); }
}
.promise .h2 { font-size: clamp(46px, 7.4vw, 84px); }

.wins { display: grid; }
.wins li {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 15px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .16);
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(29px, 4vw, 46px);
  line-height: 1.06;
  letter-spacing: .02em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .55s ease, transform .55s cubic-bezier(.2, .7, .3, 1);
  transition-delay: calc(var(--i) * 85ms);
}
.wins li:first-child { border-top: 1px solid rgba(255, 255, 255, .16); }
.is-in .wins li { opacity: 1; transform: none; }
.wins__bar {
  flex: 0 0 auto;
  width: 26px;
  height: var(--rule);
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s cubic-bezier(.2, .7, .3, 1);
  transition-delay: calc(var(--i) * 85ms + 140ms);
}
.is-in .wins__bar { transform: scaleX(1); }

/* ==========================================================================
   3 — WHAT WE HANDLE. The green line lives here and nowhere else.
   ========================================================================== */
.run {
  position: relative;
  margin-top: clamp(30px, 4vw, 52px);
}

/* Two coincident lines: the grey track, and the green fill drawn on top with
   stroke-dashoffset — the same mechanic v1 used, driven from scroll progress.

   The viewBox is 2 x 1000 in a box that is exactly 2px wide, so the x scale is
   exactly 1 and stroke-width: 2 lands as 2 real pixels with no help. The y
   scale stretches 1000 user units over the full height, which is precisely
   what makes stroke-dasharray: 1000 equal "the whole line".

   Do NOT add vector-effect: non-scaling-stroke here. It was tried, and under a
   non-uniform viewBox it takes the dash pattern out of user space, so offset
   1000 renders as a fully drawn line and the fill never appears to move. */
.run__line {
  position: absolute;
  left: 8px;          /* 8px box centred on the 24px marker column */
  top: 0;
  width: 8px;
  height: 100%;
  overflow: visible;
}
.run__base { stroke: rgba(16, 25, 43, .16); stroke-width: 6; stroke-linecap: round; }
.run__go {
  stroke: var(--go);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
}

.run__list { display: grid; }
.run__list li {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  align-items: center;
  gap: clamp(18px, 2.6vw, 30px);
  padding: clamp(15px, 2.2vw, 22px) 0;
}
.run__list li + li { border-top: 1px solid rgba(16, 25, 43, .10); }
.run__list h3 {
  font-size: clamp(23px, 3vw, 36px);
  letter-spacing: .02em;
  color: var(--navy);
}

.run__dot {
  display: block;
  width: 24px; height: 24px;
  background: var(--paper);
  border-radius: 50%;
  /* The line is absolutely positioned, so without this it paints OVER every
     marker and runs straight through them. */
  position: relative;
  z-index: 1;
}
.run__dot svg { width: 24px; height: 24px; }
/* A status light, not a tick. A lamp sitting on the line reads as a dispatch
   board; a checkmark always fights the line it sits on. */
.run__ring {
  fill: var(--paper);
  stroke: rgba(16, 25, 43, .22);
  stroke-width: 2;
  transition: stroke .3s ease;
}
.run__lamp {
  fill: rgba(16, 25, 43, .13);
  transition: fill .35s ease, filter .35s ease;
}
.run__list li.is-done .run__ring { stroke: var(--go); }
.run__list li.is-done .run__lamp {
  fill: var(--go);
  filter: drop-shadow(0 0 5px rgba(36, 208, 122, .85));
}

/* ==========================================================================
   4 — WHY WORK WITH US
   ========================================================================== */
.why__grid {
  display: grid;
  gap: clamp(28px, 3.6vw, 44px);
  grid-template-columns: 1fr;
  margin-top: clamp(34px, 4vw, 56px);
}
@media (min-width: 620px)  { .why__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .why__grid { grid-template-columns: repeat(4, 1fr); } }

/* These are cards, so they are built as cards rather than dressed as section
   heads. No rule: a rule under each one produced a second horizontal directly
   below the section rule and read as two stray lines.

   The base is cut at the same oblique as the LFG letterforms and the swoosh in
   the client's own logo, so the form is derived from his mark rather than
   invented, and it reads as motion without a speed line.

   --cut is the depth of that slant at the left edge, where it bites deepest.
   The bottom padding clears it explicitly, or the last line of copy sits in the
   wedge. Cards stretch to a common height so the cut is consistent across all
   four despite the copy wrapping to different depths. */
.why__item {
  --card-pad: clamp(26px, 2.4vw, 32px);
  --cut: 26px;
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--navy);
  color: var(--white);
  border: 0;
  padding: var(--card-pad);
  padding-bottom: calc(var(--card-pad) + var(--cut) + 12px);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - var(--cut)));
}
.why__item p { margin-bottom: 0; }
.why__grid { align-items: stretch; }
.why__item h3 {
  font-size: clamp(26px, 2.6vw, 33px);
  color: var(--white);
  letter-spacing: .025em;
  max-width: 12ch;
  margin-bottom: auto;      /* copy sits to the base, headings top-align */
}
.why__item p {
  margin: 12px 0 0;
  font-size: 17px;
  line-height: 1.6;
  color: #BDCCE4;
  max-width: 26ch;
}

/* ==========================================================================
   5 — NO CATCH. Fear removal. Loud, and carries no heading of ours.
   ========================================================================== */
.nocatch__list {
  display: grid;
  gap: 0;
}
.nocatch__list li {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(34px, 6.2vw, 74px);
  line-height: 1.04;
  letter-spacing: .015em;
  text-transform: uppercase;
  /* Was navy on an orange band. Orange is an accent, never a surface: white on
     navy is 16.2:1 against navy on orange at 5.11:1, and it stops a full orange
     band competing with the orange CTA. The rules keep the orange. */
  color: var(--white);
  padding: clamp(14px, 2vw, 22px) 0;

  /* A hairline, not a rule. Orange at 3px on this band fought the type and the
     CTA; lane markings were worse. At 74px the Bebas caps already separate
     themselves, so the divider only has to organise, not decorate. */
  border: 0;
  border-bottom: 1px solid rgba(246, 246, 246, .18);
}
/* No divider above the first item: the orange section rule already sits there. */

/* ==========================================================================
   6 — THE ASK
   ========================================================================== */
.ask__grid {
  display: grid;
  gap: clamp(38px, 5vw, 64px);
  grid-template-columns: 1fr;
  align-items: start;
  margin-top: clamp(34px, 4vw, 52px);
}
@media (min-width: 980px) {
  .ask__grid { grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); }
}

.ask__sub {
  font-size: clamp(26px, 3vw, 34px);
  letter-spacing: .05em;
  color: var(--orange-soft);
}
.ask__lines { margin: 18px 0 30px; display: grid; gap: 14px; }
.ask__tel {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(38px, 5.4vw, 58px);
  letter-spacing: .02em;
  color: var(--white);
  text-decoration: none;
  display: inline-block;
}
.ask__tel:hover { color: var(--orange-soft); }
/* The address stays in the text face and in lower case on purpose: the local
   part of an email address is case-sensitive and must never be shown in caps. */
.ask__mail {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: clamp(18px, 2vw, 22px);
  font-weight: 700;
  color: #DCE6F4;
  text-decoration: none;
}
/* The rule sits on the address only, never under the glyph — same construction
   as .vcard, so the two lines share one underline behaviour. */
.ask__mail span {
  border-bottom: 2px solid var(--orange);
  padding-bottom: 3px;
  word-break: break-word;
}
.ask__mail:hover { color: var(--white); }

/* Digital card link. Sized and weighted to sit UNDER the email in the hierarchy
   — the phone is the headline, the email is second, this is third — so it reads
   as one more way to reach him rather than competing with the two that matter.

   The glyph is drawn in the site's existing icon language: 24x24 box, single
   1.7 stroke for the card, solid fills for the figure and the two rules, all
   currentColor so it takes the link's own colour and its hover with no second
   rule. Same construction as the phone glyph in the call button. */
.vcard {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-weight: 400;              /* Bebas Neue has no bold cut. Never raise. */
  font-size: clamp(19px, 2.1vw, 24px);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #DCE6F4;
  text-decoration: none;
  /* The underline is on the label only, not the icon, so the rule does not run
     under the glyph and clip its lower stroke. */
}
.vcard span {
  border-bottom: 2px solid var(--orange);
  padding-bottom: 3px;
}
.vcard__ico {
  width: 1.15em;
  height: 1.15em;
  flex: 0 0 auto;
  /* Optical, not mathematical: the glyph's mass sits above its box centre
     because the card is wider than it is tall, so it rides a touch low against
     cap-height type. */
  transform: translateY(1px);
}
.vcard:hover,
.vcard:focus-visible { color: var(--white); }

/* Stands in for the Jotform embed while the client's account is over quota. It
   is deliberately plain: a panel that looks like a broken form invites a retry,
   and there is nothing to retry. Says what happened and gives the two routes
   that do work. Delete this rule when the embed is restored. */
.apply__down {
  margin: 0 0 26px;
  padding: 22px 24px;
  border-left: var(--rule) solid var(--orange);
  background: rgba(255, 255, 255, .05);
  font-size: clamp(17px, 1.9vw, 20px);
  line-height: 1.55;
  color: #DCE6F4;
}
.apply__down-lines { margin-top: 0; }

/* One per row. Flex-wrap packed these by width, and because the four labels are
   very different lengths it produced a ragged 2 / 1 / 1. A single column is also
   what .ask__lines above it does, so the whole side column now reads as one
   stack instead of two competing rhythms. */
.assurances {
  display: grid;
  gap: 11px;
}
.assurances li {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 16px;
  line-height: 1.5;
  color: #C3D3E8;
  font-weight: 600;
}
.assurances .dot { width: 9px; height: 9px; background: var(--go); flex: 0 0 auto; }

.apply__frame {
  margin-top: 18px;
  border: 1px solid rgba(255, 255, 255, .18);
  background: var(--white);
}
/* Jotform's jsform embed builds and SIZES its own iframe. Deliberately no
   height or min-height here: a fixed height fought Jotform's own sizing and
   left the form clipped and mis-laid-out. Width and border only. */
.apply__frame iframe {
  display: block;
  width: 100%;
  border: 0;
}
.apply__fallback {
  margin: 16px 0 0;
  font-size: 16px;
  color: #C3D3E8;
}
.apply__fallback a { color: var(--white); font-weight: 700; }

/* ==========================================================================
   7 — FOOTER
   ========================================================================== */
/* The footer runs on white so the full-colour lockup can be used instead of the
   white knockout, and so every element in it is measured against the strongest
   ground available. Contrast on #FFFFFF: --ink #10192B 17.56:1, --navy #001F4C
   16.7:1, --muted #4A5568 7.5:1, credit #757681 4.50:1, --orange-text 4.88:1.

   Two columns on desktop: identity on the left, the ways to reach him on the
   right, closed by a rail carrying the copyright, the policy links and the
   studio credit. */
.f6-foot {
  background: #FFFFFF;
  color: var(--navy);
  padding-block: clamp(44px, 5.2vw, 68px) clamp(30px, 3.4vw, 40px);

  /* ------------------------------------------------------------------------
     --id-w IS A DERIVED VALUE, NOT A TASTE CALL.

     It is the natural rendered width of the endorsement string at its locked
     type spec — Montserrat Medium 500, 14px, letter-spacing .0663em, uppercase
     — measured in the browser at 281.06px and rounded up to the next whole
     pixel so a sub-pixel rounding difference can never wrap the line.

     The mark is then set to this width, which is what makes the two share both
     vertical edges and read as one object.

     IF THE ENDORSEMENT'S TYPE SPEC EVER CHANGES, THIS NUMBER MUST BE
     RE-MEASURED. It is not 282 because 282 looked right; it is 282 because the
     line is 281.06 wide. A changed size or tracking silently breaks the flush
     relationship this block is built on.
     ------------------------------------------------------------------------ */
  --id-w: 282px;
}
.f6-wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }

.f6-grid { display: grid; grid-template-columns: 1fr; gap: 34px; }
@media (min-width: 760px) {
  .f6-grid { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 40px 48px; }
  .f6-grid .f6-sig { max-width: 62ch; }
}

/* The mark carries the identity block's width so its edges line up with the
   endorsement directly beneath it. min() keeps it inside the column on the
   narrowest phones instead of overflowing. */
.f6-mark { display: block; width: min(100%, var(--id-w)); height: auto; }

/* THE ENDORSEMENT.

   Type is solved against the client's own artwork — cap height 24px, ink width
   686px, 28.583 cap-heights across the line — and is not re-opened.

   The two margins around it are the entire grouping mechanic and they are a
   ratio, not two independent numbers: the gap above is roughly half the gap
   below, so proximity assigns the line to the mark rather than to the copy
   under it. Near-equal gaps make it read as a third peer statement instead of
   part of the lockup. If either number is ever edited, edit both and keep the
   ratio.

   Markup stays sentence case and the caps are presentational, as elsewhere on
   this site, so the line copies, reads aloud and translates correctly. */
.f6-endorse {
  margin: 14px 0 0;                 /* tight to the mark — half the gap below */
  font-family: 'Montserrat', 'Avenir Next', 'Century Gothic', 'Segoe UI', sans-serif;
  font-weight: 500;
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: .0663em;
  text-transform: uppercase;
  color: var(--ink);                /* #10192B — 17.56:1 on white */
  max-width: 100%;
  /* The client's name is the only part that changes; see .f6-endorse__name. */
  /* Below roughly 336px of column the line cannot hold one row. It breaks into
     two balanced rows rather than orphaning the last word; the mark does not
     move and nothing re-lays out. */
  text-wrap: balance;
}

/* THE CLIENT'S NAME, picked out of the endorsement.

   COLOUR. Sampled from the LF letterforms in logo-lfg-colour-720.png rather
   than guessed: 13,874 interior pixels, eroded to drop antialiased edges. The
   mark is a gradient, not a flat fill — the ink spans R 0-18, G 3-53, B 24-92
   and the single most common value accounts for only 1.45% of it — so there is
   no one "logo blue" to copy. Its median is #032144.

   That is 2.47 dE2000 from --navy #001F4C, which this site already declares and
   uses everywhere. At 14px on antialiased letterforms that difference is not
   visible, and --navy separates from --ink slightly more strongly (8.80 dE2000
   against 6.79), which is what the line is actually trying to do. A fourth
   near-duplicate navy token would be a permanent maintenance cost for an
   invisible gain, so this takes the existing token.  16.15:1 on white.

   WEIGHT. A real instance from the variable font, never a synthesised bold —
   with only a static 500 on disk, asking for anything heavier would make the
   browser fake it.

   600, chosen off a rendered ladder rather than picked: at 540 and 570 the step
   up from the surrounding 500 is so slight it reads as a rendering artefact
   rather than an intention. 600 is the first value that is legible as a
   deliberate change at 14px, and it is still SemiBold — a full stop short of
   the 700 that would start competing with the logo's own FREIGHT SOLUTIONS. */
.f6-endorse__name {
  font-weight: 600;
  color: var(--navy);
}

.f6-sig { margin: 30px 0 0;         /* the separation that makes the grouping read */
          font-size: 16px; line-height: 1.5; color: var(--navy);
          max-width: 46ch; text-wrap: pretty; }

/* If the endorsement is ever absent the signature keeps its distance from the
   mark, so the block does not collapse upward. */
.f6-mark + .f6-sig { margin-top: 26px; }

.f6-reach { display: grid; gap: 12px; justify-items: start; align-content: start; }
/* The address carries a marker glyph, so it takes the same inline-flex row the
   card and LinkedIn links use. It is NOT a link: no hover, no focus, nothing
   that would invite a click. The glyph paints in currentColor, which means it
   inherits --muted from the address itself rather than the --navy the two links
   above it carry — see the note on .f6-place__ico. */
.f6-place {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--muted);
}
.f6-tel {
  font-family: var(--display);
  font-weight: 400;                 /* Bebas Neue has no bold cut. Never raise. */
  font-size: clamp(30px, 3.4vw, 38px);
  line-height: 1; letter-spacing: .02em; color: var(--navy); text-decoration: none;
}
.f6-tel:hover, .f6-tel:focus-visible { color: var(--orange-text, #C74600); }
/* The email row now carries a glyph like the three below it, so it is the same
   flex row — which means the rule under the address can no longer live on the
   row itself, or it would run under the envelope too. It moves to the text span,
   where padding-bottom 2px and a 1px border reproduce exactly what the row-level
   border drew before (measured at dsf 8: rule top 26px from the row top, text
   font box 3–21px, unchanged by this rewrite).

   That makes the span 27px against a 24px line box, so the row is 3px taller
   than its siblings — which is what it always was, and the reason the column's
   spacing is untouched. The cost is that align-items:center would then centre
   the glyph on 27 and drop it 1.5px below where the other three sit. The glyph
   takes those 3px back as bottom margin, so it centres on the text's line box
   like every other row rather than on the rule's overhang. */
.f6-mail { display: inline-flex; align-items: center; gap: 9px;
           font-size: 16px; line-height: 1.5; color: var(--navy); text-decoration: none; }
.f6-mail__text { border-bottom: 1px solid rgba(0,31,76,.30); padding-bottom: 2px; }
.f6-mail__ico { margin-bottom: 3px; }
.f6-mail:hover, .f6-mail:focus-visible { color: var(--orange-text, #C74600); }
.f6-mail:hover .f6-mail__text,
.f6-mail:focus-visible .f6-mail__text { border-bottom-color: currentColor; }
.f6-card { display: inline-flex; align-items: center; gap: 9px;
           font-size: 16px; line-height: 1.5; color: var(--navy); text-decoration: none; }
/* One sizing rule for every glyph in the reach column, so the three can never
   drift apart. Each paints in currentColor and therefore takes the colour of
   the row it belongs to: --navy on the two links, --muted on the address. That
   is the established behaviour, not an exception — the card and the "in" mark
   are navy because their own text is, not because glyphs here are navy. Making
   the marker navy would have pushed it louder than the address it labels and
   grouped it with the two links, which are clickable and it is not. */
.f6-card__ico,
.f6-mail__ico,
.f6-place__ico { width: 1.2em; height: 1.2em; flex: 0 0 auto; transform: translateY(1px); }

/* The 9px gap above is measured box to box, and all four boxes are the same
   1.2em. What the eye reads is the gap from INK to text, and that only matches
   if the glyphs fill their boxes to the same width. Measured at dsf 8, the four
   were never going to agree on their own: the card filled 21.5 of its 24 units,
   the "in" mark 16.5, and the marker — drawn small enough to share the card's
   ink top and bottom edges — only 12.3. Left alone that puts three different
   gaps down one short column, which is the defect that got noticed.

   All four are now drawn to 16.5 units of ink, so all four carry the same 14px
   ink-to-text gap. 16.5 is not a compromise, it is the only width all four can
   physically reach: the marker is a teardrop, its ink height grows with its
   width, and past about 17.5 units it no longer fits the 24-unit box at all. So
   the card comes down to meet the other three rather than the others stretching
   up to meet the card. The card and the envelope keep a landscape 14.8 x 12.4 —
   square enough to hold their weight beside the "in" mark, still oblong enough
   that neither is mistaken for it. The card's interior is the shipped artwork
   scaled uniformly and recentred, never stretched.

   What survives all of it is first ink at 1.25 units on every glyph, so the four
   hang off one left edge. Geometry lives in the SVG, one source of truth per
   page, identical across index / privacy / terms.

   The offset below is OPTICAL, the one place in this column where the numbers
   are meant to disagree. The card and the "in" mark are rounded rectangles with
   evenly spread ink, so their geometric and optical centres coincide. The marker
   is a teardrop — mass packed into the head, tapering to a point — so its ink
   centroid sits above the middle of its own box and it reads high at the shared
   1px, more so now that the head is bigger. Centroid from the row top, text
   hidden, dsf 8: card 13.84px, "in" 13.92px, marker 12.39px — 1.49px high.
   Correcting the whole 1.49 drops the marker's ink visibly below the other two;
   +0.6px removes about 40% and is where the render stops reading high. Tuned by
   eye against the two rows either side of it, not solved in the numbers. */
.f6-place__ico { transform: translateY(1.6px); }

.f6-card:hover, .f6-card:focus-visible { color: var(--orange-text, #C74600); }

.f6-rail {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 12px 30px;
  margin-top: clamp(34px, 4vw, 48px); padding-top: 18px;
  border-top: 1px solid rgba(0, 31, 76, .14);
}
.f6-rail__set { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 26px; }
.f6-rail__item { font-size: 14px; line-height: 1.6; color: var(--muted); }
a.f6-rail__item { text-decoration: none; border-bottom: 1px solid rgba(74,85,104,.34);
                  padding-bottom: 1px; }
a.f6-rail__item:hover, a.f6-rail__item:focus-visible { color: var(--navy);
                  border-bottom-color: currentColor; }

/* No underline in any state; hover is a colour shift only. */
.f6-credit {
  font-family: 'Space Mono', ui-monospace, Menlo, monospace;
  font-weight: 400; font-size: 11px; line-height: 1.6; letter-spacing: .05em;
  color: #757681;                   /* 4.50:1 on #FFFFFF */
  text-decoration: none; white-space: nowrap;
}
.f6-credit b { font-weight: 700; }
.f6-credit:hover, .f6-credit:focus-visible { color: var(--navy); text-decoration: none; }

/* --------------------------------------------------- reversed footer -- */
/* THE SAME FOOTER ON A DARK GROUND — privacy and terms only, never the
   homepage. A modifier, not a second component: one markup structure, one
   block of overrides, so the two can never drift apart.

   WHY IT EXISTS. The legal pages are a white reading column. A white footer
   under a white page has no edge, so the block floats with nothing closing
   the page beneath it. The homepage does not have this problem — its footer
   follows a navy band and reads as the light end of a sequence.

   THE GROUND IS NOT A TASTE CALL. These pages already open on --navy
   (.legal-hero, legal.css). Closing them on the same value frames the reading
   column between two instances of one ground rather than introducing a third.
   --navy-deep would put a second dark into a page that has one, and would
   disagree with the hero it is answering.

   EVERY COLOUR BELOW IS EITHER A TOKEN THIS SITE ALREADY DECLARES OR A TINT IT
   ALREADY SHIPS ON NAVY. Nothing here is invented. Measured on #001F4C:
   --white 16.15:1 · #DCE6F4 12.82:1 · #BDCCE4 9.94:1 · --orange 5.11:1 ·
   credit rgba(255,255,255,.55) composites to #8C9AAE, 5.65:1. All AA or
   better at their rendered size. */
.f6-foot--reverse {
  background: var(--navy);
  color: var(--white);

  /* THE CALL-BAR RESERVATION HAD TO BE PAINTED, NOT REMOVED.
     body reserves --callbar-h plus the safe-area inset at the foot of every
     page so the sticky tap-to-call bar can never sit on the last footer line.
     These two pages carry no .callbar, so on a phone that reservation is 58px
     of bare body below the footer. Against a white footer it was invisible and
     nobody had cause to notice it; against navy it reads as a torn bottom edge.

     The reservation itself is untouched — it is shared with the homepage and
     removing it there would put the bar over the credit. Instead the footer
     paints its own box again, offset down by exactly the reserved height, so
     the strip fills with the same navy. box-shadow takes no part in layout, so
     document height, scroll extent and every measurement in this file are
     unchanged. It tracks --callbar-h on its own: above 767px that token is 0,
     the offset collapses, and the shadow lands precisely behind the footer
     where it cannot be seen. */
  box-shadow: 0 calc(var(--callbar-h) + env(safe-area-inset-bottom, 0px)) 0 0 var(--navy);
}

/* Focus rings follow the site's standing rule for a dark ground — white, as
   on .hero, .band--navy and .band--deep — not the --orange default. */
.f6-foot--reverse :focus-visible { outline-color: var(--white); }

/* THE ENDORSEMENT'S TWO TONES SURVIVE BY OBEYING THE ORIGINAL INSTRUCTION,
   NOT BY INVERTING ITS HEX. The rule was: pick the client's name out by
   matching it to the LF in the logo. The logo on this ground is the white
   knockout, so the LF is white and the name is white. "Powered by" takes
   #DCE6F4, the tint this site already uses for copy on navy.

   The step is 16.15 : 12.82 (1.26x) against 17.56 : 16.15 (1.09x) on white —
   the closest an established token gets. Weight 600 on the name is unchanged
   and carries the rest of the distinction exactly as it does on white. */
.f6-foot--reverse .f6-endorse { color: #DCE6F4; }
.f6-foot--reverse .f6-endorse__name { color: var(--white); }

.f6-foot--reverse .f6-sig,
.f6-foot--reverse .f6-tel,
.f6-foot--reverse .f6-mail,
.f6-foot--reverse .f6-card { color: var(--white); }

/* The muted tier. #BDCCE4 is not a new value: it is exactly what
   .legal-hero__meta already carries against a white h1 on this same ground,
   900px up the same page. The address and the rail keep their documented
   quieter-than-the-links relationship, and the glyphs keep taking currentColor
   from the row they belong to — marker muted, card and envelope and "in" at
   full strength — with no rule of their own. */
.f6-foot--reverse .f6-place,
.f6-foot--reverse .f6-rail__item { color: #BDCCE4; }

/* HOVER. --orange-text #C74600 is 3.31 : 1 here and cannot carry text, which
   is expected: its own declaration says it exists as "the link colour for
   orange type on a LIGHT ground" — the darkened cut that solves --orange's
   2.6 : 1 on white. That constraint is absent on navy, where the true brand
   --orange #FD5A01 measures 5.11 : 1 and clears AA for body text. So the dark
   variant takes the real orange, which is the same reasoning that produced
   --orange-text, followed to the opposite conclusion. --orange-soft #FF7A33
   (6.22 : 1) is not used: it is the hero's accent over photography, where the
   ground is a variable image rather than a flat fill. */
.f6-foot--reverse .f6-tel:hover,
.f6-foot--reverse .f6-tel:focus-visible,
.f6-foot--reverse .f6-mail:hover,
.f6-foot--reverse .f6-mail:focus-visible,
.f6-foot--reverse .f6-card:hover,
.f6-foot--reverse .f6-card:focus-visible { color: var(--orange); }

/* THE THREE HAIRLINES ARE SOLVED, NOT GUESSED, AND THEY ARE NOT THE LIGHT
   ALPHAS RE-POINTED AT WHITE. Navy's luminance is so low that white at a
   given alpha lifts it far harder than navy at that alpha darkens white, so
   copying .30 / .14 / .34 across would draw three rules markedly louder than
   the ones they replace. Each alpha below is the value whose perceived
   lightness step off its own ground (CIE L*) matches what the light footer
   draws: mail underline dL* 24.15 -> .255, rail border dL* 11.11 -> .115,
   rail item underline dL* 20.52 -> .215. Rounded to two places. A WCAG-ratio
   match independently lands at .21 / .10 / .18, so the two models agree the
   correct values sit well under the light ones. */
.f6-foot--reverse .f6-mail__text { border-bottom-color: rgba(255, 255, 255, .26); }
.f6-foot--reverse .f6-rail { border-top-color: rgba(255, 255, 255, .12); }
.f6-foot--reverse a.f6-rail__item { border-bottom-color: rgba(255, 255, 255, .22); }
.f6-foot--reverse a.f6-rail__item:hover,
.f6-foot--reverse a.f6-rail__item:focus-visible { color: var(--white);
                  border-bottom-color: currentColor; }

/* Brand guide § 10.5 gives an explicit dark-background value for the credit
   and this is it, unaltered: rgba(255,255,255,0.55), which composites to
   #8C9AAE at 5.65 : 1. Space Mono, 11px, .05em, 400 with the name at 700 and
   the polynexalabs.com target all carry over from the light rule unchanged.

   § 10.5's hover value is a light-ground token and is effectively invisible
   here, so it is a documented gap rather than a spec to follow. The light
   footer hovers the credit from its muted grey up to the page's strongest ink
   (--navy); the dark analogue of that move is white. No underline in any
   state, per the 2026-07-27 ruling — hover is a colour shift only. */
.f6-foot--reverse .f6-credit { color: rgba(255, 255, 255, .55); }
.f6-foot--reverse .f6-credit:hover,
.f6-foot--reverse .f6-credit:focus-visible { color: var(--white); text-decoration: none; }

/* ==========================================================================
   STICKY TAP-TO-CALL — phones only, present throughout.
   ========================================================================== */
.callbar { display: none; }
@media (max-width: 767px) {
  .callbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 120;
    height: var(--callbar-h);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    background: var(--orange);
    color: var(--navy);          /* 5.11 : 1 — white would be 3.16 : 1 */
    text-decoration: none;
    font-family: var(--display);
    font-weight: 400;
    font-size: 26px;
    letter-spacing: .06em;
    border-top: var(--rule) solid var(--navy);
    padding-bottom: env(safe-area-inset-bottom);
    box-sizing: content-box;
  }
  .callbar svg { width: 21px; height: 21px; fill: currentColor; }
}

/* ------------------------------------------------------- reveal states -- */
.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2, .7, .3, 1);
  transition-delay: calc(var(--i, 0) * 90ms);
}
.reveal.is-in, .is-in .reveal { opacity: 1; transform: none; }

/* ------------------------------------------------------- no javascript -- */
/* The page must be complete and legible with the script blocked. Everything
   that would otherwise wait for an observer resolves to its finished state. */
.no-js .reveal { opacity: 1; transform: none; }
.no-js .wins li { opacity: 1; transform: none; }
.no-js .wins__bar { transform: scaleX(1); }
.no-js .run__go { stroke-dashoffset: 0; }
.no-js .run__ring { stroke: var(--go); }
.no-js .run__lamp { fill: var(--go); }

/* ---------------------------------------------------- reduced motion -- */
/* Everything resolves to its finished state. All copy legible, both calls to
   action reachable, nothing depends on an animation having run. The parallax
   is not merely slowed here — site.js never binds it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
  }
  .reveal, .wins li { opacity: 1 !important; transform: none !important; }
  .wins__bar { transform: scaleX(1) !important; }
  .run__go { stroke-dashoffset: 0 !important; }
  .run__ring { stroke: var(--go) !important; }
  .run__lamp { fill: var(--go) !important; }
}
