/* ==========================================================================
   Spence Logistics — landing page
   Implementation of design 2a ("dark bookends, white middle")
   ========================================================================== */

:root{
  /* ink */
  --night:      #0a0c09;
  --night-2:    #0e110c;
  --card:       #12160f;
  --footer:     #060705;

  /* green */
  --green:      #63c045;   /* on dark */
  --green-hi:   #7ad25c;   /* hover on dark */
  --green-soft: #8fdb76;   /* light green text on dark */
  --green-ink:  #3e9e2d;   /* on light */
  --green-deep: #2f7d22;

  /* light */
  --mist:       #f5f6f2;
  --paper:      #fdfdfb;
  --ink:        #141712;
  --ink-muted:  #5a6055;
  --ink-faint:  #8b9184;
  --line:       #e4e7df;
  --line-2:     #dfe2d9;
  --chip:       #eef0ea;

  /* on-dark text */
  --on-dark:        rgba(255,255,255,.92);
  --on-dark-muted:  rgba(255,255,255,.62);
  --on-dark-faint:  rgba(255,255,255,.42);
  --hairline:       rgba(255,255,255,.10);

  --display: 'Space Grotesk', system-ui, sans-serif;
  --body:    'Archivo', system-ui, -apple-system, sans-serif;
  --mono:    ui-monospace, SFMono-Regular, Menlo, monospace;

  --gutter: clamp(20px, 4.5vw, 48px);
  --radius: 14px;
  --nav-h:  72px;
  --quote-bar-h: 60px;   /* fixed band under the nav — see .quote-bar */
}

/* --- reset ---------------------------------------------------------------- */
*,*::before,*::after{ box-sizing:border-box }
html{ -webkit-text-size-adjust:100% }
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--body);
  font-size:16px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
}
img{ max-width:100%; height:auto; display:block }
h1,h2,h3,h4{ font-family:var(--display); font-weight:700; letter-spacing:-.01em; margin:0; line-height:1.12 }
p{ margin:0 }
ul,ol{ margin:0; padding:0; list-style:none }
a{ color:inherit; text-decoration:none }
button,input,select{ font:inherit; color:inherit }

:where(a,button,input,select,[tabindex]):focus-visible{
  outline:2px solid var(--green);
  outline-offset:3px;
  border-radius:4px;
}

html{ scroll-behavior:smooth; scroll-padding-top:calc(var(--nav-h) + 12px) }
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto }
  *,*::before,*::after{ animation-duration:.001ms !important; transition-duration:.001ms !important }
}

.container{ width:100%; max-width:1200px; margin-inline:auto; padding-inline:var(--gutter) }

.skip-link{
  position:absolute; left:-9999px; top:0; z-index:200;
  background:var(--green); color:var(--night); font-weight:700;
  padding:12px 20px; border-radius:0 0 10px 0;
}
.skip-link:focus{ left:0 }

/* --- brand ---------------------------------------------------------------- */
/* spence-mark.png / spence-logo.png are alpha-keyed builds of the supplied
   JPEG (see tools/key-black.js) so the logo sits on any background. */
.brand{ display:flex; align-items:center; gap:12px; flex-shrink:0 }
.brand__mark{
  --mark-w:64px;
  width:var(--mark-w);
  height:calc(var(--mark-w) / 1.604);        /* 300 × 187 asset */
  background:url("assets/spence-mark.png") no-repeat 50% 50%/contain;
  flex-shrink:0;
}
.brand__mark--sm{ --mark-w:52px }
.brand__word{
  font-family:var(--display); font-weight:700; font-size:17px;
  letter-spacing:.04em; color:#fff; white-space:nowrap;
}
.brand__word span{ color:var(--green) }

/* --- buttons -------------------------------------------------------------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  font-weight:700; font-size:16px; line-height:1;
  padding:16px 30px; border-radius:999px; border:1px solid transparent;
  white-space:nowrap; cursor:pointer;
  transition:background-color .18s ease, border-color .18s ease, color .18s ease, transform .18s ease;
}
.btn:active{ transform:translateY(1px) }
.btn--primary{ background:var(--green); color:var(--night) }
.btn--primary:hover{ background:var(--green-hi) }
.btn--ghost{ border-color:rgba(255,255,255,.25); color:#fff; font-weight:600 }
.btn--ghost:hover{ border-color:var(--green); color:var(--green-soft) }
.btn--sm{ padding:11px 22px; font-size:14px }
.btn--square{ border-radius:10px; padding:14px 24px; font-size:15px }
.btn--block{ width:100%; border-radius:12px; padding:16px }
.btn[disabled]{ opacity:.65; cursor:default }

/* --- eyebrows / tags ------------------------------------------------------ */
.eyebrow{
  font-size:13px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  color:var(--green-ink); margin-bottom:12px;
}
.eyebrow--light{ color:var(--green) }
.eyebrow-pill{
  display:inline-block; border:1px solid rgba(99,192,69,.45); color:var(--green-soft);
  font-size:13px; font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  padding:8px 16px; border-radius:999px; margin:0 0 28px;
}
.mono-tag{
  font-family:var(--mono); font-size:13px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--green-ink);
}
.note{ font-size:12.5px; font-style:italic; color:#a8ada1; margin-top:28px }

/* --- header --------------------------------------------------------------- */
.nav{
  position:sticky; top:0; z-index:100;
  background:var(--night);
  border-bottom:1px solid var(--hairline);
  transition:box-shadow .2s ease;
}
.nav.is-stuck{ box-shadow:0 10px 30px rgba(0,0,0,.45) }
.nav__inner{ display:flex; align-items:center; justify-content:space-between; gap:20px; min-height:var(--nav-h) }
.nav__links{ display:flex; align-items:center; gap:26px; font-size:14px; font-weight:500; color:rgba(255,255,255,.75) }
.nav__links a{ transition:color .18s ease }
.nav__links a:hover{ color:#fff }
.nav__tel{ color:#fff; font-weight:600; white-space:nowrap }
.nav__toggle{
  display:none; flex-direction:column; justify-content:center; gap:5px;
  width:44px; height:44px; padding:0 10px;
  background:none; border:1px solid var(--hairline); border-radius:10px; cursor:pointer;
}
.nav__toggle span{ display:block; height:2px; background:#fff; border-radius:2px; transition:transform .2s ease, opacity .2s ease }
.nav__toggle[aria-expanded="true"] span:nth-child(1){ transform:translateY(7px) rotate(45deg) }
.nav__toggle[aria-expanded="true"] span:nth-child(2){ opacity:0 }
.nav__toggle[aria-expanded="true"] span:nth-child(3){ transform:translateY(-7px) rotate(-45deg) }

/* persistent quote bar — sits right under the sticky nav, hidden by default
   and toggled to .is-visible by main.js once the estimator has scrolled out
   of view above the viewport (an IntersectionObserver on .estimate, not a
   scroll listener). It's position:fixed and overlays whatever's currently
   at the top of the viewport rather than reflowing the page's own layout
   when it appears — reflowing on every show/hide would jolt the scroll
   position under the visitor's cursor, which is worse than a slim bar
   briefly overlapping a few pixels of content. Opacity carries the actual
   show/hide state; the translateY is just a small lift on top of that, and
   is the only part reduced-motion drops — matching .reveal's own convention
   elsewhere on the page of keeping the fade but losing the movement. */
.quote-bar{
  position:fixed; top:var(--nav-h); left:0; right:0; z-index:99;
  background:var(--night-2); border-bottom:1px solid var(--hairline);
  opacity:0; transform:translateY(-6px); pointer-events:none;
  transition:opacity .3s ease, transform .3s ease;
}
.quote-bar.is-visible{ opacity:1; transform:none; pointer-events:auto }
/* a fixed-height band, so the floating "How it works" heading can be placed
   beneath it at a known position (see .section__head-pin.is-pinned) */
.quote-bar__inner{
  display:flex; align-items:center; justify-content:center; gap:16px; flex-wrap:wrap;
  height:var(--quote-bar-h); padding:0 var(--gutter);
}
.quote-bar__inner p{ font-size:14px; font-weight:600; color:#fff }
@media (prefers-reduced-motion:reduce){ .quote-bar{ transition:opacity .3s ease; transform:none } }
@media (max-width:560px){ .quote-bar__inner p{ display:none } }

/* --- hero ----------------------------------------------------------------- */
.hero{
  grid-row:1; grid-column:1;
  position:relative; z-index:2; overflow:hidden;
  padding:clamp(48px, 7vw, 88px) 0 clamp(44px, 6vw, 72px);
}
.hero__glow{
  position:absolute; top:-180px; right:-120px; width:560px; height:560px; border-radius:50%;
  background:radial-gradient(circle, rgba(99,192,69,.22) 0%, rgba(99,192,69,0) 65%);
  pointer-events:none;
}
.hero .container{ position:relative }
.hero__grid{ display:flex; align-items:flex-start; gap:40px }
.hero__copy{ flex:1; max-width:780px }
.hero__title{
  font-size:clamp(2.35rem, 5.2vw, 4rem);
  line-height:1.04; letter-spacing:-.02em; color:#fff;
}
.hero__title em{ color:var(--green); font-style:normal }
.hero__lede{
  font-size:clamp(1rem, 1.4vw, 1.1875rem); line-height:1.6;
  color:var(--on-dark-muted); max-width:560px; margin-top:24px;
}
.hero__cta{ display:flex; flex-wrap:wrap; gap:14px; margin-top:36px }

.hero{ min-height:min(92vh, 820px); isolation:isolate }
.hero__glow{ z-index:1 }
.hero .container{ z-index:2 }
.hero__title{ text-shadow:0 2px 40px rgba(10,12,9,.9) }

/* --- scroll-driven van scene ------------------------------------------------
   .hero-stage is the whole pinned run: the sticky van/scrim layer stays put
   in the viewport while .hero's copy, the runway spacer and the transition
   strip all scroll past underneath it. van-scene.js measures its own scroll
   progress through .hero-stage (see computeProgress() in van-scene.js) and
   drives the van from that — no scroll-jacking, just position:sticky.

   .hero-pin and .hero share a single grid row/cell (an overlap, not a stack)
   rather than using the more common margin-bottom:-100vh trick — that trick
   cancels .hero-pin's reserved flow space so .hero isn't pushed 100vh down
   the page, but it also confuses the browser's own sticky-release math: on
   test, .hero-pin never released at all, it just stayed pinned at the
   viewport top indefinitely, overlapping straight into the section below
   once scrolled past .hero-stage. The grid row's height comes from its
   tallest item (.hero's real content), and *that* box is what constrains
   .hero-pin's sticky release — reliably, unlike the margin hack. */
.hero-stage{ display:grid; grid-template-columns:1fr }
.hero-pin{
  grid-row:1; grid-column:1;
  position:sticky; top:0; z-index:1;
  height:100vh; overflow:hidden; pointer-events:none;
}
.hero__van{ position:absolute; inset:0 }
.hero__scrim{
  position:absolute; inset:0; pointer-events:none;
  background:
    linear-gradient(100deg, var(--night) 0%, rgba(10,12,9,.95) 30%,
      rgba(10,12,9,.72) 46%, rgba(10,12,9,.24) 66%,
      rgba(10,12,9,.08) 82%, rgba(10,12,9,.34) 100%),
    linear-gradient(180deg, rgba(10,12,9,.55) 0%, rgba(10,12,9,0) 22%,
      rgba(10,12,9,0) 52%, rgba(10,12,9,.86) 88%, var(--night) 100%);
}
/* gives the keyframe scrub (see scrollFrame() in van-scene.js) room to read
   as a scroll-driven animation rather than a flick, even on a short-content
   desktop viewport — and gives #how's heading (see .section__head.is-pinned
   below, and van-scene.js) room to float in over this stretch once .hero
   clears, rather than being crowded straight into its landing position */
.hero-stage__runway{ grid-row:2; grid-column:1; height:90vh }
.hero-stage .transition{ grid-row:3; grid-column:1 }

/* stat strip */
.stats{
  display:grid; grid-template-columns:repeat(4,1fr); gap:1px;
  background:var(--hairline); border:1px solid var(--hairline);
  border-radius:var(--radius); overflow:hidden;
  margin-top:clamp(40px, 5vw, 64px);
}
.stats li{ background:var(--night-2); padding:24px 28px }
.stats strong{
  display:block; font-family:var(--display); font-weight:700;
  font-size:clamp(1.5rem, 2.6vw, 2rem); color:var(--green); line-height:1.1;
}
.stats span{ display:block; font-size:13px; color:rgba(255,255,255,.55); margin-top:4px }

/* quick estimate — this is the page's primary conversion path, so it's
   framed to visually lead the hero rather than read as just another card.
   Three things do the work:
     1. A rich green fill (a diagonal blend between --green-ink and
        --green-deep) rather than a near-black or a stark white card. Plain
        white stood out but read as an off-brand slab dropped into an
        otherwise dark, green-accented page; near-black-with-a-green-tint
        (the very first version of this) didn't stand out from the hero at
        all, since the hero itself is already dark and green-tinted
        (.hero__glow, the van scene's night colours). A confidently green
        card is different enough in both hue and lightness to read as its
        own distinct panel against that backdrop, while still tying
        straight back into the brand rather than introducing white.
     2. Every piece of text that sits *directly* on that green card uses the
        on-dark tokens (#fff / --on-dark-muted, not --on-dark-faint — this
        card is meaningfully lighter than the near-black sections those
        fainter tokens were tuned for, so they need the boost) — same
        family as the rest of the page's dark sections, just tuned one
        notch brighter. Inputs, the vehicle cards and the delivery-type
        pills stay as their own dark "windows" set into the card (they
        already carry their own --night background) rather than being
        flipped too — the fleet photos are light-bodied vans shot for a
        dark background, so keeping their cards dark is what keeps the
        vans themselves visible, regardless of what the outer card's own
        colour is doing.
     3. A slow ambient pulse, done as an opacity fade on a glow ::before
        layer rather than by animating box-shadow directly. box-shadow
        isn't compositor-only — animating its value forces a full repaint
        of the card on every frame, which showed up as visibly smeared/
        ghosted text on a real screen (fine in a single still screenshot,
        which just happens to land on one settled frame — the bug only
        shows up on a live, continuously-repainting display). Fading a
        layer's opacity is compositor-only and has nothing to repaint. */
.estimate{
  position:relative; isolation:isolate;
  display:flex; flex-direction:column; gap:18px;
  background:linear-gradient(155deg, var(--green-ink) 0%, var(--green-deep) 100%);
  border:1.5px solid rgba(255,255,255,.22);
  border-radius:var(--radius); padding:26px 24px 22px; margin-top:20px;
  box-shadow:
    0 0 0 1px rgba(0,0,0,.15),
    0 20px 50px rgba(0,0,0,.55);
}
.estimate::before{
  content:""; position:absolute; inset:-3px; z-index:-1; border-radius:inherit;
  box-shadow:0 26px 70px -16px rgba(99,192,69,.85);
  opacity:.45; animation:estimateGlowPulse 4.5s ease-in-out infinite;
}
@keyframes estimateGlowPulse{ 0%, 100%{ opacity:.3 } 50%{ opacity:.75 } }
@media (prefers-reduced-motion:reduce){ .estimate::before{ animation:none; opacity:.5 } }
.estimate__badge{
  position:absolute; top:0; right:28px; transform:translateY(-50%);
  background:var(--green); color:var(--night); font-weight:700; font-size:11px;
  letter-spacing:.05em; text-transform:uppercase; white-space:nowrap;
  padding:6px 14px; border-radius:999px; box-shadow:0 6px 16px rgba(0,0,0,.4);
}
.estimate__head{ display:flex; flex-direction:column; gap:5px }
.estimate__label{
  font-family:var(--mono); font-size:11px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--green-soft);
}
.estimate__pitch{ font-size:15px; font-weight:600; color:#fff; letter-spacing:-.01em }
.estimate__reassure{ font-size:12px; color:var(--on-dark-muted) }

/* step 1 → 2 defaults to step 1 active; expandEstimate() in main.js marks
   step 1 .is-done and step 2 .is-active the moment "Full quote" is clicked */
.estimate__steps{ display:flex; align-items:center; gap:8px; margin-top:2px }
.estimate__step{
  display:flex; align-items:center; gap:6px;
  font-size:11px; font-weight:600; color:var(--on-dark-faint);
}
.estimate__step-num{
  display:inline-flex; align-items:center; justify-content:center; flex-shrink:0;
  width:16px; height:16px; border-radius:50%;
  background:rgba(255,255,255,.18); color:#fff; font-size:9.5px;
}
.estimate__step.is-active{ color:#fff }
.estimate__step.is-active .estimate__step-num{ background:#fff; color:var(--green-deep) }
.estimate__step.is-done .estimate__step-num{ background:var(--green-soft); color:var(--green-deep) }
.estimate__step-sep{ width:16px; height:1px; background:rgba(255,255,255,.25); flex-shrink:0 }

.estimate__locations{ display:flex; align-items:flex-end; flex-wrap:wrap; gap:14px }
.estimate__field{ flex:1 1 200px; min-width:0 }
.estimate__field label{
  display:block; font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--on-dark-muted); margin-bottom:5px;
}
.estimate__input{
  width:100%; background:var(--night); color:#fff;
  border:1px solid rgba(255,255,255,.15); border-radius:10px;
  padding:12px 12px; font-size:14px;
}
.estimate__input::placeholder{ color:var(--on-dark-faint) }
.estimate__arrow{ color:#fff; font-weight:700; padding-bottom:12px }

/* van picker */
/* starts hidden (see index.html) and fades/lifts in the first time it's
   revealed — main.js removes [hidden] then adds .van-picker--in a frame
   later, so the browser has a real "opacity:0 → 1" transition to animate
   rather than a display:none → block snap (which can't be transitioned) */
.van-picker{
  border:0; margin:0; padding:0;
  opacity:0; transform:translateY(10px); transition:opacity .4s ease, transform .4s ease;
}
.van-picker--in{ opacity:1; transform:none }
@media (prefers-reduced-motion:reduce){ .van-picker{ transition:none } }
.van-picker legend{
  font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--on-dark-muted); margin-bottom:8px; padding:0;
}
.van-picker__grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px }
.van-option{
  display:flex; flex-direction:column; align-items:center; gap:6px;
  padding:10px 6px 12px; text-align:center; cursor:pointer;
  background:var(--night); border:1px solid rgba(255,255,255,.15); border-radius:10px;
  transition:border-color .15s ease, background-color .15s ease;
}
.van-option input{ position:absolute; opacity:0; width:1px; height:1px }
/* layered rather than a flat translucent fill — .van-option needs to stay a
   solid dark island regardless of what colour now sits behind it (the outer
   card's own background has changed more than once; a translucent
   rgba(99,192,69,.12) alone lets whatever that is bleed straight through,
   which previously washed out the white text on top of it) */
.van-option:has(input:checked){
  border-color:var(--green);
  background:linear-gradient(rgba(99,192,69,.16), rgba(99,192,69,.16)), var(--night);
}
.van-option:has(input:focus-visible){ outline:2px solid var(--green); outline-offset:2px }
.van-option__img{ width:100%; aspect-ratio:2800/1080 }
.van-option__img img{ width:100%; height:100%; object-fit:contain }
.van-option__name{ font-size:12.5px; font-weight:600; color:#fff }
.van-option__meta{ font-size:11px; color:var(--on-dark-faint); line-height:1.3 }
.van-option__spec{
  font-family:var(--mono); font-size:11px; font-weight:600; letter-spacing:.02em;
  color:var(--green-soft); line-height:1.3;
}

.estimate__submit{ display:flex; align-items:center; flex-wrap:wrap; gap:14px }
.estimate .btn{ margin-bottom:0 }
/* pushed to the far side rather than "Full quote" (previously the other way
   round) — "Full quote" needs to sit right next to "Estimate" so it isn't
   easily missed; the result readout is the one thing that makes sense to
   separate out, and it's hidden until there's an actual price to show */
.estimate__result{
  font-family:var(--display); font-weight:700; font-size:24px; color:#fff;
  white-space:nowrap; padding-bottom:0; margin-left:auto;
}
.estimate__result small{
  display:block; font:400 11px/1.3 var(--body); color:var(--on-dark-muted);
  white-space:normal;
}
.estimate__result small a{ color:var(--green-soft); text-decoration:underline }
/* a real outlined (ghost) button, not a plain text link — next to a
   solid-fill primary button ("Estimate"), a bare link reads as an
   afterthought even with a saturated colour, since it has no shape/weight
   of its own to compete with. White-on-green here, filling solid white on
   hover, mirrors .btn--ghost's own dark-section treatment elsewhere on the
   page rather than introducing a new pattern. */
.estimate__more{
  appearance:none; cursor:pointer; font-family:inherit;
  display:inline-flex; align-items:center; gap:6px;
  font-size:14px; font-weight:700; white-space:nowrap; color:#fff; background:transparent;
  border:1.5px solid rgba(255,255,255,.55); border-radius:10px; padding:13px 22px;
  transition:background-color .15s ease, color .15s ease, border-color .15s ease;
}
.estimate__more:hover{ background:#fff; color:var(--green-deep); border-color:#fff }
.estimate__more[hidden]{ display:none }

.estimate__field-error{ font-size:11px; color:#ffb4a8; margin-top:5px }
.estimate__field-hint{ font-size:11px; color:var(--on-dark-muted); margin-top:5px }
.estimate__input[aria-invalid="true"]{ border-color:#ff6b5a }

/* .estimate__date-btn is main.js's visual swap-in for "Collect from" (see
   the comment on it in index.html) — a real <button> that reads as a
   selector rather than a text field, sitting in front of the actual
   datetime-local input. .is-date-enhanced is what hides that real input —
   opacity/position rather than [hidden] or display:none, since
   showPicker() throws once an element stops being rendered at all. */
.estimate__date-btn{
  display:flex; align-items:center; gap:10px; width:100%;
  background:var(--night); color:var(--on-dark-faint); text-align:left;
  border:1px solid rgba(255,255,255,.15); border-radius:10px;
  padding:12px 12px; font-size:14px; font-family:inherit; cursor:pointer;
  transition:border-color .15s ease;
}
.estimate__date-btn[hidden]{ display:none }
.estimate__date-btn:hover{ border-color:rgba(255,255,255,.3) }
.estimate__date-btn.has-value{ color:#fff }
.estimate__date-btn[aria-invalid="true"]{ border-color:#ff6b5a }
.estimate__date-icon{ width:18px; height:18px; flex-shrink:0 }
.estimate__input.is-date-enhanced{
  position:absolute; opacity:0; width:1px; height:1px; pointer-events:none;
}

/* --- estimate expansion: revealed in place once "Full quote" is clicked --- */
.estimate__expand{
  display:flex; flex-direction:column; gap:16px;
  padding-top:16px; margin-top:2px; border-top:1px dashed rgba(255,255,255,.2);
}
.estimate__expand[hidden]{ display:none }
.estimate__grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:14px }
.estimate__field-label{
  display:block; font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--on-dark-muted); margin-bottom:5px;
}
.estimate__choices{ display:flex; flex-wrap:wrap; gap:8px }
.estimate__choice{
  display:flex; align-items:center; padding:10px 14px; cursor:pointer;
  background:var(--night); border:1px solid rgba(255,255,255,.15); border-radius:10px;
  font-size:13px; color:var(--on-dark-muted);
  transition:border-color .15s ease, background-color .15s ease;
}
.estimate__choice input{ position:absolute; opacity:0; width:1px; height:1px }
/* same reasoning as .van-option:has(input:checked) above — layered so this
   stays a solid dark pill regardless of the card's own background */
.estimate__choice:has(input:checked){
  border-color:var(--green); color:#fff;
  background:linear-gradient(rgba(99,192,69,.16), rgba(99,192,69,.16)), var(--night);
}
.estimate__choice:has(input:focus-visible){ outline:2px solid var(--green); outline-offset:2px }
.estimate__send{ display:flex; flex-direction:column; gap:8px }
.estimate__send-note{ font-size:12px; color:var(--on-dark-muted); text-align:center }
.estimate__send-note.is-good{ color:var(--green-soft) }

@media (max-width:640px){
  .estimate__grid{ grid-template-columns:1fr }
}

/* Google Places dropdown, kept legible against the page's own fonts */
.pac-container{ font-family:var(--body); border-radius:10px; margin-top:4px; z-index:1000 }

/* --- dark/light transitions ---------------------------------------------- */
.transition{ position:relative; height:clamp(110px, 14vw, 180px); overflow:hidden }
.transition--down{ background:linear-gradient(180deg, var(--night) 0%, #0e130b 45%, var(--mist) 100%) }
.transition--up{ background:linear-gradient(180deg, var(--mist) 0%, #3a4433 55%, var(--night-2) 100%) }
.transition__line{
  position:absolute; left:50%; top:0; bottom:0; width:2px;
  background:repeating-linear-gradient(180deg, var(--green) 0 14px, transparent 14px 24px);
}
.transition__badge{
  position:absolute; left:50%; top:56%; transform:translate(-50%,-50%);
  font-family:var(--mono); font-size:12.5px; letter-spacing:.18em; text-transform:uppercase;
  padding:10px 22px; border-radius:999px; white-space:nowrap;
  box-shadow:0 10px 34px rgba(0,0,0,.35);
}
.transition__badge--light{ background:var(--paper); border:2px solid var(--green-ink); color:var(--green-deep) }
.transition__badge--green{ background:var(--green); color:var(--night); font-weight:700 }

/* --- sections ------------------------------------------------------------- */
.section{ padding:clamp(48px, 6.5vw, 72px) 0 }
.section--mist{ background:var(--mist) }
.section--paper{ background:var(--paper) }
.section--night{ background:var(--night-2); color:var(--on-dark) }
.section--ruled{ border-top:1px solid var(--line) }
.section--faq{ padding-top:0 }

.section__head{ margin-bottom:clamp(36px, 4.5vw, 52px) }
.section__head h2{ font-size:clamp(1.75rem, 3.4vw, 2.5rem) }
.section__head--center{ text-align:center; max-width:620px; margin-inline:auto }
.section__head--split{ display:flex; flex-wrap:wrap; gap:16px; justify-content:space-between; align-items:flex-end }
.section__title--narrow{ max-width:560px }
.section--night h2{ color:#fff }
.h2--sm{ font-size:clamp(1.5rem, 2.6vw, 2rem) !important }

/* .section__head-slot reserves the heading's own flow space while it's
   pinned — see the comment on it in index.html. van-scene.js sets its
   min-height explicitly (measured from the heading itself just before
   pinning) for as long as .is-pinned is applied, and clears it on release. */
.section__head-slot{ position:relative }

/* #how's heading is wrapped in .section__head-pin so the *pinning* (position)
   and the heading's own text layout (.section__head--center's max-width:620px)
   stay on separate elements. Pinning this wrapper — rather than the heading
   itself — instead of the width/padding-inline it into the same box as
   max-width:620px, replicates .container's own box model (width:100%,
   max-width:1200px, margin-inline:auto, padding-inline:var(--gutter)) so the
   heading resolves to *exactly* the same available width whether pinned or
   sitting in its natural place inside .container — otherwise the pinned
   version had extra padding eating into its own 620px cap that the natural
   version never had, so the same text wrapped differently in each state and
   visibly reflowed ("three" jumping rows) right at the handoff.

   van-scene.js adds .is-pinned (position:fixed, floating over the van/
   runway) once .hero has scrolled away and the heading's natural document
   position hasn't caught up to HEADING_FLOAT_TOP yet, and removes it the
   instant it has — at that exact instant the two positions coincide by
   construction, so the handoff from fixed back to static has nothing to
   visibly jump. HEADING_FLOAT_TOP in van-scene.js must match the "24px"
   below (plus the quote bar's height), or the handoff will jump by the difference.

   The opacity transition here (not tied to .is-pinned — van-scene.js drives
   the value directly) is what actually fades the heading in/out around that
   same handoff; reduced-motion users get the value change with no animation,
   matching .reveal's convention elsewhere on the page. */
.section__head-pin{ transition:opacity .35s ease }
.section__head-pin.is-pinned{
  position:fixed; left:0; right:0; top:calc(var(--nav-h) + var(--quote-bar-h) + 24px);
  z-index:5; width:100%; max-width:1200px; margin-inline:auto; padding-inline:var(--gutter);
}
@media (prefers-reduced-motion:reduce){
  .section__head-pin{ transition:none }
}

/* --- how it works --------------------------------------------------------- */
.steps{
  --steps-gap: 32px;
  display:grid; grid-template-columns:repeat(4,1fr); gap:var(--steps-gap);
  position:relative; padding:0 12px;
}
.steps__item{ position:relative }
/* Each item (bar the last) owns the line segment running from *its own* dot
   to the *next* item's dot — not one line spanning the whole row — so each
   segment can be given its own, independently-timed fill animation: ::before
   is the plain grey track for that segment (always drawn), ::after is the
   green fill sat on top of it, scaled from the left so it can be animated in
   growing from 0 → full width without the track ever needing to change.
   The segment has to reach past this item's own right edge and across the
   grid gap to reach the next dot, hence the `+ var(--steps-gap)` — the dot
   itself sits at a fixed 12px in from each item's left edge (matching
   .steps's own 12px inline padding, so the first dot lines up with it).
   Defaulting the fill to its *finished* scaleX(1) state means anyone without
   JS, or with reduced-motion (see below), just sees the completed route —
   nothing here depends on the animation actually running to look right. */
.steps__item:not(:last-child)::before, .steps__item:not(:last-child)::after{
  content:""; position:absolute; left:12px; top:11px; height:2px;
  width:calc(100% + var(--steps-gap));
}
.steps__item::before{ background:#c9cfc2 }
.steps__item::after{ background:var(--green-ink); transform-origin:left center; transform:scaleX(1) }
.steps__dot{
  display:block; width:24px; height:24px; border-radius:50%;
  background:var(--green-ink); border:5px solid var(--mist); position:relative; z-index:1;
}
.steps__time{
  font-family:var(--mono); font-size:13px; font-weight:600; text-transform:uppercase;
  color:var(--green-ink); margin-top:16px;
}
.steps__time--muted{ color:var(--ink-faint) }
.steps h3{ font-size:19px; font-weight:600; margin-top:6px }
.steps p:last-child{ font-size:14.5px; line-height:1.6; color:var(--ink-muted); margin-top:6px }

/* .steps.is-playing is added once, by main.js, the first time this strip
   scrolls into view (mirrors the site's generic .reveal/.is-in trigger
   pattern, but kept separate from it since this drives a multi-stage
   sequence rather than a single opacity swap). Only once that class lands
   do the dots/segments reset back to "nothing has happened yet" and then
   animate forward to the same finished state the CSS above already shows
   by default — so there's no flash-of-wrong-state if JS is slow, and no
   special-casing needed for the no-JS fallback.

   The sequence is strictly one thing at a time — dot 1, *then* (only once
   it's finished) the line to dot 2, *then* dot 2, and so on — each stage's
   animation-delay is the previous stage's own delay + its duration + a
   short pause, not a fraction of some overall run time, so nothing overlaps:
     dot1   0s               → .5s
     line1   .7s ( .5+.2)    → 1.4s
     dot2   1.6s (1.4+.2)    → 2.1s
     line2  2.3s (2.1+.2)    → 3.0s
     dot3   3.2s (3.0+.2)    → 3.7s
     line3  3.9s (3.7+.2)    → 4.6s
     dot4   4.8s (4.6+.2)    → 5.3s
   A segment lives on the item *before* it (item N's ::after is "the line
   from dot N to dot N+1"), so line1/2/3 above map directly to items 1/2/3. */
.steps.is-playing .steps__dot{ background:#fff; border:2px solid #c9cfc2 }
.steps.is-playing .steps__item:not(:last-child)::after{ transform:scaleX(0) }
.steps.is-playing .steps__item:nth-child(1) .steps__dot{ animation:stepsDot .5s ease forwards; animation-delay:0s }
.steps.is-playing .steps__item:nth-child(1)::after{ animation:stepsSegment .7s ease forwards; animation-delay:.7s }
.steps.is-playing .steps__item:nth-child(2) .steps__dot{ animation:stepsDot .5s ease forwards; animation-delay:1.6s }
.steps.is-playing .steps__item:nth-child(2)::after{ animation:stepsSegment .7s ease forwards; animation-delay:2.3s }
.steps.is-playing .steps__item:nth-child(3) .steps__dot{ animation:stepsDot .5s ease forwards; animation-delay:3.2s }
.steps.is-playing .steps__item:nth-child(3)::after{ animation:stepsSegment .7s ease forwards; animation-delay:3.9s }
.steps.is-playing .steps__item:nth-child(4) .steps__dot{ animation:stepsDot .5s ease forwards; animation-delay:4.8s }

@keyframes stepsDot{
  0%   { transform:scale(1); box-shadow:0 0 0 0 rgba(62,158,45,.55) }
  40%  { transform:scale(1.35); box-shadow:0 0 0 9px rgba(62,158,45,0); background:var(--green-ink); border-color:var(--mist) }
  100% { transform:scale(1); background:var(--green-ink); border-color:var(--mist); box-shadow:0 0 0 0 rgba(62,158,45,0) }
}
@keyframes stepsSegment{ to{ transform:scaleX(1) } }
@media (prefers-reduced-motion:reduce){
  /* main.js already skips adding .is-playing at all for reduced-motion
     visitors, but guarantee it here too (matching .reveal's own convention)
     in case that class lands anyway — animation:none alone would otherwise
     strand the dots/segments at their pre-animation grey/hollow look
     forever, with nothing left to bring them back to the finished state. */
  .steps.is-playing .steps__dot{ animation:none; background:var(--green-ink); border-color:var(--mist) }
  .steps.is-playing .steps__item::after{ animation:none; transform:scaleX(1) }
}

/* --- services scale ------------------------------------------------------- */
.scale{ display:grid; grid-template-columns:repeat(4,1fr) }
/* fixed first row so every box sits on the same baseline and the headings
   below line up across all four columns */
.scale__item{
  display:grid; grid-template-rows:110px auto 1fr;
  border-left:2px solid var(--ink); padding:0 28px 0 24px;
}
/* the box still grows letter → parcel → pallet → full load (the original
   scale metaphor), the icon inside just says *what* is scaling — colour
   flips to white on the last box since --green-ink is dark enough that the
   --ink icon colour the first three use would disappear into it */
.scale__box{
  display:flex; align-items:center; justify-content:center;
  border:2px solid var(--ink); border-radius:4px; align-self:end;
}
.scale__box svg{ display:block; width:55%; height:55%; color:var(--ink) }
.scale__box--xs{ width:34px;  height:24px; background:var(--line-2); border-radius:3px }
.scale__box--sm{ width:52px;  height:46px; background:#cde8c3 }
.scale__box--md{ width:74px;  height:68px; background:#9ed389 }
.scale__box--lg{ width:110px; height:96px; background:var(--green-ink) }
.scale__box--lg svg{ color:#fff }
.scale h3{ font-size:19px; font-weight:600; margin-top:18px }
.scale p{ font-size:14.5px; line-height:1.6; color:var(--ink-muted); margin-top:6px }

.chips{
  display:flex; flex-wrap:wrap; gap:10px; align-items:center;
  margin-top:44px; padding-top:24px; border-top:1px solid var(--line);
}
.chips__label{ font-size:13.5px; font-weight:600; color:var(--ink-faint); margin-right:6px }
.chip{
  font-size:13.5px; font-weight:600; color:var(--ink);
  background:var(--chip); padding:8px 16px; border-radius:999px;
  transition:background-color .18s ease, color .18s ease;
}
.chip:hover{ background:#eaf5e5; color:var(--green-deep) }

/* --- what we move --------------------------------------------------------- */
.rows li{ border-top:1px solid var(--line-2) }
.rows li:last-child{ border-bottom:1px solid var(--line-2) }
.rows a{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,440px) 20px;
  align-items:center; gap:24px;
  padding:24px 8px; transition:background-color .18s ease, padding .18s ease;
}
.rows a:hover{ background:var(--chip); padding-inline:16px }
.rows__name{ font-family:var(--display); font-weight:600; font-size:clamp(1.15rem,2vw,1.5rem) }
.rows__desc{ font-size:15px; color:var(--ink-muted) }
.rows__arrow{ font-size:20px; color:var(--green-ink); text-align:right }

/* --- reviews -------------------------------------------------------------- */
.rating{ display:flex; align-items:center; gap:10px; font-size:14px; font-weight:600; color:var(--ink-muted) }
.rating__stars{ color:#f0a814; font-size:17px; letter-spacing:2px }
.quotes{ display:grid; grid-template-columns:repeat(3,1fr) }
.quotes li{ border-left:2px solid var(--ink); padding:4px 32px 0 24px }
.quotes blockquote{
  margin:0; font-family:var(--display); font-weight:500; font-size:19px; line-height:1.45; color:var(--ink);
}
.quotes blockquote::before{ content:"\201C" }
.quotes blockquote::after{ content:"\201D" }
.quotes__by{ font-size:13.5px; font-weight:600; color:var(--ink-faint); margin-top:14px }

/* --- quote ---------------------------------------------------------------- */
.quote{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(32px, 4.5vw, 56px); align-items:center }
.quote__intro h2{ font-size:clamp(1.9rem, 3.6vw, 2.75rem); color:#fff; line-height:1.1 }
.quote__lede{ font-size:16px; line-height:1.6; color:var(--on-dark-muted); margin-top:16px }
.contact{ display:flex; flex-direction:column; gap:12px; margin-top:28px }
.quote__intro .btn{ margin-top:28px }
.contact a{ display:flex; align-items:center; gap:12px; color:#fff; font-weight:600; font-size:16px; width:fit-content }
.contact a:hover{ color:var(--green-soft) }
.contact__icon{
  width:38px; height:38px; border-radius:10px; background:rgba(99,192,69,.14);
  display:flex; align-items:center; justify-content:center; flex-shrink:0;
}

.form{ background:var(--card); border:1px solid var(--hairline); border-radius:18px; padding:clamp(22px, 3vw, 32px) }
.form__grid{ display:grid; grid-template-columns:1fr 1fr; gap:12px }
.field--wide{ grid-column:span 2 }
.field label{
  display:block; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase;
  color:rgba(255,255,255,.55); margin-bottom:6px;
}
.field input, .field textarea{
  width:100%; background:var(--night); color:#fff;
  border:1px solid rgba(255,255,255,.12); border-radius:10px;
  padding:13px 14px; font-size:14px; outline:none;
  font-family:inherit; transition:border-color .18s ease;
}
.field textarea{ resize:vertical; min-height:100px }
.field input::placeholder, .field textarea::placeholder{ color:rgba(255,255,255,.34) }
.field input:focus, .field textarea:focus{ border-color:var(--green) }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"]{ border-color:#ff6b5a }
.field__error{ font-size:12px; color:#ff8a7a; margin-top:5px }
.form .btn{ margin-top:16px }
.form__note{ font-size:12px; color:var(--on-dark-faint); text-align:center; margin-top:12px }
.form__note.is-good{ color:var(--green-soft) }
.hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden }

/* --- FAQ ------------------------------------------------------------------ */
.faq{ max-width:780px }
.faq__item{ border-top:1px solid var(--hairline) }
.faq h3{ margin:0; font:inherit }
.faq__q{
  display:flex; justify-content:space-between; align-items:center; gap:16px;
  width:100%; padding:18px 4px; background:none; border:0; cursor:pointer;
  font-family:var(--body); font-weight:600; font-size:16px; color:#fff; text-align:left;
}
.faq__q:hover{ color:var(--green-soft) }
.faq__mark{
  flex-shrink:0; width:20px; height:20px; position:relative; color:var(--green);
}
.faq__mark::before,.faq__mark::after{
  content:""; position:absolute; inset:50% 0 auto 0; height:2px; background:currentColor;
  transform:translateY(-50%); transition:transform .2s ease, opacity .2s ease;
}
.faq__mark::after{ transform:translateY(-50%) rotate(90deg) }
.faq__q[aria-expanded="true"] .faq__mark::after{ transform:translateY(-50%) rotate(0deg); opacity:0 }
.faq__a p{
  font-size:14.5px; line-height:1.65; color:var(--on-dark-muted);
  padding:0 4px 18px; max-width:640px;
}

/* --- floating WhatsApp ---------------------------------------------------- */
.wa{
  position:fixed; right:clamp(16px, 3vw, 28px); bottom:clamp(16px, 3vw, 24px); z-index:90;
  width:58px; height:58px; border-radius:50%; background:#25D366;
  display:flex; align-items:center; justify-content:center; font-size:27px;
  box-shadow:0 10px 28px rgba(0,0,0,.45);
  transition:transform .18s ease;
}
.wa:hover{ transform:scale(1.06) }

/* --- footer --------------------------------------------------------------- */
.footer{ background:var(--footer); color:rgba(255,255,255,.45); padding:44px 0 28px }
.footer__top{
  display:grid; grid-template-columns:1.4fr 1fr 1fr; gap:32px;
  padding-bottom:32px; margin-bottom:24px; border-bottom:1px solid rgba(255,255,255,.08);
}
.footer__brand{ display:flex; align-items:flex-start; gap:12px; font-size:13px }
.footer__brand .brand__word{ font-size:15px }
.footer__tag{ font-size:13px; line-height:1.5; color:rgba(255,255,255,.4); margin-top:6px; max-width:260px }
.footer__links, .footer__contact{ display:flex; flex-direction:column; font-size:13.5px }
/* padding rather than gap for the vertical spacing — grows each link's own
   tap target to a real touch-friendly height instead of just the text's
   line-height, without inflating the visible gap between lines */
.footer__links a, .footer__contact a{
  color:rgba(255,255,255,.6); transition:color .15s ease;
  width:fit-content; padding:9px 0;
}
.footer__links a:hover, .footer__contact a:hover{ color:#fff }
.footer__inner{ display:flex; flex-wrap:wrap; gap:20px; align-items:center; justify-content:space-between; font-size:13px }
.footer__meta{ display:flex; flex-wrap:wrap; gap:8px 24px; font-size:13px }
.footer__meta > *{ padding:8px 0 }
.footer__meta a:hover{ color:#fff }

/* --- reveal on scroll ----------------------------------------------------- */
.reveal{ opacity:0; transform:translateY(18px); transition:opacity .6s ease, transform .6s ease }
.reveal.is-in{ opacity:1; transform:none }
@media (prefers-reduced-motion:reduce){ .reveal{ opacity:1; transform:none } }

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width:1080px){
  .van-picker__grid{ grid-template-columns:repeat(2,1fr) }
}

@media (max-width:940px){
  .nav__toggle{ display:flex }
  .nav__links{
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column; align-items:stretch; gap:0;
    background:var(--night); border-bottom:1px solid var(--hairline);
    padding:8px var(--gutter) 20px;
    box-shadow:0 18px 40px rgba(0,0,0,.5);
    display:none;
  }
  .nav__links.is-open{ display:flex }
  .nav__links a{ padding:14px 2px; font-size:16px; border-bottom:1px solid var(--hairline) }
  .nav__links .btn{ margin-top:14px; padding:14px; border-bottom:0 }

  .hero__grid{ align-items:flex-start; gap:28px }

  .steps{ grid-template-columns:repeat(2,1fr); gap:36px 32px }
  .steps__item::before, .steps__item::after{ display:none }
  .steps__dot{ margin-bottom:2px }

  .scale{ grid-template-columns:repeat(2,1fr); gap:36px 0 }
  .quotes{ grid-template-columns:1fr; gap:28px }
  .quote{ grid-template-columns:1fr }
  .rows a{ grid-template-columns:minmax(0,1fr) 20px; gap:6px 16px }
  .rows__desc{ grid-column:1 / -1; grid-row:2 }
}

@media (max-width:720px){
  .stats{ grid-template-columns:repeat(2,1fr) }
  .stats li{ padding:20px }
  .estimate{ gap:14px }
  .estimate__locations{ flex-direction:column; align-items:stretch; gap:12px }
  .estimate__field{ flex:1 1 auto }
  .estimate__arrow{ display:none }
  .van-picker__grid{ grid-template-columns:repeat(2,1fr) }
  .estimate__submit{ flex-direction:column; align-items:stretch }
  .estimate .btn{ width:100% }
  .estimate__result{ text-align:center; margin-left:0 }
  .estimate__more{ justify-content:center }
  .section__head--split{ align-items:flex-start }
  .hero__cta .btn{ flex:1 1 100% }
  .footer__top{ grid-template-columns:1fr; gap:28px }
  .footer__inner{ flex-direction:column; align-items:flex-start }
}

@media (max-width:540px){
  .brand__mark{ --mark-w:60px }
  .brand__word{ font-size:15px }
  .scale{ grid-template-columns:1fr }
  /* single column: no need to reserve the tall box row */
  .scale__item{ grid-template-rows:auto auto auto; padding-right:0 }
  .form__grid{ grid-template-columns:1fr }
  .field--wide{ grid-column:span 1 }
  .quotes li{ padding-right:0 }
}

/* --- print ---------------------------------------------------------------- */
@media print{
  .nav,.wa,.transition,.estimate,.hero__glow,.hero-pin,.hero-stage__runway{ display:none !important }
  body{ background:#fff; color:#000 }
  .section--night,.hero,.footer{ background:#fff !important; color:#000 !important }
  .section--night h2,.hero__title,.quote__intro h2{ color:#000 !important }
  .faq__a[hidden]{ display:block !important }
}

/* fleet photos are <picture> now: let it fill the photo box like the img did */
.van-option__img picture { display: block; width: 100%; height: 100%; }

/* -- audit fixes (touch, contrast, phone order) ----------------------------- */
/* on phones the estimator follows the hero copy; the stats strip comes after it */
@media (max-width:720px){
  .hero .container{ display:flex; flex-direction:column }
  .hero__grid{ order:1 }
  .estimate{ order:2 }
  .stats{ order:3; margin-top:28px }
}
/* the primary button keeps dark text on its lighter hover green */
.btn.btn--primary:hover, .btn.btn--primary:focus-visible{ color:var(--night) }
/* placeholders: readable on the dark fields */
.estimate__input::placeholder{ color:rgba(255,255,255,.62) }
.field input::placeholder, .field textarea::placeholder{ color:rgba(255,255,255,.6) }
/* the letter option is a text card that spans the picker, above the four vans */
.van-option--letter{ grid-column:1 / -1 }

