  /* ============ TOKENS ============ */
  :root{
    --ink:#14140f;
    --ink-soft:#1d2019;
    --forest:#004e38;
    --forest-deep:#00301f;
    --sage:#bfd3cd;
    --sage-tint:#eef3f0;
    --warm-gray:#5f5b5b;
    --charcoal:#343434;
    --paper:#fafaf8;
    --white:#ffffff;
    --signal:#e2a64b;
    --signal-soft:#f2cf98;
    --error:#c0392b;

    --font-display:'Manrope', sans-serif;
    --font-body:'Instrument Sans', sans-serif;
    --font-mono:'JetBrains Mono', monospace;

    --container:1240px;
    --gutter:clamp(20px, 5vw, 64px);
    --radius:6px;

    --ease:cubic-bezier(.22,.7,.2,1);
  }

  *,*::before,*::after{box-sizing:border-box;}
  html{overflow-anchor:none;}
  @media (prefers-reduced-motion: reduce){
    html{scroll-behavior:auto;}
    *,*::before,*::after{animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; scroll-behavior:auto !important;}
  }
  body{
    margin:0;
    font-family:var(--font-body);
    color:var(--charcoal);
    background:var(--paper);
    -webkit-font-smoothing:antialiased;
    line-height:1.55;
  }
  img,video{max-width:100%; display:block;}
  a{color:inherit; text-decoration:none;}
  button{font:inherit; color:inherit; background:none; border:0; cursor:pointer;}
  h1,h2,h3,h4{font-family:var(--font-display); margin:0; line-height:1.08; letter-spacing:-.01em; color:var(--ink); text-wrap:balance;}
  p{margin:0;}
  ul{margin:0; padding:0; list-style:none;}
  :focus-visible{outline:2px solid var(--signal); outline-offset:3px;}

  .wrap{max-width:var(--container); margin:0 auto; padding-left:var(--gutter); padding-right:var(--gutter);}
  .eyebrow{
    font-family:var(--font-mono); font-size:.72rem; letter-spacing:.14em; text-transform:uppercase;
    color:var(--forest); display:inline-flex; align-items:center; gap:.55em; font-weight:500;
  }
  .eyebrow.on-dark{color:var(--sage);}
  .signal-dot{
    position:relative; width:7px; height:7px; border-radius:50%; background:var(--signal); flex:none;
  }
  .signal-dot::before{
    content:""; position:absolute; inset:0; border-radius:50%; background:var(--signal);
    animation:pulse 2.2s var(--ease) infinite;
  }
  @keyframes pulse{
    0%{opacity:.55; transform:scale(1);}
    70%{opacity:0; transform:scale(2.6);}
    100%{opacity:0; transform:scale(2.6);}
  }

  .btn{
    display:inline-flex; align-items:center; gap:.6em;
    padding:.9em 1.5em; border-radius:var(--radius);
    font-family:var(--font-display); font-weight:700; font-size:.95rem;
    letter-spacing:.01em;
    transition:transform .35s var(--ease), background .25s, color .25s, border-color .25s;
    white-space:nowrap;
  }
  .btn-primary{background:var(--forest); color:var(--white);}
  .btn-primary:hover{background:var(--forest-deep); transform:translateY(-2px);}
  .btn-ghost{border:1px solid rgba(255,255,255,.55); color:var(--white);}
  .btn-ghost:hover{background:rgba(255,255,255,.12); transform:translateY(-2px);}
  .btn-outline{border:1px solid var(--forest); color:var(--forest);}
  .btn-outline:hover{background:var(--forest); color:var(--white); transform:translateY(-2px);}

  /* route-line divider — the recurring signature motif */
  .route-line{
    display:flex; align-items:center; gap:8px; height:1px; margin:0 auto;
    background-image:linear-gradient(to right, rgba(95,91,91,.35) 0 6px, transparent 6px 14px);
    background-size:14px 1px; background-repeat:repeat-x;
  }
  .route-line::before,.route-line::after{content:"";width:5px;height:5px;border-radius:50%;background:var(--warm-gray);opacity:.5;flex:none;}
  .section-divider{max-width:var(--container); margin:0 auto; padding:0 var(--gutter);}

  /* ============ HEADER / MENU ============ */
  /* mix-blend-mode:difference lives on the fixed header itself, not on the
     logo/button individually (that was the earlier bug: two sibling
     elements each isolating their own blend group tends to fail to reach
     the real page backdrop reliably). One blend on the shared fixed
     container is the standard version of this technique — see
     https://lodisna.com/en/ — and reliably inverts white content against
     whatever's actually scrolling underneath, light or dark, always. */
  header.site-header{
    position:fixed; inset:0 0 auto 0; z-index:60;
    display:flex; align-items:center; gap:14px;
    padding:18px var(--gutter) 18px 20px;
    mix-blend-mode:difference;
  }
  /* the overlay (z-index:70) is opaque and full-screen, so the header needs
     to jump above it while open or its own toggle button — the only way to
     close the menu — becomes invisible behind it */
  body.menu-open header.site-header{z-index:75;}
  /* the only screen the quote button doesn't appear on, per request */
  body.menu-open .quote-btn{display:none;}
  .brand{display:flex; align-items:center;}
  /* invert(.776) on solid black (from brightness(0)) lands at rgb(198,198,198)
     instead of pure white — chosen so difference(198, --paper #fafaf8) comes
     out to exactly --charcoal (#343434) rather than near-black */
  .brand img{height:56.25px; width:auto; filter:brightness(0) invert(.776);}

  /* fill uses the exact same source (#c6c6c6, 198) as the logo so it reads
     as visually congruent with it at every backdrop — always the same
     charcoal on light pages, the same light gray over the dark hero.
     Text/icon uses a totally different source (pure black, 0) so it doesn't
     resolve to the same diffed color as its own background: two sources
     that are 198 apart in value stay well-separated after each is
     independently difference-blended, except in a narrow, rarely-hit
     midtone band (~backdrop luminance 99) where they'd converge. */
  /* 800 (ExtraBold) is the heaviest weight JetBrains Mono actually ships —
     there's no 900/Black cut to request, the browser would just silently
     fall back to this same 800 face. text-stroke adds real extra heft on
     top of it instead of asking for a weight that doesn't exist. */
  .menu-btn{
    display:flex; align-items:center; gap:.6em;
    padding:.55em .9em; border-radius:999px; border:1px solid #c6c6c6;
    background:#c6c6c6; color:#000; font-family:var(--font-mono); font-size:.72rem; letter-spacing:.12em; text-transform:uppercase;
    font-weight:800; -webkit-text-stroke:.4px currentColor; transition:opacity .25s;
    /* tap straight through: no double-tap-zoom wait, so a single tap opens it */
    touch-action:manipulation; -webkit-tap-highlight-color:transparent;
  }
  /* hover only where there is a real pointer. On a phone the first tap on
     something with a :hover rule can be spent "hovering" it, which reads as
     having to press Menu twice. */
  @media (hover:hover){
    .menu-btn:hover{opacity:.7;}
  }
  .quote-btn{margin-left:auto;}
  .quote-label-short{display:none;}
  @media (max-width:480px){
    .quote-label-full{display:none;}
    .quote-label-short{display:inline;}
  }
  .menu-btn .bars{display:flex; flex-direction:column; gap:4px; width:16px;}
  .menu-btn .bars span{height:2px; background:currentColor; display:block; transition:transform .3s var(--ease), opacity .3s;}
  body.menu-open .menu-btn .bars span:nth-child(1){transform:translateY(6px) rotate(45deg);}
  body.menu-open .menu-btn .bars span:nth-child(2){opacity:0;}
  body.menu-open .menu-btn .bars span:nth-child(3){transform:translateY(-6px) rotate(-45deg);}

  .menu-overlay{
    position:fixed; inset:0; z-index:70; background:var(--ink);
    clip-path:inset(0 100% 0 0);
    transition:clip-path .7s var(--ease);
    pointer-events:none;
    overflow:hidden;
  }
  body.menu-open .menu-overlay{clip-path:inset(0 0 0 0); pointer-events:auto;}
  /* the scroll container spans the FULL overlay width (text column + photo),
     not just the text column, so its native scrollbar renders at the true
     right edge of the screen — the standard position — instead of appearing
     to the left of the photo the way it did when only .menu-overlay-inner
     (the text column alone) had its own overflow-y:auto. */
  .menu-overlay-scroll{
    display:flex; align-items:stretch; width:100%; height:100%;
    overflow-y:auto; overflow-x:auto;
    scrollbar-width:thin; scrollbar-color:rgba(191,211,205,.35) transparent;
  }
  .menu-overlay-scroll::-webkit-scrollbar{width:6px;}
  .menu-overlay-scroll::-webkit-scrollbar-track{background:transparent;}
  .menu-overlay-scroll::-webkit-scrollbar-thumb{background:rgba(191,211,205,.35); border-radius:999px;}
  .menu-overlay-scroll::-webkit-scrollbar-thumb:hover{background:rgba(191,211,205,.55);}

  /* Single pre-composed graphic (van photo + "MOODY GOOSE" wordmark baked
     in together on a black background by the user) replacing what used to
     be a live CSS composition of a cropped photo plus two independently
     absolutely-positioned, rotated text elements hand-tuned against
     percentages. That system could never be visually verified in this
     tool (local images don't render here), so every adjustment to it was
     a blind guess against the numbers, not the actual result — this is
     simpler and, more importantly, WYSIWYG for whoever's editing it: open
     the file in an image editor, see exactly what ships.
     height:calc(100% - 80px) (not a fixed px, and not vh) fills whatever
     vertical space the row actually has, 80px short of the top so it
     doesn't jam against the header — this is different from the earlier
     "must be a fixed constant" rule for the old wordmark+photo system, and
     deliberately so: that rule existed because two SEPARATELY-tuned
     elements (a photo box and independently letter-spaced rotated text)
     would drift out of alignment with each other as their shared vh-driven
     size changed. That failure mode doesn't exist here — it's one image
     with its composition baked in, so it can safely scale as a single
     unit. width:auto + aspect-ratio (from the asset's own natural
     3145×4858 pixels) derives the width from that height, so nothing gets
     stretched — on a short screen the whole graphic is both shorter AND
     narrower, which also eases the horizontal-fit problem instead of
     worsening it. flex:none + align-self:flex-start (not position:
     absolute) keeps it a normal row sibling, so it scrolls together with
     the nav columns instead of staying pinned while content scrolls under
     it, and sits flush against the row's true right edge whenever the row
     isn't wider than the viewport. */
  .menu-visual{
    position:relative; flex:none; align-self:flex-start; margin-top:80px;
    width:auto; height:calc(100% - 80px); aspect-ratio:2014 / 3458;
    opacity:0; transform:translateX(28px);
    transition:opacity .6s var(--ease) .2s, transform .6s var(--ease) .2s;
    pointer-events:none;
  }
  body.menu-open .menu-visual{opacity:1; transform:translateX(0);}
  .menu-visual img{width:100%; height:100%; object-fit:cover; display:block;}

  /* min-width instead of the old "shrink to fit, wrap if it must" approach
     — below this width the nav columns would start wrapping and eventually
     collapsing, which reads as the composition falling apart rather than
     responding gracefully. Better to hold the composition rigid and let
     .menu-overlay-scroll's horizontal scrollbar (now overflow-x:auto, was
     hidden) pick up the slack, same philosophy as the vertical scrollbar
     already in place for short screens. Reset to nothing under the 900px
     mobile breakpoint, where the stacked single-column layout doesn't need
     it and shouldn't force phones into horizontal scrolling. flex:1 (no
     longer paired with padding-right reserving the photo's width) because
     the photo is a real flex sibling taking its own space again now.
     900px, not the earlier 1100px — that number was a rough guess, not a
     measured one, and came in noticeably above what the content actually
     needs (canvas-measured against the real fonts/weights: the longest
     column entry is "Refrigerated Transport" at 171px, three columns plus
     two 64px gaps plus the left gutter comes to roughly 790-850px
     depending on gutter width), so it was forcing horizontal scroll on
     screens that had enough room. 900px keeps a real margin above the
     measured minimum without adding back an arbitrary amount of slack. */
  .menu-overlay-inner{
    position:relative; z-index:1; flex:1;
    display:flex; flex-direction:column;
    padding:170px var(--gutter) 40px var(--gutter);
    min-width:900px;
  }
  /* flex:1 (not margin:auto centering) — .menu-nav-col and .menu-nav both
     stretch to fill every bit of available vertical space between the
     padding edges. That's what makes align-self:end on .menu-meta below
     actually mean "bottom of the screen" rather than "bottom of however
     tall the content happens to be" — the grid row it's aligning within
     spans the full height, not just a content-hugging one. */
  .menu-nav-col{flex:1; display:flex; width:100%;}
  /* Three equal columns, not two — .menu-meta is now a genuine grid column
     in its own right, not a separately-positioned element sharing space
     with the nav text. That's the actual fix for the overlap this replaces:
     two things in separate grid tracks can never collide regardless of how
     tall either one's content gets, where a shared-space + reserved-padding
     approach could always be exceeded by content that's taller than
     guessed. align-self differs per column on purpose: the meta column
     anchors to the bottom (the "fixed to the corner" requirement) while the
     nav groups stay vertically centered (unchanged from before). */
  .menu-nav{
    display:grid; grid-template-columns:repeat(3, 1fr);
    column-gap:64px; width:100%; flex:1;
  }
  .menu-nav-group{align-self:start; display:flex; flex-direction:column; gap:2px;}
  /* Fixed px size, not clamp/vw — matching the earlier request that
     neither this nor the item links below should scale with viewport
     width. Size (.76rem, uppercase) matches the same title:item font-size
     ratio the footer columns use (.7rem : .92rem) — a small mono label
     over larger body-font items, not the other way around. */
  .menu-nav-title{
    font-family:var(--font-mono); font-weight:800; color:var(--sage);
    font-size:.76rem; text-transform:uppercase; margin-bottom:18px;
    opacity:0; transform:translateY(16px);
    transition:opacity .5s var(--ease), transform .5s var(--ease);
  }
  body.menu-open .menu-nav-title{opacity:1; transform:translateY(0);}
  /* scoped to .menu-nav-group (not .menu-nav) — .menu-meta's email link and
     the Get a Quote button are also descendants of .menu-nav now (it's the
     shared grid), and a broader ".menu-nav a" selector was matching them
     too, stomping their intended button/link styling with this dim nav-item
     look (that's what "the button text is messed up" was: it had this
     rule's font-weight:500 and rgba(...,.78) color instead of its own). */
  .menu-nav-group a{
    font-family:var(--font-display); font-weight:500; color:var(--white);
    font-size:1rem; padding:.12em 0; position:relative;
    opacity:0; transform:translateY(16px);
    transition:opacity .5s var(--ease), transform .5s var(--ease), color .25s;
  }
  body.menu-open .menu-nav-group a{opacity:1; transform:translateY(0);}
  .menu-nav-group a:hover{color:var(--signal);}
  .menu-meta{
    align-self:end;
    display:flex; flex-direction:column; align-items:flex-start; gap:14px; max-width:340px;
    opacity:0; transition:opacity .6s var(--ease) .3s;
  }
  body.menu-open .menu-meta{opacity:1;}
  .menu-meta .eyebrow{color:var(--sage);}
  .menu-meta a.email{font-family:var(--font-mono); color:var(--white); font-size:1rem;}
  .menu-meta a.email:hover{color:var(--signal);}
  .menu-cta{margin-top:6px;}

  @media (max-width:900px){
    .menu-visual{display:none;}
    .menu-overlay-inner{padding:96px var(--gutter) 48px; min-width:0;}
    /* align-self:end/center on .menu-meta/.menu-nav-group are for the
       desktop GRID's vertical axis — in this flex-COLUMN layout the same
       properties control the horizontal axis instead, which would shove
       both out of left-alignment, so both get reset here. */
    .menu-nav{
      display:flex; flex-direction:column; gap:28px;
    }
    .menu-nav-group{align-self:flex-start;}
    .menu-meta{order:3; align-self:flex-start;}
    .menu-nav-title{font-size:1.06rem;}
    .menu-nav-group a{font-size:1.4rem;}
  }

  /* ============ PAGE VISIBILITY / ROUTER ============ */
  .page{display:none;}
  .page.is-active{display:block;}
  #page-home.is-active{display:block;}

  /* ============ HERO ============ */
  .hero{
    position:relative; min-height:100vh; display:flex; align-items:flex-end;
    overflow:hidden; background:var(--ink);
  }
  /* The video stays hidden over the dark hero until it is actually playing
     (script.js adds .is-playing), then fades in. It used to fade in from the
     start with a still photo as its poster, so the photo showed first and
     then jumped to the video's first frame. */
  .hero video{
    position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
    opacity:0; transform:scale(1.05);
    transition:opacity 1.6s var(--ease), transform 1.6s var(--ease);
  }
  .hero video.is-playing{opacity:1; transform:scale(1);}
  .hero::after{
    content:""; position:absolute; inset:0;
    background:linear-gradient(180deg, rgba(20,20,15,.35) 0%, rgba(20,20,15,.15) 35%, rgba(20,20,15,.85) 100%);
  }
  .hero-content{position:relative; z-index:2; width:100%; padding:0 var(--gutter) 84px;}
  .hero-content .eyebrow{margin-bottom:22px; opacity:0; animation:fadeUp .8s var(--ease) .3s forwards;}
  .hero h1{
    color:var(--white); font-size:clamp(2.6rem, 7vw, 5.4rem); font-weight:800; max-width:16ch;
    opacity:0; animation:fadeUp .8s var(--ease) .45s forwards;
  }
  .hero .sub{
    color:var(--sage); font-size:clamp(1.05rem, 2vw, 1.3rem); max-width:56ch; margin-top:20px; font-family:var(--font-body);
    opacity:0; animation:fadeUp .8s var(--ease) .6s forwards;
  }
  .hero-actions{display:flex; gap:16px; margin-top:36px; flex-wrap:wrap; opacity:0; animation:fadeUp .8s var(--ease) .75s forwards;}
  @keyframes fadeUp{from{opacity:0; transform:translateY(18px);} to{opacity:1; transform:translateY(0);}}
  /* fixed (not absolute) so it stays on screen through the hero and the
     van-scrolly zoom demo right after it, not just while the hero itself is
     in view. Once vanTick reports the demo fully scrolled past, JS switches
     it to is-released (position:absolute, top set inline in px) — same
     trick as the old hero-only behavior, just anchored to the bottom of the
     van-scrolly section instead of the bottom of the hero, so it rides along
     with the page and scrolls off naturally instead of just vanishing. */
  .scroll-cue{
    position:fixed; right:var(--gutter); bottom:32px; z-index:55; color:var(--sage);
    display:flex; flex-direction:column; align-items:center; gap:8px; font-family:var(--font-mono); font-size:.68rem;
    letter-spacing:.14em; text-transform:uppercase;
  }
  .scroll-cue.is-released{position:absolute; bottom:auto;}
  .scroll-cue .line{width:1px; height:34px; background:linear-gradient(var(--sage), transparent); animation:scrollLine 1.8s var(--ease) infinite;}
  @keyframes scrollLine{0%{transform:scaleY(0); transform-origin:top;} 50%{transform:scaleY(1); transform-origin:top;} 51%{transform-origin:bottom;} 100%{transform:scaleY(0); transform-origin:bottom;}}

  /* ============ SECTION LAYOUT ============ */
  section.block{padding:118px 0;}
  .block-head{max-width:640px; margin-bottom:64px;}
  .block-head h2{font-size:clamp(1.9rem, 3.6vw, 2.9rem); margin-top:16px;}
  .block-head p.lede{margin-top:18px; color:var(--warm-gray); font-size:1.05rem; max-width:56ch;}
  /* wider variant for block-heads whose copy reads too cramped at the
     640px default (e.g. the case study, which has a longer lede) —
     scoped to just this modifier so the shared default is unaffected */
  .block-head-wide{max-width:920px;}
  .block-head-wide p.lede{max-width:none;}
  .on-sage{background:var(--sage-tint);}
  .on-ink{background:var(--ink); color:var(--sage);}
  .on-ink h2, .on-ink h3, .on-ink h4{color:var(--white);}
  .on-ink .block-head p.lede{color:rgba(191,211,205,.85);}

  /* ============ LOGO MARQUEE ============ */
  /* A slim banner, not a full .block section — its own lighter padding so
     it reads as a strip between the hero and the real content below. */
  .logo-band{padding:48px 0 56px; overflow:hidden; text-align:center;}
  .logo-band .eyebrow{justify-content:center; margin-bottom:28px;}
  /* The edge fade is two gradient overlays, not a CSS mask: a mask over a
     continuously animating strip is a known cause of mobile browsers
     dropping the strip's layer, which showed as a blank bar or a single
     logo moving. */
  .logo-marquee{position:relative; width:100%; overflow:hidden;}
  .logo-marquee::before, .logo-marquee::after{
    content:""; position:absolute; top:0; bottom:0; width:8%; z-index:1; pointer-events:none;
  }
  .logo-marquee::before{left:0; background:linear-gradient(90deg, var(--sage-tint), rgba(238,243,240,0));}
  .logo-marquee::after{right:0; background:linear-gradient(270deg, var(--sage-tint), rgba(238,243,240,0));}
  .logo-track{
    display:flex; width:max-content; animation:logo-scroll 34s linear infinite;
    will-change:transform; backface-visibility:hidden; -webkit-backface-visibility:hidden;
  }
  .logo-marquee:hover .logo-track{animation-play-state:paused;}
  .logo-set{display:flex; align-items:center; flex:none;}
  .logo-set img{
    height:46px; width:auto; flex:none; margin:0 44px;
    filter:grayscale(1); opacity:.55; transition:opacity .25s, filter .25s;
  }
  .logo-set img:hover{filter:grayscale(0); opacity:1;}
  /* optical balance: the wordmark-shaped logos read bigger than the compact
     ones at the same height, so each is nudged individually */
  .logo-set img[src*="rove"]{height:34px;}
  .logo-set img[src*="elevate"]{height:58px;}
  .logo-set img[src*="trip"]{height:56px;}
  @keyframes logo-scroll{
    from{transform:translate3d(0,0,0);}
    to{transform:translate3d(-50%,0,0);}
  }

  .reveal{opacity:0; transform:translateY(26px); transition:opacity .7s var(--ease), transform .7s var(--ease);}
  .reveal.is-visible{opacity:1; transform:translateY(0);}

  /* ============ SERVICES / TEASER GRID ============ */
  .card-grid{display:grid; grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)); gap:1px; background:rgba(95,91,91,.22); border:1px solid rgba(95,91,91,.22); border-radius:var(--radius); overflow:hidden;}
  .card{background:var(--paper); padding:36px 32px; display:flex; flex-direction:column; gap:14px; transition:background .3s;}
  .on-sage .card{background:var(--sage-tint);}
  .card:hover{background:var(--white);}
  /* "What we do" and "Who we are" tiles are one size: every row inside a grid
     matches its tallest tile, and on wider screens both grids share the same
     tile height so the two sections read as a set. */
  #services .card-grid, #who-moves-it .card-grid{grid-auto-rows:1fr;}
  #services .card, #who-moves-it .card{min-height:300px;}
  @media (min-width:700px){
    #services .card, #who-moves-it .card{min-height:345px;}
  }
  @media (min-width:1280px){
    #services .card, #who-moves-it .card{min-height:300px;}
  }
  .card .idx{font-family:var(--font-mono); color:var(--signal); font-size:.78rem; letter-spacing:.1em;}
  .card h3{font-size:1.22rem;}
  .card p{color:var(--warm-gray); font-size:.96rem; flex:1;}
  .card .go{font-family:var(--font-mono); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--forest); display:inline-flex; align-items:center; gap:.5em; margin-top:4px;}
  .card .go::after{content:"→"; transition:transform .25s var(--ease);}
  .card:hover .go::after{transform:translateX(4px);}

  /* ============ QUOTE CTA BAND ============ */
  .quote-band{background:var(--forest); color:var(--white); padding:96px 0; text-align:center;}
  .quote-band h2{color:var(--white); font-size:clamp(1.8rem, 4vw, 2.7rem); max-width:20ch; margin:0 auto;}
  .quote-band p{color:var(--sage); max-width:52ch; margin:18px auto 34px; font-size:1.05rem;}

  /* ============ FOOTER ============ */
  footer{background:var(--ink); color:rgba(191,211,205,.75); padding:64px 0 32px;}
  .footer-top{display:flex; flex-wrap:wrap; justify-content:space-between; gap:40px; padding-bottom:40px;}
  .footer-brand{display:flex; flex-direction:column; align-items:flex-start; gap:6px;}
  .footer-brand .logo-word{height:34px; width:auto; filter:brightness(0) invert(1);}
  .footer-brand .logo-swatches{width:31px; height:auto; display:block;}
  .footer-tag{margin-top:10px; font-size:.9rem; max-width:32ch;}
  .footer-cols{display:flex; gap:64px; flex-wrap:wrap;}
  .footer-col h4{font-family:var(--font-mono); font-size:.7rem; letter-spacing:.12em; text-transform:uppercase; color:var(--sage); margin-bottom:14px; font-weight:500;}
  .footer-col a, .footer-col span{display:block; font-size:.92rem; padding:5px 0; color:rgba(250,250,248,.82);}
  .footer-col a:hover{color:var(--signal);}
  /* .footer-col a's higher specificity (class+element) would otherwise
     override .btn's own display/padding/color and flatten it into a plain
     text link — this restores just what's contested, everything else
     (font-family, weight, border-radius, hover transition) still comes
     from .btn/.btn-primary untouched. */
  .footer-col .btn{display:inline-flex; width:fit-content; padding:.9em 1.5em; font-size:.95rem; color:var(--white); margin-top:6px;}
  /* same override reason as .btn above — .social-link needs to beat
     .footer-col a's block/padding to read as a small icon button, not a
     text line */
  .footer-col .social-link{display:flex; align-items:center; justify-content:center; width:46px; height:46px; padding:0; margin-top:14px; border:1px solid rgba(250,250,248,.25); border-radius:50%; color:rgba(250,250,248,.82); transition:color .2s, border-color .2s;}
  .footer-col .social-link svg{width:22px; height:22px;}
  .footer-col .social-link:hover{color:var(--signal); border-color:var(--signal);}
  .footer-bottom{border-top:1px solid rgba(191,211,205,.15); padding-top:24px; display:flex; justify-content:space-between; flex-wrap:wrap; gap:12px; font-size:.78rem; font-family:var(--font-mono); color:rgba(191,211,205,.5);}

  /* ============ SUBPAGE HERO ============ */
  .page-hero{padding:172px 0 72px; background:var(--ink); color:var(--sage);}
  .page-hero h1{color:var(--white); font-size:clamp(2.1rem, 5vw, 3.6rem); margin-top:18px; max-width:18ch;}
  .page-hero .sub{margin-top:18px; color:rgba(191,211,205,.85); font-size:1.05rem; max-width:60ch;}
  /* image variant (Our Fleet) — a dark flat overlay (not the multi-stop
     scroll-tuned gradient the home hero video uses, which is calibrated for
     a full-viewport-height element you scroll through) is enough here
     since the whole subpage-hero box is short and always fully visible at
     once, no scroll-position-dependent legibility to solve for. */
  .page-hero.has-image{position:relative; overflow:hidden;}
  .page-hero-bg{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0;}
  .page-hero.has-image::after{content:""; position:absolute; inset:0; background:rgba(20,20,15,.6); z-index:1;}
  .page-hero.has-image .wrap{position:relative; z-index:2;}

  /* value / scope lists */
  .stat-line{display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-size:.78rem; letter-spacing:.08em; color:var(--forest); text-transform:uppercase; padding:10px 16px; border:1px solid rgba(0,78,56,.25); border-radius:999px; width:fit-content; background:var(--sage-tint);}
  .on-ink .stat-line{color:var(--signal-soft); border-color:rgba(226,166,75,.35); background:rgba(226,166,75,.08);}

  /* big proof numbers — divided bar, not pills, so the figures read as
     headline-weight stats rather than small labels. Shared by the
     page-hero (dark) and any plain-background block (light default
     below, matching how .stat-line already has a light default plus an
     .on-ink override) so both places get the same treatment instead of
     two near-duplicate components. */
  .stat-bar{display:flex; flex-wrap:wrap; gap:0; margin-top:40px;}
  .stat-bar-item{display:flex; flex-direction:column; gap:8px; padding:0 36px; border-left:1px solid rgba(53,53,53,.15);}
  .stat-bar-item:first-child{padding-left:0; border-left:none;}
  .stat-bar-item .num{font-family:var(--font-display); font-weight:800; font-size:clamp(2rem, 3.4vw, 2.75rem); color:var(--ink); line-height:1;}
  .stat-bar-item .label{font-family:var(--font-mono); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--warm-gray);}
  .on-ink .stat-bar-item{border-left-color:rgba(191,211,205,.25);}
  .on-ink .stat-bar-item .num{color:var(--white);}
  .on-ink .stat-bar-item .label{color:var(--signal-soft);}
  @media (max-width:700px){
    .stat-bar{flex-direction:column; gap:20px;}
    .stat-bar-item{padding:0; border-left:none;}
  }

  /* two vehicle-type icons (Our Fleet) — simple line-art silhouettes,
     built as inline SVG rather than raster images so they render crisp at
     any size and pick up the same neutral ink tone as the surrounding
     copy, no separate asset to manage */
  .vehicle-row{display:flex; flex-wrap:wrap; justify-content:center; align-items:flex-end; gap:150px; margin:40px 0;}
  .vehicle-item{display:flex; flex-direction:column; align-items:center; flex:0 1 240px;}
  .vehicle-item svg, .vehicle-item img{width:100%; max-width:240px; height:auto;}

  /* Four short capability chips under the vehicle icons — reuses
     .stat-line's existing pill conventions (999px radius, sage-tint fill,
     forest-tinted border, mono/uppercase/tracked type) rather than
     inventing a new chip style, plus a small icon per tile in --signal,
     the same accent color already used for numbered labels (.card .idx)
     and the pulsing nav dot, so both established accent colors — signal
     orange for the small marks, forest green for anything load-bearing —
     stay in their existing roles instead of competing. */
  .capability-row{
    display:grid; grid-template-columns:repeat(4, 1fr); gap:14px;
    margin:48px 0;
  }
  .capability-tile{
    display:flex; align-items:center; gap:10px;
    background:var(--sage-tint); border:1px solid rgba(0,78,56,.18);
    border-radius:999px; padding:14px 16px;
  }
  .capability-tile svg{flex:none; width:22px; height:22px; color:var(--signal);}
  /* flex:1 + text-align:center centers the label in whatever space is left
     after the icon, rather than it just trailing the icon on the left;
     nowrap + the smaller .66rem/tighter tracking keeps every label
     (including "Insured for Controlled Cargo") on one line within an
     equal 1fr column at the container's normal widths. */
  .capability-tile span{
    flex:1; text-align:center;
    font-family:var(--font-mono); font-weight:800; font-size:.66rem;
    letter-spacing:.04em; text-transform:uppercase; color:var(--forest);
    white-space:nowrap;
  }
  /* one column on phones: two columns of single-line labels (longest is
     "Insured for Controlled Cargo") don't fit a phone's width */
  @media (max-width:700px){
    .capability-row{grid-template-columns:1fr;}
  }

  .feature-list{display:flex; flex-direction:column; gap:0;}
  .feature-list > li{display:grid; grid-template-columns:200px 1fr; gap:32px; padding:28px 0; border-top:1px solid rgba(95,91,91,.2);}
  .feature-list > li:last-child{border-bottom:1px solid rgba(95,91,91,.2);}
  .feature-list h3{font-size:1.1rem;}
  .feature-list p{color:var(--warm-gray); font-size:.98rem;}
  .step-list{display:flex; flex-direction:column; gap:8px; list-style:none; margin:0; padding:0;}
  .step-list li{display:flex; align-items:baseline; gap:12px; color:var(--warm-gray); font-size:.98rem;}
  .step-list .step-num{flex:none; font-family:var(--font-mono); color:var(--signal); font-size:.85rem; letter-spacing:.05em;}
  .on-ink .feature-list > li{border-color:rgba(191,211,205,.18);}
  .on-ink .feature-list p{color:rgba(191,211,205,.8);}
  @media (max-width:700px){
    .feature-list > li{grid-template-columns:1fr; gap:8px;}
    .step-list li{gap:8px;}
  }

  .two-col{display:grid; grid-template-columns:1fr 1fr; gap:64px; align-items:start;}
  @media (max-width:820px){.two-col{grid-template-columns:1fr; gap:32px;}}

  .tag-row{display:flex; gap:10px; flex-wrap:wrap; margin-top:22px;}

  /* ============ VAN SECURITY DIAGRAM (home, pinned scrollytelling) ============ */
  .van-security{--van-bg:#152032; background:var(--van-bg); padding:118px 0 0; overflow:clip;}
  .van-security .block-head{color:var(--sage);}
  .van-security .block-head h2{color:var(--white);}
  .van-security .block-head p.lede{color:rgba(191,211,205,.85);}

  /* --van-step-vh is vh-per-timeline-weight-unit (see HOLD_W/TRANS_*_W in JS);
     multiplier below (45) is the current total timeline weight — keep the two in sync. */
  .van-scrolly{position:relative; height:calc(var(--van-step-vh, 24vh) * 45); margin-top:24px; background:var(--van-bg);}
  .van-sticky{
    position:sticky; top:0; height:100vh;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    padding:80px var(--gutter) 40px;
    background:var(--van-bg);
  }
  .van-stage-frame{
    position:relative; flex:1; min-height:0; width:100%; align-self:stretch; background:var(--van-bg);
    display:flex; align-items:center; justify-content:center;
  }
  /* Box width/height are set in pixels by JS (vanFitBox), not by CSS
     aspect-ratio + max-width/max-height together — that combination doesn't
     reliably resolve "contain" sizing across browsers/displays, sometimes
     shrinking the box far smaller than the frame actually allows. Computing
     it directly removes that ambiguity. .van-stage-frame's flex centering
     positions the sized box automatically. */
  .van-stage-box{ flex:none; }
  /* Zoom/pan is done via CSS transform on the svg, not by animating viewBox —
     animating viewBox forces re-rasterization of the embedded raster image
     on a fractional-DPI display, which is a confirmed, reproducible paint
     bug (not a timing artifact) on this project. viewBox stays fixed at
     "0 0 1900 1000" permanently. */
  .van-stage-box svg{width:100%; height:100%; display:block; transform-origin:0 0;}

  .van-caption-bar{align-self:stretch; width:100%; position:relative; height:148px; margin-top:22px;}
  .van-caption{
    position:absolute; inset:0; display:flex; flex-direction:column; justify-content:center;
    opacity:0; transform:translateY(14px); transition:opacity .55s var(--ease), transform .55s var(--ease);
    pointer-events:none;
  }
  .van-caption.is-active{opacity:1; transform:translateY(0); pointer-events:auto;}
  .van-caption .van-num{font-family:var(--font-mono); font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; color:var(--signal);}
  .van-caption h3{color:var(--white); font-size:clamp(1.2rem, 2.6vw, 1.65rem); margin-top:8px; max-width:560px;}
  .van-caption p{color:rgba(191,211,205,.85); margin-top:8px; font-size:.98rem; max-width:64ch;}

  .van-track{position:absolute; inset:0 auto auto 0; width:100%; height:100%; pointer-events:none;}
  .van-marker{position:absolute; left:0; width:1px; height:var(--van-step-vh, 85vh); pointer-events:none;}
  .van-marker:nth-child(1){top:calc(var(--van-step-vh, 85vh) * 0);}
  .van-marker:nth-child(2){top:calc(var(--van-step-vh, 85vh) * 1);}
  .van-marker:nth-child(3){top:calc(var(--van-step-vh, 85vh) * 2);}
  .van-marker:nth-child(4){top:calc(var(--van-step-vh, 85vh) * 3);}
  .van-marker:nth-child(5){top:calc(var(--van-step-vh, 85vh) * 4);}
  .van-marker:nth-child(6){top:calc(var(--van-step-vh, 85vh) * 5);}

  /* feature dim/focus states, ported from the diagram prototype */
  .feature{transition:opacity .5s ease;}
  .feature.fx-dim{opacity:.12;}
  .feature.fx-focus{opacity:1;}
  .dot-marker{transform-box:fill-box; transform-origin:center;}
  .dot-marker.fx-pulse{animation:dotPulse 1.4s ease-in-out infinite;}
  @keyframes dotPulse{0%,100%{transform:scale(1);} 50%{transform:scale(1.6);}}
  .radiate-ping{transform-box:fill-box; transform-origin:center; animation:radiatePing 2.4s ease-out infinite;}
  @keyframes radiatePing{0%{transform:scale(.25); opacity:0;} 18%{opacity:.9;} 100%{transform:scale(2.6); opacity:0;}}
  /* Cameras glow rather than grow: the dots used to scale up on focus, which on
     a camera shape reads as a bounce, so only the light around it pulses. */
  .cam-bloom{opacity:.5;}
  .cam-halo{opacity:.7;}
  .feature.fx-focus .cam-bloom{animation:camBloom 1.4s ease-in-out infinite;}
  .feature.fx-focus .cam-halo{animation:camHalo 1.4s ease-in-out infinite;}
  @keyframes camBloom{0%,100%{opacity:.3;} 50%{opacity:1;}}
  @keyframes camHalo{0%,100%{opacity:.45;} 50%{opacity:1;}}
  .dvr-pulse{animation:dvrPulse 2s ease-in-out infinite;}
  @keyframes dvrPulse{0%,100%{opacity:.18;} 50%{opacity:.6;}}
  .wifi-arc{transform-box:view-box; animation:arcRadiate 1.8s ease-out infinite;}
  @keyframes arcRadiate{0%{transform:scale(.55); opacity:0;} 20%{opacity:1;} 100%{transform:scale(1.25); opacity:0;}}

  @media (max-width:820px){
    .van-scrolly{--van-step-vh:19.7vh;}
    .van-sticky{padding:88px var(--gutter) 24px;}
    .van-caption-bar{height:170px;}
  }
  @media (prefers-reduced-motion: reduce){
    .dot-marker.fx-pulse, .radiate-ping, .wifi-arc, .dvr-pulse{animation:none;}
    .feature.fx-focus .cam-bloom, .feature.fx-focus .cam-halo{animation:none;}
  }

  /* ============ TEAM ============ */
  .team-grid{display:grid; grid-template-columns:repeat(auto-fit, minmax(240px,1fr)); gap:32px;}
  .team-card{background:var(--white); border:1px solid rgba(95,91,91,.18); border-radius:var(--radius); padding:32px; text-align:center;}
  .avatar{
    width:84px; height:84px; border-radius:50%; margin:0 auto 18px; display:flex; align-items:center; justify-content:center;
    background:var(--forest); color:var(--white); font-family:var(--font-display); font-weight:800; font-size:1.4rem;
  }
  /* headshots are 3:4 portraits (head and shoulders), not cropped faces in a
     circle, so people read as people instead of floating faces */
  .avatar-photo{width:100%; max-width:240px; aspect-ratio:3 / 4; margin:0 auto 22px; border-radius:var(--radius); overflow:hidden; background:var(--sage-tint);}
  .avatar-photo img{width:100%; height:100%; object-fit:cover; display:block;}
  .team-card h3{font-size:1.15rem;}
  .team-card .role{color:var(--warm-gray); font-size:.9rem; margin-top:4px;}
  .team-card .li-link{display:inline-block; margin-top:16px; font-family:var(--font-mono); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--forest); border-bottom:1px solid currentColor;}

  /* borrows .team-card's visual language (white, thin border, var(--radius),
     generous padding) rather than a new card style */
  .testimonial-grid{display:grid; grid-template-columns:repeat(auto-fit, minmax(300px,1fr)); gap:32px;}
  .testimonial-card{background:var(--white); border:1px solid rgba(95,91,91,.18); border-radius:var(--radius); padding:32px;}
  .testimonial-head{display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:18px;}
  .testimonial-logo{height:26px; width:auto; max-width:45%; object-fit:contain; object-position:left center;}
  .testimonial-card .stars{color:var(--signal); font-size:1.1rem; letter-spacing:.15em; flex:none;}
  .testimonial-card p{color:var(--warm-gray); font-size:1rem; line-height:1.6;}
  .testimonial-card p + p{margin-top:14px;}
  .testimonial-card .testimonial-lead{color:var(--ink); font-weight:700;}
  .testimonial-card .testimonial-name{display:block; margin-top:20px; font-family:var(--font-mono); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--forest);}

  /* ============ QUOTE FORM ============ */
  .form-grid{display:grid; grid-template-columns:1fr 1fr; gap:22px;}
  .form-grid.full, .field.full{grid-column:1/-1;}
  @media (max-width:700px){.form-grid{grid-template-columns:1fr;}}
  .field label{display:block; font-family:var(--font-mono); font-size:.7rem; letter-spacing:.1em; text-transform:uppercase; color:var(--warm-gray); margin-bottom:8px;}
  .field input[type=text], .field input[type=email], .field input[type=tel], .field input[type=number], .field textarea, .field select{
    width:100%; padding:.85em 1em; border:1px solid rgba(95,91,91,.35); border-radius:var(--radius);
    font-family:var(--font-body); font-size:.98rem; background:var(--white); color:var(--charcoal);
    transition:border-color .2s, box-shadow .2s;
  }
  .field input:focus, .field textarea:focus, .field select:focus{border-color:var(--forest); box-shadow:0 0 0 3px rgba(0,78,56,.12); outline:none;}
  .field input.invalid, .field textarea.invalid, .field select.invalid{border-color:var(--error); box-shadow:0 0 0 3px rgba(192,57,43,.12);}
  .field textarea{resize:vertical; min-height:120px;}
  .check-grid{display:flex; flex-wrap:wrap; gap:14px 26px;}
  .check-item{display:flex; align-items:center; gap:8px; font-size:.95rem;}
  .check-item input{width:16px; height:16px; accent-color:var(--forest);}
  fieldset{border:0; padding:0; margin:0;}
  legend{font-family:var(--font-mono); font-size:.7rem; letter-spacing:.1em; text-transform:uppercase; color:var(--warm-gray); margin-bottom:10px; padding:0;}
  .radio-row{display:flex; gap:20px;}
  .radio-row label{display:flex; align-items:center; gap:8px; font-size:.95rem;}
  .form-section-title{font-family:var(--font-mono); font-size:.75rem; letter-spacing:.12em; text-transform:uppercase; color:var(--forest); margin:36px 0 20px; padding-top:28px; border-top:1px solid rgba(95,91,91,.2);}
  .form-section-title:first-of-type{margin-top:0; padding-top:0; border-top:0;}
  .form-status{margin-top:22px; padding:16px 18px; border-radius:var(--radius); font-size:.95rem; display:none;}
  .form-status.show{display:block;}
  .form-status.ok{background:var(--sage-tint); color:var(--forest-deep); border:1px solid rgba(0,78,56,.25);}
  .form-status.err{background:#fbeaea; color:#8a2b2b; border:1px solid #e3b7b7;}

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