/* ==========================================================================
   Highway Heroes Logistics — Design System
   hhlogs.com · one stylesheet, every page, EN + ES

   Brand rules that must not drift:
     · Sharp corners. No border-radius on layout surfaces. The logo is a
       shield with hard edges and the whole system follows it.
     · Numbers are always mono. The promise is "we show our work", so every
       figure on the site is typeset like data, never like marketing.
     · One primary action per screen: Talk to a Dispatcher.
     · Chrome (the logo's metal) appears as a gradient rule, never as a fill
       behind text.

   Contents
     01 Tokens              10 Recap card          19 Blog
     02 Reset + base        11 Trust strip         20 Long-form / legal
     03 Typography          12 Sections            21 Final CTA
     04 Motion utilities    13 Services            22 Footer
     05 Loader              14 Process             23 Chat widget
     06 Layout              15 Load board          24 Utilities
     07 Buttons             16 Gallery + video     25 Print
     08 Header              17 Calculators
     09 Hero                18 Forms
   ========================================================================== */

/* == 01 Tokens ============================================================ */
:root {
  /* Brand */
  --asphalt: #121A2C;
  --asphalt-2: #18213A;
  --asphalt-3: #0C1220;
  --steel: #3C4658;
  --accent: #D6303C;
  --accent-lo: #B0242F;
  --accent-hi: #F04552;
  --paper: #F6F6F8;
  --paper-2: #EAECF1;
  --white: #FFFFFF;
  --slate: #59626E;
  --signal: #2E9E6D;
  --warn: #E8A33D;
  --chrome: #D7DBE2;

  /* Back-compat with earlier prototype markup that used var(--amber). */
  --amber: var(--accent);

  /* Surfaces + lines */
  --line: rgba(247, 245, 241, 0.14);
  --line-soft: rgba(247, 245, 241, 0.08);
  --line-light: rgba(18, 26, 44, 0.10);
  --line-light-2: rgba(18, 26, 44, 0.20);
  --on-dark: rgba(247, 245, 241, 0.74);
  --on-dark-mute: rgba(247, 245, 241, 0.58);

  /* The logo's metal, reused as rules and edges. */
  --chrome-grad: linear-gradient(180deg, #FFFFFF 0%, #C9CFD9 42%, #8B93A2 58%, #EDF0F4 100%);
  --accent-grad: linear-gradient(135deg, var(--accent-hi), var(--accent-lo));

  /* Type
     Montserrat for display, Inter for everything else.
     There is no third webfont for data. Inter carries real tabular figures,
     so the "readout" character comes from uppercase + wide tracking + tabular
     numerals instead of a monospace face. One less font file to download. */
  --font-display: 'Montserrat', 'Montserrat Fallback', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Inter', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-mono: var(--font-body);

  /* Space + rhythm */
  --wrap: 1180px;
  --gutter: 24px;
  --section-y: clamp(64px, 8.5vw, 108px);
  --header-h: 84px;

  /* Shape.
     The original handoff sample specified sharp corners throughout. That was
     the client's reference, not our design. Softening the geometry is the
     single biggest thing separating this from that sample: rounded surfaces,
     pill actions, and real elevation instead of hairline boxes. */
  --r-xs: 8px;
  --r-sm: 12px;
  --r-md: 18px;
  --r-lg: 26px;
  --r-xl: 34px;
  --r-pill: 999px;

  /* Elevation — deeper and softer, so cards sit above the page instead of
     being drawn onto it. */
  --shadow-1: 0 2px 8px rgba(18, 26, 44, 0.05), 0 1px 2px rgba(18, 26, 44, 0.04);
  --shadow-2: 0 12px 32px rgba(18, 26, 44, 0.10), 0 2px 8px rgba(18, 26, 44, 0.06);
  --shadow-3: 0 28px 80px rgba(10, 14, 24, 0.40), 0 8px 24px rgba(10, 14, 24, 0.24);
  --shadow-lift: 0 20px 50px rgba(18, 26, 44, 0.16);
  --glow-accent: 0 0 0 1px rgba(214, 48, 60, 0.35), 0 10px 34px rgba(214, 48, 60, 0.28);

  /* Motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* Scroll reveals sit at 520ms with a strong ease-out: long enough to read
     as motion, short enough that a fast scroller is never waiting on it. */
  --t-fast: 160ms;
  --t-base: 280ms;
  --t-slow: 620ms;
}

/* == 02 Reset + base ====================================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 20px);
  -webkit-text-size-adjust: 100%;
}

/* Nothing on this site should ever scroll sideways. Two things would otherwise
   cause it: the closed mobile nav parked off-screen, and the oversized hero
   road plane. `clip` rather than `hidden` on purpose — `hidden` turns the
   element into a scroll container and breaks the sticky header. */
html, body { overflow-x: clip; }

body {
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--asphalt);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word;
}

img, svg, video, iframe { max-width: 100%; display: block; }
img { height: auto; }
a { color: inherit; }
[hidden] { display: none !important; }
/* Anchor offset is handled once, by scroll-padding-top on <html> above.
   Adding scroll-margin-top on :target as well makes the two stack and every
   in-page link lands a header-height too far down. */

::selection { background: var(--accent); color: var(--white); }

/* Links, buttons and other controls get the offset outline ring.
   Form fields are handled separately below: they already carry a border, and
   stacking an offset outline on top of it reads as two borders. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent-hi);
  outline-offset: 3px;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 300;
  background: var(--accent); color: var(--white);
  padding: 14px 22px; font-family: var(--font-mono); font-size: 13px; text-decoration: none;
}
.skip-link:focus { left: 0; }

/* == 03 Typography ========================================================
   Montserrat is not condensed, so headings run wider than the earlier
   Barlow Condensed setting. Sizes come down a step, tracking goes slightly
   negative, and line-height opens up to suit the larger x-height. */
h1, h2, h3, h4, .display {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: -0.02em;
  font-weight: 800;
  line-height: 1.08;
  text-wrap: balance;
}
h1 { font-size: clamp(33px, 4.6vw, 60px); }
h2 { font-size: clamp(25px, 3.4vw, 40px); }
h3 { font-size: clamp(18px, 2vw, 23px); letter-spacing: -0.015em; font-weight: 700; }
h4 { font-size: 16px; font-weight: 700; }
p { text-wrap: pretty; }

/* Figures and micro-labels. Tabular numerals keep columns from dancing as
   values update in the calculators and on the load board. */
.mono, .num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'cv05' 1;
}

.eyebrow {
  color: var(--accent);
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.eyebrow::before { content: ''; width: 28px; height: 2px; background: var(--accent); flex: none; }

/* Chrome rule — the logo's metal used as a divider. */
.chrome-rule { height: 2px; background: var(--chrome-grad); opacity: 0.55; border: 0; }

/* Every readout on the site. Grouped here so the figures stay consistent
   wherever they appear: hero recap, load board, calculators, fact rows. */
.metric-value,
.recap-week,
.board-rpm, .board-age, .board-lane,
.calc-result .big,
.calc-break .mono,
.fact .v,
.hos-clock .v,
.ifta-due,
.trust-row,
.crumbs,
[data-count] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* Small uppercase labels need extra weight to hold their shape in Inter at
   10–12px, where the old monospace face carried them on stroke width alone. */
.section-eyebrow,
.metric-label,
.recap-bar-label,
.board-title, .board-scope, .board-scan, .board-row.head, .board-rank,
.loader-text,
.crumbs,
.post-meta,
.foot-grid h2,
.calc-result .lbl,
.hos-clock .k,
.ifta-table th,
label,
.updated,
.trust-row,
.gal-cap,
.mile .mm,
.tool-link,
.read-more,
.ai-pick {
  font-weight: 600;
}

/* == A background picture behind a section ================================
   The picture lives in its own child element, not on the section and not in a
   pseudo-element: sections already spend ::before and ::after on scan lines,
   rules and gradients, so borrowing one would break a different section every
   time. The fade sits on top of the picture and takes its colour from whether
   the band is dark or light, because a photograph under white text and a
   photograph under dark text want opposite treatment.

   The section's own background colour is dropped while a picture is set —
   otherwise the picture would be behind an opaque panel and the client would
   be told the option does not work. */
.has-sec-bg { position: relative; isolation: isolate; background-color: transparent; }
.sec-bg {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  background-image: var(--sec-bg);
  background-size: cover;
  background-position: center var(--sec-bg-focus, center);
  background-repeat: no-repeat;
  pointer-events: none;
}
.sec-bg::after {
  content: ''; position: absolute; inset: 0;
  background: rgba(var(--sec-bg-tint, 246, 246, 248), var(--sec-bg-scrim, 0.86));
}
/* Everything the template drew has to come back above the picture. */
.has-sec-bg > *:not(.sec-bg) { position: relative; z-index: 1; }

/* And the picture has to keep the position it was given.
 *
 * Several sections lift their own children above their decoration with a rule
 * of the form `.thing > * { position: relative; z-index: 1 }` — .final-cta,
 * .on-dark and .aurora all do. One class each, exactly the weight of .sec-bg,
 * and every one of them written further down the file, so each quietly won:
 * the picture layer became position:relative, collapsed to 1440x0, and painted
 * nothing at all. The option looked broken on the sections most likely to want
 * a photograph behind them, and looked fine everywhere else.
 *
 * Two classes rather than one, so it outranks all of them, and nothing has to
 * remember to add :not(.sec-bg) to the next rule of that shape. .page-hero
 * already carries a hand-written :not(.hero-photo) for the same reason — that
 * is the version of this fix that has to be repeated. */
.has-sec-bg > .sec-bg { position: absolute; inset: 0; z-index: 0; }

/* == The footer map =======================================================
   A still card until the visitor asks for it, then the real map. The preview
   is drawn here rather than fetched, so the page costs nothing extra to
   anyone who never presses it — and there is no third-party request to put a
   consent question on a page that otherwise has none. */
.foot-map { margin-top: 22px; }
.map-facade {
  position: relative; display: block; width: 100%; aspect-ratio: 16 / 10;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: linear-gradient(160deg, #1B2438 0%, #141C2E 60%, #10182A 100%);
  overflow: hidden; cursor: pointer; padding: 0;
  transition: border-color var(--t-base), box-shadow var(--t-base);
}
/* Streets, suggested rather than drawn. Two repeating gradients cost nothing
   and read as a map at a glance without pretending to be the real one. */
.map-grid {
  position: absolute; inset: 0; opacity: 0.5;
  background-image:
    repeating-linear-gradient(90deg, rgba(247,245,241,0.10) 0 1px, transparent 1px 46px),
    repeating-linear-gradient(0deg, rgba(247,245,241,0.10) 0 1px, transparent 1px 46px),
    linear-gradient(115deg, transparent 46%, rgba(214,48,60,0.30) 46% 48%, transparent 48%);
}
.map-pin {
  position: absolute; left: 50%; top: 44%; transform: translate(-50%, -50%);
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--accent); color: var(--white);
  display: grid; place-items: center;
  box-shadow: 0 8px 24px rgba(6, 10, 20, 0.5);
}
.map-pin svg { width: 22px; height: 22px; }
.map-label {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
  font-size: 12px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--paper); background: rgba(8, 12, 22, 0.66);
  padding: 7px 14px; border-radius: var(--r-pill);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  white-space: nowrap;
}
@media (hover: hover) {
  .map-facade:hover { border-color: rgba(214, 48, 60, 0.55); box-shadow: 0 10px 30px rgba(6, 10, 20, 0.4); }
  .map-facade:hover .map-pin { transform: translate(-50%, -58%); }
}
.map-pin { transition: transform var(--t-base) var(--ease-out); }
/* Once loaded, the iframe takes the facade's place and its shape with it. */
.foot-map iframe {
  display: block; width: 100%; aspect-ratio: 16 / 10; border: 0;
  border-radius: var(--r-md);
}
.map-open {
  display: inline-block; margin-top: 10px;
  font-size: 12.5px; color: var(--on-dark); text-decoration: none;
}

/* -- A map inside an office column ---------------------------------------- */
/* The original was one map across the foot of the site. Three of them sit in
   three columns instead, so everything shrinks: a shorter card, a smaller pin
   and a label that fits without wrapping. The facade is a drawing, not a
   picture, so making it smaller costs nothing to load. */
.foot-map--office { margin-top: 14px; }
.foot-map--office .map-facade,
.foot-map--office iframe { aspect-ratio: 16 / 9; border-radius: var(--r-sm, 8px); }
.foot-map--office .map-pin { width: 34px; height: 34px; }
.foot-map--office .map-pin svg { width: 16px; height: 16px; }
.foot-map--office .map-label {
  bottom: 10px; font-size: 10.5px; padding: 5px 10px; letter-spacing: 0.05em;
}
.foot-map--office .map-grid {
  background-size: 34px 34px, 34px 34px, auto;
}
/* One column on a phone, so the card has the full width and does not need to
   shrink twice over. */
@media (max-width: 760px) {
  .foot-map--office .map-facade,
  .foot-map--office iframe { aspect-ratio: 16 / 10; }
}
@media (hover: hover) { .map-open:hover { color: var(--paper); } }
.fc-note {
  display: block; margin-top: 6px;
  font-size: 12.5px; line-height: 1.5; color: var(--on-dark-mute); font-style: italic;
}

/* == 04 Motion utilities ================================================== */
/* Elements start hidden and are revealed by IntersectionObserver in hhl.js.
   .js-on is set on <html> so that without JS nothing is ever stuck invisible. */
.js-on [data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.js-on [data-reveal='left']  { transform: translateX(-26px); }
.js-on [data-reveal='right'] { transform: translateX(26px); }
.js-on [data-reveal='scale'] { transform: scale(0.965); }
.js-on [data-reveal].is-in { opacity: 1; transform: none; }

/* A full-width block on a phone has nowhere to slide in from. The 26px it
   starts at puts its right edge past the screen, so while the animation runs
   the card looks cut off against the glass — which reads as a padding mistake
   rather than as motion. Below the tablet breakpoint everything arrives
   vertically instead, where there is room. */
@media (max-width: 900px) {
  .js-on [data-reveal='left'],
  .js-on [data-reveal='right'] { transform: translateY(22px); }
}

/* == Scroll-driven accents ================================================
   Progressive enhancement, no JS and no library. Where the browser supports
   scroll-driven animations these run entirely off the compositor; where it
   does not, nothing happens and every element is already in its final state.

   The fallback direction matters and is the reason this is CSS rather than
   another JS-observed reveal. A first attempt wiped photographs open with
   clip-path driven by the same IntersectionObserver the text reveals use.
   Measured on the blog index, `is-in` never landed on those wrappers, so
   every card image sat at `clip-path: inset(0 0 100%)` — permanently
   invisible, on a page that still looked plausible because the captions were
   fine. A decorative effect must never be able to hide content: if the
   mechanism fails here, the image is simply not animated.

   Deliberately limited to two effects. The guidance this pass followed is one
   or two animated elements per view; past that the motion stops carrying
   meaning and competes with the content. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Section photographs drift against the scroll. Small enough to read as
       depth rather than as an effect. */
    .svc-row-media img,
    .post-lead img,
    .pc-media img {
      animation: mediaDrift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    /* Oversized figures settle as they arrive, so the number is what the eye
       lands on rather than the box around it. */
    .stat-xl, .ph-fact .v, .mc-v, .mc-eq b {
      animation: figureSettle linear both;
      animation-timeline: view();
      animation-range: entry 10% entry 65%;
    }
  }
}
/* scale() holds the drift inside the frame — without it the translate would
   expose an edge at each end of the range. */
@keyframes mediaDrift {
  from { transform: scale(1.07) translateY(-1.5%); }
  to   { transform: scale(1.07) translateY(1.5%); }
}
@keyframes figureSettle {
  from { opacity: 0.4; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .js-on [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* == 05 Loader ============================================================ */
/* Covers the first paint so the hero animation never appears half-built.
   Removed by hhl.js on window load, with a hard timeout as a safety net. */
#hhl-loader {
  position: fixed; inset: 0; z-index: 500;
  background: var(--asphalt);
  display: grid; place-items: center;
  transition: opacity 460ms var(--ease-out), visibility 460ms;
}
#hhl-loader.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
.loader-inner { display: grid; place-items: center; gap: 26px; width: min(280px, 74vw); }
.loader-mark { width: 88px; height: 88px; position: relative; }
.loader-mark img { width: 100%; height: 100%; object-fit: contain; }
.loader-mark::after {
  content: ''; position: absolute; inset: -14px;
  border: 1px solid var(--line);
  animation: loaderPulse 1.6s var(--ease-in-out) infinite;
}
@keyframes loaderPulse {
  0%, 100% { transform: scale(0.92); opacity: 0.25; }
  50%      { transform: scale(1);    opacity: 0.7; }
}
.loader-track { width: 100%; height: 2px; background: var(--line-soft); overflow: hidden; }
.loader-fill { height: 100%; width: 0; background: var(--accent); transition: width 220ms linear; }
.loader-text {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--on-dark-mute);
}
/* Without JS the loader must never trap the page. */
html:not(.js-on) #hhl-loader { display: none; }

/* == 06 Layout ============================================================ */
.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
section { padding-block: var(--section-y); position: relative; }

.section-head { max-width: 660px; margin-bottom: clamp(36px, 5vw, 60px); }
.section-head h2 { margin-bottom: 16px; }
.section-head p { color: var(--slate); font-size: clamp(15px, 1.7vw, 17px); max-width: 580px; }

/* The brand colour is the pill, not the type. Red type on the asphalt pill
   measured 3.59:1 — under the 4.5:1 that 12px needs — and read as a smudge.
   White on the accent is 4.83:1, and it makes the label identical on light
   pages and dark bands, which is what the .on-dark rule below already did. */
.section-eyebrow {
  font-family: var(--font-mono); font-size: 12px; color: var(--white);
  background: var(--accent); display: inline-block; padding: 6px 13px;
  letter-spacing: 0.1em; margin-bottom: 20px; text-transform: uppercase;
}
.on-dark { background: var(--asphalt); color: var(--paper); }
.on-dark .section-eyebrow { background: var(--accent); color: var(--white); }
.on-dark .section-head h2 { color: var(--paper); }
.on-dark .section-head p { color: var(--on-dark); }

/* Three dark bands run back to back on the homepage. Left alone that is two
   full section paddings of empty navy between them, which reads as a gap
   rather than a rhythm. Adjacent dark bands share one boundary instead. */
.on-dark + .on-dark {
  padding-top: calc(var(--section-y) * 0.45);
  border-top: 1px solid var(--line-soft);
}
.on-dark + .on-dark::after {
  content: ''; position: absolute; top: -1px; left: 0;
  width: 120px; height: 2px; background: var(--accent); opacity: 0.85;
}

/* Fine grain over dark bands — stops large flat areas looking like plastic. */
.on-dark::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0.5;
  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'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
}
.on-dark > * { position: relative; z-index: 1; }

/* == 07 Buttons =========================================================== */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 16px 30px;
  font-family: var(--font-body); font-weight: 600; font-size: 15px; line-height: 1.2;
  text-decoration: none; border: 1px solid transparent; cursor: pointer;
  transition: transform var(--t-fast) var(--ease-out), background var(--t-fast), border-color var(--t-fast), box-shadow var(--t-base);
}
.btn-primary { background: var(--accent); color: var(--white); }
.btn-ghost { background: transparent; color: var(--paper); border-color: rgba(247, 245, 241, 0.34); }
/* .btn-ghost defaults to pale text and a pale border for a dark or photo
   background. A couple of spots put it on the plain paper background instead,
   where that reads as invisible — this flips it dark. A class, not an inline
   style, because a raw-HTML section body has its inline styles stripped on
   the way into WordPress. */
.btn-ghost.on-paper { border-color: var(--line-light-2); color: var(--asphalt); }
.btn-dark { background: var(--asphalt); color: var(--white); }
.btn-sm { padding: 11px 20px; font-size: 14px; }

/* Sweep of light across the primary CTA on hover. */
.btn-primary::after {
  content: ''; position: absolute; top: 0; left: -120%;
  width: 60%; height: 100%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.28), transparent);
  transition: left 620ms var(--ease-out);
}
@media (hover: hover) {
  .btn-primary:hover { background: var(--accent-lo); transform: translateY(-2px); box-shadow: var(--glow-accent); }
  .btn-primary:hover::after { left: 130%; }
  .btn-ghost:hover { border-color: var(--paper); background: rgba(247, 245, 241, 0.07); }
  .btn-ghost.on-paper:hover { border-color: var(--asphalt); background: rgba(18, 26, 44, 0.05); }
  .btn-dark:hover { background: #0A0F1C; transform: translateY(-2px); }
}
.btn:active { transform: translateY(0); }
.cta-row { display: flex; gap: 14px; flex-wrap: wrap; }

.tool-link {
  font-family: var(--font-mono); font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--asphalt); text-decoration: none;
  border-bottom: 2px solid var(--accent); padding-bottom: 3px;
  display: inline-flex; align-items: center; gap: 7px;
  transition: gap var(--t-fast) var(--ease-out), color var(--t-fast);
}
@media (hover: hover) { .tool-link:hover { color: var(--accent); gap: 12px; } }

/* == 08 Header ============================================================ */
/* The blur lives on a pseudo-element, not on the header itself.
   backdrop-filter makes an element the containing block for any
   position:fixed descendant. The mobile menu is a child of this header, so
   with the filter here its `inset: header-h 0 0 0` resolved against the
   85px-tall header instead of the viewport: the overlay collapsed to a thin
   strip showing one link, with the page visible behind it. Keeping the filter
   on ::before leaves the header itself an ordinary containing block. */
/* A floating pill, not a full-width bar.
 *
 * The bar spanned edge to edge with a hairline under it, which is the default
 * every site has had for a decade and which cuts the page in two right where
 * the hero photograph is strongest. Detaching it lets the image run under and
 * past it, and turns the nav into an object sitting on the page rather than a
 * lid clamped over it.
 *
 * The blur stays on ::before rather than on .site-header itself. backdrop-filter
 * makes an element the containing block for fixed-position descendants, and the
 * mobile sheet is a fixed descendant — with the filter on the header the sheet
 * resolved its inset against the header box and rendered 85px tall. */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: transparent; border-bottom: 0;
  padding: 10px var(--gutter) 0;
  transition: padding var(--t-base) var(--ease-out);
}
.site-header.is-stuck { padding-top: 8px; }

.navbar {
  position: relative;
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 10px 12px 10px 20px; max-width: var(--wrap); margin-inline: auto;
  min-height: 64px; border-radius: var(--r-pill);
  border: 1px solid rgba(247, 245, 241, 0.12);
  box-shadow: 0 18px 50px rgba(6, 10, 20, 0.32);
  transition: min-height var(--t-base) var(--ease-out), padding var(--t-base) var(--ease-out),
              box-shadow var(--t-base), border-color var(--t-base);
}
.navbar::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background: rgba(18, 26, 44, 0.72);
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
  transition: background var(--t-base);
}
.site-header.is-stuck .navbar {
  min-height: 58px; padding-block: 7px;
  border-color: rgba(247, 245, 241, 0.18);
  box-shadow: 0 16px 44px rgba(6, 10, 20, 0.5);
}
.site-header.is-stuck .navbar::before { background: rgba(18, 26, 44, 0.9); }


.logo {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--font-display); font-weight: 800; font-size: 19px;
  color: var(--paper); text-transform: uppercase; letter-spacing: -0.01em;
  text-decoration: none; flex: none; line-height: 1;
}
.logo img { height: 52px; width: auto; transition: height var(--t-base) var(--ease-out); }
.site-header.is-stuck .logo img { height: 42px; }
.logo .logo-sub {
  display: block; font-family: var(--font-mono); font-size: 9px;
  letter-spacing: 0.22em; color: var(--accent); margin-top: 3px; font-weight: 500;
}

nav.links { display: flex; gap: 26px; align-items: center; }

/* A two-word label breaking across two lines makes the whole bar look ragged,
   so the links stay on one line and the row gets narrower instead. */
@media (min-width: 1101px) {
  nav.links a { white-space: nowrap; }
}

/* Between the point the bottom bar takes over and roughly 1120px there is only
   just enough room for eight links and the call button. Tightening the gap in
   that band keeps the button on screen instead of letting it run off the edge. */
@media (min-width: 1101px) and (max-width: 1300px) {
  nav.links { gap: 15px; }
}

nav.links a {
  position: relative;
  color: var(--paper); text-decoration: none; font-size: 14px; font-weight: 500;
  opacity: 0.82; transition: opacity var(--t-fast); padding-block: 4px;
}
nav.links a::after {
  content: ''; position: absolute; left: 0; bottom: -2px; height: 2px; width: 0;
  background: var(--accent); transition: width var(--t-base) var(--ease-out);
}
@media (hover: hover) {
  nav.links a:hover { opacity: 1; }
  nav.links a:hover::after { width: 100%; }
}
nav.links a[aria-current='page'] { opacity: 1; }
nav.links a[aria-current='page']::after { width: 100%; }

.nav-cta {
  background: var(--accent); color: var(--white) !important;
  padding: 12px 22px; font-weight: 600; font-size: 14px;
  text-decoration: none; opacity: 1 !important; white-space: nowrap;
  transition: background var(--t-fast), box-shadow var(--t-base);
}
.nav-cta::after { display: none !important; }
@media (hover: hover) { .nav-cta:hover { background: var(--accent-lo); box-shadow: var(--glow-accent); } }

.lang-toggle {
  font-family: var(--font-mono) !important; font-size: 11px !important;
  letter-spacing: 0.1em; border: 1px solid rgba(247, 245, 241, 0.28);
  padding: 7px 11px !important; opacity: 0.9 !important;
}
.lang-toggle::after { display: none !important; }
@media (hover: hover) { .lang-toggle:hover { border-color: var(--accent); color: var(--accent); } }

.mobile-toggle {
  display: none; background: rgba(247, 245, 241, 0.06);
  border: 1px solid rgba(247, 245, 241, 0.20);
  border-radius: 50%;
  color: var(--paper); cursor: pointer; width: 44px; height: 44px; flex: none;
  align-items: center; justify-content: center;
  transition: background var(--t-fast), border-color var(--t-fast);
}
.mobile-toggle:active { background: rgba(247, 245, 241, 0.14); }
.mobile-toggle .bars,
.mobile-toggle .bars::before,
.mobile-toggle .bars::after {
  display: block; width: 21px; height: 2px; background: var(--paper);
  transition: transform var(--t-base) var(--ease-out), opacity var(--t-fast);
}
.mobile-toggle .bars { position: relative; }
.mobile-toggle .bars::before, .mobile-toggle .bars::after { content: ''; position: absolute; left: 0; }
.mobile-toggle .bars::before { top: -6px; }
.mobile-toggle .bars::after { top: 6px; }
.mobile-toggle[aria-expanded='true'] .bars { background: transparent; }
.mobile-toggle[aria-expanded='true'] .bars::before { transform: translateY(6px) rotate(45deg); }
.mobile-toggle[aria-expanded='true'] .bars::after { transform: translateY(-6px) rotate(-45deg); }

/* == Icon chip ============================================================
   The component that makes a row of icons read as a set rather than as
   decoration: a bordered box at a fixed size holding a 1.75-stroke line icon
   that inherits currentColor. Because the colour comes from the chip, a
   parent hover can move the icon, the border and the fill together.
   ======================================================================== */
/* The base is the LIGHT palette, because most cards on the site are white.
   A first pass defaulted to the dark palette and every chip on a white bento
   tile rendered white-on-white — present in the DOM, invisible on screen.
   Dark surfaces opt in below rather than the other way round. */
.icon-chip {
  flex: none; display: inline-grid; place-items: center;
  width: 36px; height: 36px; border-radius: var(--r-sm);
  border: 1px solid var(--line-light); background: var(--paper-2); color: var(--slate);
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.icon-chip svg { display: block; }

/* Every dark surface the chip can land on. */
.on-dark .icon-chip,
nav.links .icon-chip,
.bento-tile.dark > .icon-chip,
.bento-tile.deep > .icon-chip,
.bento-tile.glass > .icon-chip,
.claim-card .icon-chip,
.icon-chip.is-dark {
  border-color: var(--line); background: rgba(247, 245, 241, 0.06); color: var(--on-dark);
}

/* The wrapper is invisible to layout on desktop, so the header's flex row is
   unchanged by it. It only becomes a real box in the mobile sheet below. */
.nav-grid { display: contents; }
.nav-grip, .nav-call, .nav-scrim { display: none; }
/* Desktop is a text bar. Chips and notes belong to the mobile sheet only —
   seven chips strung across a horizontal bar is clutter, not wayfinding. */
nav.links .icon-chip, nav.links .nav-note { display: none; }

/* == Mobile menu: a bottom sheet, not a full-screen takeover ===============
   It used to be a full-screen panel sliding in from the right, holding eight
   links at 19px in a single column: 623px of content that overflowed and
   scrolled on any phone shorter than an iPhone 14, and that read as a page
   change rather than a menu.

   A sheet fixes all of it. It rises from the bottom, where the thumb already
   is and where this site's own bottom bar taught the visitor to look. Two
   columns halve the height so it never scrolls. The page stays visible behind
   a scrim, so opening the menu does not feel like leaving the page.
   ======================================================================== */
@media (max-width: 1100px) {
  .mobile-toggle { display: flex; }

  .nav-scrim {
    display: block; position: fixed; inset: 0; z-index: 108;
    background: rgba(8, 12, 22, 0.6);
    opacity: 0; visibility: hidden;
    transition: opacity var(--t-base), visibility var(--t-base);
  }
  body.nav-open .nav-scrim { opacity: 1; visibility: visible; }

  nav.links {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 140;
    display: block;
    background: var(--asphalt);
    border-top: 1px solid var(--line);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: 0 -18px 50px rgba(6, 10, 20, 0.55);
    padding: 12px var(--gutter) calc(20px + env(safe-area-inset-bottom, 0px));
    /* A cap rather than a fixed height: the sheet is as tall as its contents
       and no taller, and if a future menu outgrows the screen it scrolls
       inside itself instead of pushing the page. */
    max-height: 86svh; overflow-y: auto;
    /* visibility, not just transform. A closed panel parked at translateY
       still occupies layout: it grows the document's scrollable area and its
       links stay in the tab order while the menu looks shut. */
    transform: translateY(100%);
    visibility: hidden;
    transition: transform var(--t-base) var(--ease-out), visibility var(--t-base);
  }
  nav.links.open { transform: translateY(0); visibility: visible; }

  /* The grab handle. It does nothing on its own — the scrim and the toggle
     both close the sheet — but it is the shape that tells a phone user this
     panel came from the bottom edge and can go back there. */
  .nav-grip {
    display: block; width: 38px; height: 4px; margin: 0 auto 14px;
    border-radius: var(--r-pill); background: rgba(247, 245, 241, 0.24);
  }

  /* One column, not two. With an icon chip and a note on every row there is
     no longer a reason to halve the width, and a single column lets each item
     say what is behind it instead of being a two-word tile. */
  .nav-grid { display: grid; grid-template-columns: 1fr; gap: 6px; }
  nav.links a {
    display: flex; align-items: center; gap: 13px;
    font-size: 15px; font-weight: 600; opacity: 1;
    padding: 11px 12px; border: 1px solid transparent;
    border-radius: var(--r-sm); background: transparent;
  }
  nav.links a::after { display: none; }
  /* Both of these are switched off for the desktop bar further up, so the
     sheet has to turn them back on explicitly. */
  nav.links .icon-chip { display: inline-grid; }
  nav.links .nav-note { display: block; }
  .nav-text { display: grid; gap: 2px; min-width: 0; }
  .nav-label { line-height: 1.25; }
  .nav-note {
    display: block; font-size: 12px; font-weight: 500; letter-spacing: 0;
    color: var(--on-dark-mute); line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  nav.links a[aria-current='page'] {
    color: var(--accent);
    border-color: rgba(214, 48, 60, 0.38);
    background: rgba(214, 48, 60, 0.10);
  }
  nav.links a[aria-current='page'] .icon-chip {
    border-color: rgba(214, 48, 60, 0.45); background: rgba(214, 48, 60, 0.14); color: var(--accent);
  }
  nav.links a[aria-current='page'] .nav-note { color: rgba(214, 48, 60, 0.75); }
  /* :active rather than :hover — this panel only exists on touch. */
  nav.links a:active { background: rgba(247, 245, 241, 0.06); }
  nav.links a:active .icon-chip { border-color: rgba(247, 245, 241, 0.28); color: var(--paper); }

  /* Both of these need the `nav.links a` prefix to outrank the tile styling
     above — a bare `.nav-cta` loses to it on specificity and the primary
     action came out looking like a seventh menu tile. */
  nav.links a.nav-cta {
    display: block; text-align: center; padding: 17px 20px; margin-top: 10px;
    background: var(--accent); border-color: transparent; border-radius: var(--r-pill);
    font-size: 15px;
  }
  /* A quiet text line, not another tile. */
  nav.links a.nav-call {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    margin-top: 14px; padding: 0; border: 0; background: none; border-radius: 0;
    font-size: 13px; font-weight: 500; opacity: 0.68; text-decoration: none;
  }
  nav.links a.nav-call svg { flex: none; }
  .nav-call::after { display: none !important; }

  .lang-toggle { display: block; text-align: center; margin-top: 14px; }

  /* The sheet lives inside .site-header, which is a stacking context of its
     own (sticky + z-index: 100). So the sheet's z-index only orders it
     against its siblings there — against the rest of the page it is pinned at
     100, and the bottom bar at 130 painted straight over it. Raising the
     header while the menu is open is what actually lifts the sheet. */
  body.nav-open .site-header { z-index: 150; }

  /* The page behind stops scrolling and stays exactly where it is. Chrome
     keeps the scroll offset under overflow:hidden, so nothing has to be saved
     and put back — and an earlier attempt that fixed the body and restored an
     offset was itself the reason the page shifted a few pixels every time the
     menu closed.

     What did have to change is the header. It is sticky, and a sticky element
     resolves against a scrolling ancestor; with the body no longer scrolling
     it fell back to its place at the top of the document, which is off screen
     for a reader who is halfway down. Fixed while the sheet is open, it stays
     where the reader can see it — and so does the button that closes it. */
  body.nav-open {
    overflow: hidden;
  }

  body.nav-open .site-header {
    position: fixed;
    top: 0; left: 0; right: 0;
  }

  /* The scrim dims the page, not the header.

     The scrim is a child of .navbar, not a sibling of it — so raising the
     navbar cannot help: the scrim would come up with it. What has to sit above
     the scrim is the navbar's own two children, the logo and the toggle.
     Without this a tap meant for the close button lands on the dimmed layer,
     which is the whole bar's worth of the screen that stops responding. */
  body.nav-open .navbar > .logo,
  body.nav-open .navbar > .mobile-toggle {
    position: relative;
    z-index: 120;
  }
}

/* == 09 Hero (motion graphics) ============================================ */
/* No photography and no video file. The background is drawn entirely in CSS
   and SVG: a receding road plane, a radar sweep over the load sources, and
   load chips that surface and fade. Everything stops under reduced-motion. */
.hero {
  position: relative; overflow: hidden;
  background: var(--asphalt-3);
  color: var(--paper);
  margin-top: calc(var(--header-h) * -1);
  padding: calc(var(--header-h) + clamp(34px, 4vw, 56px)) 0 clamp(56px, 7vw, 84px);
  min-height: min(88vh, 840px);
  display: flex; align-items: center;
  isolation: isolate;
}
.hero > .wrap { width: 100%; }

/* Photography behind the hero.
   Uses the full-colour plate, not one of the pre-graded ones: those are
   already darkened for text-directly-on-image use, and stacking these
   gradients on top of one flattened the truck into solid navy. All the
   darkening happens here, so the contrast is tunable in one place. */
.hero-photo {
  position: absolute; inset: 0; z-index: 0;

  /* The path is relative to THIS FILE, not to the page.
     A url() inside a custom property resolves against the stylesheet where
     the var() is substituted, so setting --hero-img from an inline style
     attribute produced assets/css/assets/img/... and silently 404'd, leaving
     the hero flat navy with nothing in the console. Keep it here.

     Subject choice matters as much as the path. A close-up of a cab put
     another carrier's name across the hero, where it reads as if it were
     HHL's. The drone shot of a silo on a lowboy carries no third-party
     branding and says "we move the hard stuff" without saying anything we
     cannot back up. */
  --hero-img: url("../img/w/load-silo-lowboy-1600.jpg");
  --hero-img-sm: url("../img/w/load-silo-lowboy-800.jpg");

  /* Heavier on the left, where all the type sits. Every text colour used on
     top of this clears 4.5:1 against the darkest point of its own band. */
  background-image:
    linear-gradient(100deg, rgba(10,15,27,0.97) 0%, rgba(10,15,27,0.93) 32%, rgba(10,15,27,0.62) 62%, rgba(10,15,27,0.80) 100%),
    linear-gradient(0deg, rgba(10,15,27,0.94) 0%, rgba(10,15,27,0.30) 40%, rgba(10,15,27,0.55) 100%),
    var(--hero-img, none);
  background-size: cover, cover, cover;
  background-position: center, center, center 42%;
}
@media (max-width: 860px) {
  .hero-photo {
    /* Half the pixels for a background nobody zooms into.
       Reads --hero-img-sm rather than redeclaring --hero-img: a value set in an
       inline style outranks any stylesheet rule at every breakpoint, so
       redeclaring here would be ignored the moment PHP sets the image. */
    background-image:
      linear-gradient(180deg, rgba(12,18,32,0.82) 0%, rgba(12,18,32,0.92) 55%, rgba(12,18,32,0.97) 100%),
      var(--hero-img-sm, var(--hero-img, none));
    background-size: cover, cover;
    background-position: center, center 30%;
  }
}

/* Two columns: the argument on the left, the proof on the right. */
.hero-inner {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: 1.02fr 0.98fr;
  gap: clamp(32px, 4vw, 64px); align-items: center;
}
@media (max-width: 980px) { .hero-inner { grid-template-columns: 1fr; gap: 40px; } }

.hero h1 { margin-bottom: 20px; color: var(--paper); }
.hero h1 em {
  font-style: normal;
  background: linear-gradient(100deg, var(--accent-hi), var(--accent));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p.lede { font-size: clamp(16px, 1.9vw, 19px); color: var(--on-dark); max-width: 54ch; margin-bottom: 30px; }

/* Small proof row under the CTAs: the three claims we can actually stand on. */
.hero-proof {
  display: flex; flex-wrap: wrap; gap: 26px;
  margin-top: 34px; padding-top: 26px;
  border-top: 1px solid rgba(247, 245, 241, 0.14);
}
.hero-proof div { min-width: 96px; }
.hero-proof .v {
  font-size: clamp(22px, 2.6vw, 30px); font-weight: 700; letter-spacing: -0.03em;
  color: var(--paper); font-variant-numeric: tabular-nums; line-height: 1;
}
.hero-proof .k {
  font-size: 11px; font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--on-dark-mute); margin-top: 8px;
}

/* The recap panel: the signature object, now a properly elevated card. */
.hero-card {
  position: relative;
  background: linear-gradient(165deg, rgba(30, 41, 70, 0.92), rgba(16, 23, 41, 0.94));
  border: 1px solid rgba(247, 245, 241, 0.14);
  box-shadow: var(--shadow-3);
  backdrop-filter: blur(22px) saturate(150%);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  padding: clamp(22px, 2.6vw, 32px);
}
.hero-card-top {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px; flex-wrap: wrap; padding-bottom: 18px;
  border-bottom: 1px solid rgba(247, 245, 241, 0.12);
}
.hero-card-top .who { font-size: 15px; font-weight: 600; color: var(--paper); }
.hero-card-top .when {
  font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-dark-mute);
}
.hero-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; padding: 24px 0 20px; }
.hero-metric .v {
  font-size: clamp(24px, 3vw, 34px); font-weight: 700; letter-spacing: -0.035em;
  line-height: 1; color: var(--paper); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.hero-metric .v.good { color: var(--signal); }
.hero-metric .k {
  font-size: 10px; font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--on-dark-mute); margin-top: 9px;
}
.hero-card .recap-bar-track { border-radius: var(--r-pill); height: 7px; }
.hero-card .recap-bar-fill { border-radius: var(--r-pill); }
.hero-card-foot {
  margin-top: 20px; padding-top: 16px;
  border-top: 1px solid rgba(247, 245, 241, 0.12);
  font-size: 13px; color: var(--on-dark-mute);
  display: flex; align-items: center; gap: 9px;
}

/* Two photo chips tucked under the card, so the fold shows real freight. */
.hero-thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px; }
.hero-thumb {
  position: relative; overflow: hidden; border-radius: var(--r-md);
  aspect-ratio: 16 / 10; border: 1px solid rgba(247, 245, 241, 0.12);
}
.hero-thumb img { width: 100%; height: 100%; object-fit: cover; }
.hero-thumb span {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 20px 12px 9px;
  background: linear-gradient(transparent, rgba(8, 12, 22, 0.9));
  font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--paper);
}
.hero-bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; }

/* Horizon wash. Sits where the road plane vanishes, so the two read as one
   scene rather than two unrelated effects. */
.hero-bg .glow {
  position: absolute; left: 50%; top: 70%; transform: translate(-50%, -50%);
  width: min(1200px, 140%); height: 480px;
  background: radial-gradient(ellipse at center, rgba(214, 48, 60, 0.34), rgba(214, 48, 60, 0.08) 44%, transparent 72%);
  filter: blur(14px);
}
/* Darkens the edges and the type area without flattening the road, which
   lives in the lower half. */
.hero-bg .vignette {
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 80% at 50% 30%, transparent 42%, rgba(6, 10, 20, 0.5) 100%),
    linear-gradient(180deg, rgba(12, 18, 32, 0.88) 0%, rgba(12, 18, 32, 0.30) 46%, rgba(12, 18, 32, 0.55) 100%);
}

/* Receding road: a plane tilted in 3D with lane lines pulled toward camera. */
/* A steeper rotateX squeezes the whole plane into a few pixels at the bottom
   of the frame. 66deg keeps enough of it on screen to actually read as a road
   pulling toward the viewer. */
.hero-bg .road {
  position: absolute; left: 50%; bottom: -2%;
  width: 300%; height: 82%;
  transform: translateX(-50%) perspective(560px) rotateX(66deg);
  transform-origin: 50% 100%;
  background-image:
    repeating-linear-gradient(0deg, rgba(215, 219, 226, 0.42) 0 3px, transparent 3px 74px),
    repeating-linear-gradient(90deg, rgba(215, 219, 226, 0.16) 0 2px, transparent 2px 128px);
  animation: roadPull 1.5s linear infinite;
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,0.55) 34%, transparent 74%);
          mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,0.55) 34%, transparent 74%);
}
@keyframes roadPull { to { background-position: 0 74px, 0 0; } }

/* Centre lane in brand red */
.hero-bg .lane {
  position: absolute; left: 50%; bottom: -2%;
  width: 9px; height: 82%;
  transform: translateX(-50%) perspective(560px) rotateX(66deg);
  transform-origin: 50% 100%;
  background: repeating-linear-gradient(0deg, var(--accent) 0 38px, transparent 38px 92px);
  animation: roadPull 1.5s linear infinite;
  opacity: 0.9;
  -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 70%);
          mask-image: linear-gradient(to top, #000 0%, transparent 70%);
}

/* Radar. Sits low-right, in the gap under the recap card, so it reads as its
   own element instead of a shape hidden behind the card. */
.hero-bg .radar {
  position: absolute; right: 3%; bottom: 4%;
  width: min(280px, 26vw); aspect-ratio: 1;
  opacity: 0.62;
}
.hero-bg .radar .ring { fill: none; stroke: rgba(247, 245, 241, 0.13); stroke-width: 1; }
.hero-bg .radar .cross { stroke: rgba(247, 245, 241, 0.09); stroke-width: 1; }
.hero-bg .radar .blip { fill: var(--accent); animation: blip 3.4s ease-in-out infinite; }
.hero-bg .radar .blip:nth-of-type(2) { animation-delay: 0.7s; }
.hero-bg .radar .blip:nth-of-type(3) { animation-delay: 1.5s; }
.hero-bg .radar .blip:nth-of-type(4) { animation-delay: 2.2s; }
@keyframes blip { 0%, 70%, 100% { opacity: 0; r: 3; } 12% { opacity: 1; r: 5; } 40% { opacity: 0.35; r: 8; } }
.hero-bg .sweep {
  position: absolute; inset: 0;
  background: conic-gradient(from 0deg, rgba(46, 158, 109, 0.30), transparent 22%, transparent 100%);
  border-radius: 50%;
  animation: sweep 4.6s linear infinite;
  -webkit-mask-image: radial-gradient(circle, #000 68%, transparent 69%);
          mask-image: radial-gradient(circle, #000 68%, transparent 69%);
}
@keyframes sweep { to { transform: rotate(360deg); } }

/* Load chips drifting up out of the feed */
.hero-bg .chips { position: absolute; inset: 0; overflow: hidden; }
.hero-chip-fly {
  position: absolute;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em;
  color: rgba(247, 245, 241, 0.5);
  background: rgba(24, 33, 58, 0.72);
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  padding: 6px 11px; white-space: nowrap;
  animation: chipFly 11s linear infinite;
  opacity: 0;
}
@keyframes chipFly {
  0%   { opacity: 0; transform: translateY(30px); }
  12%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-90px); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-bg .road, .hero-bg .lane, .hero-bg .sweep, .hero-chip-fly, .hero-bg .radar .blip { animation: none; }
  .hero-chip-fly { opacity: 1; }
}
/* Below the two-column breakpoint the recap card stacks under the copy and
   there is no free quadrant left for the chips or the radar to live in. */
@media (max-width: 980px) {
  .hero-bg .radar { display: none; }
  .hero-bg .chips { display: none; }
}

.hero-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: 1.08fr 0.92fr;
  gap: clamp(36px, 5vw, 60px); align-items: center;
}
.hero h1 { margin-bottom: 22px; color: var(--paper); }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero p.lede { font-size: clamp(16px, 2vw, 19px); color: var(--on-dark); max-width: 520px; margin-bottom: 34px; }
@media (max-width: 980px) { .hero-grid { grid-template-columns: 1fr; } }

/* Live strip under the hero CTAs */
.hero-live {
  display: flex; align-items: center; gap: 10px; margin-top: 30px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--on-dark-mute);
}
.hero-live .dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--signal); flex: none;
  box-shadow: 0 0 0 0 rgba(46, 158, 109, 0.6); animation: ping 2.4s ease-out infinite;
}
@keyframes ping { 70%, 100% { box-shadow: 0 0 0 10px rgba(46, 158, 109, 0); } }

/* Interior page hero */
/* The header is a floating pill now, so the hero runs underneath it and the
   photograph is uninterrupted. The negative margin pulls the band up behind
   the pill and the top padding puts the content back below it — the offset
   has to live on this rule rather than beside .site-header, because this
   padding shorthand appears later in the file and would otherwise win. */
.page-hero {
  position: relative; overflow: hidden;
  background: var(--asphalt); color: var(--paper);
  margin-top: calc(var(--header-h) * -1);
  padding: calc(var(--header-h) + clamp(40px, 5vw, 64px)) 0 clamp(44px, 6vw, 94px);
}
/* Interior heroes carry the same photo layer as the homepage, but quieter.
   Fading the whole layer with opacity was wrong: it faded the darkening
   gradients too, so the image washed back into the navy and vanished. The
   gradients stay at full strength and are simply set heavier here. */
.page-hero.has-plate { position: relative; }

/* The gradient used to run 0.96 / 0.90 / 0.66 / 0.86 across the whole width,
   which put a near-opaque sheet over the photograph on every one of eighteen
   pages. The image was still being downloaded and still being decoded; it just
   could not be seen.
 *
 * Contrast is only needed where the type is, so the wash is heavy on the left
 * and clears off the right, and the freight reads.
 *
 * The stops are measured, not eyeballed. Method: render the page with
 * .ph-copy/.ph-facts/.crumbs/.post-lead hidden, sample the brightest pixel in
 * the copy column strictly inside the hero's own bounds, and check the
 * smallest text colour against it. Across all ten plate pages these stops
 * measure 6.2:1 to 8.1:1, against the 4.5:1 body text needs.
 *
 * Two traps worth knowing before touching them. A sample box even one pixel
 * taller than the hero catches the boundary row against the light section
 * below and reports a false fail. And the culvert-pipe plate is the bright
 * one — how-it-works and carrier-setup use it, and a wash that clears too
 * early puts those two at 3.9:1 while every other page still looks fine. */
.page-hero.has-plate .hero-photo {
  z-index: 0; opacity: 1;
  background-image:
    linear-gradient(100deg,
      rgba(10,15,27,0.97) 0%,
      rgba(10,15,27,0.95) 38%,
      rgba(10,15,27,0.74) 60%,
      rgba(10,15,27,0.32) 82%,
      rgba(10,15,27,0.50) 100%),
    linear-gradient(0deg, rgba(10,15,27,0.88) 0%, rgba(10,15,27,0.16) 55%, rgba(10,15,27,0.42) 100%),
    var(--hero-img, none);
  background-size: cover, cover, cover;
  background-position: center, center, right 34%;
}

/* Film grain. A flat dark field at this size reads as cheap; a little noise
   over it reads as a surface. Generated inline so it costs no request, and
   sits under the content but over the photograph. */
.page-hero.has-plate::after {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: 0.42; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

/* The band no longer ends on a hard horizontal cut. A single accent hairline
   along the bottom edge, brightest under the headline and fading out to the
   right, gives the section a finish line instead of a stop. */
.page-hero.has-plate::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; z-index: 2;
  background: linear-gradient(90deg, var(--accent) 0%, rgba(214,48,60,0.35) 34%, transparent 72%);
}

/* Slow drift on the plate as the hero leaves. Same mechanism as the rest of
   the motion pass: where it is unsupported the plate is simply static. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .page-hero.has-plate .hero-photo {
      animation: plateDrift linear both;
      animation-timeline: view();
      animation-range: entry 100% exit 100%;
    }
  }
}
@keyframes plateDrift {
  from { transform: scale(1.06) translateY(0); }
  to   { transform: scale(1.06) translateY(3.5%); }
}
.page-hero h1 { color: var(--paper); margin-bottom: 16px; }
.page-hero p { color: var(--on-dark); max-width: 640px; font-size: clamp(16px, 2vw, 18px); }

/* Copy left, spec panel right. Aligned to the baseline of the copy rather
   than centred: the headline is the anchor of the band and the panel should
   read as sitting on the same line, not floating beside it. */
.ph-grid {
  display: grid; grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(28px, 5vw, 72px); align-items: end;
}
.page-hero.is-solo .ph-grid { grid-template-columns: 1fr; }
@media (max-width: 900px) { .ph-grid { grid-template-columns: 1fr; gap: 28px; } }

/* Now that the photograph shows through, the panel has to hold its own
   against it rather than sit on flat navy. Heavier blur, a real edge, an
   accent hairline along the top, and the rows hairlined instead of gapped —
   one card, not four stacked chips. */
.ph-facts {
  position: relative; border-radius: var(--r-md); overflow: hidden;
  background: rgba(14, 21, 36, 0.62);
  border: 1px solid rgba(247, 245, 241, 0.14);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  box-shadow: 0 24px 60px rgba(6, 10, 20, 0.42);
}
.ph-facts::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: linear-gradient(90deg, var(--accent), rgba(214, 48, 60, 0.15));
}
.ph-fact {
  display: flex; align-items: baseline; justify-content: space-between; gap: 18px;
  padding: 15px clamp(16px, 1.8vw, 22px);
  border-top: 1px solid rgba(247, 245, 241, 0.09);
}
.ph-fact:first-child { border-top: 0; }
.ph-fact .k {
  font-size: 10px; font-weight: 600; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--on-dark-mute);
}
.ph-fact .v {
  font-size: clamp(17px, 1.9vw, 22px); font-weight: 700; letter-spacing: -0.02em;
  color: var(--paper); font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Two across on a phone: four full-width rows of one figure each is a lot of
   hero for very little information. */
@media (max-width: 620px) {
  .ph-facts { grid-template-columns: 1fr 1fr; }
  .ph-fact { flex-direction: column; align-items: flex-start; gap: 5px; }
  .ph-fact .v { font-size: 18px; }
}

.ph-actions { margin-top: 28px; }

/* ---- Before / after ------------------------------------------------------
   Two panels and an arrow. The "after" side is the one carrying the claim, so
   it gets the accent and the elevation; the "before" side is deliberately
   flatter and quieter, because that is the state being argued against. */
.swap {
  display: grid; grid-template-columns: 1fr auto 1fr;
  gap: clamp(16px, 2.4vw, 30px); align-items: stretch;
}
.swap-side {
  padding: clamp(22px, 2.6vw, 32px);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: rgba(247, 245, 241, 0.03);
}
.swap-side.is-after {
  background: var(--asphalt-2);
  border-color: rgba(214, 48, 60, 0.42);
  box-shadow: var(--shadow-2);
}
.swap-side h3 { font-size: clamp(19px, 2.2vw, 25px); color: var(--paper); margin-bottom: 12px; }
.swap-side > p { font-size: 15px; color: var(--on-dark); line-height: 1.7; }
.swap-list { list-style: none; margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--line); }
.swap-list li {
  position: relative; padding-left: 20px; margin-bottom: 10px;
  font-size: 13.5px; color: var(--on-dark-mute); line-height: 1.55;
}
.swap-list li::before {
  content: ''; position: absolute; left: 0; top: 8px;
  /* On the dark "before" panel --line-light-2 rendered as nothing at all.
     Quieter than the accent, but still visible. */
  width: 6px; height: 6px; background: rgba(247, 245, 241, 0.35); transform: rotate(45deg);
}
.is-after .swap-list li::before { background: var(--accent); }

.swap-arrow {
  display: grid; place-items: center; color: var(--accent);
  width: 44px; align-self: center;
}
.swap-arrow svg { width: 30px; height: 30px; }

/* The arrow points the wrong way once the panels stack, so it turns with
   them rather than being hidden — it is the only thing saying these two are
   a sequence and not a pair of alternatives. */
@media (max-width: 860px) {
  .swap { grid-template-columns: 1fr; }
  .swap-arrow { transform: rotate(90deg); width: auto; padding: 4px 0; }
}

.swap-caveat {
  margin-top: 26px; padding: 18px 22px;
  border-left: 3px solid var(--accent);
  background: rgba(247, 245, 241, 0.04);
  font-size: 14.5px; line-height: 1.7; color: var(--on-dark); max-width: inherit;
}
.swap-caveat strong { color: var(--paper); }

/* ---- CTA variants ------------------------------------------------------
   See finalCta() in _build/partials.js for which page gets which and why. */

/* panel: the ask on the left, the two ways to reach a human on the right. */
.cta-panel { background: var(--asphalt); }
.cp-grid {
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(30px, 5vw, 72px); align-items: center;
}
@media (max-width: 900px) { .cp-grid { grid-template-columns: 1fr; gap: 28px; } }
.cp-lede { color: var(--on-dark); font-size: clamp(16px, 1.9vw, 18px); margin-top: 14px; max-width: 46ch; }

.cp-actions { display: grid; gap: 10px; }
.cp-action {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 18px; text-decoration: none;
  background: rgba(247, 245, 241, 0.045);
  border: 1px solid var(--line); border-radius: var(--r-md);
  transition: border-color var(--t-base), background var(--t-base), transform var(--t-base) var(--ease-out);
}
.cp-action span { display: grid; gap: 3px; min-width: 0; }
.cp-k { font-size: 11px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--on-dark-mute); }
.cp-v { font-size: clamp(17px, 1.9vw, 21px); font-weight: 700; color: var(--paper); letter-spacing: -0.01em; }
@media (hover: hover) {
  .cp-action:hover { border-color: rgba(214, 48, 60, 0.5); background: rgba(214, 48, 60, 0.09); transform: translateX(3px); }
  .cp-action:hover .icon-chip { border-color: rgba(214, 48, 60, 0.5); background: rgba(214, 48, 60, 0.14); color: var(--accent); }
}
.cp-note { font-size: 13px; color: var(--on-dark-mute); margin-top: 6px; line-height: 1.6; }

/* quiet: one line and one link, for the end of an article. */
.cta-quiet { background: var(--paper-2); }
.cq-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(20px, 3vw, 44px); flex-wrap: wrap;
  padding: clamp(24px, 3vw, 34px) clamp(24px, 3vw, 38px);
  background: var(--white); border: 1px solid var(--line-light);
  border-radius: var(--r-md); box-shadow: var(--shadow-1);
}
.cq-inner h2 { font-size: clamp(20px, 2.4vw, 26px); margin-bottom: 8px; }
.cq-inner p { color: var(--slate); font-size: 15px; max-width: 58ch; line-height: 1.7; }
.cq-inner .btn { white-space: nowrap; }

/* ---- Blog post: a reading layout --------------------------------------
   The lead photo bleeds the full width and overlaps up into the hero, which
   is what makes the article feel like a piece rather than a page with a
   picture on it. */
.post-lead {
  position: relative; z-index: 2;
  max-width: 1100px; margin: -70px auto 0; padding-inline: var(--gutter);
}
.post-lead img {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: var(--r-lg); box-shadow: var(--shadow-3);
}
@media (max-width: 760px) { .post-lead { margin-top: -34px; } .post-lead img { aspect-ratio: 3 / 2; } }

.post-body-band { padding-top: clamp(40px, 5vw, 64px); }
.post-article { max-width: none; }

/* 17px at a 68-character measure. The site's 15px body is right for tiles and
   tables and too small for 1,200 words of continuous prose. */
.prose.prose-lg { max-width: none; }
.prose-lg p, .prose-lg li { font-size: 17px; line-height: 1.82; }
.prose-lg p { margin-bottom: 20px; }
.prose-lg h2 { font-size: clamp(24px, 3.2vw, 32px); margin: 52px 0 16px; }
.prose-lg h3 { font-size: 21px; margin: 32px 0 12px; }
/* First paragraph reads as a stand-first, so the article opens at a size that
   invites the read rather than starting flat. */
.prose-lg > p:first-of-type { font-size: 20px; line-height: 1.7; color: var(--slate); }

.post-tool {
  margin-top: 52px; padding: clamp(26px, 3vw, 36px);
  background: var(--asphalt); color: var(--paper);
  border-radius: var(--r-md); border-top: 3px solid var(--accent);
  display: grid; gap: 24px;
}
.post-tool h2 { font-size: clamp(20px, 2.4vw, 26px); color: var(--paper); margin-bottom: 10px; }
.post-tool p { font-size: 15px; color: var(--on-dark); line-height: 1.7; }
.pt-actions { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.pt-call { color: var(--on-dark-mute); font-size: 14px; text-decoration: none; }
@media (hover: hover) { .pt-call:hover { color: var(--paper); } }

.post-more { background: var(--paper-2); }
.post-more .section-head { margin-bottom: clamp(24px, 3vw, 36px); }

/* ---- Blog index: one grid, every post a peer ---------------------------
   The lead spans the full width with a landscape image; the rest sit beside
   each other underneath. Every card carries the same parts in the same order,
   so scanning is one movement rather than three different reading tasks. */
.post-grid {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: clamp(18px, 2.2vw, 28px);
}
.post-card {
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--white); border: 1px solid var(--line-light);
  border-radius: var(--r-md); box-shadow: var(--shadow-1);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base), border-color var(--t-base);
}
.post-card.is-lead { grid-column: 1 / -1; flex-direction: row; align-items: stretch; }
@media (max-width: 900px) {
  .post-grid { grid-template-columns: 1fr; }
  .post-card.is-lead { flex-direction: column; }
}

.pc-media { display: block; overflow: hidden; background: var(--asphalt-2); }
/* <picture> is inline by default, so the <img> inside it sat on a text
   baseline and left a white gutter down the side of every card. */
.pc-media picture { display: block; height: 100%; }
.post-card.is-lead .pc-media { flex: 0 0 52%; }
.pc-media img { display: block; width: 100%; object-fit: cover; transition: transform 720ms var(--ease-out); }
/* Two different jobs, so two different rules rather than one with an
   override. In the grid the card's height follows its image, so the image
   sets the ratio. In the lead the image is a column beside the copy, so the
   card's height is already decided and the image fills it — an aspect-ratio
   there fights the stretch and leaves a white gutter around the photo. */
.post-card:not(.is-lead) .pc-media img { aspect-ratio: 16 / 10; height: auto; }
.post-card.is-lead .pc-media img { height: 100%; min-height: 340px; }

.pc-body { padding: clamp(20px, 2.4vw, 30px); display: flex; flex-direction: column; flex: 1; }
.post-card.is-lead .pc-body { justify-content: center; }
/* flex:1 on the excerpt is what pushes the CTA to the bottom of a grid card.
   In the lead the block is vertically centred instead, so the same rule just
   opens a hole between the paragraph and the link. */
.post-card.is-lead .excerpt { flex: 0 0 auto; }
.pc-body h3 { font-size: clamp(19px, 2.1vw, 24px); margin: 12px 0 10px; }
.post-card.is-lead .pc-body h3 { font-size: clamp(24px, 3vw, 34px); }
.pc-body h3 a { text-decoration: none; color: inherit; }
.pc-body .excerpt { font-size: 15px; color: var(--slate); line-height: 1.7; flex: 1; }
.pc-go {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 18px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--asphalt); transition: color var(--t-fast), gap var(--t-fast);
}
/* The whole card is the link target — the anchor sits on the heading and the
   media, and this is the affordance, so it must not read as a second button. */
@media (hover: hover) {
  .post-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: var(--line-light-2); }
  .post-card:hover .pc-media img { transform: scale(1.05); }
  .post-card:hover .pc-go { color: var(--accent); gap: 12px; }
}
.post-card:focus-within { border-color: var(--accent); }

/* ---- Dispatch vs DIY: verdict band + worked example --------------------- */
.verdict-band { background: var(--asphalt); color: var(--paper); }
.verdict {
  display: grid; grid-template-columns: 200px 1fr;
  gap: clamp(24px, 4vw, 60px); align-items: start;
}
@media (max-width: 820px) { .verdict { grid-template-columns: 1fr; gap: 18px; } }
.verdict-mark { padding-top: 8px; }
.verdict-lead {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(21px, 2.6vw, 30px); line-height: 1.32; letter-spacing: -0.015em;
  color: var(--paper); margin-bottom: 20px;
}
.verdict-body p { color: var(--on-dark); font-size: clamp(15px, 1.8vw, 17px); line-height: 1.75; max-width: 66ch; }
.verdict-body p + p { margin-top: 14px; }
.verdict-p { color: var(--paper) !important; font-weight: 600; }

.math-split {
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(28px, 4.5vw, 66px); align-items: center;
}
@media (max-width: 900px) { .math-split { grid-template-columns: 1fr; gap: 30px; } }
.math-lede { color: var(--slate); font-size: clamp(16px, 1.9vw, 18px); margin-top: 14px; max-width: 48ch; }
.math-card {
  background: var(--asphalt); color: var(--paper);
  padding: clamp(24px, 2.8vw, 34px); border-radius: var(--r-md);
  border-top: 3px solid var(--accent); box-shadow: var(--shadow-2);
}
.mc-head { padding-bottom: 16px; border-bottom: 1px solid var(--line); margin-bottom: 18px; }

/* Both sides of the comparison the section asks you to run, side by side. */
.mc-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--line); }
.mc-col {
  display: grid; gap: 4px; padding: 16px 18px;
  background: var(--asphalt); font-variant-numeric: tabular-nums;
}
.mc-col.is-alt { background: rgba(214, 48, 60, 0.09); }
.mc-k { font-size: 10px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--on-dark-mute); }
.mc-v { font-size: clamp(24px, 2.8vw, 32px); font-weight: 700; letter-spacing: -0.03em; color: var(--paper); }
.mc-sub { font-size: 12.5px; color: var(--on-dark-mute); }

/* The equation is the answer, so it gets the weight the old "$1,966" had. */
.mc-verdict { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line); }
.mc-eq {
  display: flex; align-items: center; gap: clamp(10px, 1.6vw, 18px);
  flex-wrap: wrap; margin-bottom: 16px;
}
.mc-eq span { display: grid; }
.mc-eq b {
  font-size: clamp(19px, 2.2vw, 26px); font-weight: 700; letter-spacing: -0.02em;
  color: var(--paper); font-variant-numeric: tabular-nums;
}
.mc-eq i { font-style: normal; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--on-dark-mute); }
.mc-eq em { font-style: normal; font-size: 18px; color: var(--on-dark-mute); }
.mc-eq .hot b { color: var(--accent-hi); }
.mc-verdict p { font-size: 14.5px; line-height: 1.72; color: var(--on-dark); }

.math-note { font-size: 12.5px; color: var(--on-dark-mute); line-height: 1.65; margin-top: 16px; }

/* Per-page hero plates.
   url() inside a custom property resolves against this stylesheet, not the
   page, which is why every one of these is written with ../img/ and why they
   live here rather than in an inline style.

   Only the freight photographs are in this list, and that is deliberate. The
   truck-* and equip-* shots all carry another carrier's identity in legible
   type — "Patterson Carriers LLC" with its MC and DOT numbers, "Chris Y
   Saulito Inc", an ITS Logistics trailer. At gallery-tile size under a caption
   that says "a carrier we dispatch" that is honest. Bled across a hero it
   reads as HHL's own truck and its own authority number, which it is not. */
.ph-img-flatbed { --hero-img: url("../img/w/load-crated-flatbed-1600.jpg"); --hero-img-sm: url("../img/w/load-crated-flatbed-800.jpg"); }
.ph-img-lowboy { --hero-img: url("../img/w/load-silo-lowboy-1600.jpg"); --hero-img-sm: url("../img/w/load-silo-lowboy-800.jpg"); }
.ph-img-reel { --hero-img: url("../img/w/load-cable-reel-1600.jpg"); --hero-img-sm: url("../img/w/load-cable-reel-800.jpg"); }
.ph-img-pipe { --hero-img: url("../img/w/load-culvert-pipe-1600.jpg"); --hero-img-sm: url("../img/w/load-culvert-pipe-800.jpg"); }
.ph-img-tanks { --hero-img: url("../img/w/load-tanks-flatbed-1600.jpg"); --hero-img-sm: url("../img/w/load-tanks-flatbed-800.jpg"); }
@media (max-width: 860px) {
  .ph-img-flatbed   { --hero-img: url("../img/w/load-crated-flatbed-800.jpg"); }
  .ph-img-lowboy    { --hero-img: url("../img/w/load-silo-lowboy-800.jpg"); }
  .ph-img-reel      { --hero-img: url("../img/w/load-cable-reel-800.jpg"); }
  .ph-img-pipe      { --hero-img: url("../img/w/load-culvert-pipe-800.jpg"); }
  .ph-img-tanks     { --hero-img: url("../img/w/load-tanks-flatbed-800.jpg"); }
}

/* Lift the content above the photo layer — but not the photo layer itself.
   `.page-hero > *` outranks `.hero-photo` on specificity, so without the
   :not() it forced the background layer from absolute back to relative,
   collapsing it to zero height with no error anywhere. */
.page-hero > *:not(.hero-photo) { position: relative; z-index: 1; }

.crumbs {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--on-dark-mute); margin-bottom: 22px;
}
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 9px; }
.crumbs li + li::before { content: '/'; margin-right: 9px; opacity: 0.45; }
.crumbs a { text-decoration: none; }
@media (hover: hover) { .crumbs a:hover { color: var(--accent); } }

/* == 10 Recap card ======================================================== */
.recap-card {
  position: relative;
  background: linear-gradient(165deg, rgba(24, 33, 58, 0.96), rgba(14, 20, 36, 0.96));
  border: 1px solid var(--line);
  padding: 30px;
  box-shadow: var(--shadow-3);
  backdrop-filter: blur(6px);
}
.recap-card::before {
  content: attr(data-label); position: absolute; top: -11px; left: 26px;
  background: var(--accent); color: var(--white); font-family: var(--font-mono);
  font-size: 10px; font-weight: 600; letter-spacing: 0.12em; padding: 5px 11px;
}
/* Chrome edge along the top, echoing the logo's metal. */
.recap-card::after {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: var(--chrome-grad); opacity: 0.5;
}
.recap-top {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; flex-wrap: wrap; margin-top: 10px;
  padding-bottom: 20px; border-bottom: 1px solid var(--line);
}
.recap-name { font-family: var(--font-display); font-size: 21px; font-weight: 600; letter-spacing: 0.01em; }
.recap-week { font-family: var(--font-mono); font-size: 12px; color: var(--on-dark-mute); }
.recap-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; padding: 24px 0; }
.metric-label {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--on-dark-mute); margin-bottom: 8px;
}
.metric-value {
  font-family: var(--font-mono); font-size: clamp(23px, 3vw, 29px);
  font-weight: 600; color: var(--chrome); font-variant-numeric: tabular-nums;
}
.metric-value.good { color: var(--signal); }
.recap-bar-row { display: flex; align-items: center; gap: 12px; padding: 7px 0; }
.recap-bar-label { font-family: var(--font-mono); font-size: 10px; color: var(--on-dark-mute); width: 42px; flex: none; }
.recap-bar-track { flex: 1; height: 6px; background: rgba(247, 245, 241, 0.09); overflow: hidden; }
.recap-bar-fill {
  height: 100%; background: var(--accent-grad); width: 0;
  transition: width 1100ms var(--ease-out); transition-delay: var(--bar-delay, 0ms);
}
[data-bars].is-in .recap-bar-fill, .recap-card.is-in .recap-bar-fill { width: var(--bar-w, 0%); }
html:not(.js-on) .recap-bar-fill { width: var(--bar-w, 0%); }
.recap-foot {
  margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--line);
  font-size: 13px; color: var(--on-dark-mute);
}

/* == 11 Trust strip ======================================================= */
.trust-strip { background: var(--paper); padding: 22px 0; border-bottom: 1px solid var(--line-light); }
.trust-row {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 14px 28px;
  font-family: var(--font-mono); font-size: 12px; color: var(--slate);
  letter-spacing: 0.05em; text-transform: uppercase; list-style: none;
}
.trust-row li { display: flex; align-items: center; gap: 9px; }
.trust-row li::before {
  content: ''; width: 6px; height: 6px; background: var(--accent);
  transform: rotate(45deg); flex: none;
}

/* == 12 Sections ========================================================== */
/* Mile-marker divider between major bands. */
.mm-divider {
  display: flex; align-items: center; gap: 16px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--slate); margin-bottom: 26px;
}
.mm-divider::before, .mm-divider::after { content: ''; height: 1px; background: var(--line-light); flex: 1; }

/* == 13 Services ========================================================== */
.services-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
  background: var(--line-light); border: 1px solid var(--line-light);
}
.service-tile {
  position: relative; background: var(--paper); padding: 34px 28px;
  transition: background var(--t-base), transform var(--t-base) var(--ease-out);
  overflow: hidden;
}
.service-tile::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent); transform: scaleY(0); transform-origin: top;
  transition: transform var(--t-base) var(--ease-out);
}
@media (hover: hover) {
  .service-tile:hover { background: var(--white); }
  .service-tile:hover::before { transform: scaleY(1); }
}
.service-tile .num { font-family: var(--font-mono); font-size: 12px; color: var(--accent); margin-bottom: 16px; display: block; }
.service-tile h3 { font-size: 25px; margin-bottom: 10px; }
.service-tile p { font-size: 14px; color: var(--slate); }
@media (max-width: 900px) { .services-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .services-grid { grid-template-columns: 1fr; } }

/* Detailed equipment cards with photography */
.svc-detail { display: grid; grid-template-columns: repeat(2, 1fr); gap: 26px; }
.svc-block {
  background: var(--white); border: 1px solid var(--line-light);
  box-shadow: var(--shadow-1); overflow: hidden;
  transition: box-shadow var(--t-base), transform var(--t-base) var(--ease-out);
  display: flex; flex-direction: column;
}
@media (hover: hover) { .svc-block:hover { box-shadow: var(--shadow-2); transform: translateY(-3px); } }
.svc-media { position: relative; aspect-ratio: 16 / 9; overflow: hidden; background: var(--asphalt-2); }
.svc-media img { width: 100%; height: 100%; object-fit: cover; transition: transform 700ms var(--ease-out); }
@media (hover: hover) { .svc-block:hover .svc-media img { transform: scale(1.05); } }
.svc-media .num {
  position: absolute; top: 0; left: 0; z-index: 1;
  background: var(--accent); color: var(--white);
  font-family: var(--font-mono); font-size: 12px; padding: 7px 12px;
}
.svc-body { padding: 28px; flex: 1; display: flex; flex-direction: column; }
.svc-block h3 { font-size: 26px; margin-bottom: 12px; }
.svc-block p { font-size: 14px; color: var(--slate); margin-bottom: 16px; }
.svc-block ul { list-style: none; font-size: 14px; margin-top: auto; }
.svc-block li { padding: 7px 0 7px 24px; position: relative; color: var(--asphalt); }
.svc-block li::before {
  content: ''; position: absolute; left: 0; top: 15px;
  width: 8px; height: 2px; background: var(--accent);
}
.svc-block a { color: var(--asphalt); font-weight: 600; }
@media (max-width: 760px) { .svc-detail { grid-template-columns: 1fr; } }

/* == 14 Process (mile markers) ============================================ */
/* == Band rhythm ==========================================================
   Dark bands are punctuation. The rule this file now follows: never more
   than two dark bands in a row, and never a dark run longer than about two
   viewport heights. Measured before the change, the homepage ran 5,735px of
   unbroken navy across three sections and every interior page was
   dark → light → dark → dark. That is what made the site feel heavy and
   samey regardless of what was inside the sections.
   ======================================================================== */
.process-strip { background: var(--asphalt); color: var(--paper); }

/* Light variant: the band is paper, the tiles inside stay dark. The section
   keeps its visual weight, but the page gets a breath between dark runs. */
.process-strip.is-light { background: var(--paper-2); color: var(--asphalt); }
.process-strip.is-light .section-head h2 { color: var(--asphalt); }
.process-strip.is-light .section-head p { color: var(--slate); }
.process-strip.is-light .pin-dots span { background: rgba(18, 26, 44, 0.16); }
.process-strip.is-light .pin-dots span.on { background: var(--accent); }
.process-strip.is-light .pin-progress { background: rgba(18, 26, 44, 0.10); }
.mile-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(26px, 3vw, 40px); }
.mile { position: relative; border-left: 2px solid var(--accent); padding-left: 24px; }
.mile::before {
  content: ''; position: absolute; left: -6px; top: 0;
  width: 10px; height: 10px; background: var(--accent); transform: rotate(45deg);
}
.mile .mm { font-family: var(--font-mono); font-size: 13px; color: var(--accent); display: block; margin-bottom: 12px; letter-spacing: 0.08em; }
.mile h3 { font-size: 23px; margin-bottom: 10px; color: var(--paper); }
.mile p { font-size: 14px; color: var(--on-dark); }
@media (max-width: 760px) { .mile-list { grid-template-columns: 1fr; } }

.steps-inline { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; margin-top: 52px; }
.steps-inline .mile h3 { color: var(--asphalt); font-size: 20px; }
.steps-inline .mile p { color: var(--slate); }
@media (max-width: 760px) { .steps-inline { grid-template-columns: 1fr; } }

/* == 15 Load board ======================================================== */
.board-band { background: var(--asphalt); color: var(--paper); }
/* Full width, not capped at 960. The board is the evidence for the section's
   claim, and a table of six columns squeezed into two thirds of the page while
   the last third sits empty made it look like a widget rather than the point. */
.board {
  background: linear-gradient(165deg, rgba(24, 33, 58, 0.96), rgba(14, 20, 36, 0.98));
  border: 1px solid var(--line); box-shadow: var(--shadow-3);
}
.board-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 16px 22px; border-bottom: 1px solid var(--line); }
.board-title { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--on-dark-mute); }
/* Replaces the pulsing "LIVE FEED" badge. Nothing here is live, and a site
   whose argument is "we show you the real numbers" should not lead with a fake
   telemetry light. What a carrier actually needs to know to read the table is
   whose truck it is scored against, so that is what the slot says now. */
.board-scope {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em;
  color: var(--on-dark-mute); text-align: right;
}
.board-scan {
  display: flex; align-items: center; gap: 12px; padding: 11px 22px;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 11px; color: var(--on-dark-mute); letter-spacing: 0.06em;
}
.scan-bar { flex: 1; height: 3px; background: rgba(247, 245, 241, 0.08); position: relative; overflow: hidden; }
.scan-bar::after {
  content: ''; position: absolute; top: 0; left: -30%; width: 30%; height: 100%;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: scan 2.6s linear infinite;
}
@keyframes scan { to { left: 110%; } }
.board-row {
  display: grid;
  grid-template-columns: 34px minmax(0, 1.9fr) 0.62fr 0.42fr 0.62fr 0.72fr;
  gap: 14px; align-items: center;
  padding: 14px 22px; border-bottom: 1px solid var(--line-soft); font-size: 14px;
}
.board-row:last-of-type { border-bottom: 0; }
.board-row.head {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--on-dark-mute); padding: 11px 22px;
  border-bottom: 1px solid var(--line);
}
/* Figures right-aligned so the decimal points line up down the column. A rate
   table where $2.74 and $2.96 do not stack is harder to compare than it needs
   to be, which is the one job this table has. */
.board-row > *:nth-child(n+3) { text-align: right; font-variant-numeric: tabular-nums; }

.board-rank { font-family: var(--font-mono); font-size: 11px; color: rgba(247, 245, 241, 0.34); }
.board-lane { font-weight: 600; color: var(--paper); display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Deliberately not colour-coded by value. Green on the highest rate said
   "this is the good one" about the $3.12 Laredo load, while the row beneath
   the table explains at length that it is the wrong load. Two signals
   disagreeing is worse than one signal: the accent bar and the AI PICK badge
   carry the recommendation, and the rate column just reports the rate. */
.board-rpm { font-family: var(--font-mono); font-weight: 600; color: var(--chrome); }
.board-net { font-weight: 700; color: var(--paper); }

/* The recommended row carries a left accent rather than a filled background:
   at 5 rows a filled row reads as "selected", and this is a recommendation. */
.board-row.is-pick { position: relative; background: rgba(214, 48, 60, 0.07); }
.board-row.is-pick::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--accent);
}
.board-row.is-pick .board-rank { color: var(--accent); }

.ai-pick {
  font-family: var(--font-mono); font-size: 9px; background: var(--accent); color: var(--white);
  padding: 3px 8px; letter-spacing: 0.08em; display: inline-block;
}

/* The reasoning behind the pick. This is the part that turns an assertion into
   a demonstration, so it gets its own band inside the board rather than being
   folded into the footnote. */
.board-why {
  padding: 20px 22px; border-top: 1px solid var(--line);
  background: rgba(247, 245, 241, 0.035);
}
.board-why p { font-size: 14.5px; line-height: 1.72; color: var(--on-dark); max-width: 88ch; }
.board-note { padding: 14px 22px; font-size: 12.5px; color: var(--on-dark-mute); border-top: 1px solid var(--line-soft); }

@media (max-width: 760px) {
  .board-row { grid-template-columns: 26px minmax(0, 1.5fr) 0.7fr 0.8fr; gap: 10px; padding: 13px 16px; font-size: 13px; }
  .board-row .hide-m { display: none; }
  .board-head, .board-scan, .board-why, .board-note { padding-inline: 16px; }
  .board-head { flex-direction: column; align-items: flex-start; gap: 6px; }
  .board-scope { text-align: left; }
}

/* == 16 Gallery + video =================================================== */
.gallery-band { background: var(--asphalt); }
.gal-grid { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 200px; gap: 14px; }
.gal-item { position: relative; overflow: hidden; border: 1px solid var(--line); margin: 0; }
.gal-item img { width: 100%; height: 100%; object-fit: cover; transition: transform 720ms var(--ease-out); }
@media (hover: hover) { .gal-item:hover img { transform: scale(1.06); } }
.gal-item.wide { grid-column: span 2; }
.gal-item.tall { grid-row: span 2; }
.gal-item.hero-tile { grid-column: span 2; grid-row: span 2; }
.gal-cap {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 30px 16px 12px;
  background: linear-gradient(transparent, rgba(8, 12, 22, 0.9));
  color: var(--paper); font-family: var(--font-mono); font-size: 11px;
  letter-spacing: 0.07em; text-transform: uppercase;
  transform: translateY(6px); opacity: 0.9;
  transition: transform var(--t-base) var(--ease-out), opacity var(--t-base);
}
@media (hover: hover) { .gal-item:hover .gal-cap { transform: translateY(0); opacity: 1; } }
@media (max-width: 900px) { .gal-grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 168px; } .gal-item.hero-tile { grid-column: span 2; } }
@media (max-width: 560px) { .gal-grid { grid-auto-rows: 140px; } }

.reviews-band { background: var(--paper); }

/* These are 9:16 phone videos. Making one "featured" by giving it more columns
   also makes it proportionally taller, so it hung far below the others and the
   row read as broken rather than hierarchical. Portrait media wants a set of
   equals, so all five share a row and the positioning statement gets its own
   full-width band underneath. */
/* Head and claim share a row, so the statement about paid badges sits beside
   the promise instead of being tacked on under the videos, and the right half
   of the band stops being empty. */
.head-split {
  display: grid; grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(28px, 4vw, 60px); align-items: end;
  margin-bottom: clamp(28px, 3.4vw, 42px);
}
@media (max-width: 900px) { .head-split { grid-template-columns: 1fr; gap: 26px; align-items: start; } }
.head-lede { color: var(--slate); font-size: clamp(16px, 1.9vw, 18px); margin-top: 14px; max-width: 46ch; }
.claim-card {
  background: var(--asphalt); color: var(--paper);
  padding: clamp(22px, 2.4vw, 28px); border-radius: var(--r-md);
  border-top: 3px solid var(--accent);
}
.claim-card p { font-size: 14.5px; color: var(--on-dark); line-height: 1.7; margin-bottom: 16px; }
.claim-card .tool-link { color: var(--paper); }
@media (hover: hover) { .claim-card .tool-link:hover { color: var(--accent-hi); } }

/* Five 9:16 videos. They were in a 5-column grid that a stray `.narrow`
   utility (max-width: 800px) was capping, so each frame came out 144px wide:
   a phone video shrunk to a postage stamp with a caption printed over the
   speaker's own burned-in subtitles.

   Now the caption lives under the frame, and the rail is a snap row at every
   width — same gesture on a desktop trackpad as on a phone, and it grows with
   the video count instead of dividing a fixed width by it. */
.review-rail {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  gap: 16px; align-items: start;
}
.short-card { display: block; }
.short-card .short-frame { position: relative; }
.short-card .sc-n {
  position: absolute; z-index: 2; top: 10px; left: 10px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums; color: var(--paper);
  background: rgba(8, 12, 22, 0.62); padding: 4px 8px; border-radius: var(--r-pill);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}

/* Below the point where five across stops being legible, scroll instead of
   shrink. A 9:16 video under about 180px wide shows nothing useful. */
@media (max-width: 1080px) {
  .review-rail {
    display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter); scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .review-rail::-webkit-scrollbar { display: none; }
  .short-card { flex: 0 0 clamp(190px, 44vw, 240px); scroll-snap-align: start; }
}


/* == The reviews page =====================================================
   The homepage arrangement is right where these videos are one section among
   ten: a rail beside a claim card. On a page of their own it stops working —
   the rail puts two of the five behind a swipe, and the claim card leaves the
   other half of the head row empty, so the page reads thinner than the
   evidence sitting on it. Here the claim is the section's own statement across
   the full width, and every video is on screen. */
.reviews-page { background: var(--paper); }
.rv-claim {
  display: grid; grid-template-columns: 1fr; gap: 22px; align-items: start;
  padding-bottom: clamp(26px, 3.2vw, 38px);
  border-bottom: 1px solid var(--line-light);
  margin-bottom: clamp(28px, 3.6vw, 46px);
}
.rv-claim-body {
  font-size: clamp(19px, 2.2vw, 26px); line-height: 1.5; font-weight: 500;
  color: var(--asphalt); margin-top: 18px; max-width: 34ch;
}
.rv-claim-act { display: flex; }
@media (min-width: 901px) {
  .rv-claim { grid-template-columns: minmax(0, 1.6fr) minmax(0, 0.4fr); gap: clamp(28px, 4vw, 56px); align-items: end; }
  .rv-claim-act { justify-content: flex-end; }
}

/* Five 9:16 videos as a set of equals. The alternating drop is not decoration:
   five tall, narrow frames in a dead-flat row read as one filmstrip, and these
   are five separate people. */
.review-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
@media (min-width: 621px) { .review-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; } }
@media (min-width: 1021px) {
  .review-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .review-grid > .short-card:nth-child(even) { margin-top: clamp(20px, 3vw, 44px); }
}

.shorts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(196px, 1fr)); gap: 18px; }
.short-frame {
  aspect-ratio: 9 / 16; border: 1px solid var(--line-light-2); background: var(--asphalt);
  overflow: hidden; box-shadow: var(--shadow-1);
  transition: box-shadow var(--t-base), transform var(--t-base) var(--ease-out);
}
@media (hover: hover) { .short-frame:hover { box-shadow: var(--shadow-2); transform: translateY(-3px); } }
.short-frame iframe { width: 100%; height: 100%; border: 0; }

/* YouTube facade: nothing loads from youtube.com until the visitor clicks. */
.yt-facade {
  position: relative; width: 100%; height: 100%;
  border: 0; padding: 0; margin: 0; cursor: pointer;
  background: var(--asphalt-2) center / cover no-repeat;
  display: flex; align-items: center; justify-content: center;
}
.yt-facade::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10, 14, 24, 0.12), rgba(10, 14, 24, 0.62));
}
.yt-facade .play {
  position: relative; z-index: 1; width: 62px; height: 62px; border-radius: 50%;
  background: var(--accent); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  transition: transform var(--t-base) var(--ease-out);
}
.yt-facade .play::after {
  content: ''; border-style: solid; border-width: 10px 0 10px 17px;
  border-color: transparent transparent transparent var(--white); margin-left: 4px;
}
@media (hover: hover) { .yt-facade:hover .play { transform: scale(1.12); } }

/* == 17 Calculators ======================================================= */
.tools-band { background: var(--paper-2); }
.tools-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.tool-card {
  background: var(--paper); border: 1px solid var(--line-light); padding: 28px;
  display: flex; flex-direction: column; position: relative; overflow: hidden;
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base);
}
.tool-card::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--accent-grad); transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-base) var(--ease-out);
}
@media (hover: hover) {
  .tool-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-2); }
  .tool-card:hover::before { transform: scaleX(1); }
}
.tool-card .tool-ico { color: var(--accent); margin-bottom: 16px; }
.tool-card h3 { font-size: 23px; margin-bottom: 10px; }
.tool-card p { font-size: 14px; color: var(--slate); margin-bottom: 20px; flex: 1; }
@media (max-width: 900px) { .tools-grid { grid-template-columns: 1fr; } }

/* Calculators as one column of full-width rows.
   As a four-across grid every blurb was squeezed into the same narrow box and
   four different tools read as one undifferentiated set. A row gives each its
   number, icon, name, a sentence with room, and its own action. */
.tools-stack { display: grid; gap: 12px; }
.tool-line {
  display: grid; grid-template-columns: auto auto 1fr auto;
  align-items: center; gap: clamp(16px, 2.4vw, 30px);
  padding: clamp(18px, 2.2vw, 26px) clamp(20px, 2.6vw, 32px);
  background: var(--paper); border: 1px solid var(--line-light);
  text-decoration: none; color: inherit; position: relative; overflow: hidden;
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base), border-color var(--t-base);
}
/* The accent wipes in from the left edge on hover, the same gesture the tool
   cards used, kept so the interaction language does not change. */
.tool-line::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent-grad); transform: scaleY(0); transform-origin: top;
  transition: transform var(--t-base) var(--ease-out);
}
@media (hover: hover) {
  .tool-line:hover { box-shadow: var(--shadow-2); border-color: var(--line-light-2); transform: translateX(3px); }
  .tool-line:hover::before { transform: scaleY(1); }
  .tool-line:hover .tl-cta { color: var(--accent); gap: 12px; }
}
.tl-num {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.1em;
  color: var(--line-light-2); font-variant-numeric: tabular-nums;
}
.tl-ico { color: var(--accent); display: grid; place-items: center; }
.tl-h { display: block; font-family: var(--font-display); font-weight: 700; font-size: clamp(19px, 2vw, 23px); letter-spacing: -0.01em; }
.tl-p { display: block; font-size: 14px; color: var(--slate); margin-top: 5px; max-width: 62ch; line-height: 1.6; }
.tl-cta {
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--asphalt); transition: color var(--t-fast), gap var(--t-fast);
}
/* On a phone the four-column row has nowhere to put its fourth column, so it
   is rebuilt as a card and every child is placed by hand.

   Leaving the placement to auto-flow is what broke it: the number goes away,
   which leaves three children in two columns, so the action wraps onto a
   second row underneath the icon and the row reads as half a picture, half a
   sentence, and a stray link below. Naming the cell each child sits in means
   the layout cannot rearrange itself again when a rule elsewhere shows or
   hides one of them.

   The icon gets a tinted square rather than floating loose in a column as
   wide as the words next to it. The action keeps its label, in accent, sitting
   under the sentence where it belongs — the whole card is still the link, so
   it is a signpost rather than a separate tap target. */
@media (max-width: 760px) {
  .tool-line {
    grid-template-columns: 46px minmax(0, 1fr);
    column-gap: 14px;
    row-gap: 12px;
    align-items: start;
    padding: 18px 18px 16px;
  }
  .tl-num { display: none; }
  .tl-ico {
    grid-area: 1 / 1;
    width: 46px; height: 46px;
    background: var(--paper);
    border: 1px solid var(--line-light);
    border-radius: 2px;
  }
  .tl-ico svg { width: 24px; height: 24px; }
  .tl-body { grid-area: 1 / 2; align-self: center; }
  .tl-h { font-size: 19px; line-height: 1.25; }
  .tl-p { font-size: 14px; margin-top: 6px; }
  .tl-cta {
    grid-area: 2 / 1 / auto / -1;
    color: var(--accent);
    padding-top: 4px;
    border-top: 1px solid var(--line-light);
    width: 100%;
  }
}

.calc-card {
  background: var(--white); border: 1px solid var(--line-light);
  padding: clamp(22px, 4vw, 38px); box-shadow: var(--shadow-1);
}
.calc-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 34px; align-items: start; }
@media (max-width: 860px) { .calc-grid { grid-template-columns: 1fr; } }

.calc-fields { display: grid; gap: 18px; }
.calc-fields .row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 520px) { .calc-fields .row-2 { grid-template-columns: 1fr; } }

.calc-result {
  background: var(--asphalt); color: var(--paper); padding: 30px;
  position: sticky; top: calc(var(--header-h) + 20px);
  border-top: 3px solid var(--accent);
}
@media (max-width: 860px) { .calc-result { position: static; } }
.calc-result .lbl {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--on-dark-mute); margin-bottom: 8px;
}
.calc-result .big {
  font-family: var(--font-mono); font-size: clamp(36px, 6.5vw, 50px);
  font-weight: 600; color: var(--accent); line-height: 1; font-variant-numeric: tabular-nums;
}
.calc-result .big.pos { color: var(--signal); }
.calc-result .big.neg { color: var(--accent-hi); }
.calc-break { margin-top: 22px; border-top: 1px solid var(--line); padding-top: 16px; font-size: 13px; }
.calc-break div { display: flex; justify-content: space-between; gap: 14px; padding: 6px 0; color: var(--on-dark); }
.calc-break .mono { color: var(--paper); }
.calc-break .total { border-top: 1px solid var(--line); margin-top: 10px; padding-top: 12px; font-weight: 600; }
.calc-break .mono.pos { color: var(--signal); }
.calc-break .mono.neg { color: #FF7A82; }
/* No accent rule down the side: it pushed the text 3px off centre inside its
   own box, and the tint already says which of the three states this is. The
   hairline keeps the box defined without taking a side. */
.calc-verdict {
  margin-top: 20px; padding: 14px 16px;
  border: 1px solid rgba(46, 158, 109, 0.35);
  background: rgba(46, 158, 109, 0.12); font-size: 13px; color: var(--paper);
}
.calc-verdict.bad { border-color: rgba(240, 69, 82, 0.38); background: rgba(240, 69, 82, 0.14); }
.calc-verdict.warn { border-color: rgba(232, 163, 61, 0.38); background: rgba(232, 163, 61, 0.14); }

/* IFTA state rows */
.ifta-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.ifta-table th {
  text-align: left; font-family: var(--font-mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--slate);
  padding: 10px 8px; border-bottom: 1px solid var(--line-light);
  font-weight: 600;
}
.ifta-table td { padding: 7px 8px; border-bottom: 1px solid var(--line-light); vertical-align: middle; }
.ifta-table input, .ifta-table select { padding: 9px 10px; font-size: 14px; }
/* Four columns carry .col-num — Miles, Gallons, Rate/gal and Owed — so 22%
   each claimed 88% of the table, and with the 44px action column that left the
   state column about 29px. Its <select> was squeezed to a sliver and the state
   abbreviation was unreadable, which is the one value in the row you cannot
   retype from memory. 18% leaves room for the state, and the min-width is a
   floor so no future column count can collapse it again. */
.ifta-table .col-num { width: 18%; }
.ifta-table .col-act { width: 44px; }
.ifta-table th:first-child,
.ifta-table td:first-child { min-width: 104px; }
.row-remove {
  background: none; border: 1px solid var(--line-light-2); color: var(--slate);
  width: 32px; height: 32px; cursor: pointer; font-size: 18px; line-height: 1;
  transition: border-color var(--t-fast), color var(--t-fast);
}
@media (hover: hover) { .row-remove:hover { border-color: var(--accent); color: var(--accent); } }
.ifta-scroll { overflow-x: auto; }

/* HOS clocks */
.hos-clocks { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 8px; }
@media (max-width: 620px) { .hos-clocks { grid-template-columns: 1fr; } }
.hos-clock { background: rgba(247, 245, 241, 0.05); border: 1px solid var(--line); padding: 18px; }
.hos-clock .k { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--on-dark-mute); margin-bottom: 10px; }
/* "6h 30m" has to stay on one line. Wrapped to "6h" over "30m" it stops
   reading as a clock, and at three-across the box is only about 115px wide on a
   1300px screen — so the size scales with the column instead of being fixed at
   26px, and nowrap makes a break impossible rather than unlikely. */
.hos-clock .v {
  font-family: var(--font-mono); font-size: clamp(19px, 2vw, 26px);
  font-weight: 600; color: var(--chrome); white-space: nowrap;
}
.hos-clock .track { height: 5px; background: rgba(247, 245, 241, 0.1); margin-top: 12px; overflow: hidden; }
.hos-clock .fill { height: 100%; background: var(--signal); transition: width var(--t-base) var(--ease-out); }
.hos-clock .fill.warn { background: var(--warn); }
.hos-clock .fill.over { background: var(--accent-hi); }

/* == Form submit veil ===================================================== */
/* Covers the card while a submission is in flight. Two reasons it is a veil over
   the card rather than a disabled button: on a nine-field form the button is off
   screen by the time someone presses it on a phone, and a form that still looks
   editable invites a second submission. */
.form-veil {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-items: center;
  background: rgba(246, 246, 248, .82);
  backdrop-filter: blur(2px);
  opacity: 0; transition: opacity .2s var(--ease-out);
  border-radius: inherit;
}
.form-veil.is-on { opacity: 1; }

.form-veil-inner {
  display: flex; align-items: center; gap: 12px;
  background: var(--asphalt); color: var(--paper);
  padding: 14px 20px; border-radius: var(--r-sm);
  box-shadow: var(--shadow-2);
  font-size: 14px; font-weight: 600;
}

.form-veil-spin {
  width: 18px; height: 18px; flex: none;
  border: 2px solid rgba(246, 246, 248, .28);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: hhl-veil-spin .7s linear infinite;
}

@keyframes hhl-veil-spin { to { transform: rotate(360deg); } }

/* Reduced motion: the veil still appears, the spinner just stops turning. The
   message is what carries the meaning. */
@media (prefers-reduced-motion: reduce) {
  .form-veil { transition: none; }
  .form-veil-spin { animation: none; border-top-color: var(--accent); }
}

/* == 18 Forms ============================================================= */
.form-card {
  background: var(--white); border: 1px solid var(--line-light);
  padding: clamp(22px, 4vw, 40px); max-width: 820px; box-shadow: var(--shadow-1);
}
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.form-grid .full { grid-column: 1 / -1; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }

label {
  display: block; font-family: var(--font-mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.1em; color: var(--slate); margin-bottom: 8px;
}
.req { color: var(--accent); }

input, select, textarea {
  width: 100%; padding: 14px 15px;
  border: 1px solid var(--line-light-2); background: var(--white);
  font-family: var(--font-body); font-size: 16px; /* 16px keeps iOS from zooming */
  color: var(--asphalt); border-radius: 0;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
textarea { resize: vertical; min-height: 100px; }
select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='none' stroke='%2359626E' stroke-width='1.6' d='M1 1.5l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 15px center; padding-right: 40px;
}
/* Chrome and Safari reserve room for spin buttons inside a number field's
   content box. In the wide calculator fields that is invisible; in the narrow
   IFTA cells it clipped the rate placeholder to "0.000". Nobody nudges a fuel
   tax rate 0.0001 at a time with a mouse, and the arrows are too small to hit
   on a phone, so the field keeps its full width instead. */
input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* One focus indicator, not three. The field's own border turns accent and a
   single 3px ring sits flush against it — no offset outline on top.
   `outline: 2px solid transparent` is not decorative: Windows High Contrast
   mode discards box-shadow but forces transparent outlines to a system
   colour, so the ring survives where the shadow would not. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 0;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(214, 48, 60, 0.30);
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
input:disabled, select:disabled, textarea:disabled, button:disabled { opacity: 0.55; cursor: not-allowed; }
input[type='number'] { font-family: var(--font-mono); }

input[aria-invalid='true'], select[aria-invalid='true'], textarea[aria-invalid='true'] {
  border-color: var(--accent); background: #FDF4F5;
}
.field-error {
  display: none; font-family: var(--font-mono); font-size: 11px;
  color: var(--accent-lo); margin-top: 7px; text-transform: none; letter-spacing: 0;
}
.field-error.show { display: block; }
.field-hint { font-size: 12px; color: var(--slate); margin-top: 7px; }

.form-note { font-size: 13px; color: var(--slate); margin-top: 16px; line-height: 1.65; }
.consent { display: flex; align-items: flex-start; gap: 12px; margin-top: 20px; }
.consent input { width: auto; flex: none; margin-top: 4px; accent-color: var(--accent); }
.consent label {
  text-transform: none; font-family: var(--font-body); font-size: 13px;
  letter-spacing: 0; color: var(--slate); margin-bottom: 0; line-height: 1.6;
}
.consent a { color: var(--asphalt); font-weight: 600; }

/* Radio options, used only by forms built in the admin. They wrap rather than
   sitting in a row, because a client can put six choices in one and a row of
   six on a phone is a horizontal scroll nobody expects inside a form. Each
   option is a whole label, so the words are part of the tap target. */
.radio-set { display: flex; flex-wrap: wrap; gap: 10px 18px; padding-top: 4px; }
.radio-opt {
  display: flex; align-items: center; gap: 9px; min-height: 34px;
  text-transform: none; font-family: var(--font-body); font-size: 14px;
  letter-spacing: 0; color: var(--asphalt); margin-bottom: 0; cursor: pointer;
}
.radio-opt input { width: auto; flex: none; margin: 0; accent-color: var(--accent); }

/* Honeypot: off-screen for people, inviting to naive bots. */
.hp-field { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; }

.form-status {
  display: none; margin-top: 24px; padding: 18px 20px;
  background: #EEF7F2;
  font-size: 14px; color: var(--asphalt); line-height: 1.6;
}
.form-status.error { background: #FDF4F5; }
.form-status.warn { background: #FDF7EC; }
.form-status.show { display: block; animation: statusIn 380ms var(--ease-out); }
@keyframes statusIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.form-status strong {
  display: block; font-family: var(--font-display); text-transform: uppercase;
  font-size: 19px; margin-bottom: 5px; letter-spacing: 0.01em;
}

/* Submit button spinner */
.btn .spin {
  width: 15px; height: 15px; border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: var(--white); border-radius: 50%; animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* == 19 Blog ============================================================== */
.post-meta {
  font-family: var(--font-mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--slate); margin-bottom: 14px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.post-meta .tag { background: var(--accent); color: var(--white); padding: 4px 9px; }
.post-body {
  font-size: 15px; color: var(--asphalt); line-height: 1.8;
  border-top: 1px solid var(--line-light); padding-top: 20px; margin-top: 18px;
}
.post-body p { margin-bottom: 15px; }
.post-body p:last-child { margin-bottom: 0; }
.post-body a { color: var(--accent-lo); font-weight: 600; }
.read-more {
  font-family: var(--font-mono); font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--asphalt); background: none; border: none;
  border-bottom: 2px solid var(--accent); cursor: pointer; padding: 0 0 3px;
  transition: color var(--t-fast);
}
@media (hover: hover) { .read-more:hover { color: var(--accent); } }

/* Previous / next links at the foot of a blog post. */
.post-nav {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
  margin-top: 48px; padding-top: 32px; border-top: 1px solid var(--line-light);
}
@media (max-width: 620px) { .post-nav { grid-template-columns: 1fr; } }
.post-nav-link {
  display: block; padding: 20px 22px; text-decoration: none;
  border: 1px solid var(--line-light); border-radius: var(--r-md);
  background: var(--white);
  transition: border-color var(--t-fast), transform var(--t-base) var(--ease-out), box-shadow var(--t-base);
}
@media (hover: hover) {
  .post-nav-link:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-2); }
}
.post-nav-link.align-end { text-align: right; }
.post-nav-link .k {
  display: block; font-size: 10px; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 8px;
}
.post-nav-link .t { display: block; font-size: 15px; font-weight: 600; color: var(--asphalt); line-height: 1.45; }

/* Two-column comparison. Deliberately not a feature table with ticks down our
   column and crosses down theirs: both sides carry real advantages and real
   costs, and a comparison nobody believes persuades nobody. */
/* stretch, not start: one column has more points than the other, and two
   cards ending at different heights read as an unfinished layout rather than
   as an argument with two sides. */
.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; align-items: stretch; }
@media (max-width: 860px) { .compare { grid-template-columns: 1fr; } }
.compare-col {
  background: var(--white); border: 1px solid var(--line-light);
  border-radius: var(--r-lg); padding: clamp(24px, 3vw, 34px);
  box-shadow: var(--shadow-1);
}
.compare-col.featured { border-color: var(--accent); box-shadow: var(--shadow-2); }
.compare-head { padding-bottom: 20px; margin-bottom: 20px; border-bottom: 1px solid var(--line-light); }
.compare-head h2 { font-size: clamp(22px, 2.6vw, 28px); }
.compare-list { list-style: none; display: grid; gap: 2px; }
.compare-list li {
  position: relative; padding: 13px 0 13px 34px;
  font-size: 15px; line-height: 1.6; color: var(--asphalt);
  border-top: 1px solid var(--line-light);
}
.compare-list li:first-child { border-top: 0; }
.compare-list li::before {
  content: ''; position: absolute; left: 2px; top: 18px;
  width: 16px; height: 16px; border-radius: 50%;
}
.compare-list li.good::before {
  background: rgba(46, 158, 109, 0.14);
  box-shadow: inset 0 0 0 2px var(--signal);
}
.compare-list li.bad::before {
  background: rgba(214, 48, 60, 0.12);
  box-shadow: inset 0 0 0 2px var(--accent);
}
/* A dot alone does not carry meaning for a screen reader, so name it. */
.compare-list li.good::after,
.compare-list li.bad::after {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.compare-list li.good::after { content: 'Advantage: '; }
.compare-list li.bad::after { content: 'Cost: '; }

/* == 20 Long-form / legal ================================================= */
/* == The explainer under a calculator ======================================
   It was one reading column on an 1180px band, so the right half of the
   section sat empty and the clearest writing on these pages read like a
   footnote under the tool. The heading takes a rail of its own and holds
   position while the text scrolls past it; each sub-topic sits in its own
   card, so a reader looking for one part of the answer can see where it is
   instead of reading to find out. */
.explain {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
}
.explain-aside { position: sticky; top: calc(var(--header-h) + 20px); }
.explain-aside h2 { font-size: clamp(24px, 3.4vw, 34px); line-height: 1.12; }
/* The rule is the same mark the section eyebrows use, so the rail reads as
   part of the same family rather than as a new device. */
.explain-aside::after {
  content: ''; display: block; width: 56px; height: 3px;
  background: var(--accent); margin-top: 18px; border-radius: 2px;
}
.explain-lede p { font-size: 16px; line-height: 1.8; color: var(--slate); margin-bottom: 14px; }
.explain-lede p:first-child { font-size: 19px; line-height: 1.65; color: var(--asphalt); }
.explain-lede p:last-child { margin-bottom: 0; }
.explain-part {
  padding: clamp(20px, 2.4vw, 28px);
  background: var(--white);
  border: 1px solid var(--line-light);
  border-radius: var(--r-md);
}
.explain-lede + .explain-part { margin-top: 30px; }
.explain-part + .explain-part { margin-top: 16px; }
.explain-part h3 { font-size: 19px; margin-bottom: 10px; }
.explain-part p { font-size: 15px; line-height: 1.8; color: var(--slate); margin-bottom: 12px; }
.explain-part p:last-child { margin-bottom: 0; }
.explain-part a { color: var(--accent-lo); font-weight: 600; }
@media (hover: hover) { .explain-part a:hover { color: var(--accent); } }
/* One column below the tablet breakpoint, and the rail stops holding position:
   a heading pinned to the top of a phone screen is a heading in the way. */
@media (max-width: 900px) {
  .explain { grid-template-columns: 1fr; gap: 20px; }
  .explain-aside { position: static; }
  .explain-aside::after { margin-top: 14px; }
  .explain-lede + .explain-part { margin-top: 22px; }
}

.prose { max-width: 780px; }
.prose h2 { font-size: clamp(22px, 3vw, 30px); margin: 44px 0 14px; }
.prose h2:first-child { margin-top: 0; }
.prose h3 { font-size: 19px; margin: 26px 0 10px; }
.prose p, .prose li { font-size: 15px; line-height: 1.8; color: var(--asphalt); }
.prose p { margin-bottom: 15px; }
.prose ul, .prose ol { margin: 0 0 18px 22px; }
.prose li { margin-bottom: 9px; }
.prose a { color: var(--accent-lo); font-weight: 600; }
.legal-note {
  background: var(--paper-2);
  padding: 18px 20px; font-size: 14px; color: var(--slate); margin-bottom: 36px; line-height: 1.7;
}
/* A notice under a tile grid (the pricing page's sample-figures caveat) has
   nothing above it but the grid's own bottom edge — without this the box
   sits flush against the last row of tiles. */
.bento + .legal-note { margin-top: 32px; }
.updated {
  font-family: var(--font-mono); font-size: 12px; color: var(--slate);
  text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 30px;
}

/* Was a 1px-gap grid whose background showed through as hairlines. Once the
   tiles gained radius those hairlines became visible grey bars between the
   cards, so the divider trick is gone and the gap is a real gap. */
.facts-row {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--bento-gap);
  background: transparent; border: 0;
  margin-top: 40px;
}
.fact { background: var(--white); border: 1px solid var(--line-light); padding: 30px; }
.on-dark .fact, .process-strip .fact {
  background: var(--asphalt-2); border-color: var(--line);
}
.on-dark .fact .v, .process-strip .fact .v { color: var(--paper); }
.on-dark .fact .k, .process-strip .fact .k { color: var(--on-dark-mute); }
.fact .v {
  font-family: var(--font-mono); font-size: clamp(28px, 4vw, 36px);
  font-weight: 600; color: var(--asphalt); font-variant-numeric: tabular-nums;
}
.fact .k { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--slate); margin-top: 8px; }
@media (max-width: 640px) { .facts-row { grid-template-columns: 1fr; } }

.about-story p { font-size: 16px; color: var(--asphalt); max-width: 680px; margin-bottom: 20px; line-height: 1.8; }

/* FAQ: sticky intro rail on the left, numbered accordion on the right.
   A bare <details> stack read as an afterthought next to the rest of the
   page; this gives the section its own structure and a place to put the
   "still stuck, here is the phone number" card where it actually helps. */
.faq-layout { display: grid; grid-template-columns: 340px 1fr; gap: 56px; align-items: start; }
@media (max-width: 1000px) { .faq-layout { grid-template-columns: 1fr; gap: 32px; } }

.faq-aside { position: sticky; top: calc(var(--header-h) + 28px); }
@media (max-width: 1000px) { .faq-aside { position: static; } }
.faq-help {
  margin-top: 28px; background: var(--asphalt); color: var(--paper);
  padding: 26px; border-top: 3px solid var(--accent);
}
.faq-help .k { font-size: 10px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-dark-mute); margin-bottom: 10px; }
.faq-help p { font-size: 14px; color: var(--on-dark); margin-bottom: 18px; line-height: 1.6; }
.faq-help .num-lg { font-size: clamp(22px, 2.6vw, 28px); font-weight: 600; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.faq-help .num-lg a { text-decoration: none; }
@media (hover: hover) { .faq-help .num-lg a:hover { color: var(--accent-hi); } }

.faq-list { counter-reset: faq; border-top: 1px solid var(--line-light); }
.faq-item {
  counter-increment: faq;
  border-bottom: 1px solid var(--line-light);
  transition: background var(--t-base);
}
.faq-item[open] { background: var(--white); }
.faq-item summary {
  list-style: none; cursor: pointer; position: relative;
  display: grid; grid-template-columns: 46px 1fr 32px; align-items: baseline; gap: 12px;
  padding: 26px 20px 26px 16px;
  font-family: var(--font-display); font-size: clamp(17px, 2.1vw, 21px);
  text-transform: none; font-weight: 700; line-height: 1.3; letter-spacing: -0.015em;
  transition: color var(--t-fast);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::before {
  content: '0' counter(faq);
  font-family: var(--font-body); font-size: 12px; font-weight: 600;
  letter-spacing: 0.08em; color: var(--accent); font-variant-numeric: tabular-nums;
}
@media (hover: hover) { .faq-item summary:hover { color: var(--accent); } }

/* Chevron in its own grid cell rather than absolutely positioned, so it can
   never collide with a long question. */
.faq-icon { position: relative; width: 32px; height: 32px; justify-self: end; align-self: center; }
.faq-icon::before, .faq-icon::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  background: var(--accent); transition: transform var(--t-base) var(--ease-out);
}
.faq-icon::before { width: 14px; height: 2px; transform: translate(-50%, -50%); }
.faq-icon::after  { width: 2px; height: 14px; transform: translate(-50%, -50%); }
.faq-item[open] .faq-icon::after { transform: translate(-50%, -50%) scaleY(0); }

.faq-item .answer {
  padding: 0 20px 28px 74px;
  font-size: 15px; color: var(--slate); line-height: 1.8; max-width: 72ch;
}
.faq-item .answer a { color: var(--accent-lo); font-weight: 600; }
@media (max-width: 560px) {
  .faq-item summary { grid-template-columns: 34px 1fr 26px; padding: 22px 8px 22px 8px; gap: 10px; }
  .faq-item .answer { padding: 0 8px 24px 52px; }
}

/* == 21 Final CTA ========================================================= */
.final-cta {
  position: relative; overflow: hidden; text-align: center;
  background: linear-gradient(150deg, #C22733, #8E1B24);
}
.final-cta::before {
  content: ''; position: absolute; inset: 0; opacity: 0.16;
  background-image: repeating-linear-gradient(115deg, transparent 0 38px, rgba(255, 255, 255, 0.5) 38px 40px);
}
.final-cta > * { position: relative; z-index: 1; }
.final-cta h2 { color: var(--white); font-size: clamp(30px, 5.2vw, 56px); margin-bottom: 18px; }
.final-cta p {
  font-size: clamp(16px, 2vw, 18px); margin-bottom: 34px;
  color: rgba(255, 255, 255, 0.9); max-width: 540px; margin-inline: auto;
}
.final-cta .btn-dark { background: var(--asphalt); color: var(--white); }
.final-cta .cta-row { justify-content: center; }
.final-cta .btn-ghost { border-color: rgba(255, 255, 255, 0.5); color: var(--white); }

/* == 22 Footer ============================================================ */
.site-footer { background: var(--asphalt-3); color: var(--on-dark); padding: 64px 0 30px; font-size: 14px; }
.foot-grid {
  display: grid; grid-template-columns: 1.5fr 1fr 1fr 1.15fr; gap: 36px;
  padding-bottom: 40px; border-bottom: 1px solid var(--line);
}
.foot-grid h2 {
  color: var(--paper); font-family: var(--font-mono); font-size: 11px;
  letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 18px; font-weight: 600;
}
/* The column headings are buttons so they can fold on a phone. Everywhere
   above that width they have to read as headings and nothing else.

   A <button> with no styling of its own is not neutral: the browser paints it
   with its own control chrome — a light box, a border, centred text and a
   pointer cursor — which is why Company, Resources and Contact were sitting on
   white rectangles on the desktop footer and inviting a click that had nothing
   to open. The accordion rules live in the phone block and layer back on top
   of this. */
.foot-toggle {
  display: block; width: 100%; margin: 0; padding: 0;
  background: none; border: 0; border-radius: 0;
  -webkit-appearance: none; appearance: none;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  text-align: left; cursor: default;
}
.foot-toggle .foot-chev { display: none; }

/* -- More than one office ------------------------------------------------- */
/* One office sits in the fourth column as it always did. Two or three take the
   whole width of the footer instead, on their own row under the links.

   The reason is proportion. Three addresses stacked in a quarter-width column
   run to roughly the height of both link lists put together, which leaves
   Company and Resources looking like leftovers beside it — and the links are
   what most people came to the footer for. Across the full width the addresses
   are three short blocks side by side, and everything above them stays even. */
.foot-grid--offices { grid-template-columns: 1.5fr 1fr 1fr; }
.foot-grid--offices .foot-col--contact {
  grid-column: 1 / -1;
  margin-top: 34px;
  padding-top: 30px;
  border-top: 1px solid var(--line);
}

.foot-offices { list-style: none; display: grid; gap: 26px 34px; }
.foot-grid--offices .foot-offices {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.foot-offices li { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 0; }
/* The block beside the pin is a flex item, so without this it is only as wide
   as the longest line of the address — which was invisible while it held text
   that wraps, and obvious the moment it held a map: three cards 156, 170 and
   258 wide in three columns of the same size. */
.foot-offices li > div { flex: 1; min-width: 0; }
/* The office's own pin, and only that one. Written as a descendant it also
   caught the pin inside the map card once the map moved into this list —
   red glyph on a red disc, which reads as no glyph at all. */
.foot-offices > li > svg { width: 17px; height: 17px; flex: none; margin-top: 3px; color: var(--accent); }
.foot-offices address {
  font-style: normal; color: var(--paper); opacity: 0.92; line-height: 1.55;
}
.foot-offices .fc-note {
  display: block; margin-top: 6px; font-size: 13px;
  color: var(--on-dark-mute); font-style: italic;
}

/* A link out per office, because one embedded map cannot show three places. */
.fc-map {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 9px;
  font-family: var(--font-display); font-size: 12px; font-weight: 700;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--accent-hi); text-decoration: none; opacity: 1;
}
@media (hover: hover) {
  .fc-map:hover { color: var(--accent-hi); gap: 11px; }
}

.foot-grid ul { list-style: none; }
.foot-grid li { margin-bottom: 11px; }

/* -- Company and Resources, two abreast ----------------------------------- */
/* Seven links and six links, each running straight down a column three times
   wider than the longest word in it. The lists ended up as two tall thin
   ribbons with a hand's width of empty footer beside each, and the addresses
   below them made the whole block look bottom-heavy.

   Two columns halves the height and uses the width that was already paid for.
 *
 * column-width before column-count is what makes this safe to leave switched
 * on. `columns: 118px 2` reads as "two columns, but only if each can be 118px
 * wide" — so the lists split wherever there is room and fall back to one
 * column where there is not, which is the band around 900px where a column
 * would be 96px and "Hours of Service" would wrap on every line. No
 * breakpoint to guess at, and none to revisit when the client adds a link
 * with a longer name.
 *
 * 112 against a longest measured label of 111px, with a 24px gutter rather
   than the 30 it started at: the tighter gutter buys three more pixels per
   column, which is the difference between a 1024-wide laptop splitting the
   lists and not. Neither number is round, and neither should be — they came
   off the ruler.
 *
 * Above 760px only: below that these lists are inside an accordion, where one
 * column per panel is the whole point. */
@media (min-width: 761px) {
  .foot-col:not(.foot-col--contact) .foot-panel ul {
    columns: 112px 2;
    column-gap: 24px;
  }
  /* Without this a link that wraps to two lines can be split down the middle,
     with its first line at the foot of one column and its second at the head
     of the next. */
  .foot-col:not(.foot-col--contact) .foot-panel li { break-inside: avoid; }
}
.foot-grid a { text-decoration: none; opacity: 0.85; transition: opacity var(--t-fast), color var(--t-fast); }
@media (hover: hover) { .foot-grid a:hover { opacity: 1; color: var(--accent); } }
/* Contact column: an icon, a quiet label, then the value. The label matters
   as much as the icon — a phone glyph alone cannot say which of two numbers
   is sales and which is the 24/7 line. */
.foot-contact li { display: flex; gap: 12px; margin-bottom: 18px; align-items: flex-start; }
.foot-contact svg { width: 17px; height: 17px; flex: none; margin-top: 2px; color: var(--accent); }
.foot-contact .fc-k {
  display: block; font-size: 10px; font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--on-dark-mute); margin-bottom: 3px;
}
.foot-contact a { color: var(--paper); opacity: 0.92; }
.foot-contact address { font-style: normal; color: var(--paper); opacity: 0.92; line-height: 1.55; }

.foot-blurb { max-width: 300px; opacity: 0.72; margin-bottom: 22px; }
.foot-social { display: flex; gap: 14px; }
.foot-social a {
  opacity: 0.75; display: grid; place-items: center;
  width: 40px; height: 40px; border: 1px solid var(--line);
  transition: opacity var(--t-fast), border-color var(--t-fast), transform var(--t-fast) var(--ease-out);
}
@media (hover: hover) { .foot-social a:hover { opacity: 1; border-color: var(--accent); transform: translateY(-2px); } }
.foot-bottom {
  padding-top: 26px; display: flex; justify-content: space-between;
  flex-wrap: wrap; gap: 14px; font-size: 13px; opacity: 0.75;
}
.foot-bottom nav { display: flex; gap: 18px; flex-wrap: wrap; list-style: none; }
/* No underline, hover included: these two sit under a copyright line and read
   as a pair of quiet labels rather than as running text with links in it.
   Keyboard focus still shows its own ring, so a keyboard user is not left
   guessing where they are. */
.foot-bottom nav a { text-decoration: none; }
.foot-bottom nav a:hover { text-decoration: none; }
/* Belt and braces. The walker emits bare anchors, but any menu that ever comes
   through here with list items should not sprout bullets either. */
.foot-bottom nav li { list-style: none; display: inline; }
@media (max-width: 860px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .foot-grid { grid-template-columns: 1fr; gap: 30px; } }

/* == 23 Chat widget ======================================================= */
/* Navy, not the accent red the contact button wears. The two corners hold two
   different offers — reach a person on the left, ask a question on the right —
   and two red buttons at the same height read as one control that has been
   printed twice. Navy is also the colour of the panel this opens, so the
   button and what it produces are recognisably the same thing. */
.hero-fab {
  position: fixed; right: 22px; bottom: 22px; z-index: 120;
  background: var(--asphalt); color: var(--paper); border: 1px solid rgba(247, 245, 241, 0.16);
  cursor: pointer;
  padding: 15px 22px; font-family: var(--font-display); font-weight: 700; font-size: 16px;
  text-transform: uppercase; letter-spacing: 0.04em;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.32);
  display: flex; align-items: center; gap: 9px;
  transition: background var(--t-fast), transform var(--t-fast) var(--ease-out);
}
/* Over a dark band it inverts rather than tinting: light face, dark glyph.
   The script measures what is actually behind the button and adds this class
   — see hhl.js. Without it the button spends half the home page invisible. */
.hero-fab.on-dark {
  background: var(--paper); color: var(--asphalt);
  border-color: rgba(18, 26, 44, 0.16);
}
@media (hover: hover) {
  .hero-fab:hover,
  .hero-fab.on-dark:hover { background: var(--accent); border-color: var(--accent); color: var(--white); transform: translateY(-2px); }
}
/* == Ask Hero =============================================================
   Rebuilt around one observation: six questions of six different lengths,
   rendered as pills that wrap, is not a list. It reads as a scatter of
   buttons, the eye has no column to run down, and the longest question ends
   up on a line of its own beside a two-word one.

   So the questions are rows now — full width, left aligned, one per line, with
   a chevron. That is a menu, and a menu is what this is.

   Three other things the old panel got wrong:
     · the call to action sat among the questions wearing the same shape, so
       the one control that reaches a human looked like a seventh question;
     · on a phone it was a small floating box with margins, which is a worse
       version of the sheet a phone already has a convention for;
     · nothing showed which questions had been asked, so a reader working
       through them lost their place.
   ======================================================================== */
.hero-panel {
  position: fixed; right: 22px; bottom: 88px; z-index: 121;
  width: 380px; max-width: calc(100vw - 44px);
  background: var(--white);
  border: 1px solid var(--line-light-2);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  display: none; flex-direction: column; overflow: hidden;
  max-height: min(78vh, 640px);
}
.hero-panel.open { display: flex; animation: panelIn 300ms var(--ease-out); }
@keyframes panelIn { from { opacity: 0; transform: translateY(14px) scale(0.985); } to { opacity: 1; transform: none; } }

/* -- Head ----------------------------------------------------------------- */
.hero-head {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--asphalt); color: var(--paper); padding: 16px 16px 16px 18px;
}
.hero-avatar {
  flex: none; width: 38px; height: 38px; border-radius: 50%;
  background: var(--accent); color: var(--white);
  display: grid; place-items: center;
}
.hero-head-text { flex: 1; min-width: 0; padding-top: 1px; }
.hero-head .t {
  font-family: var(--font-display); font-weight: 800; font-size: 16px;
  letter-spacing: 0.02em; text-transform: uppercase; line-height: 1.2;
}
.hero-head .s {
  font-size: 12.5px; line-height: 1.45; color: var(--on-dark-mute); margin-top: 3px;
}
.hero-close {
  flex: none; background: none; border: 0; color: var(--paper);
  width: 30px; height: 30px; border-radius: 8px; cursor: pointer;
  display: grid; place-items: center; opacity: 0.6;
  transition: opacity var(--t-fast), background var(--t-fast);
}
@media (hover: hover) { .hero-close:hover { opacity: 1; background: rgba(247, 245, 241, 0.12); } }

/* -- The conversation ------------------------------------------------------ */
.hero-msgs {
  padding: 16px 18px; overflow-y: auto; flex: 1;
  display: flex; flex-direction: column; gap: 10px;
  background: var(--paper);
  scrollbar-width: thin; scrollbar-color: var(--chrome) transparent;
}
.hero-msgs::-webkit-scrollbar { width: 8px; }
.hero-msgs::-webkit-scrollbar-thumb { background: var(--chrome); border-radius: 4px; }

.hero-msg {
  font-size: 14px; line-height: 1.58; padding: 11px 14px; max-width: 90%;
  animation: msgIn 240ms var(--ease-out);
}
@keyframes msgIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* A tail on one corner, so which side said what is legible without colour. */
.hero-msg.bot {
  background: var(--white); color: var(--asphalt); align-self: flex-start;
  border: 1px solid var(--line-light); border-radius: 14px 14px 14px 4px;
}
.hero-msg.user {
  background: var(--asphalt); color: var(--paper); align-self: flex-end;
  border-radius: 14px 14px 4px 14px;
}

.hero-typing {
  display: flex; gap: 5px; align-self: flex-start; padding: 14px 16px;
  background: var(--white); border: 1px solid var(--line-light);
  border-radius: 14px 14px 14px 4px;
}
.hero-typing span { width: 6px; height: 6px; border-radius: 50%; background: var(--slate); animation: typing 1.2s ease-in-out infinite; }
.hero-typing span:nth-child(2) { animation-delay: 0.16s; }
.hero-typing span:nth-child(3) { animation-delay: 0.32s; }
@keyframes typing { 0%, 60%, 100% { opacity: 0.25; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-4px); } }

/* -- The questions --------------------------------------------------------- */
.hero-ask {
  border-top: 1px solid var(--line-light);
  padding: 12px 14px 6px; background: var(--white);
  max-height: 42vh; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--chrome) transparent;
}
.hero-ask::-webkit-scrollbar { width: 8px; }
.hero-ask::-webkit-scrollbar-thumb { background: var(--chrome); border-radius: 4px; }
.hero-ask-lab {
  font-family: var(--font-display); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--slate);
  margin: 0 4px 8px;
}
.hero-chips { display: flex; flex-direction: column; gap: 4px; }

/* The list scrolls when there are more questions than fit. A hard cut through
   the middle of a row looks like a rendering fault; a fade says there is more
   under it. Sticky rather than fixed so it stays at the bottom edge of the
   scroller as it scrolls, and pointer-events:none so it never eats a tap. */
.hero-ask::after {
  content: ''; position: sticky; bottom: -6px; display: block;
  height: 26px; margin-top: -26px; pointer-events: none;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--white));
}

.hero-chip {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 11px 12px; min-height: 44px;
  background: none; border: 0; border-radius: 10px;
  font-family: var(--font-body); font-size: 13.5px; line-height: 1.4;
  color: var(--asphalt); text-align: left; cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.hero-chip .q { flex: 1; min-width: 0; }
/* The chevron is the affordance the pills never had: it says a tap produces
   something here rather than navigating away. */
.hero-chip .go {
  flex: none; width: 15px; height: 15px; color: var(--accent);
  transition: transform var(--t-fast);
}
@media (hover: hover) {
  .hero-chip:hover { background: var(--paper-2); }
  .hero-chip:hover .go { transform: translateX(3px); }
}
.hero-chip:focus-visible { background: var(--paper-2); }
/* Asked already: dimmed rather than removed, because a reader who wants to
   re-read an answer should not have to hunt for a question that vanished. */
.hero-chip.is-asked { color: var(--slate); }
.hero-chip.is-asked .go { color: var(--chrome); }

/* -- The way out ----------------------------------------------------------- */
/* Its own band, not another chip. The one control here that reaches a person
   should not wear the same shape as the six that do not. */
.hero-foot {
  border-top: 1px solid var(--line-light);
  padding: 13px 16px 15px; background: var(--paper);
}
.hero-foot-lab { font-size: 12.5px; color: var(--slate); margin: 0 0 9px; }
.hero-cta {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 46px; padding: 0 18px; border-radius: var(--r-pill);
  background: var(--accent); color: var(--white); text-decoration: none;
  font-family: var(--font-display); font-weight: 700; font-size: 14px;
  letter-spacing: 0.03em; transition: background var(--t-fast), gap var(--t-fast);
}
@media (hover: hover) { .hero-cta:hover { background: var(--accent-lo); gap: 13px; color: var(--white); } }

.hero-scrim {
  display: none; position: fixed; inset: 0; z-index: 120;
  background: rgba(8, 12, 22, 0.5);
}

/* -- On a phone it is a sheet ---------------------------------------------- */
/* A 380px box floating with margins on a 390px screen is a sheet that has not
   admitted it yet. Below this width it becomes one: full bleed, anchored to
   the bottom, rounded at the top, with a scrim behind it. */
@media (max-width: 560px) {
  .hero-panel {
    /* Resting on the bottom bar, not behind it. Anchored to 0 the sheet ran
       under the bar and took its own call-to-action with it — the one control
       in the panel that reaches a person was the one you could not press.
       Above the bar, both work: the sheet's button and the raised Call. */
    right: 0; left: 0; width: auto; max-width: none;
    bottom: calc(82px + env(safe-area-inset-bottom, 0px));
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-left: 0; border-right: 0; border-bottom: 0;
    max-height: calc(88vh - 82px);
  }
  .hero-panel.open { animation: sheetIn 300ms var(--ease-out); }
  @keyframes sheetIn { from { transform: translateY(100%); } to { transform: none; } }
  body.hero-open .hero-scrim { display: block; }
  body.hero-open { overflow: hidden; }
  .hero-head { padding: 15px 14px 15px 16px; }
  .hero-msgs { max-height: 34vh; }
  .hero-ask { max-height: none; }
  .hero-fab { right: 12px; bottom: 12px; padding: 13px 17px; font-size: 15px; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-panel.open { animation: none; }
  .hero-msg { animation: none; }
  .hero-chip .go, .hero-cta { transition: none; }
}

/* Consent bar. Bottom-anchored and never a full-screen blocker: a carrier
   looking up a phone number should not have to answer a cookie question to
   see it. */
.consent-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 150;
  background: var(--asphalt); border-top: 2px solid var(--accent);
  transform: translateY(110%); transition: transform 340ms var(--ease-out);
  box-shadow: 0 -10px 40px rgba(6, 10, 20, 0.4);
}
.consent-bar.is-in { transform: none; }
.consent-inner {
  max-width: var(--wrap); margin-inline: auto;
  padding: 18px var(--gutter);
  display: flex; align-items: center; justify-content: space-between; gap: 22px; flex-wrap: wrap;
}
.consent-inner p { font-size: 13.5px; color: var(--on-dark); max-width: 70ch; line-height: 1.6; }
.consent-inner a { color: var(--paper); font-weight: 600; }
.consent-actions { display: flex; gap: 10px; flex: none; }
@media (max-width: 620px) {
  .consent-inner { padding-bottom: 22px; }
  .consent-actions { width: 100%; }
  .consent-actions .btn { flex: 1; }
  /* Keep the chat button clear of the bar while it is up. */
  .consent-bar.is-in ~ .hero-fab { bottom: 130px; }
}

/* Offline notice on the calculator pages. */
.offline-note {
  position: fixed; left: 50%; bottom: 22px; z-index: 140;
  transform: translate(-50%, 140%); transition: transform 320ms var(--ease-out);
  background: var(--asphalt); color: var(--paper);
  padding: 14px 22px; border-radius: var(--r-pill);
  border: 1px solid var(--line); box-shadow: var(--shadow-3);
  font-size: 13.5px; font-weight: 500; max-width: calc(100vw - 32px); text-align: center;
}
.offline-note.is-in { transform: translate(-50%, 0); }

/* == Mobile carousels =====================================================
   Nine gallery photos or five videos stacked one per row turn into a very
   long scroll on a phone, and most of it never gets seen. Below the tablet
   breakpoint those grids become a swipeable snap row instead.

   Built on scroll-snap, so it works with no JavaScript at all: the row is
   swipeable the moment the CSS loads. The dots are an enhancement added by
   hhl-motion.js, and their absence costs nothing.
   ======================================================================== */
@media (max-width: 760px) {
  .m-carousel {
    display: flex !important;
    grid-template-columns: none !important;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* Full-bleed so cards run to the screen edge, which reads as swipeable. */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
    gap: 12px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .m-carousel::-webkit-scrollbar { display: none; }

  .m-carousel > * {
    flex: 0 0 var(--slide, 78%);
    scroll-snap-align: start;
    /* Spans from the bento grid mean nothing in a flex row. */
    grid-column: auto !important;
    grid-row: auto !important;
  }
  /* A sliver of the next card is the affordance that says "there is more". */
  .m-carousel.m-wide > * { --slide: 86%; }
  .m-carousel.m-narrow > * { --slide: 46%; }

  /* In a grid the row track gave every tile its height. In a flex row nothing
     does, so a short tile would collapse to its text and the row would look
     ragged. Give the slides one shared height per carousel. */
  .m-carousel > .bento-tile { min-height: 190px; }
  .m-carousel.m-wide > .bento-tile { min-height: 0; aspect-ratio: 5 / 4; }
  .m-carousel.m-narrow > .short-frame { min-height: 0; }
  .m-carousel > .tool-card { min-height: 210px; }
  /* Milestones carry no surface of their own, just a rule down the left, so
     they need the padding a tile gets from its own box. */
  .m-carousel > .mile { min-height: 150px; padding-right: 8px; }

  /* Items that are not slides (a full-width note, a CTA) leave the row. */
  .m-carousel > .no-slide { display: none; }

  .carousel-after { margin-top: 16px; }

  /* The dot you see is 7px; the thing you tap is 24px. Drawing the dot in a
     pseudo-element lets the button carry a full-size hit area without changing
     what the design looks like — a 7px button with a 7px gap fails the target
     size minimum on both counts, and on a phone it is genuinely hard to hit. */
  .carousel-dots {
    display: flex; justify-content: center; gap: 0; margin-top: 9px;
  }
  .carousel-dots button {
    width: 24px; height: 24px; padding: 0; border: 0; background: none;
    display: grid; place-items: center; cursor: pointer;
  }
  .carousel-dots button::before {
    content: ''; width: 7px; height: 7px; border-radius: 50%;
    background: var(--line-light-2);
    transition: background var(--t-fast), width var(--t-fast);
  }
  .on-dark .carousel-dots button::before { background: rgba(247, 245, 241, 0.25); }
  .carousel-dots button[aria-current='true']::before { background: var(--accent); width: 20px; border-radius: var(--r-pill); }

  /* A bar instead of dots once a rail is long enough that a dot per slide
     would be a second row of clutter. */
  .carousel-progress {
    align-items: center; gap: 12px; margin-top: 12px;
    padding-inline: 2px;
  }
  .cp-track {
    flex: 1; height: 3px; border-radius: var(--r-pill);
    background: var(--line-light-2); overflow: hidden;
  }
  .on-dark .cp-track { background: rgba(247, 245, 241, 0.18); }
  .cp-fill {
    display: block; height: 100%; border-radius: var(--r-pill);
    background: var(--accent);
    transition: transform var(--t-fast) linear;
  }
  .cp-count {
    font-size: 11px; letter-spacing: 0.06em; color: var(--slate);
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .on-dark .cp-count { color: var(--on-dark-mute); }
}
/* Above the breakpoint the dots are meaningless: everything is already visible. */
@media (min-width: 761px) { .carousel-dots { display: none; } }

/* == Mobile pass ==========================================================
   Measured, not guessed. A probe at 390x844 reported the homepage stacking to
   roughly sixteen screens of scrolling, with every bento tile taking a full
   row of its own regardless of how little was in it.

   Three moves fix most of it: pair up the short tiles, tighten the vertical
   rhythm, and stop the display type from eating a third of the fold.
   ======================================================================== */
@media (max-width: 620px) {
  /* A tile holding a number and a two-word label does not deserve a whole
     row. .m-half opts those into the two-column track the grid already has. */
  .bento > .m-half { grid-column: span 1; }
  .bento > .m-half .stat-lg { font-size: 26px; }
  .bento > .m-half .tile-note { font-size: 12px; }

  /* Sections were sized for a desktop rhythm; on a phone the same figure is
     a third of the viewport of empty space between every band.

     52px each side measured out at 104px of nothing between bands, and up to
     218px where a band adds its own padding on top — a quarter of an 844px
     screen, scrolled past nine times on the homepage. 38px is enough to say
     "new section" and stops the page feeling like it is mostly gaps. */
  :root { --section-y: 38px; }
  .section-head { margin-bottom: 20px; }
  .bento-tile { padding: 18px; }

  /* clamp() vw terms bottom out fine on desktop but leave headlines large
     enough on a phone that two of them fill the screen on their own. */
  h1 { font-size: clamp(30px, 8.6vw, 38px); }
  h2 { font-size: clamp(24px, 6.6vw, 30px); }
  .stat-xl { font-size: 40px; }

  /* Desktop gaps between a block and the one under it. */
  .mt-56 { margin-top: 30px; }
  .mt-32 { margin-top: 22px; }

  /* Each step of the process is two tiles: the marker and the copy. Side by
     side on desktop that reads as one card. Stacked at full width it read as
     six unrelated boxes, and a marker tile 100px tall holding two words was
     the worst of them. Flatten the marker into a header strip on its step. */
  .pin-panel .bento { gap: 0; }
  .pin-panel .bento-tile.sp-3 {
    flex-direction: row; align-items: center; justify-content: space-between;
    gap: 14px; min-height: 0; padding: 11px 18px;
    border-bottom: 0; border-radius: var(--r-md) var(--r-md) 0 0;
  }
  .pin-panel .bento-tile.sp-3 .tile-k { margin-bottom: 0; }
  .pin-panel .bento-tile.sp-3 .stat-xl { font-size: 24px; line-height: 1; }
  .pin-panel .bento-tile.sp-9 { border-radius: 0 0 var(--r-md) var(--r-md); }
}

@media (max-width: 760px) {
  /* The step between the desktop rhythm and the phone one. Bounded at the
     bottom because this block sits after the 620px one in the file: an
     unbounded max-width here would win at 390px and undo it. */
  @media (min-width: 621px) { :root { --section-y: 46px; } }

  /* Side by side, not stacked. Two 16:10 chips at half width are 98px tall
     together; stacked they were 196px of hero for the same two photos. */
  .hero-thumbs { grid-template-columns: 1fr 1fr; }

  /* The hero has a photo, a headline, a lede, two buttons, three proof
     figures, a recap card and two thumbnails. It does not also need to be
     880px before any of that is laid out.

     The header height has to stay in the top padding. The hero is pulled up by
     a full header height so the photograph runs behind the floating bar, so a
     flat 40px here left the first badge sitting underneath it — present in the
     markup, invisible on the phone. */
  .hero { min-height: 0; padding-block: calc(var(--header-h) + 22px) 44px; }
  .hero-inner { gap: 28px; }
  .hero p.lede { margin-bottom: 22px; }
  .hero-proof { gap: 18px 26px; margin-top: 24px; padding-top: 20px; }

  /* Full-width buttons in a stack, because a thumb misses a 140px pill that
     is sharing a row with another one. */
  .hero .cta-row { display: grid; gap: 10px; }
  .hero .cta-row .btn { width: 100%; }

  /* At 12px none of the three badges fit two to a line, so each took a line
     of its own and the set cost 110px of the fold. Two points smaller and
     they pair up. */
  .pill-row { gap: 7px; margin-bottom: 20px; }
  .pill { font-size: 10.5px; padding: 6px 12px; letter-spacing: 0.05em; }
}

/* == Mobile bottom navigation =============================================
   The audience is on a phone, often in a cab or at a pump, using one thumb.
   The two actions that matter (call someone, start setup) were both buried
   behind a hamburger. This puts them in the thumb zone.

   Five slots, with Call raised in the centre because on a phone a call is
   the highest-intent thing a carrier can do.
   ======================================================================== */
.bottom-nav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 130;
  display: none;
  /* Solid, not translucent-plus-backdrop-filter. iOS Safari has long-standing
     repaint bugs with position:fixed + backdrop-filter: the bar flickers or
     fails to paint entirely while scrolling. At 0.96 opacity the blur was
     doing nothing visible anyway. */
  background: #141C2F;
  border-top: 1px solid var(--line);
  /* max(), not a bare env(): on a phone with no safe-area inset the value is
     0 and the row sits flush against the screen edge. */
  padding-bottom: max(env(safe-area-inset-bottom, 0px), 6px);
  box-shadow: 0 -8px 30px rgba(6, 10, 20, 0.4);
  /* Own compositing layer, which settles iOS repaint during momentum scroll. */
  transform: translateZ(0);
}

.bottom-nav-inner {
  display: grid; grid-template-columns: repeat(5, 1fr);
  align-items: end; max-width: 560px; margin-inline: auto;
}

.bn-item {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 10px 2px 9px; text-decoration: none;
  color: var(--on-dark-mute); background: none; border: 0; cursor: pointer;
  font-family: inherit; font-size: 10px; font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase;
  /* 48px minimum touch target even though the label is small. */
  min-height: 56px;
  /* Grid children default to min-width:auto and will happily overflow the
     track rather than shrink. */
  min-width: 0;
  transition: color var(--t-fast);
}
/* One line, always. A wrapped label makes one column taller than the rest and
   the whole bar looks broken. */
.bn-item span:last-child {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 380px) {
  .bn-item { font-size: 9px; letter-spacing: 0; }
  .bn-item svg { width: 19px; height: 19px; }
}
.bn-item svg { width: 21px; height: 21px; display: block; }
.bn-item[aria-current='page'] { color: var(--paper); }
.bn-item[aria-current='page'] svg { color: var(--accent); }
@media (hover: hover) { .bn-item:hover { color: var(--paper); } }

/* The raised call button. */
.bn-call {
  position: relative;
  color: var(--white);
}
.bn-call .bn-ring {
  display: grid; place-items: center;
  width: 54px; height: 54px; border-radius: 50%;
  background: var(--accent-grad);
  box-shadow: 0 6px 20px rgba(214, 48, 60, 0.45);
  margin-top: -22px;
  border: 3px solid rgba(18, 26, 44, 0.96);
  transition: transform var(--t-fast) var(--ease-out);
}
.bn-call svg { width: 23px; height: 23px; }
.bn-call:active .bn-ring { transform: scale(0.94); }

@media (max-width: 1100px) {
  .bottom-nav { display: block; }

  /* Nothing at the bottom of the page should sit under the bar. */
  /* Measured height of the bar, not a guess: 75px + a little breathing room.
   *
   * On the footer rather than on the body, which is where it started. The
   * reserve has to be a little taller than the bar or the last line can end
   * up under it, and "a little taller" is exactly the problem: the bar renders
   * 79px here against an 84px reserve, so five pixels of body background — a
   * near-white — showed as a strip between the dark footer and the bar. On a
   * phone with a home-bar inset the two safe-area allowances stack differently
   * and the strip is wider.
   *
   * Chasing an exact match would mean measuring the bar in script and keeping
   * the two numbers in step forever. Painting the reserve in the footer's own
   * colour makes the size stop mattering: whatever it comes to, it is the same
   * dark as the footer above it. The footer is the last thing on every page,
   * so this is the only place the body padding was ever visible. */
  body { padding-bottom: 0; }
  .site-footer { padding-bottom: calc(30px + 84px + env(safe-area-inset-bottom, 0px)); }

  /* Everything else anchored to the bottom stacks above it. The extra 24px on
     the consent bar clears the raised Call button, which sits proud of the
     bar's top edge by design. */
  .hero-fab { bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }
  .consent-bar { bottom: calc(90px + env(safe-area-inset-bottom, 0px)); }
  .consent-bar.is-in ~ .hero-fab { bottom: calc(230px + env(safe-area-inset-bottom, 0px)); }
  .offline-note { bottom: calc(92px + env(safe-area-inset-bottom, 0px)); }

  /* The sheet covers the bottom bar on its own, so that one stays put —
     hiding it made the bar blink out a beat before the sheet arrived. The
     rest float above the page on higher z-indexes than the header the menu
     lives in, so without this they sit on top of the menu's own CTA. */
  body.nav-open .consent-bar,
  body.nav-open .hero-fab,
  body.nav-open .offline-note { display: none; }
}

/* Below ~375px the text pill runs into the bottom bar's raised Call ring —
   the ring is centred on the viewport while the fab is right-anchored, so
   the two close the gap between them as the screen narrows. Icon-only avoids
   the collision instead of chasing it with more breakpoints. */
@media (max-width: 400px) {
  .hero-fab { width: 46px; height: 46px; padding: 0; justify-content: center; border-radius: 50%; }
  .hero-fab span { display: none; }
}

/* == 22c The contact button ===============================================
   Bottom-left. Ask Hero holds the opposite corner — see section 23. Both sit
   at z-index 120, under the bottom bar at 130, so on the rare overlap the bar
   wins: a navigation bar half-covered by a decorative button is the worse of
   the two outcomes.

   It is an enhancement. With the script absent the rows are simply never
   revealed, and that costs nothing, since every number here is also in the
   footer.
   ======================================================================== */
.hhl-fab {
  position: fixed; left: 24px; bottom: 24px; z-index: 120;
  display: flex; flex-direction: column; align-items: flex-start;
  /* The container is as tall as the rows it holds whether they are showing or
     not — 272 by 321 on a phone, which is a third of the width and better
     than a third of the height. Transparent, but transparent is not the same
     as absent: a fixed box with pointer events on hit-tests first and wins,
     so everything in that corner of every page was catching clicks meant for
     the page underneath. The rows already opt out; the box around them never
     did.

     Off here, back on for the button, and the open rows switch themselves on
     below. A descendant can re-enable hit testing under a `none` ancestor,
     which is what makes this the whole fix rather than the start of one. */
  pointer-events: none;
}

.fab-rows {
  display: flex; flex-direction: column; gap: 9px;
  margin-bottom: 12px; min-width: 224px;
  /* Not display:none. The rows animate in, and something that was never
     rendered cannot transition — it would appear instantly on the first tap
     and slide only on the second. */
  pointer-events: none;
}

.fab-row {
  display: flex; align-items: center; gap: 13px;
  padding: 10px 15px 10px 11px; border-radius: 50px;
  background: var(--white); color: var(--asphalt); text-decoration: none;
  box-shadow: 0 6px 24px rgba(8, 12, 22, 0.22);
  opacity: 0; transform: translateX(-16px) scale(0.92);
  transition: opacity 0.26s var(--ease-out),
              transform 0.3s cubic-bezier(0.22, 0.68, 0, 1.2),
              box-shadow var(--t-fast);
  pointer-events: none;
}

.fab-row-ico {
  flex: none; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; color: var(--white);
}
.fab-row-ico svg { width: 19px; height: 19px; }

/* A colour per row. Three white pills in a stack are hard to tell apart at a
   glance; the disc is what makes each one findable without reading it. */
.fab-row--whatsapp .fab-row-ico { background: #25D366; }
.fab-row--sales .fab-row-ico    { background: var(--accent); }
.fab-row--tel .fab-row-ico      { background: var(--asphalt); }
.fab-row--email .fab-row-ico    { background: var(--accent-lo); }
/* Steel, not another red. This is the one row that does not put the reader
   through to a person, and the colour is where that gets said. */
.fab-row--hero .fab-row-ico     { background: var(--steel); }

/* A row that opens the panel is a <button>, and a bare button is not neutral:
   the browser paints it with its own control chrome — a light box, a border,
   centred text — which is exactly how the footer's column headings ended up
   looking like white rectangles on the desktop. */
button.fab-row {
  width: 100%; margin: 0; text-align: left;
  border: 0; -webkit-appearance: none; appearance: none;
  font: inherit; cursor: pointer;
}

.fab-row-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.fab-row-text strong {
  font-family: var(--font-display); font-size: 14px; font-weight: 700;
  color: var(--asphalt); letter-spacing: -0.01em;
}
.fab-row-text span {
  font-size: 12px; color: var(--slate);
  /* A long address must not stretch the pill off the left edge. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Opened. The rows arrive one after another rather than together, which is
   what makes the stack read as a list rather than a flash. */
.hhl-fab.is-open .fab-rows,
.hhl-fab.is-open .fab-row { pointer-events: auto; }
.hhl-fab.is-open .fab-row { opacity: 1; transform: none; }
.hhl-fab.is-open .fab-row:nth-child(1) { transition-delay: 0.06s; }
.hhl-fab.is-open .fab-row:nth-child(2) { transition-delay: 0.13s; }
.hhl-fab.is-open .fab-row:nth-child(3) { transition-delay: 0.2s; }
.hhl-fab.is-open .fab-row:nth-child(4) { transition-delay: 0.27s; }
/* Five, because a second email address and a WhatsApp number both add a
   row. A row past the last delay still arrives — it just arrives with the
   one before it, which is the wrong shape for a list. */
.hhl-fab.is-open .fab-row:nth-child(5) { transition-delay: 0.34s; }

.fab-btn {
  pointer-events: auto;
  position: relative; flex: none;
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--accent); border: 0; cursor: pointer; color: var(--white);
  display: grid; place-items: center;
  box-shadow: 0 8px 26px rgba(214, 48, 60, 0.42);
  transition: background var(--t-fast), transform var(--t-fast) var(--ease-out);
}
@media (hover: hover) {
  .fab-btn:hover { background: var(--accent-lo); transform: scale(1.07); }
  .fab-row:hover { box-shadow: 0 10px 32px rgba(8, 12, 22, 0.3); color: var(--asphalt); }
}

/* Both icons are stacked in the same cell and cross-faded, so the button
   never changes size as it swaps. */
.fab-ico {
  grid-area: 1 / 1; display: grid; place-items: center; z-index: 2;
  transition: opacity 0.2s, transform 0.26s cubic-bezier(0.22, 0.68, 0, 1.2);
}
.fab-ico-close { opacity: 0; transform: rotate(-90deg) scale(0.5); }
.hhl-fab.is-open .fab-ico-open { opacity: 0; transform: rotate(90deg) scale(0.5); }
.hhl-fab.is-open .fab-ico-close { opacity: 1; transform: none; }

.fab-ring {
  position: absolute; inset: 0; border-radius: 50%; z-index: 1;
  background: rgba(214, 48, 60, 0.36);
  animation: fabPulse 2.4s var(--ease-out) infinite;
  pointer-events: none;
}
.hhl-fab.is-open .fab-ring { animation: none; opacity: 0; }

@keyframes fabPulse {
  0%   { transform: scale(1);    opacity: 0.7; }
  70%  { transform: scale(1.65); opacity: 0; }
  100% { transform: scale(1.65); opacity: 0; }
}

/* -- Where it sits once the bottom bar appears ---------------------------- */
@media (max-width: 1100px) {
  /* Above the bar, at the same height the other floating furniture uses. */
  .hhl-fab { left: 14px; bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }
  .consent-bar.is-in ~ .hhl-fab { bottom: calc(230px + env(safe-area-inset-bottom, 0px)); }

  /* The menu sheet rises from the bottom over it. It floats on a higher layer
     than the header the sheet lives in, so without this it hovers on top of
     an open menu. */
  body.nav-open .hhl-fab { display: none; }

  .fab-btn { width: 52px; height: 52px; }
}

/* On a narrow phone the pills have to fit between the left edge and the
   raised Call ring in the middle of the bar, which is centred on the viewport
   while these are anchored to the sides. */
@media (max-width: 430px) {
  .hhl-fab { left: 12px; }
  .fab-rows { min-width: 0; width: min(272px, calc(100vw - 34px)); }
  .fab-btn { width: 50px; height: 50px; }
}

/* An open panel is the one thing here that must never be animated away from
   someone who asked for less motion — but the sliding and the pulse can go. */
@media (prefers-reduced-motion: reduce) {
  .fab-row { transition: opacity 0.01ms; transform: none; }
  .hhl-fab.is-open .fab-row { transition-delay: 0s; }
  .fab-ring { animation: none; opacity: 0; }
}

/* Scroll progress rail along the top of the viewport */
.scroll-rail { position: fixed; top: 0; left: 0; height: 3px; width: 0; background: var(--accent); z-index: 200; transition: width 80ms linear; }

/* == 23b Bento system =====================================================
   The site's core claim is that HHL shows you your numbers. A modular tile
   grid is the honest form for that: every figure gets its own surface with
   its own label, at a size you can read from across the cab.

   12 columns on desktop, 6 on tablet, 2 on phone. Tiles declare their span
   with .sp-* / .rw-* and collapse predictably.
   ======================================================================== */
:root { --bento-gap: 14px; }

.bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--bento-gap);
  grid-auto-rows: minmax(112px, auto);
}
@media (max-width: 1000px) { .bento { grid-template-columns: repeat(6, 1fr); } }
@media (max-width: 620px)  { .bento { grid-template-columns: repeat(2, 1fr); gap: 10px; } }

.sp-2 { grid-column: span 2; } .sp-3 { grid-column: span 3; }
.sp-4 { grid-column: span 4; } .sp-5 { grid-column: span 5; }
.sp-6 { grid-column: span 6; } .sp-7 { grid-column: span 7; }
.sp-8 { grid-column: span 8; } .sp-9 { grid-column: span 9; }
.sp-12 { grid-column: 1 / -1; }
.rw-2 { grid-row: span 2; } .rw-3 { grid-row: span 3; } .rw-4 { grid-row: span 4; }

@media (max-width: 1000px) {
  .sp-7, .sp-8, .sp-9, .sp-5 { grid-column: span 6; }
  .sp-4 { grid-column: span 3; }
  .sp-3 { grid-column: span 3; }
  .sp-2 { grid-column: span 2; }
}
@media (max-width: 620px) {
  .sp-3, .sp-4, .sp-5, .sp-6, .sp-7, .sp-8, .sp-9 { grid-column: 1 / -1; }
  .sp-2 { grid-column: span 1; }
  .rw-2, .rw-3, .rw-4 { grid-row: span 1; }
}

.bento-tile {
  --tile-pad: clamp(20px, 2.4vw, 30px);
  position: relative; overflow: hidden;
  background: var(--white);
  border: 1px solid var(--line-light);
  padding: var(--tile-pad);
  display: flex; flex-direction: column; justify-content: space-between;
  transition: border-color var(--t-base), transform var(--t-base) var(--ease-out);
}
.bento-tile > * { position: relative; z-index: 1; }
.bento-tile.dark { background: var(--asphalt-2); color: var(--paper); border-color: var(--line); }
.bento-tile.deep { background: var(--asphalt-3); color: var(--paper); border-color: var(--line); }
.bento-tile.accent { background: var(--accent-grad); color: var(--white); border-color: transparent; }
.bento-tile.accent .tile-k { color: rgba(255, 255, 255, 0.78); }
.bento-tile.flush { padding: 0; }

/* Frosted panel over the dark hero field. */
.bento-tile.glass {
  background: rgba(24, 33, 58, 0.55);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  border: 1px solid rgba(247, 245, 241, 0.11);
  color: var(--paper);
}

/* The chip inside a bento tile sits above the label and moves with the
   card: hovering the tile carries the border, fill and icon with it, which is
   what makes six of them read as one set rather than six separate widgets. */
.bento-tile > .icon-chip { margin-bottom: 14px; }
@media (hover: hover) {
  .bento-tile:hover > .icon-chip {
    border-color: rgba(214, 48, 60, 0.45);
    background: rgba(214, 48, 60, 0.12);
    color: var(--accent);
  }
}
.bento-tile.accent > .icon-chip { border-color: rgba(255,255,255,0.3); background: rgba(255,255,255,0.14); color: #fff; }

/* Tile chrome */
.tile-k {
  font-size: 10px; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--slate); margin-bottom: 12px;
}
.dark .tile-k, .deep .tile-k, .glass .tile-k { color: var(--on-dark-mute); }
.tile-note { font-size: 13px; color: var(--slate); margin-top: 10px; }
.dark .tile-note, .deep .tile-note, .glass .tile-note { color: var(--on-dark-mute); }
.accent .tile-note { color: rgba(255, 255, 255, 0.82); }

/* The oversized readouts the whole design is built around. */
/* nowrap matters: "$2.61" breaking to "$2.3 / 1" in a narrow tile reads as a
   different number, which is the one thing this design cannot afford. */
.stat-xl {
  font-size: clamp(34px, 5vw, 72px); font-weight: 600; line-height: 0.92;
  letter-spacing: -0.045em; font-variant-numeric: tabular-nums; color: var(--asphalt);
  white-space: nowrap;
}
.dark .stat-xl, .deep .stat-xl, .glass .stat-xl { color: var(--paper); }
.stat-xl.good { color: var(--signal); }
.stat-xl.hot { color: var(--accent); }
.stat-lg {
  font-size: clamp(24px, 2.5vw, 36px); font-weight: 600; line-height: 1;
  letter-spacing: -0.035em; font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Cursor spotlight. --mx/--my are written by hhl-motion.js. */
.spot::after {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(340px circle at var(--mx, 50%) var(--my, 50%), rgba(214, 48, 60, 0.16), transparent 62%);
  opacity: 0; transition: opacity var(--t-base);
}
@media (hover: hover) {
  .spot:hover::after { opacity: 1; }
  .bento-tile:hover { border-color: rgba(214, 48, 60, 0.45); }
}

/* Media tile with parallax inner image */
.tile-media { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
/* --obj-pos is set per photo so the subject survives the crop instead of
   defaulting to dead centre. The extra height is the parallax travel. */
.tile-media img {
  width: 100%; height: 118%;
  object-fit: cover; object-position: var(--obj-pos, center center);
  will-change: transform;
}
.tile-media::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8, 12, 22, 0.15), rgba(8, 12, 22, 0.82));
}

/* A tile spanning two rows has far more height than its copy needs. With three
   flex children — the icon, the label and the heading block — space-between
   scattered them into three islands with a ~125px void between each, so the
   label floated on its own in the middle, cut off from the heading it belongs
   to. Handing the free space to one margin collapses both voids into one and
   lands the text where the shorter tiles beside it put theirs. */
.bento-tile.rw-2 > .icon-chip + .tile-k,
.bento-tile.rw-3 > .icon-chip + .tile-k { margin-top: 12px; }
.bento-tile.rw-2 > .tile-k ~ :last-child,
.bento-tile.rw-3 > .tile-k ~ :last-child { margin-top: 12px; margin-bottom: auto; }

.bento-tile.has-media { color: var(--paper); border-color: var(--line); justify-content: flex-end; min-height: 220px; }

/* -- A tile that has both a photograph and something to say ----------------
   The old rule was that a photograph took the tile over and the writing went
   with it. That is right for a gallery tile and wrong for a card whose whole
   job is to name a trailer type, so a photograph now goes behind the words or
   above them and the words stay where they were.

   Behind is the default because it is what a background image means. Across
   the top is there for cards carrying a lot of copy, where text over a
   photograph starts to get hard to read however dark the scrim is.
   ------------------------------------------------------------------------ */
.tile-photo { overflow: hidden; }
.tile-photo img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: var(--obj-pos, center center); }

/* Behind ------------------------------------------------------------------ */
.bento-tile.photo-behind { color: var(--paper); border-color: var(--line); min-height: 240px; }
.bento-tile.photo-behind > .tile-photo {
  position: absolute; inset: 0; z-index: 0;
}
/* Two scrims doing two jobs: a flat one so the light text clears any bright
   patch of sky, and a gradient so the block of copy at the foot sits on the
   darkest part of the picture. One alone leaves either a washed-out photo or
   a heading floating on white cloud. */
.bento-tile.photo-behind > .tile-photo::after {
  content: ''; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(8, 12, 22, 0.28) 0%, rgba(8, 12, 22, 0.58) 46%, rgba(8, 12, 22, 0.88) 100%),
    rgba(8, 12, 22, 0.22);
}
.bento-tile.photo-behind h3,
.bento-tile.photo-behind .stat-xl,
.bento-tile.photo-behind .stat-lg { color: var(--paper); }
.bento-tile.photo-behind .tile-k,
.bento-tile.photo-behind .tile-note { color: var(--on-dark-mute); }
.bento-tile.photo-behind .tool-link { color: var(--paper); }
.bento-tile.photo-behind > .icon-chip {
  border-color: rgba(247, 245, 241, 0.28);
  background: rgba(247, 245, 241, 0.14);
  color: var(--paper);
}
/* The spotlight is a light wash meant for a white card. Over a photograph it
   only greys the picture, so the photo tiles get a lift on the image itself. */
.bento-tile.photo-behind::after { display: none; }
@media (hover: hover) {
  .bento-tile.photo-behind > .tile-photo img { transition: transform 620ms var(--ease-out); }
  .bento-tile.photo-behind:hover > .tile-photo img { transform: scale(1.045); }
}

/* Across the top ---------------------------------------------------------- */
/* Negative margins carry the band back out through the tile's own padding, so
   the photograph meets the border on three sides the way a card image should. */
.bento-tile.photo-top > .tile-photo {
  margin: calc(var(--tile-pad) * -1) calc(var(--tile-pad) * -1) clamp(16px, 1.6vw, 22px);
  aspect-ratio: 16 / 9; max-height: 200px;
}
.bento-tile.photo-top { justify-content: flex-start; }
/* space-between put the whole gap between the picture and the copy and pinned
   the copy to the floor. The picture is fixed at the top; the writing follows
   it and any spare height falls below. */
.bento-tile.photo-top > *:last-child { margin-bottom: auto; }
@media (hover: hover) {
  .bento-tile.photo-top > .tile-photo img { transition: transform 620ms var(--ease-out); }
  .bento-tile.photo-top:hover > .tile-photo img { transform: scale(1.045); }
}

@media (max-width: 620px) {
  .bento-tile.photo-behind { min-height: 210px; }
  .bento-tile.photo-top > .tile-photo { aspect-ratio: 16 / 10; max-height: 168px; }
  .bento-tile.m-half.photo-top > .tile-photo { max-height: 108px; }
}

@media (prefers-reduced-motion: reduce) {
  .bento-tile.photo-behind:hover > .tile-photo img,
  .bento-tile.photo-top:hover > .tile-photo img { transform: none; }
}

/* == 23c Marquee ==========================================================
   Duplicated track translated by exactly -50%, so the loop is seamless.
   ======================================================================== */
.marquee {
  overflow: hidden; position: relative;
  border-block: 1px solid var(--line-light);
  background: var(--paper);
  padding: 16px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.marquee.on-dark-strip { background: var(--asphalt); border-color: var(--line); }
.marquee-track {
  display: flex; width: max-content; gap: 0;
  animation: marqueeRun var(--marquee-speed, 46s) linear infinite;
  will-change: transform;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marqueeRun { to { transform: translateX(-50%); } }
.marquee-item {
  display: flex; align-items: center; gap: 12px;
  padding: 0 28px; white-space: nowrap;
  font-size: 13px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--slate); font-variant-numeric: tabular-nums;
}
.on-dark-strip .marquee-item { color: var(--on-dark); }
.marquee-item .rate { color: var(--accent); }
.on-dark-strip .marquee-item .rate { color: var(--accent-hi); }
.marquee-item::after {
  content: ''; width: 5px; height: 5px; background: var(--accent);
  transform: rotate(45deg); opacity: 0.7; margin-left: 16px;
}

/* == 23d Advanced motion ==================================================
   Word-by-word headline reveals, parallax layers, pinned sections and page
   transitions. Every one is inert under prefers-reduced-motion, and the
   heavier ones are dropped on small screens where the frame budget is
   tighter and the audience is usually on mobile data.
   ======================================================================== */

/* Headline reveal. hhl-motion.js splits the text into .word spans. */
.js-on .reveal-text .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.42em);
  transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
  transition-delay: calc(var(--w, 0) * 42ms);
}
.js-on .reveal-text.is-in .word { opacity: 1; transform: none; }

/* Parallax layers, driven by hhl-motion.js writing --py. */
.js-on [data-parallax] { transform: translate3d(0, var(--py, 0px), 0); }

/* Pinned storytelling stage.

   Written the safe way round on purpose. The DEFAULT is an ordinary stacked
   section that everyone can read: normal height, panels one after another,
   fully visible. Pinning is opt-in, applied by hhl-motion.js only when the
   viewport is big enough and motion is welcome. Had it been the other way
   round, a JS failure would leave three panels stacked invisibly on one grid
   cell inside a 300vh-tall empty section. */
.pin-wrap { position: relative; }
.pin-stage { padding-block: var(--section-y); }
.pin-panel { margin-bottom: clamp(20px, 3vw, 34px); }
.pin-dots, .pin-progress { display: none; }

/* The spacing that every other section carries has to live on the wrapper
   here, not on the stage. Pinned, the stage is a sticky panel that must fill
   the viewport, so its padding is zeroed just below — which left this section
   contributing nothing at its edges. Measured on the home page: every other
   join was 216px of clear space and the two either side of this one were 108,
   which is what reads as "some sections have too much and some are fine". The
   wrapper is the scroll track, so padding on it sits outside the sticky panel
   and restores the rhythm without touching the pinning. */
.pin-wrap.is-pinned {
  min-height: calc(var(--pin-steps, 3) * 100svh);
  padding-block: var(--section-y);
}
.pin-wrap.is-pinned .pin-stage {
  position: sticky; top: 0; min-height: 100svh;
  display: grid; align-content: center; overflow: hidden; padding-block: 0;
}
.pin-wrap.is-pinned .pin-panel {
  grid-area: 1 / 1; margin-bottom: 0;
  opacity: 0; transform: translateY(28px) scale(0.985);
  transition: opacity 520ms var(--ease-out), transform 520ms var(--ease-out);
  pointer-events: none;
}
.pin-wrap.is-pinned .pin-panel.is-active { opacity: 1; transform: none; pointer-events: auto; }
.pin-wrap.is-pinned .pin-dots { display: grid; }
.pin-wrap.is-pinned .pin-progress { display: block; }
.pin-progress {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: rgba(247, 245, 241, 0.10);
}
.pin-progress i { display: block; height: 100%; width: 0; background: var(--accent); transition: width 240ms linear; }
/* display is owned by the .is-pinned rules above; setting it here would
   win on source order and leave the dots showing when unpinned. */
.pin-dots { position: absolute; right: clamp(16px, 3vw, 40px); top: 50%; transform: translateY(-50%); gap: 14px; }
.pin-dots span { width: 9px; height: 9px; background: rgba(247, 245, 241, 0.22); transform: rotate(45deg); transition: background var(--t-base), transform var(--t-base); }
.pin-dots span.on { background: var(--accent); transform: rotate(45deg) scale(1.4); }


/* Aurora wash behind dark bands */
.aurora { position: relative; isolation: isolate; }
.aurora::before {
  content: ''; position: absolute; inset: -20% -10%; z-index: 0; pointer-events: none;
  background:
    radial-gradient(38% 46% at 18% 28%, rgba(214, 48, 60, 0.24), transparent 70%),
    radial-gradient(34% 40% at 82% 68%, rgba(46, 158, 109, 0.14), transparent 72%),
    radial-gradient(30% 38% at 62% 12%, rgba(120, 150, 220, 0.12), transparent 70%);
  filter: blur(46px);
  animation: auroraDrift 22s ease-in-out infinite alternate;
}
.aurora > * { position: relative; z-index: 1; }
@keyframes auroraDrift {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to   { transform: translate3d(3%, 2%, 0) scale(1.08); }
}

@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .aurora::before { animation: none; }
  .js-on .reveal-text .word { opacity: 1 !important; transform: none !important; }
  .js-on [data-parallax] { transform: none !important; }
  .pin-panel { opacity: 1; transform: none; pointer-events: auto; }
}

/* Cross-page transitions where the browser supports them. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vtOut 180ms var(--ease-in-out) both; }
  ::view-transition-new(root) { animation: vtIn 320ms var(--ease-out) both; }
}
@keyframes vtOut { to { opacity: 0; } }
@keyframes vtIn { from { opacity: 0; transform: translateY(10px); } }

/* == 23e Shape layer ======================================================
   Applied in one place rather than scattered through every component, so the
   geometry of the whole site can be re-tuned from the radius tokens above.
   This is the layer that most visibly separates our design from the sharp,
   hairline-boxed reference we were handed.
   ======================================================================== */

/* Actions read as pills. */
.btn, .nav-cta, .hero-fab, .hero-chip, .row-remove, .lang-toggle {
  border-radius: var(--r-pill);
}
.ai-pick, .post-meta .tag, .section-eyebrow, .tile-k.chip, .pill {
  border-radius: var(--r-pill);
}

/* Surfaces. */
.bento-tile, .tool-card, .tool-feature, .tool-row, .svc-block, .form-card,
.calc-card, .calc-result, .post-card, .fact, .legal-note,
.faq-help, .form-rail-inner, .board, .recap-card, .hero-panel, .short-frame,
.svc-row-media, .consent-bar, .form-status, .calc-verdict, .hos-clock,
.tool-sample, .service-tile, .gal-item {
  border-radius: var(--r-md);
}
.board, .form-card, .calc-card, .hero-card {
  border-radius: var(--r-lg);
}

/* Inputs. */
input, select, textarea { border-radius: var(--r-sm); }
.consent-bar { border-radius: var(--r-lg) var(--r-lg) 0 0; }

/* Media inside rounded surfaces has to be clipped or the corners show
   through the photograph. */
.bento-tile, .svc-block, .short-frame, .svc-row-media,
.tile-media, .gal-item { overflow: hidden; }

/* Grid children with their own radius need the parent gap back, otherwise
   1px-gap bento borders look like a broken table. */
.services-grid { gap: var(--bento-gap); background: transparent; border: 0; }
.services-grid .service-tile { border: 1px solid var(--line-light); }
.facts-row { gap: var(--bento-gap); background: transparent; border: 0; }
.facts-row .fact { border: 1px solid var(--line-light); }

/* Elevation that matches the new geometry. */
.form-card, .calc-card, .tool-feature { box-shadow: var(--shadow-2); }
@media (hover: hover) {
  .bento-tile:hover, .tool-card:hover, .svc-block:hover { box-shadow: var(--shadow-lift); }
}

/* Reusable pill for badges and eyebrow chips. */
.pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 16px; font-size: 12px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  background: rgba(247, 245, 241, 0.10);
  border: 1px solid rgba(247, 245, 241, 0.18);
  color: var(--paper);
  backdrop-filter: blur(10px);
}
.pill.light { background: var(--white); border-color: var(--line-light); color: var(--asphalt); }
.pill .dot-sm { width: 6px; height: 6px; border-radius: 50%; background: var(--signal); flex: none; }
.pill-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 26px; }

/* == 24 Per-page layouts ==================================================
   The shared system supplies colour, type and components. Composition is
   deliberately different on every page so the site does not read as one
   template with the words swapped out.
   ======================================================================== */

/* ---- Services: alternating full-bleed rows + sticky equipment index ----- */
.svc-layout { display: grid; grid-template-columns: 200px 1fr; gap: 56px; align-items: start; }
@media (max-width: 1020px) { .svc-layout { grid-template-columns: 1fr; gap: 32px; } }

.svc-index { position: sticky; top: calc(var(--header-h) + 28px); }
.svc-index h2 {
  font-family: var(--font-body); font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--slate);
  margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid var(--line-light);
}
.svc-index ol { list-style: none; counter-reset: eq; }
.svc-index li { counter-increment: eq; }
.svc-index a {
  display: flex; gap: 12px; align-items: baseline;
  padding: 10px 0; text-decoration: none; font-size: 15px; font-weight: 500;
  color: var(--slate); border-bottom: 1px solid var(--line-light);
  transition: color var(--t-fast), padding-left var(--t-fast) var(--ease-out);
}
.svc-index a::before {
  content: '0' counter(eq); font-size: 11px; font-weight: 600;
  color: var(--accent); letter-spacing: 0.06em;
}
@media (hover: hover) { .svc-index a:hover { color: var(--asphalt); padding-left: 6px; } }
@media (max-width: 1020px) {
  .svc-index { position: static; }
  .svc-index ol { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 20px; }
}
@media (max-width: 560px) { .svc-index ol { grid-template-columns: repeat(2, 1fr); } }

.svc-row {
  display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 44px; align-items: center;
  padding: clamp(36px, 5vw, 56px) 0; border-top: 1px solid var(--line-light);
}
.svc-row:first-of-type { border-top: 0; padding-top: 0; }
/* Alternation is an explicit class rather than :nth-of-type. The list now has
   a break element sitting among the rows, and a structural selector that
   depends on sibling order silently re-phases the whole page the moment
   anything is inserted into it. */
.svc-row.is-flipped .svc-row-media { order: -1; }
@media (max-width: 860px) {
  .svc-row { grid-template-columns: 1fr; gap: 26px; }
  .svc-row.is-flipped .svc-row-media { order: 0; }
}
.svc-row-media { position: relative; overflow: hidden; aspect-ratio: 4 / 3; background: var(--asphalt-2); }
.svc-row-media img { width: 100%; height: 100%; object-fit: cover; transition: transform 800ms var(--ease-out); }
@media (hover: hover) { .svc-row:hover .svc-row-media img { transform: scale(1.04); } }

/* The number moved out of the photo corner and into the copy, where it reads
   as an index entry that matches the sticky list rather than a sticker
   dropped on the picture. */
.svc-copy .eq-num {
  display: block; font-family: var(--font-mono); font-size: 12px;
  letter-spacing: 0.14em; color: var(--accent); margin-bottom: 10px;
}
.svc-row h3 { font-size: clamp(26px, 3.4vw, 38px); margin-bottom: 14px; }
.svc-row h3 a { text-decoration: none; color: inherit; }
@media (hover: hover) { .svc-row h3 a:hover { color: var(--accent); } }

/* Mid-list band. Breaks a 2,768px run of six near-identical rows and carries
   the one thing that is true of all of them. */
.svc-break {
  display: grid; grid-template-columns: 1fr auto; gap: clamp(22px, 3vw, 44px);
  align-items: center; margin: clamp(28px, 4vw, 48px) 0;
  padding: clamp(26px, 3.2vw, 40px);
  background: var(--asphalt); color: var(--paper);
  border-radius: var(--r-md); border-top: 3px solid var(--accent);
}
@media (max-width: 760px) { .svc-break { grid-template-columns: 1fr; } }
.svc-break h3 { font-size: clamp(21px, 2.6vw, 28px); color: var(--paper); margin-bottom: 12px; }
.svc-break p { font-size: 15px; color: var(--on-dark); line-height: 1.72; max-width: 62ch; }
.svc-break .btn { white-space: nowrap; }

/* Scrollspy on the jump list: the row you are reading is marked in the index.
   Set by hhl.js; without JS the list is an ordinary set of anchors. */
.svc-index a.is-current { color: var(--asphalt); font-weight: 600; }
.svc-index a.is-current::before { color: var(--accent); }
.svc-row .lede { font-size: 16px; color: var(--slate); margin-bottom: 22px; max-width: 46ch; }
.svc-row ul { list-style: none; display: grid; gap: 2px; }
.svc-row li {
  position: relative; padding: 11px 0 11px 26px; font-size: 14px;
  border-top: 1px solid var(--line-light);
}
.svc-row li::before {
  content: ''; position: absolute; left: 0; top: 18px;
  width: 10px; height: 2px; background: var(--accent);
}

/* ---- Tools hub: one feature tile, three supporting ---------------------- */
.tools-board { display: grid; grid-template-columns: 1.25fr 1fr; gap: 22px; align-items: stretch; }
@media (max-width: 900px) { .tools-board { grid-template-columns: 1fr; } }
.tools-board .stack { display: grid; gap: 22px; align-content: start; }

.tool-feature {
  position: relative; overflow: hidden;
  background: var(--asphalt); color: var(--paper);
  padding: clamp(28px, 4vw, 44px);
  display: flex; flex-direction: column; justify-content: space-between;
  min-height: 340px; border-top: 3px solid var(--accent);
}
.tool-feature h3 { color: var(--paper); font-size: clamp(28px, 3.6vw, 40px); margin-bottom: 14px; }
.tool-feature p { color: var(--on-dark); font-size: 15px; max-width: 42ch; margin-bottom: 28px; }
.tool-feature .tool-ico { color: var(--accent); margin-bottom: 20px; }
/* The caveat under the sample readout. Quiet, but present: these figures sit in
   the most persuasive panel on the page and a carrier has no other way to tell
   they are a worked example rather than a rate on offer. */
.tool-sample-note {
  margin: 10px 0 0; font-size: 12px; line-height: 1.5;
  color: var(--on-dark-mute); max-width: 42ch;
}
.tool-feature .tool-link { color: var(--paper); }
@media (hover: hover) { .tool-feature .tool-link:hover { color: var(--accent); } }
/* Sample readout so the tile shows what the tool produces, not just names it. */
.tool-sample {
  display: flex; gap: 26px; flex-wrap: wrap;
  border-top: 1px solid var(--line); padding-top: 20px; margin-top: 8px;
}
.tool-sample div { min-width: 92px; }
.tool-sample .k {
  font-size: 10px; font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--on-dark-mute); margin-bottom: 6px;
}
.tool-sample .v { font-size: 24px; font-weight: 600; color: var(--chrome); font-variant-numeric: tabular-nums; }
.tool-sample .v.good { color: var(--signal); }

.tool-row {
  display: grid; grid-template-columns: 52px 1fr auto; gap: 20px; align-items: center;
  background: var(--white); border: 1px solid var(--line-light); padding: 24px 26px;
  text-decoration: none; transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base), border-color var(--t-base);
}
@media (hover: hover) {
  .tool-row:hover { transform: translateX(4px); box-shadow: var(--shadow-2); border-color: var(--accent); }
}
.tool-row .tool-ico { color: var(--accent); margin: 0; }
.tool-row h3 { font-size: 20px; margin-bottom: 4px; }
.tool-row p { font-size: 13px; color: var(--slate); margin: 0; }
.tool-row .go { color: var(--accent); font-size: 20px; line-height: 1; }
@media (max-width: 480px) { .tool-row { grid-template-columns: 1fr; gap: 12px; } .tool-row .go { display: none; } }

/* ---- Contact: full-height split screen ---------------------------------- */
/* ---- Form layout: elevated card + sticky rail ---------------------------
   Used by every form on the site. It started as the carrier-setup layout;
   Contact used to be a full-bleed split screen with the fields sitting
   straight on the page background, so the same act — sending HHL your
   details — looked like two different products. Now there is one pattern and
   one place to change it.

   The rail sticks because on a nine-field form the answer to "what happens
   after I press this" scrolls off the top exactly when the visitor starts
   wondering about it.
   ------------------------------------------------------------------------ */
.form-layout { display: grid; grid-template-columns: 1fr 320px; gap: 48px; align-items: start; }
@media (max-width: 1020px) { .form-layout { grid-template-columns: 1fr; gap: 32px; } }

.form-card-title { font-size: clamp(23px, 2.8vw, 30px); margin-bottom: 26px; }
.form-card .eyebrow { margin-bottom: 12px; }

.form-rail { position: sticky; top: calc(var(--header-h) + 28px); }
@media (max-width: 1020px) { .form-rail { position: static; } }
.form-rail-inner { background: var(--asphalt); color: var(--paper); padding: 30px 26px; border-top: 3px solid var(--accent); }
.form-rail h2 {
  font-family: var(--font-body); font-size: 10px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-dark-mute); margin-bottom: 22px;
}
.rail-step { position: relative; padding: 0 0 26px 30px; }
.rail-step:last-of-type { padding-bottom: 0; }
.rail-step::before {
  content: ''; position: absolute; left: 4px; top: 6px;
  width: 9px; height: 9px; background: var(--accent); transform: rotate(45deg);
}
.rail-step::after {
  content: ''; position: absolute; left: 8px; top: 20px; bottom: 4px;
  width: 1px; background: var(--line);
}
.rail-step:last-of-type::after { display: none; }
.rail-step .k { font-size: 10px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); margin-bottom: 6px; }
.rail-step h3 { font-size: 17px; color: var(--paper); margin-bottom: 5px; }
/* Headings are uppercased site-wide, which is wrong for an address:
   SALES@HHLOGS.COM reads as a different string from the one you type. */
.rail-step h3.lower { text-transform: none; letter-spacing: 0; font-size: 16px; }
/* Contact's rail puts the phone numbers in the h3 slot, so they have to be
   tappable without looking like body links. */
.rail-step h3 a { color: var(--paper); text-decoration: none; }
@media (hover: hover) { .rail-step h3 a:hover { color: var(--accent-hi); } }
.rail-step p { font-size: 13px; color: var(--on-dark-mute); line-height: 1.6; }
.rail-step p a { color: var(--on-dark); }
.rail-help { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--line); font-size: 13px; color: var(--on-dark-mute); }
.rail-help a { color: var(--paper); font-weight: 600; }

/* ---- About: milestone timeline + pull quote ----------------------------- */
.pullquote {
  max-width: 900px; margin: 0 auto clamp(40px, 5vw, 60px);
  font-family: var(--font-display); font-weight: 700; text-transform: none;
  font-size: clamp(22px, 3.4vw, 36px); line-height: 1.3; letter-spacing: -0.02em;
  text-align: center; color: var(--asphalt); position: relative; padding: 0 clamp(16px, 4vw, 48px);
}
.pullquote::before, .pullquote::after {
  content: ''; display: block; width: 64px; height: 3px;
  background: var(--accent); margin: 0 auto 26px;
}
.pullquote::after { margin: 26px auto 0; }
.pullquote cite { display: block; font-family: var(--font-body); font-style: normal; font-size: 13px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--slate); margin-top: 22px; }

.timeline { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; margin-top: 8px; }
@media (max-width: 900px) { .timeline { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .timeline { grid-template-columns: 1fr; } }
.tl-item { position: relative; padding: 34px 24px 0 0; border-top: 2px solid var(--line); }
.tl-item::before {
  content: ''; position: absolute; top: -7px; left: 0;
  width: 12px; height: 12px; background: var(--accent); transform: rotate(45deg);
}
.tl-item .k { font-size: 11px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); margin-bottom: 10px; }
.tl-item h3 { font-size: 19px; margin-bottom: 8px; }
.tl-item p { font-size: 14px; color: var(--slate); }
.on-dark .tl-item { border-top-color: var(--line); }
.on-dark .tl-item h3 { color: var(--paper); }
.on-dark .tl-item p { color: var(--on-dark); }

/* ---- Blog: featured post + compact list --------------------------------- */


/* ---- Legal: sticky contents rail ---------------------------------------- */
.legal-layout { display: grid; grid-template-columns: 240px 1fr; gap: 56px; align-items: start; }
@media (max-width: 1020px) { .legal-layout { grid-template-columns: 1fr; gap: 28px; } }
.legal-toc { position: sticky; top: calc(var(--header-h) + 28px); font-size: 14px; }
@media (max-width: 1020px) { .legal-toc { position: static; border: 1px solid var(--line-light); padding: 22px; background: var(--white); } }
.legal-toc h2 {
  font-family: var(--font-body); font-size: 10px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--slate);
  margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line-light);
}
.legal-toc ol { list-style: none; counter-reset: toc; }
.legal-toc li { counter-increment: toc; }
.legal-toc a {
  display: flex; gap: 10px; padding: 7px 0; text-decoration: none;
  color: var(--slate); font-size: 13.5px; line-height: 1.45;
  transition: color var(--t-fast);
}
.legal-toc a::before { content: counter(toc); font-weight: 600; color: var(--accent); min-width: 16px; }
@media (hover: hover) { .legal-toc a:hover { color: var(--asphalt); } }

/* == 25 Utilities ========================================================= */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.text-center { text-align: center; }
/* A section-head or cta-row centered with nothing beside it — a full-width
   section that lost its second column — needs its own box (and the prose
   measure inside it) re-centered too, not just its text aligned. */
.section-head.text-center { margin-inline: auto; }
.section-head.text-center p { margin-inline: auto; }
.cta-row.text-center { justify-content: center; }
.narrow { max-width: 800px; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }
.mt-32 { margin-top: 32px; }
.mt-56 { margin-top: 56px; }

/* == 25 Print ============================================================= */
@media print {
  .site-header, .hero-fab, .hero-panel, .hhl-fab, .final-cta, .skip-link,
  .board-scan, #hhl-loader, .scroll-rail, .hero-bg { display: none !important; }
  body { background: #fff; color: #000; }
  .hero, .page-hero, .process-strip, .board-band, .gallery-band, .site-footer, .on-dark {
    background: #fff !important; color: #000 !important;
  }
  .hero h1, .page-hero h1, .on-dark .section-head h2, .mile h3, .foot-grid h2 { color: #000 !important; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 10px; }
  section { padding-block: 22px; page-break-inside: avoid; }
  .js-on [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* == Fleet ring ===========================================================
   A 3D carousel: every card sits on the surface of a cylinder at
   rotateY(θ) translateZ(r), inside one perspective container. The parent
   carries a single rotateY, so spinning the whole ring is one composited
   transform rather than 48 animated elements.

   θ comes from --i and --n, so the ring adapts to however many photos exist.
   The radius has to grow with the count or the cards overlap into a solid
   band: r ≈ (card width × n) / 2π, expressed here as a calc so nobody has to
   recompute it when the client sends the rest of the fleet.
   ======================================================================== */
.fleet-band { background: var(--asphalt); overflow: hidden; }

/* -- Floor: no JS, no 3D. A plain grid that works and opens every photo. -- */
.ring-track {
  list-style: none;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter);
}
.ring-hit {
  display: block; width: 100%; padding: 0; cursor: pointer;
  background: var(--asphalt-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); overflow: hidden;
}
.ring-hit img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.ring-focus, .ring-lightbox { display: none; }

/* -- Enhanced: the actual ring. Only once JS confirms it can run. -------- */
@media (min-width: 981px) {
  .js-on .ring-stage[data-fleet-ring] {
    position: relative;
    height: 560px; margin-top: clamp(56px, 6vw, 92px);
    display: grid; place-items: center;
    /* clip, not hidden: the ring's far side is meant to run off the edges. */
    overflow: clip;
  }

  .js-on .ring-viewport {
    position: absolute; inset: 0;
    perspective: 1250px; perspective-origin: 50% 38%;
  }

  .js-on .ring-track {
    /* 62%, not 46%. Measured: the cards spanned 438px but sat 99px above the
       stage centre, so they overhung the top edge by 38px and crowded the
       heading above. overflow:clip does not save you here — a preserve-3d
       subtree paints outside its clipping ancestor. Move the track instead. */
    position: absolute; left: 50%; top: 62%;
    display: block; width: 0; height: 0; padding: 0; margin: 0; gap: 0;
    transform-style: preserve-3d;
    /* 20deg, not 9. At a shallow angle the ring reads as a drum seen edge-on;
       tilting further looks down into it, which is what makes the far side
       visible above the near side and gives the ellipse its shape. */
    transform: rotateX(20deg) rotateY(var(--spin, 0deg));
    will-change: transform;
  }

  .js-on .ring-card {
    /* Radius is fixed; the CARD is derived from it. Deriving the radius from
       the card count flattens the ring into a straight band (that was the
       first bug here). Fixing both instead means every extra photo eats into
       the same circumference, and at 48 the cards ended up twice as wide as
       their slot — a solid belt with nothing readable in it.

       So: hold the radius, and size the card to the arc it actually gets.
       1.35 is the overlap factor — each card is 35% wider than its slot, so
       neighbours tuck behind each other without swallowing each other. That
       ratio now holds at any photo count. */
    --radius: clamp(380px, 32vw, 540px);
    --slot: calc(6.2832 * var(--radius) / var(--n));
    --card-w: calc(var(--slot) * 1.35);
    position: absolute; left: calc(var(--card-w) / -2); top: -58px;
    width: var(--card-w);
    transform: rotateY(calc(var(--i) * (360deg / var(--n)))) translateZ(var(--radius));
    transition: opacity var(--t-base);
  }

  .js-on .ring-card .ring-hit img { aspect-ratio: 3 / 4; }
  .js-on .ring-card .ring-hit {
    border-radius: var(--r-xs);
    box-shadow: 0 10px 26px rgba(6, 10, 20, 0.5);
    transition: transform var(--t-base) var(--ease-out), border-color var(--t-base), box-shadow var(--t-base);
  }
  .js-on .ring-card .ring-hit:hover,
  .js-on .ring-card .ring-hit:focus-visible {
    transform: scale(1.14); border-color: var(--accent);
    box-shadow: 0 16px 40px rgba(6, 10, 20, 0.6), 0 0 0 1px var(--accent);
    z-index: 2;
  }
  /* The far half of the ring is behind the centre card, so it steps back
     rather than competing with it. */
  .js-on .ring-card.is-back .ring-hit { opacity: 0.42; }

  /* -- Centre preview -------------------------------------------------- */
  .js-on .ring-focus {
    display: block; position: relative; z-index: 3; margin: 0;
    width: 300px; pointer-events: none;
    opacity: 0; transform: translateY(10px) scale(0.97);
    transition: opacity var(--t-base), transform var(--t-base) var(--ease-out);
  }
  .js-on .ring-focus.is-on { opacity: 1; transform: none; }
  .js-on .ring-focus img {
    display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
    border-radius: var(--r-md); border: 1px solid rgba(247, 245, 241, 0.18);
    box-shadow: 0 30px 70px rgba(6, 10, 20, 0.72);
    background: var(--asphalt-2);
  }
  .js-on .ring-focus figcaption {
    display: grid; gap: 3px; justify-items: center; text-align: center; margin-top: 14px;
  }
  .js-on .rf-k { font-size: 10px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-dark-mute); }
  .js-on .rf-v { font-family: var(--font-display); font-weight: 700; font-size: 16px; line-height: 1.35; color: var(--paper); max-width: 30ch; }
  .js-on .rf-cta { font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); }

  .js-on .ring-stage.is-grabbing { cursor: grabbing; }
  .js-on .ring-stage { cursor: grab; }
}

/* Rotation is the point of the ring; if it is unwelcome, the grid is fine. */
@media (prefers-reduced-motion: reduce) {
  .js-on .ring-stage[data-fleet-ring] { height: auto; display: block; overflow: visible; }
  .js-on .ring-viewport { position: static; perspective: none; }
  .js-on .ring-track {
    position: static; width: auto; height: auto; transform: none;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px; padding-inline: var(--gutter);
  }
  .js-on .ring-card { position: static; width: auto; transform: none; }
  .js-on .ring-focus { display: none; }
}

.ring-note {
  margin-top: clamp(24px, 3vw, 38px); font-size: 13px;
  color: var(--on-dark-mute); text-align: center; max-width: 70ch; margin-inline: auto;
}

/* -- Lightbox ------------------------------------------------------------
   Everything here is set explicitly rather than leaning on the UA stylesheet.
   A modal <dialog> centres itself with inset:0 + margin:auto, and the first
   version of this only declared display:grid — which left the panel pinned to
   the top-left, the backdrop covering part of the screen, and the close button
   escaping to the viewport corner because the dialog was not acting as its
   containing block. Declaring the box outright fixes all three. */
.ring-lightbox {
  position: fixed; inset: 0; margin: auto;
  width: min(1080px, 92vw); max-width: 92vw;
  height: fit-content; max-height: 92svh;
  padding: clamp(14px, 2vw, 20px);
  background: var(--asphalt); color: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: 0 40px 120px rgba(4, 7, 14, 0.7);
  overflow: visible;
}
.ring-lightbox:not([open]) { display: none; }
.ring-lightbox[open] { display: grid; justify-items: center; gap: 12px; }
.ring-lightbox::backdrop { background: rgba(6, 10, 20, 0.88); backdrop-filter: blur(6px); }
.ring-lightbox img {
  display: block; max-width: 100%; max-height: 78svh;
  width: auto; height: auto; border-radius: var(--r-md);
}
.rl-caption { font-size: 13px; color: var(--on-dark); text-align: center; max-width: 70ch; }
/* Sits on the panel's corner, not the viewport's — the dialog is the
   positioned ancestor now that position is declared above. */
.rl-close {
  position: absolute; top: -14px; right: -14px; z-index: 2;
  display: grid; place-items: center; width: 42px; height: 42px;
  border-radius: 50%; cursor: pointer; color: var(--paper);
  background: var(--asphalt-2); border: 1px solid rgba(247, 245, 241, 0.28);
  box-shadow: 0 8px 24px rgba(4, 7, 14, 0.6);
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
@media (hover: hover) { .rl-close:hover { background: var(--accent); border-color: var(--accent); transform: scale(1.06); } }

/* The no-JS floor for the ring. Only reachable inside <noscript>, so it
   never competes with the enhanced version. */
.ring-noscript { margin-top: clamp(24px, 3vw, 40px); }
.ring-noscript li { list-style: none; }
.ring-noscript img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--r-sm); border: 1px solid var(--line); }

/* == Unified load board ====================================================
   The client's redesigned board, ported to this site's tokens.

   Colour is doing semantic work here and the mapping is deliberate. The source
   design used amber as its highlight and a burnt orange as its warning, which
   sit too close together once amber becomes the brand red. So the three states
   map onto the three tokens this site already has:

     highlight / AI pick / score 80+   ->  --accent   (the brand red)
     neutral / mid score               ->  --steel
     warning / score under 55          ->  --warn     (amber)

   which keeps the legend's three dots telling three different stories.

   The panel is white on purpose: it reads as a screenshot of the product
   sitting inside the dark marketing band, rather than as more of the band.
   ======================================================================== */
.lb {
  background: var(--white); color: var(--asphalt);
  border-radius: var(--r-md); overflow: hidden;
  box-shadow: var(--shadow-2);
  font-size: 13px;
}
.lb a { color: var(--accent); text-decoration: none; }
.lb a:hover { color: var(--accent-lo); text-decoration: underline; }

/* --- header ------------------------------------------------------------ */
.lb-top {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 28px; flex-wrap: wrap;
  padding: 20px 22px 16px; border-bottom: 1px solid var(--line-light);
}
.lb-brand { display: flex; align-items: center; gap: 14px; }
.lb-brand img { width: 48px; height: 48px; object-fit: contain; flex: none; }
.lb-brand h3 {
  margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -0.3px;
  color: var(--asphalt);
}
.lb-titles { display: flex; flex-direction: column; gap: 4px; }
.lb-titles .row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.lb-tag {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent-lo); border: 1px solid rgba(214, 48, 60, 0.30);
  background: rgba(214, 48, 60, 0.07); padding: 3px 7px; border-radius: 4px;
}
.lb-scope { font-family: var(--font-mono); font-size: 11px; color: var(--slate); }

.lb-stats { display: flex; align-items: flex-end; gap: 26px; flex-wrap: wrap; }
.lb-stat { display: flex; flex-direction: column; gap: 3px; }
.lb-stat .k {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--slate);
}
.lb-stat .v { font-size: 17px; font-weight: 700; color: var(--asphalt); }
.lb-stat .v.fresh { color: var(--signal); font-family: var(--font-mono); font-size: 14px; }
.lb-refresh {
  background: var(--asphalt); color: var(--white); border: 0;
  border-radius: 8px; padding: 10px 16px; font-size: 13px; font-weight: 600;
  font-family: var(--font-body); cursor: pointer;
  transition: background var(--t-fast);
}
.lb-refresh:hover { background: var(--asphalt-2); }

/* --- tabs --------------------------------------------------------------- */
.lb-tabs { display: flex; gap: 4px; padding: 12px 22px 0; }
.lb-tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 15px; border-radius: var(--r-sm) var(--r-sm) 0 0;
  font-size: 13px; font-weight: 600; color: var(--slate);
  border: 1px solid transparent; border-bottom: 0; background: transparent;
  cursor: pointer;
}
.lb-tab .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: currentColor; }
.lb-tab.is-on {
  color: var(--asphalt); background: var(--white);
  border-color: var(--line-light);
}
.lb-tab.is-on .dot { background: var(--accent); }
.lb-tab:not(.is-on) .dot { background: var(--signal); }

/* --- filters ------------------------------------------------------------ */
.lb-filters {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 14px 22px; border-top: 1px solid var(--line-light);
  border-bottom: 1px solid var(--line-light);
}
.lb-field {
  font-size: 12px; color: var(--slate);
  border: 1px solid var(--line-light); border-radius: 7px;
  padding: 8px 12px; background: var(--paper);
}
.lb-eq { display: flex; gap: 6px; }
.lb-eq span {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  letter-spacing: 0.06em; color: var(--slate);
  border: 1px solid var(--line-light); border-radius: 6px; padding: 7px 11px;
}
.lb-eq span.is-on { background: var(--asphalt); color: var(--white); border-color: var(--asphalt); }
.lb-sorted { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.lb-sorted .k {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--slate);
}
.lb-sorted .v {
  font-size: 12px; font-weight: 600; color: var(--asphalt);
  border: 1px solid var(--line-light); border-radius: 7px; padding: 8px 12px;
}

/* --- the table ---------------------------------------------------------- */
/* A grid rather than a <table> so the columns can collapse independently on
   narrow screens. The row is still marked up as a row for screen readers. */
.lb-table { overflow-x: auto; }
.lb-grid { min-width: 1088px; }
.lb-hd, .lb-row {
  display: grid;
  /* Twelve columns inside a 1132px wrap. Sized to fit rather than to scroll:
     a table you have to drag sideways to read the credit score is a table
     nobody reads the credit score in. Below 1088px it scrolls instead. */
  grid-template-columns:
    44px 92px 34px 86px 66px 86px 130px 76px 76px 140px 100px 58px;
  gap: 8px; align-items: start;
  padding: 11px 22px;
}
.lb-hd {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--slate);
  background: var(--paper); border-bottom: 1px solid var(--line-light);
}
.lb-row { border-bottom: 1px solid var(--line-light); }
.lb-row:last-child { border-bottom: 0; }
.lb-row .sub { display: block; font-size: 11px; color: var(--slate); margin-top: 2px; }
.lb-row .mono { font-family: var(--font-mono); }

/* The picked row. The left edge and the wash are the brand red at low alpha —
   enough to find the row at a glance, not enough to fight the rate column. */
.lb-row.is-pick {
  background: rgba(214, 48, 60, 0.045);
  box-shadow: inset 3px 0 0 var(--accent);
}
.lb-pick {
  display: inline-block; margin-top: 4px;
  font-family: var(--font-mono); font-size: 9px; font-weight: 700;
  letter-spacing: 0.08em; color: var(--white); background: var(--accent);
  border-radius: 3px; padding: 2px 6px;
}

/* Score: three tiers, three tokens. See the note at the top of this block. */
.lb-score { font-family: var(--font-mono); font-size: 15px; font-weight: 700; }
.lb-score .bar { display: block; width: 26px; height: 2px; margin-top: 5px; background: currentColor; }
.lb-score.t-hi { color: var(--accent); }
.lb-score.t-mid { color: var(--steel); }
.lb-score.t-lo { color: var(--warn); }

.lb-src {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--asphalt);
  border: 1px solid var(--line-light); border-radius: 999px; padding: 4px 9px;
}
.lb-src .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; background: var(--steel); }
.lb-rate { font-size: 15px; font-weight: 700; color: var(--asphalt); }
.lb-lane strong { display: block; font-weight: 700; }
.lb-lane .to { color: var(--slate); }
.lb-lane .to::before { content: '→'; color: var(--accent); margin-right: 5px; }

/* --- foot + legend ------------------------------------------------------ */
.lb-legend {
  display: flex; gap: 22px; flex-wrap: wrap;
  padding: 12px 22px 16px; font-size: 11.5px; color: var(--slate);
  border-top: 1px solid var(--line-light);
}
.lb-legend span { display: inline-flex; align-items: center; gap: 7px; }
.lb-legend i { width: 7px; height: 7px; border-radius: 50%; flex: none; font-style: normal; }
.lb-legend .k-hi i { background: var(--accent); }
.lb-legend .k-ok i { background: var(--signal); }
.lb-legend .k-lo i { background: var(--warn); }

@media (max-width: 720px) {
  .lb-top { padding: 16px; }
  .lb-tabs, .lb-filters, .lb-legend { padding-left: 16px; padding-right: 16px; }
  .lb-hd, .lb-row { padding-left: 16px; padding-right: 16px; }
  .lb-brand img { width: 40px; height: 40px; }
  .lb-brand h3 { font-size: 17px; }
}

/* --- refresh: the button, and the veil it raises ------------------------ */
/* The panel is the positioning context for the veil, so the loader covers the
   whole board — header, filters and table — rather than the table alone. A
   refresh that leaves the controls live invites a second click into a board
   that is already rebuilding. */
.lb { position: relative; }

.lb-refresh[disabled] { opacity: .6; cursor: default; }

.lb-veil {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .78); backdrop-filter: blur(2px);
  border-radius: inherit;
  opacity: 0; pointer-events: none;
  transition: opacity .2s var(--ease-out);
}
.lb-veil.is-on { opacity: 1; pointer-events: auto; }
.lb-veil-inner {
  display: flex; align-items: center; gap: 12px;
  background: var(--asphalt); color: var(--paper);
  padding: 13px 19px; border-radius: var(--r-sm);
  box-shadow: var(--shadow-2); font-size: 14px; font-weight: 600;
}
.lb-veil-spin {
  width: 18px; height: 18px; flex: none;
  border: 2px solid rgba(246, 246, 248, .28); border-top-color: var(--accent);
  border-radius: 50%; animation: hhl-veil-spin .7s linear infinite;
}
/* Someone who has asked for less motion still needs to know the board is
   working, so the spinner stops turning and the veil does the telling. */
@media (prefers-reduced-motion: reduce) {
  .lb-veil { transition: none; }
  .lb-veil-spin { animation: none; border-top-color: var(--accent); }
}

/* Two tile grids that were a single section in the reviewed design. WordPress
   renders one section per grid and each brings its own padding-block, so the
   24px step the prototype puts between them arrived as 240px. Dropping the
   padding on both sides of the seam leaves the grid's own gap to do the work,
   which is what the prototype was doing all along. */
section.is-join-below { padding-block-end: 0; }
section.is-join-above { padding-block-start: 0; }

/* The legend's three colours, in the columns they are about. Without these the
   legend is a caption for something that is not on screen: "broker credit 90+"
   in green, next to a credit column printed in the same grey as everything
   else. Same three tokens as the score tiers, so one reading covers the table. */
.lb-row .t-ok   { color: var(--signal); }
.lb-row .t-lo   { color: var(--warn); }
.lb-row .t-mute { color: var(--slate); }
.lb-credit { font-weight: 600; }

/* The ring's focus slot and its lightbox ship an <img> with no src: the script
   fills them when a photo is hovered or opened. The width/height attributes are
   there to reserve the space so nothing shifts, but until a src arrives the
   element is an empty 291x218 box that some browsers draw a broken-image glyph
   into. Hiding it keeps the reserved space and loses the glyph. */
.ring-focus img:not([src]),
.ring-lightbox img:not([src]) { visibility: hidden; }

/* ==========================================================================
   The phone pass

   This site is opened on a phone more than anywhere else, so the phone layout
   is the one that has to be right rather than the one that has to survive.
   Everything below is measured: gaps between bands, button widths and section
   heights were read off a 390x844 viewport before and after.

   It sits at the end of the file on purpose. Several of these rules have to
   win against the component CSS above them, and the honest way to do that is
   source order rather than a specificity fight nobody can read.
   ========================================================================== */

@media (max-width: 760px) {

  /* -- Two buttons, one width -------------------------------------------- */
  /* A pair of centred buttons whose widths are set by their labels reads as
     an accident. Stacked and full width, they read as a choice — and each one
     becomes a target a thumb cannot miss. The homepage hero already did this;
     this is the same treatment everywhere else it applies. */
  .cta-row,
  .ph-actions {
    display: grid;
    gap: 10px;
    justify-items: stretch;
  }
  .cta-row > .btn,
  .cta-row > a,
  .cta-row > button,
  .ph-actions > .btn,
  .ph-actions > a {
    width: 100%;
    text-align: center;
    justify-content: center;
  }
  /* The quiet "or call" line under a button is a link, not a second button:
     it keeps its own size and sits centred under the pair. */
  .cta-row > .cta-call,
  .cta-row > .pt-call {
    width: auto;
    justify-self: center;
  }

  /* -- Section heads, centred -------------------------------------------- */
  /* On a wide screen a left-aligned head sits against the left column of a
     grid and belongs to it. On a phone there is no grid and no column — the
     head is the only thing on its line, and centring it is what makes the
     band read as a band. The body copy underneath stays left aligned, because
     centred paragraphs are harder to read and always have been. */
  .section-head,
  .rv-claim-copy,
  .explain-aside {
    text-align: center;
  }
  .section-head .head-lede,
  .section-head > p,
  .rv-claim-body {
    margin-inline: auto;
  }
  /* The claim's heading and body centre above; its button did not, because
     text-align centres words and this is a flex container — the button is a
     flex item and no amount of text-align moves it. So "Become one of them"
     sat at the left margin under two centred lines, which reads as a mistake
     rather than a choice. Above 900px it is deliberately at the right end of
     its own column beside the copy, and that stays. */
  .rv-claim-act { justify-content: center; }
  .explain-aside::after {
    margin-inline: auto;
  }
  /* A head that sits beside a claim card keeps its own alignment: the two are
     read as a pair and centring one of them breaks the pairing. */
  .head-split > div:first-child {
    text-align: center;
  }
  .head-split .claim-card {
    text-align: left;
  }

  /* -- The fleet rail ----------------------------------------------------- */
  /* Forty-eight photographs, two to a row, is twenty-four rows of scrolling
     for a section whose whole job is to be glanced at. As a swipe row it is
     one screen, and every photograph is still reachable.

     .m-carousel above sets the mechanics; .ring-track's own grid rules come
     later in the file than that block, so the parts it would otherwise win
     are restated here. */
  .ring-track.m-carousel {
    max-width: none;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
    gap: 10px;
  }
  .ring-track.m-carousel > .ring-card {
    --slide: 46%;
    min-width: 0;
  }
  .ring-viewport { overflow: visible; }

  /* -- The footer, folded ------------------------------------------------- */
  .foot-col > h2 {
    margin: 0;
  }
  .foot-toggle {
    display: flex; align-items: center; justify-content: space-between;
    width: 100%; gap: 12px;
    /* 48px, so the row clears the 44px a guideline asks of a touch target and
       a thumb does not have to be accurate to open a footer column. */
    padding: 16px 0; min-height: 48px;
    background: none; border: 0;
    border-top: 1px solid rgba(247, 245, 241, 0.12);
    /* The heading's own type, so folding does not change how the footer reads. */
    font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
    text-align: left; cursor: pointer;
  }
  .foot-col:first-of-type .foot-toggle { border-top: 0; }

  /* .foot-toggle .foot-chev, not .foot-chev. The desktop rule that hides this
     is written with the parent in front of it, and two class names beat one
     however far down the file the override sits — so the phone accordion has
     been folding with no arrow on it at all. Source order does not enter into
     it, which is what made this one easy to write and easy to miss. */
  .foot-toggle .foot-chev {
    display: block;
    flex: none; width: 10px; height: 10px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform var(--t-fast) var(--ease-out);
    opacity: 0.7;
  }
  .foot-toggle[aria-expanded='true'] .foot-chev {
    transform: rotate(225deg) translate(-2px, -2px);
  }
  .foot-panel { padding-bottom: 14px; }

  /* -- The line under it -------------------------------------------------- */
  /* Copyright on one line, the two legal links under it, both centred. Spread
     apart on a phone they read as two unrelated fragments at opposite edges. */
  .foot-bottom {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 10px;
  }
  .foot-bottom nav {
    justify-content: center;
  }
}

@media (max-width: 760px) {

  /* -- Things a thumb has to hit ------------------------------------------ */
  /* A 20px-tall link is a link for a mouse. Every guideline that has an
     opinion says 44px, and the ones that disagree say 40. These are all inline
     links that were sized by their type; the padding is what makes them
     reachable, and none of them move anything else by growing. */
  .tool-link,
  .map-open,
  /* One per office once there is more than one. Sized by its type like the
     rest of these, which on a phone is a 19px target for a thumb. */
  .fc-map,
  .foot-bottom nav a,
  .rf-cta,
  /* .tl-cta is not in this list: it is a span inside a link that is already
     the whole card, and giving it a 44px box here re-showed it after the
     tool-line rules had placed it, which is what broke that layout. */
  .pt-call,
  .cta-call {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  .lb-refresh,
  .lb-tab,
  .lb-field {
    min-height: 40px;
  }

  /* -- Type that stopped being readable ----------------------------------- */
  /* 10px uppercase with wide tracking survives on a desktop monitor at arm's
     length. On a phone held at a metre in daylight it does not, and these are
     the labels that say what a number means. */
  .tile-k,
  .fc-k,
  .lb-tag,
  .lb-scope,
  .recap-bar-label,
  .rf-k,
  .cp-k,
  .tl-num,
  .mile .k,
  .k {
    font-size: 11.5px;
  }

  .cp-count,
  .form-note,
  .foot-bottom {
    font-size: 12px;
  }
}

@media (max-width: 760px) {
  /* The rest of the small-label family. These are the words that say what a
     figure means — "score", "age", "deadhead" — and at 10px on a phone they
     are decoration rather than labels. The load board is a swipe row, so the
     extra width costs nothing but a little more swiping. */
  .lb-tag,
  .lb-scope,
  .lb-src,
  .lb-tab,
  .lb-hd > span,
  .lb-row .k,
  .when,
  .recap-bar-label,
  .rf-k,
  .mc-k,
  .mc-eq i,
  .sc-n,
  .tl-num,
  .ph-fact .k,
  .foot-contact .fc-k {
    font-size: 11.5px;
  }

  .lb-refresh { min-height: 44px; }
}

@media (max-width: 760px) {
  /* Nothing under 11px. These three are the last of them: the hero metric
     labels, the fact rows in a page hero, and the "AI pick" badge on the load
     board, which at 9px was a red smudge rather than a word. */
  .hero-metric .k,
  .ph-fact .k {
    font-size: 11px;
  }

  .lb-pick {
    font-size: 11px;
    padding: 3px 8px;
  }
}

@media (max-width: 760px) {
  /* The last of the links a thumb has to find: a contents list on a legal
     page, the ways-to-reach-us rail on Contact, and a post's tag row. */
  .legal-toc a,
  .rail-step a,
  .post-meta a,
  .tag {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

@media (max-width: 760px) {
  /* The footer is a grid with a 30px row gap, which is right when the three
     columns sit side by side and wrong the moment they fold: a collapsed row
     is 48px tall, so a 30px gap between them reads as a gap in the list rather
     than as the list. Folded, they stack flush and the hairline on each row is
     what separates them. */
  .foot-grid { row-gap: 0; }
  .foot-grid--offices { grid-template-columns: 1fr; }
  .foot-grid--offices .foot-col--contact {
    margin-top: 0; padding-top: 0; border-top: 0;
  }
  .foot-grid--offices .foot-offices { grid-template-columns: 1fr; gap: 22px; }

  /* :first-of-type counts elements of the same tag, not of the same class, and
     the brand block above these is a div as well — so that selector matched
     the logo block and the first column got nothing. The result was an
     accordion whose top row sat flush against the social icons. This picks the
     first column by what is actually true of it: it is the one that follows
     something that is not a column. */
  .foot-grid > :not(.foot-col) + .foot-col { margin-top: 32px; }
  .foot-grid > .foot-col:last-child { margin-bottom: 4px; }
}

@media (max-width: 760px) {
  /* == The calculators on a phone ==========================================
     Measured before touching anything: 342px wide, 1435px tall, seven labels
     at 10px, and a result panel sitting inside a card inside a band — three
     surfaces deep before the reader gets to a number.

     The card gives back the padding it does not need at this width, the type
     stops being 10px, and the panel and the button run the full width they
     are given. Nothing is removed; it is the same calculator with the boxes
     stopped from nesting. */

  .calc-card {
    padding: 16px;
    border-radius: var(--r-md);
  }

  /* 10px uppercase is a label on a monitor and a smudge on a phone. These are
     the words that say what each number is. */
  label,
  .calc-result .lbl,
  .calc-break div,
  .calc-verdict {
    font-size: 12px;
  }
  .calc-break div { font-size: 13px; }

  /* The panel takes the card's inner width rather than sitting inset inside
     it, and its own padding comes down to match. */
  .calc-result {
    padding: 18px 16px;
    border-radius: var(--r-sm);
  }
  .calc-verdict {
    padding: 12px 14px;
    border-radius: var(--r-sm);
  }

  /* A form of seven fields is long enough without 18px between every one. */
  .calc-fields { gap: 13px; }
  .calc-fields .form-note { margin-top: -4px; }

  /* The action under the panel matches every other button on a phone: full
     width, so it is a target rather than a link that happens to have a border. */
  .calc-card .btn,
  .calc-out .btn {
    width: 100%;
    justify-content: center;
  }

  /* The figure is the point of the screen. It was competing with its own
     padding for room. */
  .calc-result .big { font-size: clamp(40px, 11vw, 52px); }
  .calc-break { margin-top: 18px; padding-top: 14px; }
}

@media (max-width: 760px) {
  /* The last two the sweep found. The load board's stat labels — "Sources",
     "Loads", "Refreshed" — are the words that say what the figures beside them
     mean, and at 10px they were decoration. The privacy link under the setup
     form is a link somebody has to be able to hit. */
  .lb .k,
  .lb-stat .k,
  .lb-stats .k,
  .lb-meta .k,
  .lb-sorted .k,
  .board .k {
    font-size: 11.5px;
  }

  .form-note a,
  .consent a,
  .form-card a:not(.btn) {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
  }
}
