
/* ============================================================
   RENTLA DESIGN TOKENS  ·  single source of truth
   ============================================================ */
:root{
  color-scheme: light;

  /* — Navy — brand #141C3F sits at 900 */
  --navy-50:#F5F6F9;  --navy-100:#E8EAF1; --navy-200:#CBD0DF; --navy-300:#A3ABC3;
  --navy-400:#6E7899; --navy-500:#4A5578; --navy-600:#333E63; --navy-700:#232E52;
  --navy-800:#1A2447; --navy-900:#141C3F; --navy-950:#0B1029;

  /* — Amber — brand #F5B745 sits at 400 */
  --amber-50:#FEF7E9;  --amber-100:#FDECC8; --amber-200:#FBDA94; --amber-300:#F8C868;
  --amber-400:#F5B745; --amber-500:#E9A22A; --amber-600:#C9861A; --amber-700:#A66A12;

  /* — Neutral — brand #E2E2E2 sits at 200 */
  --n-0:#FFFFFF; --n-50:#FAFAFA; --n-100:#F2F2F2; --n-200:#E2E2E2; --n-300:#CFCFCF;
  --n-400:#A8A8A8; --n-500:#808080; --n-600:#5E5E5E; --n-700:#444444; --n-800:#2B2B2B;

  /* — Text — all AA-verified on their ground — */
  --t-head:#141C3F;    /* 16.4:1 on white */
  --t-body:#3A4256;    /* 10.5:1 on white */
  --t-muted:#5C6478;   /*  6.6:1 on white */
  --t-inv:#FFFFFF;
  --t-inv-muted:#A3ABC3;
  --t-on-amber:#141C3F;/*  9.4:1 on amber-400 */

  /* — Surfaces — */
  --s-page:#FFFFFF; --s-tint:#FAFBFC; --s-panel:#F2F2F2; --s-inv:#141C3F; --s-inv-deep:#0B1029;

  /* — Type — */
  --f-display:'Outfit',system-ui,sans-serif;
  --f-body:'Manrope',system-ui,sans-serif;
  --f-mono:ui-monospace,'SF Mono',SFMono-Regular,'Roboto Mono',Menlo,monospace;

  /* — Space — 4px base — */
  --sp-1:4px;  --sp-2:8px;  --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px;
  --sp-8:32px; --sp-10:40px; --sp-12:48px; --sp-16:64px; --sp-20:80px;
  --sp-24:96px; --sp-32:128px;

  /* — Shape — */
  --r-chip:4px; --r-btn:8px; --r-card:12px; --r-media:16px; --r-pill:999px;

  /* — Elevation — borders first; shadow only for things that float — */
  --e1:0 1px 2px rgba(20,28,63,.06), 0 1px 3px rgba(20,28,63,.10);
  --e2:0 8px 24px rgba(20,28,63,.12);

  /* — Motion — */
  --d-fast:120ms; --d-base:180ms; --d-slow:240ms; --d-slower:320ms;
  --ease-std:cubic-bezier(.2,0,0,1);
  --ease-out:cubic-bezier(.25,.46,.45,.94);
  --ease-over:cubic-bezier(.34,1.26,.64,1);

  /* — Containers — */
  --c-page:1320px; --c-content:1200px; --c-prose:720px; --c-narrow:560px;
}

/* The column stopped growing at 1200px while the breakpoints stopped at 1000px,
   so a 1000px laptop and a 1920px monitor were laid out identically: on the
   monitor that left 37% of the screen as empty gutter (352px a side) around a
   1104px strip of content, and the 4-up cards shrank to 264px.

   Grow the column on monitors only. `100vw - 320px` holds the gutter at a
   constant 160px a side rather than letting it scale, so the frame reads the
   same at every monitor size, and the 1600px ceiling stops the grids stretching
   on ultrawides — at 1920 that is a 1600px column, 152px gutters, 364px cards.

   A proportional 88vw was the other option, but it made the column jump 208px
   the moment the query fired and left only ~100px of gutter just above the
   breakpoint. The fixed inset steps up by 80px instead and never crowds the
   screen edge.

   Gated at 1600px on purpose: every common laptop (1280/1366/1440/1536) stays
   at exactly 1200px, so the widths already signed off are untouched. Body text
   is unaffected either way — .sec-hd caps at 52rem and .prose at --c-prose, so
   only the grids and panels take up the extra room.

   .wrap and both .rails guides read this variable, so the rails follow the
   column automatically and the 48px of air between rail and first character
   is preserved. */
@media (min-width:1600px){
  :root{ --c-content:clamp(1200px, 100vw - 320px, 1600px) }
}

/* ============================================================
   BASE
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--s-page); color:var(--t-body);
  font-family:var(--f-body); font-size:16px; line-height:1.65;
  font-weight:400; -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
h1,h2,h3,h4{font-family:var(--f-display); color:var(--t-head); margin:0; text-wrap:balance}
p{margin:0}
ul{margin:0; padding:0; list-style:none}
a{color:inherit; text-decoration:none}
img,svg{display:block; max-width:100%}
:focus-visible{outline:2px solid var(--navy-900); outline-offset:3px; border-radius:2px}
.inv :focus-visible{outline-color:var(--amber-400)}

.skip{position:absolute; left:-9999px; top:0; z-index:200; background:var(--navy-900);
  color:#fff; padding:12px 20px; border-radius:0 0 var(--r-btn) 0; font-weight:600}
.skip:focus{left:0}

/* — Type scale — */
.t-display{font-size:clamp(2.25rem,1.4rem + 3.6vw,4rem); line-height:1.02; letter-spacing:-.03em; font-weight:700}
.t-h1{font-size:clamp(2rem,1.35rem + 2.8vw,3.25rem); line-height:1.06; letter-spacing:-.025em; font-weight:700}
.t-h2{font-size:clamp(1.625rem,1.2rem + 1.8vw,2.375rem); line-height:1.14; letter-spacing:-.02em; font-weight:600}
.t-h3{font-size:clamp(1.125rem,1.05rem + .4vw,1.375rem); line-height:1.25; letter-spacing:-.01em; font-weight:600}
/* Sentence case, not uppercase. Four shouted sentences in a row read as a
   billboard and are measurably slower to scan; the weight and size already
   carry the emphasis. The source text was always sentence case, so nothing
   in the markup had to change. */
/* Size, line-height and tracking are .t-h2's exactly, so this reads as the same
   rank as every other section head (it was 44px against their 48px). Only the
   weight differs: 800 is what gives it presence on the inverted panel. The
   authoritative .t-h2 metrics are set further down, in the type-scale block. */
.t-state{font-family:var(--f-display); font-weight:800;
  font-size:clamp(1.75rem,1.25rem + 2.1vw,2.625rem); line-height:1.04; letter-spacing:-.03em}

/* .t-h2-sm removed: it existed for one heading ("Eight device categories"),
   and holding that at 30px while every other section head sat at 48px was the
   inconsistency. Section headings now all resolve to .t-h2. */
.t-lead{font-size:clamp(1.0625rem,1rem + .3vw,1.1875rem); line-height:1.6; color:var(--t-body)}
.t-sm{font-size:.875rem; line-height:1.55}
.t-cap{font-size:.8125rem; line-height:1.4; color:var(--t-muted)}

/* — Mono: the data voice — */
.mono{font-family:var(--f-mono); font-variant-numeric:tabular-nums;
  font-size:.75rem; letter-spacing:.04em; text-transform:uppercase}
.stat-num{font-family:var(--f-mono); font-variant-numeric:tabular-nums;
  font-size:clamp(2rem,1.4rem + 2.2vw,3rem); font-weight:600; line-height:1;
  letter-spacing:-.02em; color:var(--t-head)}
.inv .stat-num{color:var(--amber-400)}

/* — Layout primitives — */
.wrap{width:100%; max-width:var(--c-content); margin-inline:auto; padding-inline:var(--sp-4)}
.prose{max-width:var(--c-prose)}
.sec{padding-block:var(--sp-16)}
.sec-tint{background:var(--s-tint)}
.stack{display:flex; flex-direction:column}
.g-2{gap:var(--sp-2)} .g-3{gap:var(--sp-3)} .g-4{gap:var(--sp-4)}
.g-6{gap:var(--sp-6)} .g-8{gap:var(--sp-8)} .g-10{gap:var(--sp-10)} .g-12{gap:var(--sp-12)}

/* — The slash: the logo's amber bar, systematised — */
.slash{display:block; width:28px; height:7px; background:var(--amber-400);
  transform:skewX(-20deg); flex:none}
.slash-sm{width:16px; height:5px}

.eyebrow{display:inline-flex; align-items:center; gap:var(--sp-3); color:var(--t-muted)}
.inv .eyebrow{color:var(--amber-400)}

/* — Blueprint grid: 24px minor, 96px major — */
.bp{
  background-image:
    linear-gradient(rgba(20,28,63,.045) 1px,transparent 1px),
    linear-gradient(90deg,rgba(20,28,63,.045) 1px,transparent 1px),
    linear-gradient(rgba(20,28,63,.055) 1px,transparent 1px),
    linear-gradient(90deg,rgba(20,28,63,.055) 1px,transparent 1px);
  background-size:24px 24px,24px 24px,96px 96px,96px 96px;
}
.inv.bp,.bp.inv{
  background-image:
    linear-gradient(rgba(255,255,255,.05) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.05) 1px,transparent 1px),
    linear-gradient(rgba(255,255,255,.08) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.08) 1px,transparent 1px);
}

/* — Inverse (navy) sections: the only place amber becomes text — */
.inv{background:var(--s-inv); color:var(--t-inv-muted)}
.inv h1,.inv h2,.inv h3,.inv h4{color:var(--t-inv)}

/* ============================================================
   BUTTONS
   ============================================================ */
.btn{display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2);
  font-family:var(--f-body); font-weight:600; font-size:.9375rem; min-height:48px;
  padding:0 var(--sp-6); border-radius:var(--r-btn); border:1px solid transparent;
  cursor:pointer; transition:background var(--d-fast) var(--ease-std),
  border-color var(--d-fast) var(--ease-std), color var(--d-fast) var(--ease-std),
  transform 80ms var(--ease-over); white-space:nowrap}
.btn:active{transform:scale(.98)}
.btn-data{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.06em;
  text-transform:uppercase; opacity:.65; font-weight:500}

.btn-primary{background:var(--amber-400); color:var(--t-on-amber)}
.btn-primary:hover{background:var(--amber-500)}
.btn-primary:active{background:var(--amber-600)}

.btn-secondary{background:var(--navy-900); color:#fff}
.btn-secondary:hover{background:var(--navy-800)}

.btn-ghost{background:transparent; color:var(--navy-900); border-color:var(--navy-200)}
.btn-ghost:hover{border-color:var(--navy-400); background:var(--navy-50)}
.inv .btn-ghost{color:#fff; border-color:rgba(255,255,255,.28)}
.inv .btn-ghost:hover{border-color:rgba(255,255,255,.6); background:rgba(255,255,255,.06)}

.btn-wa{background:#fff; color:var(--navy-900); border-color:var(--navy-200)}
.btn-wa:hover{border-color:var(--navy-400)}
.btn-wa svg{color:#25D366}
.btn-icon{width:48px; padding:0; flex:none}

/* — Unconfirmed value marker — */
/* Keyed on [title], not on .unconf itself. build.py strips the title attribute
   when SHOW_UNCONF_MARKS is off, so one flag there turns off the underline and
   the tooltip together — and the span itself stays in the html, which is what
   the pre-publish gate counts. See the note beside that flag. */
.unconf[title]{border-bottom:2px dotted var(--amber-500); cursor:help}
.inv .unconf[title]{border-bottom-color:var(--amber-400)}

/* ============================================================
   PREVIEW STRIP  ·  not part of the production site
   ============================================================ */

/* ============================================================
   HEADER
   ============================================================ */
/* ============================================================
   IN-PAGE JUMPS
   The location cards on /service-areas/ scroll to the quote form instead of
   opening a page. A hard jump there reads as a page load — the screen simply
   changes — which is exactly the wrong impression when you have just told
   someone the card does not navigate. Animating it shows the movement.

   Not gated here: the prefers-reduced-motion block already forces
   scroll-behavior:auto with !important, so anyone who has asked for less
   movement keeps the instant jump.
   ============================================================ */
html{scroll-behavior:smooth}

/* The header is sticky, so an un-offset jump parks the target underneath it
   and the heading you asked for is the one thing you cannot see. --hdr-h is
   measured in site.js and tracks the header as it shrinks on scroll. */
#quote,#main,:target{scroll-margin-top:calc(var(--hdr-h, 5rem) + var(--sp-4))}

.hdr{position:sticky; top:0; z-index:100; background:rgba(255,255,255,.92);
  backdrop-filter:blur(12px); border-bottom:1px solid var(--n-200);
  transition:box-shadow var(--d-base) var(--ease-std), border-color var(--d-base) var(--ease-std)}
.hdr.scrolled{box-shadow:var(--e1); border-color:var(--navy-200)}
.hdr .wrap{display:flex; align-items:center; gap:var(--sp-6);
  min-height:76px; transition:min-height var(--d-base) var(--ease-std)}
.hdr.scrolled .wrap{min-height:62px}

.logo{display:inline-flex; align-items:center; flex:none}
.logo img{height:34px; width:auto; transition:height var(--d-base) var(--ease-std)}
.hdr.scrolled .logo img{height:28px}

/* margin-inline, not margin-left: a single auto margin pushed the nav hard
   against the phone and CTA (147px of dead space after the logo, 24px before
   the buttons). Auto on both sides splits the leftover evenly, so the nav sits
   centred between the logo and the actions. When space runs out the auto
   margins collapse to zero on their own, so tight widths behave as before. */
.nav{display:flex; gap:var(--sp-6); margin-inline:auto}
.nav a{font-size:.875rem; font-weight:500; color:var(--t-body); padding-block:var(--sp-2);
  position:relative; transition:color var(--d-fast) var(--ease-std)}
.nav a:hover{color:var(--navy-900)}
.nav a::after{content:""; position:absolute; left:0; bottom:0; width:20px; height:4px;
  background:var(--amber-400); transform:skewX(-20deg) scaleX(0); transform-origin:left;
  transition:transform var(--d-fast) var(--ease-std)}
.nav a:hover::after{transform:skewX(-20deg) scaleX(1)}
.hdr-cta{display:flex; align-items:center; gap:var(--sp-3)}
.hdr-tel{font-family:var(--f-mono); font-size:.8125rem; font-weight:600;
  color:var(--navy-900); letter-spacing:.02em; white-space:nowrap}
.burger{display:none; width:44px; height:44px; align-items:center; justify-content:center;
  background:none; border:1px solid var(--n-200); border-radius:var(--r-btn); cursor:pointer}

/* Nav labels size to their text and never shrink. Without this the flex row
   squeezes them below content width — "Bulk Rental" and "Service Areas" were
   rendering 40px narrower than their own labels at 1000px. */
.nav a,.nav-toggle{flex:none; white-space:nowrap}

/* ============================================================
   CONTACT — INFO CARD AND MAP
   Both patterned on the Western Paints contact page: a gradient panel whose
   rows are icon tile + label + value, and a map that carries its own floating
   "open in Google Maps" pill rather than a caption underneath.

   Translated into Rentla's tokens rather than copied: navy instead of that
   site's blue, amber for the icons. Amber as icon and link colour is fine
   here because the ground is navy — the rule the rest of this site follows is
   only that amber never becomes text on white.
   ============================================================ */
.infocard{
  background:linear-gradient(155deg, var(--navy-700), var(--navy-900));
  color:var(--t-inv); border-radius:var(--r-media);
  /* --sp-7 does not exist on the scale (…5, 6, 8, 10…). An unresolvable var()
     with no fallback drops the declaration, which left the stacked mobile card
     with a row gap of 0. */
  padding:var(--sp-8); display:grid; gap:var(--sp-8);
  box-shadow:0 16px 40px rgba(12,16,40,.20);
}
/* Four rows stacked would leave a very tall, sparse panel at full width —
   the reference card is a narrow column beside a form. Two up from 700px. */
@media (min-width:700px){
  .infocard{grid-template-columns:repeat(2,1fr); gap:var(--sp-8) var(--sp-10)}
}
.inforow{display:flex; gap:var(--sp-4); align-items:flex-start}
.info-ico{
  flex:none; width:2.75rem; height:2.75rem; border-radius:10px;
  background:rgba(255,255,255,.12); color:var(--amber-400);
  display:grid; place-items:center;
}
.inforow > div{display:flex; flex-direction:column; gap:.35rem; min-width:0}
.info-label{margin:0; font-size:1.0625rem; font-weight:700; color:var(--t-inv)}
.info-value{
  font-size:.9375rem; line-height:1.55; color:rgba(255,255,255,.8);
  text-decoration:none; overflow-wrap:anywhere;
}
a.info-value{transition:color var(--d-fast) var(--ease-std)}
a.info-value:hover{color:var(--amber-300)}
.info-value .unconf[title]{color:rgba(255,255,255,.8); border-bottom-color:var(--amber-400)}

/* ── map with its own floating action ─────────────────────── */
.mapframe{
  position:relative; overflow:hidden; border-radius:var(--r-media);
  aspect-ratio:16 / 9; background:var(--navy-100);
  box-shadow:inset 0 0 0 1px var(--n-200), 0 10px 34px rgba(12,16,40,.08);
}
@media (max-width:699.98px){ .mapframe{aspect-ratio:4 / 3} }
.mapframe iframe{position:absolute; inset:0; width:100%; height:100%; border:0; display:block}
.mapframe-go{
  position:absolute; left:1rem; bottom:1rem; z-index:2;
  display:inline-flex; align-items:center; gap:.5rem;
  padding:.625rem 1rem; border-radius:var(--r-btn);
  background:var(--n-0); color:var(--navy-900);
  font-size:.8125rem; font-weight:600; text-decoration:none;
  box-shadow:0 6px 18px rgba(12,16,40,.18);
  transition:transform var(--d-fast) var(--ease-std);
}
.mapframe-go:hover{transform:translateY(-2px); color:var(--navy-900)}
@media (prefers-reduced-motion:reduce){ .mapframe-go:hover{transform:none} }

/* ── map beside the channel card ──────────────────────────────
   Contact page, "In short": map left, channels right. Two things the pair
   cannot inherit unchanged from their full-width versions —

   * the card's 2x2 grid is right across the whole content column and wrong in
     half of it, so it returns to a single stack here;
   * the map's fixed 16/9 would leave it standing at roughly half the card's
     height, so inside the split it drops the ratio and takes whatever height
     the card measures.

   Source order is card, then map. The columns are assigned explicitly at the
   breakpoint rather than with `order`, so the stacked layout keeps the phone
   number above the map — only the wide layout puts the map first, where both
   are in view at once anyway. */
.contact-split{display:grid; gap:var(--sp-6)}
@media (min-width:900px){
  .contact-split{grid-template-columns:1fr 1fr; gap:var(--sp-8); align-items:stretch}
  .contact-split > .mapframe{
    grid-area:1 / 1; aspect-ratio:auto; height:100%; min-height:26rem;
  }
  .contact-split > .infocard{
    grid-area:1 / 2; grid-template-columns:1fr; gap:var(--sp-8);
  }
}

/* ============================================================
   PRODUCTS MENU
   The toggle is a button but has to read as one of the nav links, so it
   inherits the same size, weight, colour and amber skew-underline rather
   than looking like a control dropped into a row of links.
   ============================================================ */
.navitem{position:relative; display:flex; align-items:center}
/* Both the desktop and mobile rules below set display on .navmenu, and an
   author declaration outranks the UA's [hidden]{display:none}. Without this
   the panel would be permanently open. */
.navmenu[hidden]{display:none}
.nav-toggle{display:inline-flex; align-items:center; gap:.3rem;
  font:inherit; font-size:.875rem; font-weight:500; font-family:var(--f-body);
  color:var(--t-body); padding:var(--sp-2) 0; position:relative;
  background:none; border:0; cursor:pointer;
  transition:color var(--d-fast) var(--ease-std)}
.nav-toggle:hover{color:var(--navy-900)}
.nav-toggle::after{content:""; position:absolute; left:0; bottom:0; width:20px; height:4px;
  background:var(--amber-400); transform:skewX(-20deg) scaleX(0); transform-origin:left;
  transition:transform var(--d-fast) var(--ease-std)}
.nav-toggle:hover::after,
.navitem.open .nav-toggle::after,
.nav-toggle[data-current]::after{transform:skewX(-20deg) scaleX(1)}
/* Marks the parent when the page you are on is one of the eight. */
.nav-toggle[data-current]{color:var(--navy-900); font-weight:600}
.nav-chev{transition:transform var(--d-fast) var(--ease-std)}
.navitem.open .nav-chev{transform:rotate(180deg)}

@media (min-width:1000px){
  .navmenu{
    position:absolute; top:calc(100% + .75rem); left:50%; translate:-50% 0; z-index:90;
    /* Eight items in one column runs past 320px tall and reads as a list to
       scroll. Two columns of four fits the whole range in one glance, which
       is the point of collapsing three nav items into one. */
    display:grid; grid-template-columns:repeat(2,minmax(11rem,1fr));
    gap:.15rem .5rem; padding:.75rem;
    background:var(--n-0); border:1px solid var(--n-200);
    border-radius:var(--r-media); box-shadow:var(--e2, 0 18px 48px rgba(11,16,41,.14));
  }
  /* The gap between button and panel would drop the hover if the pointer
     crossed it, so the panel's own hit area reaches back up to the button. */
  .navmenu::before{content:""; position:absolute; left:0; right:0; top:-.75rem; height:.75rem}
  .navmenu a{
    display:flex; align-items:center; min-height:40px; padding:.35rem .6rem;
    border-radius:var(--r-btn); white-space:nowrap;
    color:var(--t-head); font-size:.875rem; font-weight:500;
  }
  .navmenu a::after{display:none}          /* no skew underline inside the panel */
  .navmenu a:hover{background:var(--s-tint); color:var(--navy-900)}
  .navmenu a[aria-current="page"]{color:var(--amber-700); font-weight:600}
}

/* ============================================================
   HERO
   ============================================================ */
.hero{position:relative; background:var(--s-page); border-bottom:1px solid var(--n-200);
  padding-block:var(--sp-16) var(--sp-16); overflow:hidden}
.hero::before{content:""; position:absolute; inset:0; z-index:0;
  background-image:
    linear-gradient(rgba(20,28,63,.045) 1px,transparent 1px),
    linear-gradient(90deg,rgba(20,28,63,.045) 1px,transparent 1px),
    linear-gradient(rgba(20,28,63,.055) 1px,transparent 1px),
    linear-gradient(90deg,rgba(20,28,63,.055) 1px,transparent 1px);
  background-size:24px 24px,24px 24px,96px 96px,96px 96px;
  -webkit-mask-image:radial-gradient(120% 90% at 15% 0%,#000 20%,transparent 78%);
  mask-image:radial-gradient(120% 90% at 15% 0%,#000 20%,transparent 78%)}
.hero .wrap{position:relative; z-index:1}
.hero-grid{display:grid; grid-template-columns:1fr; gap:var(--sp-12); align-items:start}

.chip{display:inline-flex; align-items:center; gap:var(--sp-2); align-self:start;
  border:1px solid var(--navy-200); background:#fff; border-radius:var(--r-pill);
  padding:6px 14px 6px 10px; color:var(--t-muted)}
.chip .dot{width:6px; height:6px; border-radius:50%; background:var(--amber-500); flex:none}

.hero h1 em{font-style:normal; position:relative; white-space:nowrap}
.hero h1 em::after{content:""; position:absolute; left:0; right:0; bottom:.06em; height:.14em;
  background:var(--amber-400); transform:skewX(-20deg); z-index:-1}
.hero-cta{display:flex; flex-wrap:wrap; gap:var(--sp-3)}
.qualifier{display:flex; flex-wrap:wrap; gap:var(--sp-2) var(--sp-4); color:var(--t-muted)}
.qualifier span{display:flex; align-items:center; gap:var(--sp-2)}
.qualifier span::before{content:""; width:5px; height:5px; background:var(--navy-300);
  transform:skewX(-20deg); flex:none}

/* — Hero panel: the deployment record — */
.record{background:var(--s-inv); border-radius:var(--r-media); overflow:hidden;
  box-shadow:var(--e2); position:relative}
.record-inner{position:relative; z-index:1; padding:var(--sp-6)}
.record-hd{display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-4); padding-bottom:var(--sp-4); border-bottom:1px solid rgba(255,255,255,.12)}
.record-hd .mono{color:var(--amber-400)}
.record-tag{font-family:var(--f-mono); font-size:.625rem; letter-spacing:.08em;
  text-transform:uppercase; color:var(--navy-300); border:1px solid rgba(255,255,255,.18);
  border-radius:var(--r-chip); padding:3px 7px}
.rows{display:grid; grid-template-columns:auto 1fr; gap:var(--sp-3) var(--sp-6);
  padding-block:var(--sp-5)}
.rows dt{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.06em;
  text-transform:uppercase; color:var(--navy-300); align-self:center}
.rows dd{margin:0; font-family:var(--f-mono); font-size:.9375rem; font-variant-numeric:tabular-nums;
  color:#fff; font-weight:500; align-self:center}
.rows dd b{color:var(--amber-400); font-weight:600}

.timeline{display:flex; flex-direction:column; gap:0; padding-top:var(--sp-4);
  border-top:1px solid rgba(255,255,255,.12)}
.tl-step{display:grid; grid-template-columns:auto 1fr; gap:var(--sp-4); align-items:start}
.tl-mark{display:flex; flex-direction:column; align-items:center; gap:0; align-self:stretch}
.tl-dot{width:11px; height:11px; border-radius:50%; border:2px solid var(--amber-400);
  background:var(--s-inv); flex:none; margin-top:5px}
.tl-step.done .tl-dot{background:var(--amber-400)}
.tl-step.now .tl-dot{background:var(--s-inv); box-shadow:0 0 0 4px rgba(245,183,69,.18)}
.tl-line{width:2px; flex:1; background:rgba(255,255,255,.14); min-height:14px}
.tl-step.done .tl-line{background:var(--amber-400); opacity:.5}
.tl-body{padding-bottom:var(--sp-4)}
.tl-step:last-child .tl-body{padding-bottom:0}
.tl-body strong{display:block; color:#fff; font-size:.875rem; font-weight:600; line-height:1.4}
.tl-body span{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.05em;
  text-transform:uppercase; color:var(--navy-300)}

/* ============================================================
   TRUST BAR
   ============================================================ */
.trust{border-bottom:1px solid var(--n-200); background:var(--s-tint)}
.trust ul{display:grid; grid-template-columns:1fr 1fr; gap:var(--sp-5) var(--sp-4);
  padding-block:var(--sp-6)}
.trust li{display:flex; align-items:center; gap:var(--sp-3); font-size:.9375rem;
  font-weight:500; color:var(--t-body)}
/* Icon in place of the amber slash. Sized to the slash's optical weight and
   flex:none so a long label never squashes it. */
.trust-ico{width:22px; height:22px; flex:none; display:block}
.trust b{font-family:var(--f-mono); font-weight:600; color:var(--navy-900);
  font-variant-numeric:tabular-nums}

/* ============================================================
   SECTION HEADER
   ============================================================ */
.sec-hd{display:flex; flex-direction:column; gap:var(--sp-4); max-width:var(--c-prose)}
.sec-hd .rule{display:flex; align-items:center; gap:var(--sp-3); width:100%}
.sec-hd .rule i{flex:1; height:1px; background:var(--n-200)}

/* ============================================================
   SERVICES GRID
   ============================================================ */
.cards{display:grid; grid-template-columns:1fr; gap:var(--sp-4)}
.card{display:flex; flex-direction:column; gap:var(--sp-3); padding:var(--sp-6);
  background:#fff; border:1px solid var(--n-200); border-radius:var(--r-card);
  transition:border-color var(--d-fast) var(--ease-std),
    transform var(--d-fast) var(--ease-std), box-shadow var(--d-fast) var(--ease-std)}
.card:hover{border-color:var(--navy-400); transform:translateY(-2px); box-shadow:var(--e1)}
.card-ico{width:40px; height:40px; border-radius:var(--r-btn); background:var(--n-100);
  display:flex; align-items:center; justify-content:center; color:var(--navy-900); flex:none}
.card:hover .card-ico{background:var(--amber-100)}
.card p{font-size:.9375rem; color:var(--t-muted); line-height:1.55}
.card-meta{margin-top:auto; padding-top:var(--sp-3); border-top:1px solid var(--n-100);
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3)}
.card-meta .mono{color:var(--t-muted)}
.card-go{display:flex; align-items:center; gap:6px; font-size:.8125rem; font-weight:600;
  color:var(--navy-900)}
.card:hover .card-go{color:var(--amber-700)}

/* ============================================================
   BULK BAND  ·  the priority
   ============================================================ */
.bulk{position:relative; overflow:hidden; padding-block:var(--sp-20)}
.bulk .wrap{position:relative; z-index:1}
.bulk-grid{display:grid; grid-template-columns:1fr; gap:var(--sp-12)}
.bulk-stats{display:grid; grid-template-columns:1fr 1fr; gap:var(--sp-8) var(--sp-6)}
.bulk-stats div{display:flex; flex-direction:column; gap:var(--sp-2)}
.bulk-stats .mono{color:var(--navy-300)}
.bulk-stats .u{display:block; width:32px; height:5px; background:var(--amber-400);
  transform:skewX(-20deg)}

/* ============================================================
   WHY  ·  feature → outcome
   ============================================================ */
.why{display:grid; grid-template-columns:1fr; gap:0; border-top:1px solid var(--n-200)}
.why li{display:grid; grid-template-columns:auto 1fr; gap:var(--sp-4);
  padding-block:var(--sp-6); border-bottom:1px solid var(--n-200); align-items:start}
.why .slash{margin-top:10px}
.why strong{display:block; font-family:var(--f-display); font-weight:600; font-size:1.0625rem;
  color:var(--t-head); margin-bottom:var(--sp-1)}
.why span{font-size:.9375rem; color:var(--t-muted)}

/* ============================================================
   PROCESS
   ============================================================ */
.steps{display:grid; grid-template-columns:1fr; gap:var(--sp-6)}
.step{display:flex; flex-direction:column; gap:var(--sp-3); padding-top:var(--sp-5);
  border-top:2px solid var(--navy-900); position:relative}
.step:nth-child(n+2){border-top-color:var(--n-300)}
.step .mono{color:var(--t-muted)}
.step p{font-size:.9375rem; color:var(--t-muted)}

/* ============================================================
   USE CASES
   ============================================================ */
/* Even grid rather than a wrapping row. The labels range from "A new client
   project" to "Audits & short-term compliance work", so flex-wrap produced a
   ragged 4-then-3 break with a hole at the end. A fixed four columns keeps the
   tiles equal width and equal height, and the eighth cell carries the prompt
   that used to be empty space. */
.uses{display:grid; grid-template-columns:1fr; gap:var(--sp-3)}
.use{display:flex; flex-direction:column; align-items:flex-start; justify-content:flex-start;
  gap:var(--sp-4); border:1px solid var(--n-200); background:var(--n-0);
  border-radius:var(--r-card); padding:var(--sp-5);
  transition:border-color var(--d-base) var(--ease-std),
             transform var(--d-base) var(--ease-over),
             box-shadow var(--d-base) var(--ease-std)}
.use-t{font-family:var(--f-display); font-size:1rem; font-weight:600; line-height:1.3;
  letter-spacing:-.01em; color:var(--t-head); text-wrap:balance}

/* Numbered chip, same object as .process-num in the How it works list so the
   two numbered sequences on the page read as one device. Amber fill with navy
   on hover: 9.4:1. */
.use-num{display:inline-grid; place-items:center; flex:none;
  width:2.5rem; height:2.5rem; border-radius:var(--r-chip);
  background:var(--n-0); border:1px solid var(--n-200);
  font-family:var(--f-mono); font-variant-numeric:tabular-nums;
  font-size:.75rem; font-weight:600; color:var(--t-muted);
  transition:background-color var(--d-base) var(--ease-std),
             color var(--d-base) var(--ease-std),
             border-color var(--d-base) var(--ease-std)}
.use:hover .use-num{background:var(--amber-400); border-color:var(--amber-400);
  color:var(--t-on-amber)}
.use:hover{border-color:var(--amber-400); transform:translateY(-2px); box-shadow:var(--e1)}

/* The closing cell is the one actionable tile, so it carries the amber fill and
   navy text: 9.4:1, and it gives the row a single deliberate accent instead of
   seven repeated slashes competing for attention. */
.use-cta{background:var(--amber-400); border-color:var(--amber-400);
  justify-content:space-between; flex-direction:row; align-items:center}
.use-cta .use-t{color:var(--t-on-amber)}
.use-go{font-size:1.125rem; color:var(--t-on-amber); flex:none;
  transition:transform var(--d-base) var(--ease-over)}
.use-cta:hover{background:var(--amber-500); border-color:var(--amber-500)}
.use-cta:hover .use-go{transform:translateX(4px)}

@media (min-width:700px){ .uses{grid-template-columns:1fr 1fr} }
/* grid-auto-rows:1fr equalises the two rows as well as the tiles within them.
   Row two's labels wrap ("Audits & short-term compliance work"), so without it
   the second row stood 21px taller than the first. */
@media (min-width:1000px){
  .uses{grid-template-columns:repeat(4,1fr); grid-auto-rows:1fr; gap:var(--sp-4)}
}
@media (prefers-reduced-motion:reduce){
  .use:hover{transform:none}
  .use-cta:hover .use-go{transform:none}
}

/* ============================================================
   PROOF
   ============================================================ */
.proof{display:grid; grid-template-columns:1fr; gap:var(--sp-5)}
/* Proof cards: one lead card beside two stacked. Borderless on purpose, the
   way the reference has them: these sit on the tinted section, so white alone
   separates them and a hairline would only add noise. */
.proofcard{background:var(--n-0); border-radius:var(--r-media); margin:0;
  padding:var(--sp-8); display:flex; flex-direction:column}
.proofcard-lead{padding:var(--sp-10)}
.proofcard blockquote{margin:0; font-family:var(--f-display); font-weight:600;
  font-size:clamp(1.375rem,1.1rem + 1vw,1.875rem); line-height:1.32; color:var(--t-head);
  letter-spacing:-.02em; text-wrap:balance}
/* margin-top:auto pins every attribution to the foot of its card, so the three
   baselines line up even though the lead card holds far more text. */
.proofby{display:flex; align-items:center; gap:var(--sp-3); margin-top:auto;
  padding-top:var(--sp-8); color:var(--t-muted); font-size:.875rem}
.proofby-mark{width:40px; height:40px; flex:none; border-radius:var(--r-pill);
  background:var(--n-100); display:grid; place-items:center}
.proofside{display:grid; gap:var(--sp-5)}
.proofside .proofcard{justify-content:flex-start}
.proofside .stat-num{color:var(--t-head)}

/* ── reviews carousel ──────────────────────────────────────────
   Every slide occupies the SAME grid cell, so the viewport is as tall as the
   longest review and the two stat cards beside it never shift when you step
   through. Laying the slides out in a row and translating the track would have
   worked too, but then a long review after a short one resizes the card under
   the cursor — and the arrow moves out from under the pointer mid-click.

   Hidden slides get visibility:hidden as well as opacity:0. Opacity alone
   leaves them focusable and readable by a screen reader, so all four reviews
   would be announced at once and tabbing would land on text nobody can see. */
.revs{position:relative}
.revs-viewport{display:grid}
.revs-viewport > .rev{
  grid-area:1 / 1; margin:0; display:flex; flex-direction:column;
  opacity:0; visibility:hidden;
  transition:opacity var(--d-mid, .28s) var(--ease-std);
}
.revs-viewport > .rev.is-on{opacity:1; visibility:visible}
@media (prefers-reduced-motion:reduce){ .revs-viewport > .rev{transition:none} }

/* The attribution already carries padding-top:var(--sp-8) from .proofby, so the
   row only needs to clear the viewport, not restate that gap. */
.revs-nav{display:flex; align-items:center; justify-content:flex-end;
  gap:var(--sp-4); margin-top:var(--sp-6)}
.revs-nav[hidden]{display:none}   /* author display beats the UA [hidden] rule */
.revs-count{font-size:.75rem; letter-spacing:.08em; color:var(--t-muted)}

/* Amber fill with the navy arrow on it, which is the only way amber is allowed
   to carry meaning on a white card here — amber as a stroke on white would sit
   at about 1.9:1. */
.revs-next{
  flex:none; width:2.75rem; height:2.75rem; border-radius:var(--r-pill);
  border:0; padding:0; cursor:pointer;
  background:var(--amber-400); color:var(--navy-900);
  display:grid; place-items:center;
  transition:background var(--d-fast) var(--ease-std),
             transform var(--d-fast) var(--ease-std);
}
.revs-next:hover{background:var(--amber-300); transform:translateX(2px)}
.revs-next:active{transform:translateX(0)}
.revs-next:focus-visible{outline:2px solid var(--navy-900); outline-offset:3px}
@media (prefers-reduced-motion:reduce){ .revs-next:hover{transform:none} }

/* ── rent-vs-buy calculator ────────────────────────────────────
   Controls on white, outcome on navy. The split is doing work: the left half
   is what the visitor changes, the right half is what the arithmetic returns.
   On one ground people tried to type into the totals.

   The results column leads with the saving rather than ending on it. The first
   version listed four rows and put the headline figure underneath, which meant
   the payoff — the only number most people want — was the last thing read. */
.vh{position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0}

.roi{display:grid; gap:var(--sp-5); align-items:start}
@media (min-width:860px){
  .roi{grid-template-columns:minmax(0,1fr) minmax(0,1.05fr); gap:var(--sp-5); align-items:stretch}
}

.roi-in{
  /* --sp-7 is not on the scale (…5, 6, 8, 10…). An unresolvable var() with no
     fallback drops the whole declaration, which left this card at padding 0
     and its controls touching the edges. */
  background:var(--n-0); border-radius:var(--r-media); padding:var(--sp-6);
  display:grid; gap:var(--sp-5); align-content:start;
  box-shadow:inset 0 0 0 1px var(--n-200);
}
.roi-field{display:grid; gap:.6rem; margin:0}
.roi-label{display:flex; align-items:baseline; justify-content:space-between; gap:var(--sp-4);
  font-size:.9375rem; font-weight:600; color:var(--t-head)}
.roi-months{font-family:var(--f-display); font-size:1.125rem; font-weight:700;
  color:var(--navy-800); font-variant-numeric:tabular-nums; white-space:nowrap}

/* Presets first, free entry second. Most enquiries land on a round number, and
   a chip is one tap where a number field is a keyboard. */
.roi-chips{display:flex; flex-wrap:wrap; gap:.5rem}
.roi-chip{
  padding:.5rem .85rem; border-radius:var(--r-btn); cursor:pointer;
  font:inherit; font-size:.875rem; font-weight:600; font-variant-numeric:tabular-nums;
  color:var(--navy-800); background:var(--n-0); border:1.5px solid var(--n-200);
  transition:background-color var(--d-fast) var(--ease-std),
             border-color var(--d-fast) var(--ease-std), color var(--d-fast) var(--ease-std);
}
.roi-chip:hover{border-color:var(--navy-300); background:var(--navy-50)}
.roi-chip.is-on{background:var(--navy-900); border-color:var(--navy-900); color:var(--n-0)}
.roi-chip:focus-visible{outline:2px solid var(--navy-900); outline-offset:2px}
.roi-custom{display:block; margin:0; flex:1 1 5.5rem; min-width:5.5rem}
.roi-custom input{
  width:100%; padding:.5rem .7rem; font:inherit; font-size:.875rem; font-weight:600;
  font-variant-numeric:tabular-nums; color:var(--t-head); background:var(--n-0);
  border:1.5px dashed var(--n-300); border-radius:var(--r-btn); text-align:center;
}
.roi-custom input:focus-visible{outline:2px solid var(--navy-900); outline-offset:1px;
  border-style:solid; border-color:transparent}

/* Range rather than a number field: the whole point of this panel is the month
   where buying takes over, and a slider is the only control that lets someone
   sweep past it and watch the answer flip. */
.roi-range{
  -webkit-appearance:none; appearance:none; width:100%; height:6px; margin:.35rem 0 0;
  border-radius:999px; background:var(--n-200); cursor:pointer;
}
.roi-range:focus-visible{outline:2px solid var(--navy-900); outline-offset:4px}
.roi-range::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none; width:22px; height:22px; border-radius:50%;
  background:var(--amber-400); border:3px solid var(--n-0); cursor:grab;
  box-shadow:0 2px 8px rgba(12,16,40,.28);
}
.roi-range::-moz-range-thumb{
  width:22px; height:22px; border-radius:50%; background:var(--amber-400);
  border:3px solid var(--n-0); cursor:grab; box-shadow:0 2px 8px rgba(12,16,40,.28);
}
.roi-scale{display:flex; justify-content:space-between; font-size:.75rem; color:var(--t-muted)}

.roi-adv{border-top:1px solid var(--n-200); padding-top:var(--sp-5)}
.roi-adv summary{
  cursor:pointer; font-size:.875rem; font-weight:600; color:var(--navy-700);
  list-style:none; display:inline-flex; align-items:center; gap:.4rem;
  border-radius:4px;
}
.roi-adv summary::-webkit-details-marker{display:none}
.roi-adv summary::before{
  content:""; width:0; height:0; flex:none;
  border-left:5px solid currentColor; border-top:4px solid transparent;
  border-bottom:4px solid transparent;
  transition:transform var(--d-fast) var(--ease-std);
}
.roi-adv[open] summary::before{transform:rotate(90deg)}
.roi-adv summary:focus-visible{outline:2px solid var(--navy-900); outline-offset:3px}
.roi-field-sm{margin-top:var(--sp-5); gap:.35rem}
.roi-field-sm .roi-label{font-size:.8125rem; font-weight:600; color:var(--t-muted)}
.roi-field-sm input{
  width:100%; padding:.5rem .7rem; font:inherit; font-size:.875rem;
  font-variant-numeric:tabular-nums; color:var(--t-head); background:var(--n-0);
  border:1.5px solid var(--n-200); border-radius:var(--r-btn);
}
.roi-field-sm input:focus-visible{outline:2px solid var(--navy-900); outline-offset:1px;
  border-color:transparent}

.roi-out{
  background:linear-gradient(155deg, var(--navy-700), var(--navy-900));
  color:var(--t-inv); border-radius:var(--r-media); padding:var(--sp-6);
  display:grid; gap:var(--sp-4); align-content:start;
  box-shadow:0 16px 40px rgba(12,16,40,.20);
}
.roi-save{display:grid; gap:.15rem}
.roi-save-label{font-size:.75rem; letter-spacing:.09em; text-transform:uppercase;
  color:rgba(255,255,255,.65)}
/* Amber as text is allowed here and only here: on this navy it measures 8.5:1.
   The same colour on the white control card would sit near 1.9:1. */
.roi-save-num{
  font-family:var(--f-display); font-weight:700; letter-spacing:-.025em;
  font-size:clamp(1.75rem, 1.35rem + 1.3vw, 2.375rem); line-height:1.05;
  color:var(--amber-400); font-variant-numeric:tabular-nums;
}

/* The whole comparison as one sentence. The bars and the table both need
   reading and interpreting; this states the answer in words, which is what
   most people actually take away. */
.roi-summary{margin:0; font-size:.9375rem; line-height:1.6; color:rgba(255,255,255,.85)}
.roi-summary b{color:var(--t-inv); font-weight:600; white-space:nowrap}

/* The rates the arithmetic is standing on, in plain sight rather than only
   behind the toggle — a calculator that will not show its inputs is asking to
   be taken on trust. */
.roi-basis{margin:0; font-size:.8125rem; line-height:1.55; color:var(--t-muted)}
.roi-basis b{color:var(--t-head); font-weight:600; white-space:nowrap}

.roi-cta{
  display:inline-flex; align-items:center; justify-content:center; gap:.5rem;
  margin-top:var(--sp-2); padding:.8rem 1.15rem; border-radius:var(--r-btn);
  background:var(--amber-400); color:var(--navy-900);
  font-weight:700; font-size:.9375rem; text-decoration:none; border:0; cursor:pointer;
  transition:background-color var(--d-fast) var(--ease-std), transform var(--d-fast) var(--ease-std);
}
.roi-cta:hover{background:var(--amber-300); color:var(--navy-900); transform:translateY(-1px)}
.roi-cta:focus-visible{outline:2px solid var(--n-0); outline-offset:2px}
@media (prefers-reduced-motion:reduce){ .roi-cta:hover{transform:none} }

.roi-bars{display:grid; gap:var(--sp-4)}
.roi-bar{display:grid; grid-template-columns:4.5rem 1fr auto; align-items:center;
  gap:.75rem; font-size:.875rem}
.roi-bar-k{color:rgba(255,255,255,.7)}
.roi-bar-track{height:10px; border-radius:999px; background:rgba(255,255,255,.12); overflow:hidden}
.roi-bar-fill{display:block; height:100%; border-radius:999px;
  transition:width var(--d-mid, .3s) var(--ease-std)}
.roi-bar-rent{background:var(--amber-400)}
.roi-bar-buy{background:rgba(255,255,255,.42)}
.roi-bar b{font-weight:600; color:var(--t-inv); font-variant-numeric:tabular-nums;
  white-space:nowrap; font-size:.9375rem}
@media (prefers-reduced-motion:reduce){ .roi-bar-fill{transition:none} }

.roi-rows{display:grid; gap:.55rem; padding-top:var(--sp-4);
  border-top:1px solid rgba(255,255,255,.14)}
.roi-row{display:flex; justify-content:space-between; align-items:baseline; gap:var(--sp-4);
  font-size:.875rem; color:rgba(255,255,255,.72)}
.roi-row b{font-weight:600; color:rgba(255,255,255,.92); font-variant-numeric:tabular-nums;
  white-space:nowrap}

.roi-cross{margin:0; font-size:.8125rem; line-height:1.6; color:rgba(255,255,255,.7)}
.roi-cross b{color:var(--t-inv); font-weight:600}

/* ── the calculator on its own, opened from the header ─────────
   A real <dialog>. showModal() supplies the backdrop, Escape, focus trapping
   and inertness of the page behind — the last of which is what stops a screen
   reader walking off into the page underneath. */
.roi-modal{
  padding:0; border:0; background:transparent; overflow:visible;
  width:min(100% - 2rem, 52rem); max-height:calc(100dvh - 2rem);
  margin:auto;                       /* centres in both axes, not just across */
  color:var(--t-body);
}
/* Locks the page behind while the dialog is open, without any JS state to
   leak. Keyed on the dialog's own [open] attribute, so it cannot be left on. */
:root:has(.roi-modal[open]){overflow:hidden}
.roi-modal::backdrop{
  background:rgba(12,16,40,.55);
  backdrop-filter:blur(3px);
}
.roi-modal-in{
  background:var(--n-0); border-radius:var(--r-media);
  padding:clamp(1.1rem, .9rem + 1vw, var(--sp-8));
  display:grid; gap:var(--sp-5);
  max-height:calc(100dvh - 2rem); overflow-y:auto;
  box-shadow:0 30px 80px rgba(12,16,40,.36);
}
.roi-modal-hd{display:flex; align-items:flex-start; justify-content:space-between; gap:var(--sp-5)}
.roi-modal-hd .t-h3{margin:.35rem 0 0}
.roi-modal-x{
  flex:none; width:2.5rem; height:2.5rem; border-radius:var(--r-pill);
  border:1px solid var(--n-200); background:var(--n-0); color:var(--navy-800);
  cursor:pointer; display:grid; place-items:center;
  transition:background-color var(--d-fast) var(--ease-std),
             border-color var(--d-fast) var(--ease-std);
}
.roi-modal-x:hover{background:var(--navy-100); border-color:var(--navy-300)}
.roi-modal-x:focus-visible{outline:2px solid var(--navy-900); outline-offset:2px}
.roi-modal-note{margin:0; font-size:.8125rem; color:var(--t-muted)}
/* Inside the dialog the inputs card sits on the same white as the panel, so the
   inset hairline is the only thing separating them; keep it, drop the fill. */
.roi-modal .roi-in{background:transparent; box-shadow:inset 0 0 0 1px var(--n-200)}
/* The dialog states the comparison once. The bars already carry both totals
   with their figures, so the sentence repeated them in words and the two rows
   broke the buying figure into parts nobody asked for at this size — three
   tellings of one number, in a panel that then had to scroll. The full section
   on /rental-plans/ keeps all of it; there is room there. */
.roi-modal .roi-summary,
.roi-modal .roi-rows{display:none}
.roi-modal .roi-basis{font-size:.75rem}
.roi-modal .roi-modal-hd .t-h3{font-size:1.25rem}

/* On a phone the two cards stack, so the dialog is tall by construction and a
   short handset (667px) pushed the quote button below the fold — the one thing
   in here that leads anywhere. Trimmed until it fits: the rates line goes (the
   toggle above it still shows them), and the cards lose a little padding.
   Scoped to the dialog, so the section on /rental-plans/ keeps all of it. */
@media (max-width:640px){
  .roi-modal .roi-basis{display:none}
  .roi-modal .roi-in,
  .roi-modal .roi-out{padding:var(--sp-5)}
  .roi-modal .roi{gap:var(--sp-4)}
  .roi-modal .roi-modal-in{gap:var(--sp-4)}
  .roi-modal .roi-scale{font-size:.6875rem}
}
@media (prefers-reduced-motion:no-preference){
  .roi-modal[open]{animation:roiIn .22s var(--ease-std)}
  @keyframes roiIn{from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:none}}
}

/* Header button. Same 44px height as the CTA pill and the burger so the
   right-hand cluster keeps one rhythm — it is wider than the phone icon it
   replaces because "ROI" as a word is unambiguous where a calculator glyph
   is not. */
.hdr-roi{
  display:inline-flex; align-items:center; justify-content:center;
  height:44px; padding:0 .95rem; flex:none;
  font-size:.8125rem; font-weight:700; letter-spacing:.08em;
  border-radius:var(--r-pill); cursor:pointer;
  color:var(--navy-800); background:transparent;
  border:1px solid var(--navy-200);
  transition:background-color var(--d-base) var(--ease-std),
             border-color var(--d-base) var(--ease-std),
             color var(--d-base) var(--ease-std);
}
.hdr-roi:hover{background:var(--amber-50); border-color:var(--amber-400); color:var(--navy-900)}
.hdr-roi:focus-visible{outline:2px solid var(--navy-900); outline-offset:2px}
@media (max-width:999px){ .hdr-roi{display:none} }

/* ============================================================
   ZONES
   ============================================================ */
/* Intro and coverage panel share row one; the zone list spans the full width
   beneath them. Previously the list sat in the left column, which made that
   column far taller than the panel beside it and left a dead corner bottom
   right. Splitting them means neither has to match the other's height. */
.zones{display:grid; grid-template-columns:1fr; gap:var(--sp-10)}
.zonelist{display:grid; grid-template-columns:1fr 1fr; gap:0 var(--sp-8)}
/* .zone-plain shares the row exactly: an area we deliver to that has no page
   of its own yet. Same type, same rule, same rhythm — it simply carries no
   arrow, because the arrow is what promises somewhere to go. */
.zonelist a,.zonelist .zone-plain{display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);
  padding-block:var(--sp-4); border-bottom:1px solid var(--n-200); font-weight:500;
  font-size:.9375rem; color:var(--t-body); transition:color var(--d-fast) var(--ease-std)}
.zonelist a:hover{color:var(--navy-900)}
.zonelist .mono{color:var(--t-muted); font-size:.6875rem}
.zonelist a:hover .mono{color:var(--amber-700)}
.mapcard{position:relative; overflow:hidden; border-radius:var(--r-media);
  background:var(--s-inv); min-height:280px; display:flex; flex-direction:column;
  justify-content:flex-end; gap:var(--sp-3); padding:var(--sp-6)}
.mapcard .fill{position:absolute; inset:0; z-index:0}
.mapcard > *{position:relative; z-index:1}
/* Panel copy, moved out of inline styles so it is styleable and consistent. */
.zonetag{color:var(--amber-400)}
.zonehead{font-family:var(--f-display); font-size:1.25rem; font-weight:600;
  line-height:1.3; color:var(--t-inv)}
.zonebody{color:var(--t-inv-muted)}
.zonelink{margin-top:auto; display:inline-flex; align-items:center; gap:var(--sp-2);
  font-family:var(--f-mono); font-size:.75rem; font-weight:600; letter-spacing:.08em;
  text-transform:uppercase; color:var(--amber-400); align-self:flex-start;
  border-bottom:1px solid transparent; transition:border-color var(--d-fast) var(--ease-std)}
.zonelink:hover{border-bottom-color:var(--amber-400)}

/* ============================================================
   FAQ
   ============================================================ */
.faq{border-top:1px solid var(--n-200)}
.faq details{border-bottom:1px solid var(--n-200)}
.faq summary{display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--sp-6); padding-block:var(--sp-5); cursor:pointer; list-style:none;
  font-family:var(--f-display); font-weight:600; font-size:1.0625rem; color:var(--t-head);
  transition:color var(--d-fast) var(--ease-std)}
.faq summary::-webkit-details-marker{display:none}
.faq summary:hover{color:var(--navy-600)}
.faq .chev{flex:none; margin-top:4px; color:var(--navy-400);
  transition:transform var(--d-base) var(--ease-std), color var(--d-fast) var(--ease-std)}
.faq details[open] .chev{transform:rotate(180deg); color:var(--amber-600)}
.faq .ans{padding-bottom:var(--sp-6); max-width:var(--c-prose); color:var(--t-body);
  font-size:.9375rem; line-height:1.65}

/* Smooth open/close.
   <details> hides its content outright when closed, so a plain height
   transition never runs. ::details-content is the part that actually collapses;
   interpolate-size lets height animate to `auto`, and allow-discrete keeps the
   element visible for the duration of the closing run instead of vanishing on
   frame one. Browsers without ::details-content simply snap open as before —
   the accordion still works, it just does not animate. */
@supports selector(details::details-content){
  :root{interpolate-size:allow-keywords}
  .faq details::details-content{
    height:0; overflow:hidden;
    transition:height var(--d-slow) var(--ease-out),
               content-visibility var(--d-slow) allow-discrete;
  }
  .faq details[open]::details-content{height:auto}
}
.faq .ans a{color:var(--navy-900); text-decoration:underline; text-decoration-color:var(--amber-400);
  text-decoration-thickness:2px; text-underline-offset:3px}

/* ============================================================
   FINAL CTA + FORM
   ============================================================ */
.final{padding-block:var(--sp-20); position:relative; overflow:hidden}
.final .wrap{position:relative; z-index:1}
.final-grid{display:grid; grid-template-columns:1fr; gap:var(--sp-12); align-items:center}
.formcard{background:#fff; border-radius:var(--r-media); padding:var(--sp-6);
  display:flex; flex-direction:column; gap:var(--sp-5); box-shadow:var(--e2)}
.progress{display:flex; align-items:center; gap:var(--sp-2)}
.progress i{height:4px; flex:1; border-radius:2px; background:var(--n-200)}
.progress i.on{background:var(--amber-400)}
.field{display:flex; flex-direction:column; gap:var(--sp-2)}
.field label{font-size:.8125rem; font-weight:600; color:var(--t-head)}
.field input{height:48px; border:1px solid var(--n-300); border-radius:var(--r-btn);
  padding:0 var(--sp-4); font-family:var(--f-body); font-size:16px; color:var(--t-head);
  background:#fff; transition:border-color var(--d-fast) var(--ease-std),
  box-shadow var(--d-fast) var(--ease-std)}
.field input:focus{outline:none; border-color:var(--navy-900);
  box-shadow:0 0 0 3px rgba(245,183,69,.4)}
.unit-opts{display:flex; flex-wrap:wrap; gap:var(--sp-2)}
.unit-opts button{font-family:var(--f-mono); font-size:.75rem; letter-spacing:.04em;
  padding:9px 14px; border:1px solid var(--n-300); background:#fff; border-radius:var(--r-chip);
  cursor:pointer; color:var(--t-body); transition:all var(--d-fast) var(--ease-std)}
.unit-opts button:hover{border-color:var(--navy-400)}
.unit-opts button[aria-pressed="true"]{background:var(--navy-900); border-color:var(--navy-900); color:#fff}

/* ============================================================
   FOOTER
   ============================================================ */
.ftr{background:var(--s-inv-deep); color:var(--navy-300); padding-block:var(--sp-16) var(--sp-8)}
.ftr-grid{display:grid; grid-template-columns:1fr; gap:var(--sp-10)}
.ftr h2.ftr-h,.ftr h4{font-size:.75rem; font-family:var(--f-mono); letter-spacing:.08em;
  text-transform:uppercase; color:var(--amber-400); font-weight:500; margin-bottom:var(--sp-4)}
.ftr ul{display:flex; flex-direction:column; gap:var(--sp-3)}
.ftr a{font-size:.9375rem; transition:color var(--d-fast) var(--ease-std)}
.ftr a:hover{color:#fff}
.ftr .logo{margin-bottom:var(--sp-5)}
.ftr .logo img{height:54px}
.ftr address{font-style:normal; font-size:.9375rem; line-height:1.7}
.ftr-btm{margin-top:var(--sp-12); padding-top:var(--sp-6);
  border-top:1px solid rgba(255,255,255,.1); display:flex; flex-wrap:wrap;
  gap:var(--sp-4); justify-content:space-between; align-items:center}

/* ============================================================
   STICKY MOBILE CTA
   ============================================================ */
.sticky{position:fixed; left:0; right:0; bottom:0; z-index:90; background:#fff;
  border-top:1px solid var(--n-200); box-shadow:var(--e2); padding:var(--sp-3) var(--sp-4);
  display:none; gap:var(--sp-3); transform:translateY(110%);
  transition:transform var(--d-slow) var(--ease-std)}
.sticky.up{transform:translateY(0)}
.sticky .btn{flex:1; min-height:46px; padding-inline:var(--sp-3)}

/* ============================================================
   REVEAL  ·  fires once, below the fold only
   ============================================================ */
/* Reveal is an ENHANCEMENT, not a precondition. .rv is visible by default;
   only a page that has confirmed its script is running (html.rv-ready, set
   synchronously in <head>) hides it to animate it in. If site.js fails to
   load, errors, or is blocked, the content is simply there.

   This used to be the other way round, and everything below the fold —
   including the whole products section, which sits ~4000px down — was
   invisible until an IntersectionObserver callback said otherwise. One
   observer that never fires and the page ships blank. */
.rv-ready .rv{opacity:0; transform:translateY(12px);
  transition:opacity var(--d-slower) var(--ease-out), transform var(--d-slower) var(--ease-out)}
.rv-ready .rv.in{opacity:1; transform:none}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (min-width:600px){
  .wrap{padding-inline:var(--sp-6)}
  .cards{grid-template-columns:1fr 1fr}
  .trust ul{grid-template-columns:repeat(4,1fr)}
  .steps{grid-template-columns:1fr 1fr}
}
@media (min-width:1000px){
  .wrap{padding-inline:var(--sp-8)}
  .sec{padding-block:var(--sp-24)}
  .hero{padding-block:var(--sp-20) var(--sp-24)}
  .hero-grid{grid-template-columns:1.15fr .85fr; gap:var(--sp-16); align-items:center}
  /* A hero with no picture takes the whole width rather than leaving the
     right-hand track standing empty. */
  .hero-grid.hero-solo{grid-template-columns:1fr}
  .cards{grid-template-columns:repeat(4,1fr)}
  /* Three-up for the lists that hold six or nine. Four columns leaves nine as
     4+4+1 and six as 4+2; both divide evenly by three. Applied per list rather
     than globally because the four- and eight-card lists are already even at
     four, and three would strand them instead. */
  .cards.cards-3{grid-template-columns:repeat(3,1fr)}
  .bulk-grid{grid-template-columns:1.1fr .9fr; gap:var(--sp-20); align-items:center}
  .bulk-stats{grid-template-columns:1fr 1fr}
  .why{grid-template-columns:1fr 1fr; column-gap:var(--sp-12)}
  .steps{grid-template-columns:repeat(4,1fr); gap:var(--sp-8)}
  .proof{grid-template-columns:1.7fr 1fr; gap:var(--sp-5); align-items:stretch}
  /* stretch, not start: the panel and the heading block then share one row
     height instead of each sizing itself, so the box ends level with the copy
     beside it. .mapcard's 280px floor is dropped here only, since the contact
     page and the quote component still rely on it. */
  .zones{grid-template-columns:1.05fr 1fr; gap:var(--sp-12) var(--sp-16); align-items:stretch}
  .zones .mapcard{min-height:0}
  /* Four columns and sixteen areas: four exact rows, and each row happens to
     be one geographic band — north, central-west, south-central, then the OMR
     corridor. (Briefly five columns when the list was ten; back to four.) */
  .zonelist{grid-column:1 / -1; grid-template-columns:repeat(4,1fr)}
  /* .sec-hd + * adds a flow margin meant for stacked content. The panel is now
     a grid sibling of the heading, where the row gap already does that job, so
     the margin just pushed the panel 16px below the heading's top edge. */
  .zones .sec-hd + *{margin-top:0}
  .final-grid{grid-template-columns:1fr .8fr; gap:var(--sp-16)}
  .ftr-grid{grid-template-columns:1.4fr 1fr 1fr 1fr}
  .record-inner{padding:var(--sp-8)}
  /* content rails */
  .rails{position:relative}
  .rails::before,.rails::after{content:""; position:absolute; top:0; bottom:0; width:1px;
    background:var(--n-200); z-index:0}
  .rails::before{left:calc(50% - var(--c-content)/2 + var(--sp-8))}
  .rails::after{right:calc(50% - var(--c-content)/2 + var(--sp-8))}
}
@media (max-width:999px){
  .nav{display:none}
  .hdr-tel{display:none}
  .burger{display:flex}
  .sticky{display:flex}
  body{padding-bottom:76px}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important}
  .rv-ready .rv{opacity:1; transform:none}
}

/* ============================================================
   ADDITIONS FOR THE MULTI-PAGE BUILD
   Breadcrumbs (spec 1.2) and the single-column page hero used by
   every page except the homepage, whose hero carries the
   two-column deployment-record panel.
   ============================================================ */

.crumbs{padding:1.1rem 0 0;color:var(--t-muted);font-size:.78rem;letter-spacing:.02em;display:flex;gap:.5rem;align-items:center}
.crumbs a{color:var(--t-muted);text-decoration:none;border-bottom:1px solid transparent}
.crumbs a:hover{color:var(--navy-900);border-bottom-color:var(--amber-400)}
.crumbs span[aria-hidden]{color:var(--n-300)}
.crumbs>span:last-child{color:var(--t-head);font-weight:600}

.hero.hero-page{padding-block:2.4rem 3.4rem}
.hero.hero-page .stack{max-width:56rem}
.hero.hero-page .t-h1{max-width:44rem}

/* Prose blocks inside answer-first / trust pages */
.prose.t-lead p{margin:0 0 1rem}
.prose.t-lead p:last-child{margin-bottom:0}
.prose ul{margin:.4rem 0 1rem;padding-left:1.1rem}
.prose li{margin:.3rem 0}
.prose h3{margin:1.6rem 0 .5rem}

/* Two-column feature list used on device / hub pages */
.pairs{display:grid;gap:1.5rem 2.5rem;grid-template-columns:repeat(auto-fit,minmax(min(17rem,100%),1fr));list-style:none;margin:0;padding:0}
/* align-content:start, because each item is itself a grid and the row makes
   every item as tall as the tallest one. Left to stretch, a shorter item
   shares that extra height between its title row and its text row, so its
   text starts lower than its neighbour's and the row loses its baseline.
   Top-aligned, the spare height falls to the bottom where it belongs. */
.pairs li{display:grid;gap:.3rem;align-content:start}
.pairs b{color:var(--t-head);font-size:1.02rem}
.pairs span{color:var(--t-body);font-size:.94rem;line-height:1.55}

/* Photo slot on an inverted panel. The default well is a light warm gradient
   with grey grid lines, which sits on the navy like a hole punched in it.
   This inverts the same three parts — ground, grid, tag — so the placeholder
   reads as part of the panel rather than as a missing image. */
.inv .photoslot-inner{background:rgba(255,255,255,.04); border-color:rgba(255,255,255,.14)}
.inv .photoslot-grid{background-image:
    linear-gradient(rgba(255,255,255,.10) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.10) 1px,transparent 1px)}
.inv .photoslot-tag{color:var(--t-inv-muted); background:rgba(255,255,255,.06);
  border-color:rgba(255,255,255,.18)}
.inv .photoslot figcaption{color:var(--t-inv-muted)}

/* ── Three "In short" treatments ──────────────────────────────────────────
   The product pages all open with the same picture-beside-cards block. These
   three pages each get their own, because each is doing a different job and
   the shared block was flattening that difference.

   1. CHANNELS (Contact). The four items there are a phone number, a WhatsApp
   link and an email — actions, not facts, and they were set as small links
   inside card text. Here the whole row is the target: value large, note quiet,
   arrow on the right. Rows rather than a grid because there is a best channel
   for a given need, and a list can be read in priority order. */
.channels{display:grid; gap:var(--sp-3); margin:0; padding:0; list-style:none}
.channel{display:grid; grid-template-columns:1fr auto; align-items:center;
  gap:var(--sp-4); padding:var(--sp-5) var(--sp-6); text-decoration:none;
  background:var(--n-0); border:1px solid var(--n-200); border-radius:var(--r-card);
  transition:border-color var(--d-fast) var(--ease-std),
             transform var(--d-fast) var(--ease-std), box-shadow var(--d-fast) var(--ease-std)}
.channel:hover{border-color:var(--navy-400); transform:translateY(-1px); box-shadow:var(--e1)}
.channel-label{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.1em;
  text-transform:uppercase; color:var(--t-muted); display:block; margin-bottom:.35rem}
.channel-value{font-family:var(--f-display); font-size:clamp(1.125rem,1rem + .5vw,1.5rem);
  font-weight:600; color:var(--t-head); display:block; line-height:1.2}
.channel-note{font-size:.875rem; color:var(--t-body); display:block; margin-top:.3rem}
/* Amber fill with navy on it — amber text on white is 1.9:1 and fails. */
.channel-go{width:40px; height:40px; border-radius:var(--r-pill); flex:none;
  display:grid; place-items:center; background:var(--n-100); color:var(--navy-900);
  transition:background var(--d-fast) var(--ease-std), transform var(--d-fast) var(--ease-std)}
.channel:hover .channel-go{background:var(--amber-400); transform:translateX(3px)}
@media (min-width:700px){ .channels{grid-template-columns:1fr 1fr} }

/* 2. SCALE PANEL (Bulk). One inverted blueprint panel carrying all four points
   instead of four separate cards. The page is about one batch arriving as one
   thing, so the section is one object, not four. Reuses .inv/.bp, the panel
   treatment the bulk strip and final CTA already use. */
.scalepanel{display:grid; gap:var(--sp-8); padding:clamp(1.75rem,1rem + 3vw,3rem);
  border-radius:var(--r-media); position:relative; overflow:hidden}
.scalepanel > *{position:relative; z-index:1}
/* The blueprint layer must stay out of flow. `.scalepanel > *` above was
   turning it into a relative grid child, so it took a zero-height row and the
   grid gap after it pushed the content 32px down — the panel looked padded at
   the top and cramped at the bottom despite symmetric padding. */
.scalepanel > .fill{position:absolute; inset:0; z-index:0}
.scalepoints{display:grid; gap:var(--sp-6) var(--sp-10); margin:0; padding:0; list-style:none}
.scalepoints li{display:grid; gap:var(--sp-2)}
.scalepoints b{color:var(--t-inv); font-size:1.0625rem; font-family:var(--f-display); font-weight:600}
.scalepoints span{color:var(--t-inv-muted); font-size:.9375rem; line-height:1.6}
/* A hairline above each point instead of a card border: the divisions are
   inside one object rather than between four. */
.scalepoints li{padding-top:0}

/* Motion on this panel rather than icons. Icons here would be four more
   placeholder assets and would turn the panel into icon-heading-text, which is
   the template shape the rest of the page is deliberately avoiding.

   The rule the four points share is the batch arriving as one thing, so the
   hairline above each draws left-to-right in sequence and the text follows it
   in. Staggering inside a single list is the point; a uniform fade on every
   section is the thing worth avoiding, and this site already has one.

   Visible is the DEFAULT state. The animation only plays when .in arrives, so
   if the observer never fires — a hidden tab, a headless render, JS off — the
   panel still ships with its content on screen rather than blank. */
/* Inline SVG rather than image files: they take the panel's own colour, stay
   sharp at any size and add no requests. Amber on navy is 8.4:1, and these are
   line marks at 20px, so they read as marks rather than as the rounded-square
   icon tiles the card template uses. aria-hidden — each one restates the
   heading beside it. */
/* Scoped to beat .scalepoints span, which also matches this element — the icon
   wrapper is a span, so the looser selector was painting it body-grey.
   The icon now sits ABOVE the rule, so it carries the rule on its own bottom
   edge and the row's top border comes off. Order down the column: mark, line,
   heading, text. */
.scalepoints .scalepoint-ico{display:block; color:var(--amber-400); position:relative;
  padding-bottom:var(--sp-4); margin-bottom:var(--sp-4)}
.scalepoint-ico svg{display:block; width:28px; height:28px}
.scalepoints .scalepoint-ico::after{content:""; position:absolute; inset:auto 0 0 0;
  height:1px; background:rgba(255,255,255,.16)}

/* The drawn amber rule rides the icon's lower edge now, not the row's top, so
   the mark reads first and the line arrives under it. */
.scalepoints li{position:relative}
.scalepoints .scalepoint-ico::before{content:""; position:absolute; inset:auto 0 0 0;
  height:1px; z-index:1;
  background:var(--amber-400); transform:scaleX(0); transform-origin:left}
.in .scalepoints .scalepoint-ico::before{
  animation:rule-draw .55s var(--ease-out) forwards;
  animation-delay:calc(var(--i,0) * 90ms)}
@keyframes rule-draw{to{transform:scaleX(1)}}

.in .scalepoints li > b,.in .scalepoints li > span{
  animation:point-in .5s var(--ease-out) backwards;
  animation-delay:calc(var(--i,0) * 90ms + 120ms)}
@keyframes point-in{from{opacity:0; transform:translateY(6px)}}

@media (prefers-reduced-motion:reduce){
  .scalepoints .scalepoint-ico::before{transform:scaleX(1); animation:none}
  .in .scalepoints li > b,.in .scalepoints li > span{animation:none}
}
@media (min-width:800px){ .scalepoints{grid-template-columns:repeat(4,1fr)} }

/* 3. LEAD POINT (Service Areas). The first point is the page's actual promise —
   named zones rather than the whole city — so it is set large and the other
   three support it. A uniform 2x2 gave all four equal weight and buried it. */
.leadpoints{display:grid; gap:var(--sp-6); margin:0; padding:0; list-style:none}
.leadpoints li{display:grid; gap:var(--sp-2)}
.leadpoints b{color:var(--t-head); font-size:1.02rem}
.leadpoints span{color:var(--t-body); font-size:.94rem; line-height:1.55}
.leadpoints li:first-child b{font-family:var(--f-display); font-weight:600;
  font-size:clamp(1.25rem,1.05rem + .8vw,1.75rem); line-height:1.25}
.leadpoints li:first-child span{font-size:1.0625rem; line-height:1.6; color:var(--t-body)}
@media (min-width:800px){
  .leadpoints{grid-template-columns:repeat(3,1fr); gap:var(--sp-8) var(--sp-10)}
  /* The lead spans the full row above the three that support it. */
  .leadpoints li:first-child{grid-column:1 / -1; max-width:46rem;
    padding-bottom:var(--sp-6); border-bottom:1px solid var(--n-200)}
}

/* Picture matched to the block beside it. The default photo slot carries its
   own aspect ratio, so on the contact page it ran ~140px taller than the
   address panel and the two columns finished at different lines. Here the
   panel sets the height and the slot fills it — caption included, so both
   columns end together. */
@media (min-width:900px){
  .mp-match > .photoslot{align-self:stretch; grid-template-rows:1fr auto}
  .mp-match > .photoslot .photoslot-inner{aspect-ratio:auto; height:100%; min-height:9rem}
}

/* ── Product grid ─────────────────────────────────────────────────────────
   Picture-led: the well is the card, with the name and spec sitting under it
   rather than inside a bordered box. Deliberately not another bordered card —
   this page already carries several, and a product list reads better as a
   catalogue of images than as one more set of boxes.

   Three across is the target (six models make two clean rows); auto-fit keeps
   it responsive without a second breakpoint. */
.products{display:grid; gap:var(--sp-6) var(--sp-5); margin:0; padding:0; list-style:none;
  grid-template-columns:repeat(auto-fit,minmax(min(15rem,100%),1fr))}
.product{display:grid; gap:var(--sp-1); align-content:start}
.product .cardmedia{margin-bottom:var(--sp-3)}
.product b{font-family:var(--f-display); font-weight:600; font-size:1.0625rem;
  color:var(--t-head); line-height:1.3}
.product-spec{font-family:var(--f-mono); font-size:.75rem; letter-spacing:.04em;
  color:var(--t-muted); line-height:1.5}
/* The well lifts on hover so the whole tile reads as one object, even though
   only the picture has a border. */
.product .cardmedia{transition:border-color var(--d-fast) var(--ease-std),
  transform var(--d-fast) var(--ease-std)}
.product:hover .cardmedia{border-color:var(--navy-400); transform:translateY(-2px)}
/* Four models, one row. auto-fit alone would drop to three at this width, so
   the count is stated once the viewport can carry it. */
@media (min-width:1000px){ .products{grid-template-columns:repeat(4,1fr)} }
@media (prefers-reduced-motion:reduce){ .product .cardmedia{transition:none} }

/* ── Step cards ───────────────────────────────────────────────────────────
   Structure from the reference: a mono step label with a leading arrow, a
   solid square icon tile, a mono uppercase title, then the copy. Four across,
   hairline borders, light ground.

   The reference fills its tile with its own blue and puts a white glyph on it.
   Ours uses the navy for the same job — the amber cannot carry a white glyph
   (2.0:1) and amber text on white is 1.9:1, so amber stays as the arrow and
   the hover accent where it sits on navy or beside navy, never as small marks
   on white. */
.steps4{display:grid; gap:var(--sp-4); margin:0; padding:0; list-style:none}
.steps4 li{display:grid; gap:var(--sp-4); align-content:start;
  background:var(--n-0); border:1px solid var(--n-200); border-radius:var(--r-card);
  padding:var(--sp-6);
  transition:border-color var(--d-fast) var(--ease-std), box-shadow var(--d-fast) var(--ease-std)}
.steps4 li:hover{border-color:var(--navy-400); box-shadow:var(--e1)}
.step4-label{display:flex; align-items:center; gap:var(--sp-2);
  font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.12em;
  text-transform:uppercase; color:var(--t-muted)}
/* The arrow matches the label rather than taking the reference's accent
   colour. Every amber on the ramp fails as small text on white — the darkest,
   amber-700, measures 4.48:1 against the 4.5:1 minimum. Amber earns its place
   on this card as the navy tile's hover, not as an 11px glyph. */
.step4-label::before{content:"\2192"; color:currentColor}
.step4-ico{width:40px; height:40px; border-radius:var(--r-btn); flex:none;
  display:grid; place-items:center; background:var(--s-inv); color:#fff;
  transition:background var(--d-fast) var(--ease-std)}
.steps4 li:hover .step4-ico{background:var(--navy-800)}
.step4-ico svg{display:block}
.steps4 b{display:block; font-family:var(--f-mono); font-size:.8125rem;
  letter-spacing:.08em; text-transform:uppercase; color:var(--navy-900); line-height:1.4}
.steps4 span.step4-body{display:block; font-size:.9375rem; line-height:1.6; color:var(--t-body)}
@media (min-width:700px){ .steps4{grid-template-columns:1fr 1fr} }
@media (min-width:1000px){ .steps4{grid-template-columns:repeat(4,1fr); gap:var(--sp-5)} }
@media (prefers-reduced-motion:reduce){ .steps4 li,.steps4 li:hover{transition:none} }

/* ── Ruled list ───────────────────────────────────────────────────────────
   A second texture for label-and-explanation content, so a page does not run
   the same 2x2 card grid three times over. Every one of these pages had two or
   three identical card blocks; alternating card grid and ruled list gives the
   scroll a beat instead of one repeated shape.

   Full-bleed rows, label left, explanation right, separated by hairlines. It
   reads denser and quieter than cards, which is the point: the card sections
   stay the emphatic ones. Same type scale as .pairs so it is a change of
   texture, not of voice. */
.ruled{display:grid; gap:0; margin:0; padding:0; list-style:none}
.ruled li{display:grid; gap:var(--sp-1) var(--sp-10); padding-block:var(--sp-5);
  border-top:1px solid var(--n-200)}
.ruled li:last-child{border-bottom:1px solid var(--n-200)}
.ruled b{color:var(--t-head); font-size:1.02rem}
.ruled span{color:var(--t-body); font-size:.94rem; line-height:1.55}
@media (min-width:800px){
  /* The label column is capped rather than proportional so the explanations
     start on the same line down the whole list, whatever the label lengths. */
  .ruled li{grid-template-columns:minmax(11rem,.34fr) 1fr; align-items:baseline;
    padding-block:var(--sp-6)}
}

/* A section that continues the thought above it rather than opening a new one.
   The page rhythm was a flat 128px between everything; this gives the scroll
   tight-then-generous instead of one metronome. */
.sec.sec-tight{padding-block:var(--sp-16)}
@media (min-width:1000px){ .sec.sec-tight{padding-block:var(--sp-20)} }

/* Fixed 2x2 for the four-item lists. The base grid is auto-fit, which packs in
   as many 17rem columns as the width allows — at these widths that is three,
   stranding the fourth item alone on a second row. Four items want two even
   rows, and asking for two columns outright is the only way to say so:
   auto-fit decides by measurement, not by item count. */
.pairs.pairs-2{grid-template-columns:1fr}
@media (min-width:700px){
  .pairs.pairs-2{grid-template-columns:repeat(2,1fr); gap:1.75rem 3rem}
}

@media (max-width:700px){
  .hero.hero-page{padding-block:1.6rem 2.4rem}
}

/* Comparison tables in guides (spec T7 asks for data/tables where possible) */
.tbl{width:100%;border-collapse:collapse;font-size:.94rem;margin:.4rem 0}
.tbl caption{text-align:left;color:var(--t-muted);font-size:.8rem;letter-spacing:.02em;padding-bottom:.6rem}
.tbl th,.tbl td{text-align:left;padding:.7rem .9rem;border-bottom:1px solid var(--n-200);vertical-align:top}
.tbl thead th{background:var(--navy-900);color:var(--t-inv);font-weight:600;font-size:.82rem;letter-spacing:.03em;text-transform:uppercase;border-bottom:none}
.tbl tbody tr:last-child td{border-bottom:none}
.tbl th[scope="row"]{color:var(--t-head);font-weight:600;white-space:nowrap}
.tblwrap{overflow-x:auto;border:1px solid var(--n-200);border-radius:10px;background:var(--n-0)}

/* ============================================================
   LAUNCH-QA FIXES
   ============================================================ */

/* Images never overflow their container. Scoped rules such as
   .logo img{height:34px} are more specific and still win. */
img{max-width:100%; height:auto}

/* Footer column headings are h2 (they followed an h2 as h4, skipping a
   level — WCAG 1.3.1). Visual weight is unchanged. */

@media (max-width:700px){
  /* WCAG 2.5.8: give stacked link lists a >=44px tap target on touch,
     without altering desktop layout. */
  .ftr ul a,.nav a{display:inline-block; min-height:44px; line-height:44px}
  .crumbs{min-height:44px}
}

/* ── Multi-step lead form ─────────────────────────────────────── */
.fstep{border:0; margin:0; padding:0; min-width:0}
.fstep[hidden]{display:none}
.field-legend{font-size:.95rem; font-weight:600; color:var(--t-head); padding:0; margin:0 0 .6rem}

/* Unit choice: real radios, styled as the original segmented control */
.unit-opts label{position:relative; display:block}
.unit-opts input[type=radio]{position:absolute; inset:0; width:100%; height:100%; margin:0; opacity:0; cursor:pointer}
.unit-opts input[type=radio]+span{display:block; text-align:center; padding:.62rem .5rem; border:1px solid var(--n-300);
  border-radius:8px; font-family:var(--f-mono); font-size:.82rem; color:var(--t-body); background:var(--n-0);
  transition:border-color var(--d-fast) var(--ease-std), background var(--d-fast) var(--ease-std)}
.unit-opts input[type=radio]:checked+span{border-color:var(--navy-900); background:var(--navy-900); color:var(--t-inv)}
.unit-opts input[type=radio]:focus-visible+span{outline:2px solid var(--navy-900); outline-offset:2px}
.unit-opts label:hover input:not(:checked)+span{border-color:var(--navy-400)}

.formcard select{width:100%; padding:.7rem .8rem; border:1px solid var(--n-300); border-radius:8px;
  font:inherit; font-size:.95rem; color:var(--t-body); background:var(--n-0); appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235C6478' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right .7rem center}
.formcard select:focus-visible,.formcard input:focus-visible{outline:2px solid var(--navy-900); outline-offset:1px}
.formcard input:user-invalid,.formcard select:user-invalid{border-color:#B4232A}

/* Honeypot — removed from view and from the a11y tree, but still submitted */
.hp{position:absolute!important; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap}

.formmsg{margin:.2rem 0 0; padding:.7rem .85rem; border-radius:8px; font-size:.9rem; line-height:1.5}
.formmsg[data-state="ok"]{background:#E8F5EC; color:#1B5E33; border:1px solid #B7DFC4}
.formmsg[data-state="err"]{background:#FDECEC; color:#8E1B21; border:1px solid #F3C2C4}
.formmsg[data-state="unset"]{background:var(--amber-50); color:var(--amber-700); border:1px solid var(--amber-200)}

/* ============================================================
   MOBILE NAVIGATION
   Below 1000px the header nav was display:none and the burger had no
   handler, so tablet and phone visitors had no primary navigation at
   all. The nav becomes a real slide-in panel instead.
   ============================================================ */
.nav-extra,.nav-tel,.nav-roi{display:none}
.burger-close{display:none}
.nav-scrim{position:fixed; inset:0; z-index:78; background:rgba(11,16,41,.55);
  opacity:0; transition:opacity var(--d-base) var(--ease-std)}
.nav-scrim[hidden]{display:none}
.nav-scrim.on{opacity:1}

@media (max-width:999px){
  /* backdrop-filter on .hdr makes the header a containing block for
     position:fixed descendants, which pinned the nav panel inside the
     header box: top:0 started it below the header and 100dvh then
     overflowed the bottom. The header is rgba(255,255,255,.92), so the
     blur is barely visible — dropping it on mobile costs nothing and
     lets the panel resolve against the viewport. */
  .hdr{backdrop-filter:none}

  /* The panel itself */
  .nav{
    display:flex; flex-direction:column; align-items:stretch; gap:0;
    position:fixed; top:0; right:0; z-index:80;
    width:min(20rem,86vw); height:100dvh;
    /* --hdr-h is measured in site.js: the header is sticky and sits above
       the panel, and its height changes with the preview strip, so a
       hard-coded pad left the close button overlapping the first links. */
    padding:calc(var(--hdr-h, 5.2rem) + 0.75rem) 1.25rem 1.5rem;
    background:var(--n-0); box-shadow:-8px 0 40px rgba(11,16,41,.18);
    overflow-y:auto; overscroll-behavior:contain;
    transform:translateX(100%); visibility:hidden;
    /* Hide only after the slide finishes; reveal instantly on open, or
       focus() lands on a still-hidden subtree and is silently dropped. */
    transition:transform var(--d-base) var(--ease-out), visibility 0s linear var(--d-base);
  }
  .nav.open{transform:translateX(0); visibility:visible;
    transition:transform var(--d-base) var(--ease-out), visibility 0s linear 0s}
  .nav a{
    display:flex; align-items:center; min-height:48px; padding:.35rem .25rem;
    font-size:1rem; color:var(--t-head); border-bottom:1px solid var(--n-100);
  }
  .nav a[aria-current="page"]{color:var(--amber-700); font-weight:600}

  /* In the slide-in panel there is nowhere to float a menu to, so Products
     becomes an inline accordion: the eight links push the rest of the list
     down instead of covering it. */
  .navitem{display:block}
  .nav-toggle{
    display:flex; align-items:center; justify-content:space-between; width:100%;
    min-height:48px; padding:.35rem .25rem; font-size:1rem;
    color:var(--t-head); border-bottom:1px solid var(--n-100);
  }
  .nav-toggle::after{display:none}
  .navmenu{display:grid}
  .navmenu a{padding-left:1rem; font-size:.9375rem; color:var(--t-body)}
  .navmenu a::before{content:""; width:3px; align-self:stretch; margin-right:.75rem;
    margin-block:.5rem; background:var(--n-200); border-radius:2px}
  .navmenu a[aria-current="page"]::before{background:var(--amber-400)}

  .nav-extra,.nav-tel,.nav-roi{display:flex}
  .nav-tel{margin-top:.9rem; border-bottom:0; color:var(--navy-600); font-size:.9rem}
  /* Matches .nav-tel rather than the pill in the header bar: inside the panel
     these are list rows, not buttons. */
  .nav-roi{margin-top:.35rem; border:0; background:none; padding:0; cursor:pointer;
    color:var(--navy-600); font-size:.9rem; text-align:left; font-weight:600}
  .nav-roi:focus-visible{outline:2px solid var(--navy-900); outline-offset:3px}
  .burger[aria-expanded="true"] .burger-open{display:none}
  .burger[aria-expanded="true"] .burger-close{display:flex}
  /* Burger must sit above the panel so it can close it */
  .burger{position:relative; z-index:82; width:48px; height:48px; align-items:center; justify-content:center}
  body.nav-lock{overflow:hidden}
}

/* ============================================================
   TAP TARGETS & SMALL TEXT ON TOUCH
   Checklist asks 48x48 targets and no sub-16px reading text on mobile.
   Micro-labels (.mono eyebrows, table captions) stay smaller than body
   copy by design, but the floor is raised so nothing sits at 10-11px on
   a phone.
   ============================================================ */
@media (max-width:700px){
  .ftr ul a,.crumbs a{min-height:48px; line-height:48px}
  .ftr ul li{margin:0}
  .btn{min-height:48px}
  .unit-opts input[type=radio]+span{min-height:48px; display:flex; align-items:center; justify-content:center}
  .formcard input,.formcard select{min-height:48px}
  .card-go,.t-cap,.btn-data,.record-tag{font-size:.8125rem}   /* 13px floor */
  .mono,.eyebrow,.qualifier,.rows dt{font-size:.8125rem}
  .faq summary{min-height:48px}
}

/* Inline field errors (checklist 6) — beside the field, not a native bubble */
.fielderr{margin:.35rem 0 0; font-size:.85rem; line-height:1.45; color:#8E1B21; font-weight:500}
.fielderr[hidden]{display:none}
.formcard [aria-invalid="true"]{border-color:#B4232A; box-shadow:0 0 0 3px rgba(180,35,42,.12)}
.unit-opts [aria-invalid="true"]+span{border-color:#B4232A}

/* Trust signals at the conversion point (checklist 7) */
.formtrust{list-style:none; margin:.2rem 0 0; padding:0; display:grid; gap:.3rem;
  font-size:.78rem; color:var(--t-muted); line-height:1.5}
.formtrust li{position:relative; padding-left:1.05rem}
.formtrust li::before{content:""; position:absolute; left:0; top:.42em; width:.5rem; height:.5rem;
  border-radius:2px; background:var(--amber-400)}

/* ============================================================
   TEXT RESIZE TO 200% (WCAG 1.4.4)
   Buttons carried white-space:nowrap, so at 200% text their labels
   could not wrap and pushed the document wider than the viewport.
   Allow wrapping and cap widths so scaled text reflows instead.
   ============================================================ */
.btn{max-width:100%; white-space:normal; text-align:center; overflow-wrap:break-word}
.btn-data{white-space:nowrap}
.trust li,.qualifier span,.formtrust li{min-width:0; overflow-wrap:break-word}
.crumbs{flex-wrap:wrap}
.crumbs>span:last-child{overflow-wrap:break-word}
.prose,.t-lead,.ans,.card p{overflow-wrap:break-word}
.unit-opts{min-width:0}
.unit-opts input[type=radio]+span{white-space:normal}

/* Grid and flex children default to min-width:auto, i.e. their min-content
   width. At 200% text that exceeded the track, and because .final/.bulk carry
   overflow-x:hidden the surplus was clipped rather than reflowed — headings
   were cut off, which is content loss under WCAG 1.4.4. */
.hero-grid>*,.final-grid>*,.bulk-grid>*,.ftr-grid>*,.rails>*,.stack{min-width:0}
h1,h2,h3,h4,.t-h1,.t-h2,.t-h3,summary,label,legend,.field-legend{overflow-wrap:break-word}

/* ============================================================================
   DESIGN REFINEMENT PASS
   Distilled from the reference set (granola.ai, rerun.io, clay.run,
   popcorn.space, mindoo.ai, matwidget.com). What all six share:
     · a display headline far larger and tighter than ours, at a lighter weight
     · a sub-head of two short lines, never a paragraph
     · one dominant visual immediately after the hero
     · generous section rhythm and a ground that is not stark white
     · small mono/caps eyebrow labels
     · motion: staggered reveals, float, marquee, hover lift
   Everything below is additive and overrides the base layer.
   ============================================================================ */

:root{
  /* Warm off-white ground (matwidget). Navy on this is still 9.8:1. */
  --s-page:#FAF9F7;
  --s-tint:#F4F2ED;
  --s-warm:#EFEBE3;
  --ease-spring:cubic-bezier(.22,1,.36,1);
  --ease-soft:cubic-bezier(.4,0,.2,1);
  --sp-40:160px;
}
body{background:var(--s-page)}

/* ── Display type: bigger, tighter, lighter ─────────────────────────────── */
.t-h1{
  font-size:clamp(2.5rem,1.6rem + 4.4vw,4.75rem);   /* up to 76px */
  line-height:.97; letter-spacing:-.035em; font-weight:600;
  max-width:22ch;                                    /* forces short headlines */
}
.t-h2{
  font-size:clamp(1.75rem,1.25rem + 2.1vw,2.625rem); /* 28px -> 42px */
  line-height:1.04; letter-spacing:-.03em; font-weight:600; max-width:26ch;
}
.t-h3{font-size:clamp(1.1875rem,1.1rem + .45vw,1.5rem); line-height:1.2; letter-spacing:-.015em}
.t-lead{
  /* 21px read heavy next to 48px heads and 16px body. 18px still sits clearly
     above body text, so the lead keeps its rank without competing. */
  font-size:clamp(1rem,.95rem + .35vw,1.125rem);         /* 16px -> 18px */
  line-height:1.65; max-width:38ch;                      /* short sub-heads */
}
/* Body prose keeps a comfortable measure rather than the hero's short one. */
.prose{max-width:var(--c-prose)}
.prose p{line-height:1.72; margin-bottom:1.15em}

/* Two-tone headline (rerun): muted lead-in, full-strength statement. */
.t-h2 .lead-in,.t-h1 .lead-in{color:var(--t-muted); display:block; font-weight:500}
.inv .t-h2 .lead-in,.inv .t-h1 .lead-in{color:var(--navy-400)}

/* ── Rhythm: the references breathe far more than we did ────────────────── */
.sec{padding-block:var(--sp-20)}
.hero{padding-block:var(--sp-16) var(--sp-20)}
.hero.hero-page{padding-block:var(--sp-12) var(--sp-16)}
.sec-hd{max-width:52rem}
.sec-hd+*{margin-top:var(--sp-4)}
@media (min-width:1000px){
  .sec{padding-block:var(--sp-32)}
  .hero{padding-block:var(--sp-20) var(--sp-32)}
  .hero.hero-page{padding-block:var(--sp-16) var(--sp-24)}
}

/* ── Eyebrow: mono, letterspaced, with an arrow (clay / mindoo) ─────────── */
.eyebrow{font-size:.75rem; letter-spacing:.14em; text-transform:uppercase; font-weight:600}
.eyebrow.has-arrow::after{content:"→"; margin-left:.5rem; transition:transform .25s var(--ease-spring); display:inline-block}
a:hover>.eyebrow.has-arrow::after{transform:translateX(4px)}

/* ── Chunkier, softer buttons (matwidget / clay) ────────────────────────── */
.btn{border-radius:999px; font-weight:600; padding-inline:var(--sp-6); min-height:52px}
.btn-primary{box-shadow:0 1px 0 rgba(20,28,63,.12), 0 8px 20px -8px rgba(245,183,69,.75)}
.btn-primary:hover{transform:translateY(-2px); box-shadow:0 2px 0 rgba(20,28,63,.14), 0 14px 28px -10px rgba(245,183,69,.85)}
.btn,.btn-primary,.btn-ghost{transition:transform .22s var(--ease-spring), box-shadow .22s var(--ease-soft), background-color .2s var(--ease-soft)}
.btn-ghost:hover{transform:translateY(-2px)}
@media (max-width:700px){ .btn{min-height:48px} }

/* ── Cards: lift on hover, softer corners ──────────────────────────────── */
.card{border-radius:var(--r-media); transition:transform .3s var(--ease-spring), box-shadow .3s var(--ease-soft), border-color .2s linear}
.card:hover{transform:translateY(-4px); box-shadow:0 18px 40px -18px rgba(20,28,63,.28)}
.card:hover .card-go{transform:translateX(4px)}
.card-go{display:inline-block; transition:transform .25s var(--ease-spring)}

/* ============================================================================
   MOTION
   The references all move, but quietly: staggered reveals, slow float on
   decorative shapes, a marquee, hover lift. Every animation here is disabled
   wholesale under prefers-reduced-motion at the end of this block.
   ============================================================================ */

/* Reveal: was a plain fade. Now travels a little further, eases like the
   references (spring-out), and staggers children so a grid arrives in sequence
   rather than all at once. --i is set per child by site.js. */
.rv-ready .rv{opacity:0; transform:translateY(18px); will-change:opacity,transform;
  transition:opacity .7s var(--ease-soft), transform .7s var(--ease-spring);
  transition-delay:calc(var(--i,0) * 70ms)}
.rv-ready .rv.in{opacity:1; transform:none}

/* Float: slow, offset drift for decorative shapes (granola). */
@keyframes float-a{0%,100%{transform:translate3d(0,0,0) rotate(0)}50%{transform:translate3d(0,-14px,0) rotate(1.5deg)}}
@keyframes float-b{0%,100%{transform:translate3d(0,0,0) rotate(0)}50%{transform:translate3d(0,10px,0) rotate(-2deg)}}
@keyframes drift{0%{transform:translateX(-2%)}100%{transform:translateX(2%)}}
.float-a{animation:float-a 9s ease-in-out infinite}
.float-b{animation:float-b 11s ease-in-out infinite}

/* Ribbon: soft flowing colour wash behind a section (mindoo). Pure CSS. */
.ribbon{position:relative; isolation:isolate; overflow:hidden}
.ribbon::before{content:""; position:absolute; inset:-30% -10% auto -10%; height:150%; z-index:-1;
  background:
    radial-gradient(38% 44% at 18% 32%, rgba(245,183,69,.20), transparent 68%),
    radial-gradient(34% 40% at 78% 26%, rgba(35,46,82,.16), transparent 70%),
    radial-gradient(30% 36% at 52% 76%, rgba(245,183,69,.12), transparent 72%);
  animation:drift 26s ease-in-out infinite alternate}

/* Marquee: continuous strip, duplicated track, pauses on hover (clay). */
.marquee{overflow:hidden; position:relative; mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent)}
.marquee-track{display:flex; gap:var(--sp-10); width:max-content; animation:marquee 34s linear infinite}
.marquee:hover .marquee-track{animation-play-state:paused}
@keyframes marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.marquee-item{display:flex; align-items:center; gap:.55rem; white-space:nowrap;
  font-family:var(--f-mono); font-size:.8125rem; letter-spacing:.06em; text-transform:uppercase; color:var(--t-muted)}
.marquee-item svg{flex:none; color:var(--amber-600)}

/* Underline that draws in on scroll, for the hero's emphasised word. */
@keyframes draw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.t-h1 em{font-style:normal; position:relative; white-space:nowrap}
.t-h1 em::after{content:""; position:absolute; left:0; right:0; bottom:.06em; height:.14em;
  background:var(--amber-400); border-radius:2px; transform-origin:left; transform:scaleX(0)}
.in .t-h1 em::after,.t-h1.in em::after{animation:draw .9s .35s var(--ease-spring) forwards}

/* Numbered stat that counts up already exists in JS; give it presence. */
.stat-num{font-variant-numeric:tabular-nums; letter-spacing:-.02em}

@media (prefers-reduced-motion:reduce){
  .rv-ready .rv{opacity:1; transform:none; transition:none; transition-delay:0s !important}
  .float-a,.float-b,.ribbon::before,.marquee-track,.brands-track{animation:none !important}
  .t-h1 em::after{transform:scaleX(1); animation:none}
  .btn-primary:hover,.btn-ghost:hover,.card:hover{transform:none}
}

/* ── Marquee strip, stat band, photo slots ─────────────────────────────── */
.marquee{padding-block:var(--sp-5); border-block:1px solid var(--n-200); background:var(--n-0)}

/* Brand strip, on the same duplicated-track mechanism and the same
   @keyframes marquee as above, so the -50% travel lives in one place.

   Ground is --s-page, not the --n-0 the text strip uses: it sits between two
   tinted sections (About us above, the delivery section below), and stark white
   between warm greys reads as a gap rather than a band. Being the lighter band
   of the three is what separates it; the hairlines close it off.

   Slower than the text strip too — logos are taken in as a set rather than
   read word by word, so 34s felt hurried. */
.brands{overflow:hidden; position:relative; padding-block:var(--sp-6);
  border-block:1px solid var(--n-200); background:var(--s-page);
  mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
/* The spacing is margin on each slot, deliberately NOT flex `gap`: gap puts
   n-1 gaps between n slots, so half the track is one gap-width short of one
   full set and the strip jumps by gap/2 on every cycle. As a trailing margin
   each slot carries its own spacing, half the track is exactly one set, and
   -50% lands seamlessly. (The older .marquee above still has this bug — it is
   unused, so it never showed.) */
/* Own keyframes, not .marquee's -50%: the set is repeated so the strip still
   has content to show at the end of a cycle on a wide monitor, and the travel
   must equal exactly one set — n copies means -(100/n)%.

   The distance is a variable because the copy count depends on how many logos
   there are: six logos make a shorter set than eight and so need an extra
   copy. brand_marquee() counts the copies and sets --brands-travel to match,
   which is the only way the two cannot drift apart. */
@keyframes brands{from{transform:translateX(0)}
                  to{transform:translateX(var(--brands-travel,-33.3333%))}}
.brands-track{display:flex; align-items:center;
  width:max-content; animation:brands 48s linear infinite}
.brands:hover .brands-track{animation-play-state:paused}
/* Each slot is exactly as wide as its logo, so margin-right IS the gap and
   every gap is the same. A fixed 150px box did the opposite: acer filled it
   edge to edge while the Apple mark used 36px of it, leaving 57px of dead
   space on each side, so the gap either side of a narrow mark measured more
   than twice the gap between two wide ones. */
.brand{flex:none; display:flex; align-items:center; margin-right:5rem}

/* Logos arrive at every size, shape and colour. Set them to a common optical
   height and mute them at rest so the row reads as one texture instead of a
   jumble, then give the hovered one its colour back — the strip has paused by
   then. Widths are written into the markup, measured from each file. */
.brand img{display:block;
  filter:grayscale(1); opacity:.62;
  transition:filter var(--d-base) var(--ease-std), opacity var(--d-base) var(--ease-std)}
.brand img:hover{filter:none; opacity:1}

/* Empty slot: the blueprint grid the photo and card slots already use, so an
   unfilled logo looks like a slot waiting for art and not a failed request. */
.brand-ph{position:relative; display:grid; place-content:center;
  width:9.375rem; height:2.75rem; overflow:hidden;
  border:1px dashed var(--n-300); border-radius:var(--r-btn); background:var(--n-50)}
.brand-ph::before{content:""; position:absolute; inset:0; opacity:.45;
  background-image:linear-gradient(var(--n-200) 1px,transparent 1px),
                   linear-gradient(90deg,var(--n-200) 1px,transparent 1px);
  background-size:28px 28px}
.brand-ph .mono{position:relative; z-index:1; font-size:.5625rem; letter-spacing:.1em;
  text-transform:uppercase; color:var(--t-muted)}

@media (max-width:700px){
  .brands{padding-block:var(--sp-5)}
  .brand{margin-right:var(--sp-8)}
}

.statband{display:grid; gap:var(--sp-8); grid-template-columns:repeat(2,1fr)}
/* justify-items shrink-wraps the numeral and the label, then centres both in
   the column, so the number sits over its label instead of at the left end of
   it. text-align keeps the label's own lines centred when one wraps. */
.statcell{display:grid; gap:.35rem; justify-items:center; text-align:center}
/* Mirrors .stat-num exactly. The digits live in a .stat-num span, so they were
   already rendering mono at 48px; this wrapper's display font at 72px only ever
   reached the bare suffix text ("+"), which is why it came out in a different
   typeface, half again as large, and sitting high. Matching the two puts the
   suffix on the same baseline with no vertical nudge. */
.statbig{font-family:var(--f-mono); font-weight:600; letter-spacing:-.02em; line-height:1;
  font-size:clamp(2rem,1.4rem + 2.2vw,3rem); color:var(--t-head)}
.statlabel{font-size:.8125rem; letter-spacing:.1em; text-transform:uppercase; color:var(--t-muted)}
/* Columns sized to their content, with the leftover space shared out evenly.
   Four equal 1fr columns held labels of very different lengths ("units in a
   single order" vs "point of contact"), so centring inside them produced
   visually uneven gaps (95/82/111px). Auto columns plus space-between makes
   every gap identical and lands the outer text exactly on the panel padding. */
@media (min-width:760px){
  .statband{grid-template-columns:repeat(4,auto); justify-content:space-between}
}

/* ── Stat band on the inverted panel ──────────────────────────────────────
   Amber numerals on navy is 9.4:1 and is the pairing the design system
   already reached for: `.inv .stat-num{color:var(--amber-400)}` was defined
   for exactly this and had nothing using it. It has to be set on .statbig
   rather than .stat-num, because the suffix ("+") sits outside the counted
   span and would otherwise stay navy-on-navy and vanish.
   The navy sits on an inset panel rather than the section itself, so it stops
   short of the viewport edges and reads as a highlighted card instead of a
   full-bleed stripe. Radius is --r-media, the same corner the photo slots and
   post images use; the blueprint grid is clipped to it by the border-box. */
/* The inset is horizontal only.
   `.sec.statsec` beats any `.sec` padding rule wherever it sits in the file.
   padding-block:0 means the panel fills its section, so there is no light band
   above and below it; the breathing room comes from the neighbouring sections'
   own padding, which is what it looked like before the panel existed.
   The tint matches .sec-tint above it: leaving this section transparent put
   page-colour (#FAF9F7) hard against tint (#F4F2ED) and drew a visible seam
   right across the page, which is the cut that showed either side of the card. */
.sec.statsec{background:var(--s-tint); padding-block:0}
.statpanel{
  border-radius:var(--r-media);
  border:1px solid var(--navy-700);
  padding:var(--sp-12) var(--sp-10);
  overflow:hidden;
}
.statsec .statbig{color:var(--amber-400)}
.statsec .statlabel{color:var(--navy-300)}
/* The counting numeral inherits from .statbig; the rule above is kept honest
   by matching it here too, so either element can carry the colour. */
.statsec .stat-num{color:inherit}
@media (max-width:700px){ .statpanel{padding:var(--sp-8) var(--sp-6)} }

.photoslot{margin:0; display:grid; gap:.6rem}
.photoslot-inner{position:relative; aspect-ratio:var(--ratio,16/9); border-radius:var(--r-media);
  background:linear-gradient(135deg,var(--s-warm),var(--s-tint)); border:1px solid var(--n-200);
  display:grid; place-items:center; overflow:hidden}
.photoslot-grid{position:absolute; inset:0;
  background-image:linear-gradient(var(--n-200) 1px,transparent 1px),linear-gradient(90deg,var(--n-200) 1px,transparent 1px);
  background-size:36px 36px; opacity:.5}
/* A slot holding a real photograph, not a marked well: no blueprint surface,
   no dashed edge, the picture covers the box at whatever --ratio it declares. */
/* border:0, not a transparent border. A transparent one still occupies its
   1px, so an absolutely-positioned child resolving against the padding box
   landed 2px short in each axis — invisible on a cover photo, a hairline
   gutter around an embedded map. */
.photoslot.has-photo .photoslot-inner{background:none; border:0}
.photoslot-inner img{position:absolute; inset:0; display:block;
  width:100%; height:100%; object-fit:cover}
/* An embedded map fills the slot the same way a photograph does, so the
   mp-match height rule that levels this figure with the address panel keeps
   working without knowing which of the two it is holding. */
.photoslot-inner iframe{position:absolute; inset:0; display:block;
  width:100%; height:100%; border:0}
.photoslot-tag{position:relative; font-size:.75rem; letter-spacing:.1em; text-transform:uppercase;
  color:var(--t-muted); background:var(--n-0); border:1px solid var(--n-200); border-radius:999px; padding:.4rem .85rem}

/* Media pair: a visual beside a short block of copy */
.mediapair{display:grid; gap:var(--sp-8); align-items:center}
@media (min-width:900px){
  .mediapair{grid-template-columns:1.05fr .95fr; gap:var(--sp-12)}
  .mediapair.flip>*:first-child{order:2}
}
/* Top-aligned variant. The default centres both columns, which is right on the
   homepage where a short line sits beside a picture of similar height. In the
   "In short" sections the picture is much taller than the copy, so centring
   pushed the text into the middle of the column and opened a gap under the
   heading. Here the two columns start on the same line instead. */
.mediapair.mp-top{align-items:start}
/* Four pieces on a two-by-two frame:

     IN SHORT   |  Heading
     picture    |  the four cards

   Named areas rather than two stacked columns, because the two things that
   have to line up — the picture and the cards — are the SECOND item in each
   column. Stacking them would start the picture under a one-line eyebrow and
   the cards under a two-line heading, which is what left the picture sitting
   high. On a shared row they start together whatever the heading does.

   The eyebrow is end-aligned in its row so it sits just above the picture
   rather than stranded at the top of a row the heading makes tall.

   Source order is eyebrow, heading, cards, picture — so when this collapses to
   one column on a phone the words still lead and the picture follows. */
/* Only the heading steps down here — it sits beside a picture rather than
   opening the page. The cards deliberately keep the site's standard .pairs
   type: they are the same kind of content as every other card list, and a
   smaller size here made this one block read as a different component. */
.mp-cards > .t-h2{font-size:clamp(1.625rem, 1.2rem + 1.4vw, 2.375rem)}

@media (min-width:900px){
  .mediapair.mp-cards{
    grid-template-columns:.85fr 1.15fr;
    /* The label sits across the top; the picture then runs down the whole left
       side against the heading AND the cards, so its height comes out of what
       the copy actually needs. Stretching the cards to a picture-sized row was
       the mistake before — each card is a grid, so the extra height opened up
       between a card's title and its own text rather than below it. */
    grid-template-areas:"lead   lead"
                        "figure head"
                        "figure cards";
    column-gap:var(--sp-12); row-gap:var(--sp-5);
    align-items:start;
  }
  .mp-cards > .eyebrow{grid-area:lead}
  .mp-cards > .t-h2{grid-area:head}
  .mp-cards > .pairs{grid-area:cards; align-self:start}
  /* Spans both content rows and takes its height from them, so the column
     bottoms line up without either side being padded out. */
  .mp-cards > .photoslot{grid-area:figure; align-self:stretch; grid-template-rows:1fr auto;
    container-type:inline-size}

  /* height:100% is what matches the picture to the cards beside it, and on its
     own it flattens the picture without limit: the figure track keeps widening
     as the viewport grows while the cards reflow SHORTER, so a 25:18 photo sat
     in a 2.64:1 slot at 1920px and object-fit:cover discarded 47% of it.

     The amount lost also depended on how long each page's copy happened to be
     — 1% on laptop, 13% on macbook, 27% on workstation at an ordinary 1280px —
     so the same component cropped differently on every page.

     It distorts BOTH ways, which is easy to miss by only measuring wide
     screens. Just above the 900px breakpoint the figure track is narrow and
     the cards are tall, so the box went the other way — 1.00:1 on the laptop
     page against a 1.389 photo, and cover then shaved 28% off the SIDES.

     So the box is bounded at both ends, in cqw so each bound is measured
     against the figure's own width rather than the viewport's: it may still
     stretch to meet the cards anywhere between 6:5 and 16:10, and past either
     end it stops and lets the columns finish on different lines. Worst case in
     either direction is now ~13%, and identical on every page whatever the
     copy does.

     The plain 12rem line below is the fallback: a browser without container
     queries drops both cqw declarations and keeps the old behaviour, which is
     where it already was, not a new break. */
  .mp-cards > .photoslot .photoslot-inner{aspect-ratio:auto; height:100%; min-height:12rem}
  .mp-cards > .photoslot .photoslot-inner{
    min-height:max(12rem, calc(100cqw / 1.6));
    max-height:calc(100cqw / 1.2);
  }
}
/* .sec-hd+* adds a 16px flow margin meant for headings that sit above stacked
   content. Inside this column the flex gap already does that job, so the two
   were adding up: 24px of gap plus 16px of margin left 40px under the heading.
   Same trap the .zones panel hit — see the note at the 1000px breakpoint. */
.mp-top .sec-hd + *{margin-top:0}

/* Icon-only buttons must not inherit the wide text padding — it squeezed the
   WhatsApp glyph to zero width inside a 50px circle. */
.btn.btn-icon{padding-inline:0; width:52px; min-width:52px; flex:none; display:inline-grid; place-items:center}
.btn.btn-icon svg{flex:none}
@media (max-width:700px){ .btn.btn-icon{width:48px; min-width:48px} }

/* The 38ch cap belongs to hero sub-heads only. Applied globally it squeezed
   body copy into a tall thin column — worse readability, not better. Body text
   wants the 50-75 character measure. */
.t-lead{max-width:none}
.hero .t-lead{max-width:38ch}
.sec-hd .t-lead{max-width:56ch}
.prose .t-lead,.prose p{max-width:68ch}
.mediapair .t-lead{max-width:46ch}

/* .rv is declared after .card, so its transition (opacity/transform only) won
   the cascade — hover lift ran at the reveal's 0.7s and box-shadow never
   animated. Declare both properties together for cards that also reveal. */
.card,.card.rv{transition:opacity .6s var(--ease-soft),
  transform .35s var(--ease-spring), box-shadow .3s var(--ease-soft), border-color .2s linear}
@media (prefers-reduced-motion:reduce){ .card,.card.rv{transition:none} }

/* ============================================================================
   REVIEW FIXES
   ============================================================================ */

/* 1 — The vertical rails sat at the same offset as the text padding
   (both 32px), so they touched the copy instead of framing it. Move them to
   the container edge and give the content real inset, so there is ~48px of
   air between rail and first character. */
@media (min-width:1000px){
  .rails::before{left:max(var(--sp-4), calc(50% - var(--c-content)/2))}
  .rails::after{right:max(var(--sp-4), calc(50% - var(--c-content)/2))}
  .wrap{padding-inline:var(--sp-12)}
}
.rails::before,.rails::after{background:var(--n-200); opacity:.7}

/* 2 — The header phone was bare mono text crowding the amber button. Give it
   a glyph, a real tap target and a divider so it reads as a control. */
/* Icon-only call button. 44px square to match the CTA pill and the burger, so
   the right-hand cluster sits on one rhythm instead of three different heights.
   The number lives in aria-label + title, not in the layout. */
.hdr-tel{
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; flex:none; padding:0;
  border-radius:var(--r-pill); color:var(--navy-800); text-decoration:none;
  border:1px solid var(--navy-200); background:transparent;
  transition:background-color var(--d-base) var(--ease-std),
             border-color var(--d-base) var(--ease-std),
             color var(--d-base) var(--ease-std);
}
.hdr-tel::before{
  content:""; width:18px; height:18px; flex:none; background:currentColor;
  -webkit-mask:var(--ico-phone) center/contain no-repeat; mask:var(--ico-phone) center/contain no-repeat;
}
/* Same amber tint the floating buttons use, so the two call affordances feel
   like one family. Navy on amber-50 stays well past AA. */
.hdr-tel:hover{background:var(--amber-50); border-color:var(--amber-400); color:var(--navy-900)}

/* One consistent gap across the cluster; the divider carries its own breathing
   room rather than being nudged with asymmetric margins. */
.hdr-cta{gap:var(--sp-3)}
.hdr-cta::before{
  content:""; width:1px; height:24px; background:var(--n-200); margin-inline:0;
}
:root{--ico-phone:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z'/%3E%3C/svg%3E")}

/* 3 — Mobile header: logo hard left, actions hard right. They were bunched
   together against the left edge. */
@media (max-width:999px){
  .hdr .wrap{justify-content:space-between; flex-wrap:nowrap}
  .hdr-cta{margin-left:auto}
  .hdr-cta::before{display:none}
  .hdr-cta .btn-primary{padding-inline:var(--sp-4); font-size:.9rem; min-height:44px}
}
@media (max-width:420px){
  .hdr-cta .btn-primary{padding-inline:.85rem; font-size:.85rem}
}

/* 4 — Office & delivery card: background is navy but the text kept the default
   dark body colour, so it was dark-on-dark and effectively unreadable. */
.mapcard{color:var(--t-inv); padding:var(--sp-8); justify-content:flex-start; gap:var(--sp-4)}
.mapcard address,.mapcard p,.mapcard b,.mapcard span{color:var(--t-inv)}
.mapcard .t-cap{color:var(--t-inv-muted)}
.mapcard a{color:var(--amber-300)}
.mapcard a:hover{color:var(--amber-200)}
.mapcard .unconf[title]{color:var(--t-inv); border-bottom-color:var(--amber-400)}
.mapcard b{font-size:1.05rem; letter-spacing:-.01em}

/* 5 — Form: the action row floated away from the fields and the error message
   crowded the button. Tighten the stack and give the actions their own row. */
.formcard{gap:var(--sp-5)}
.formcard .fstep{display:grid; gap:var(--sp-3)}
.formact{display:flex; gap:var(--sp-3); align-items:stretch; margin-top:var(--sp-2)}
.formact .btn-primary{flex:1}
.formact .btn-ghost{flex:0 0 auto; padding-inline:var(--sp-5)}
.formmsg{margin-top:0}
.formtrust{margin-top:var(--sp-3); padding-top:var(--sp-4); border-top:1px solid var(--n-200)}

/* The new .hdr-tel rule is declared after the mobile override, so it won the
   cascade and re-showed the phone below 1000px — pushing the CTA and burger
   off screen. The panel and the sticky bar both carry a call link, so the
   header does not need one on mobile. */
@media (max-width:999px){
  .hdr-tel{display:none !important}
  .hdr .wrap{gap:var(--sp-2)}
  .logo{flex:0 0 auto}
  .hdr-cta{flex:0 0 auto}
}

/* The row used to overflow between 1000px (where the burger stops) and ~1200px,
   so the phone was hidden through that band. Making it an icon button took it
   from 157px to 44px, which is more than the shortfall — it now fits at every
   desktop width and stays visible. Only the tighter nav gap is still wanted. */
@media (min-width:1000px) and (max-width:1199px){
  .nav{gap:var(--sp-4)}
  .hdr .wrap{gap:var(--sp-4)}
}

/* The header CTA stood 52px tall around 21px of text — 31px of it empty — next
   to a 44px phone pill, so it read as oversized for the row. 44px matches that
   neighbour, matches what this same button already uses below 1000px, and is
   exactly the minimum touch target. Scoped to the header on purpose: hero,
   form and sticky-bar CTAs are page-level and stay 52px. */
/* Explicit height, not min-height: min-height:44px computes correctly here but
   the box still lays out at 51.5px, so it never wins. An explicit height does,
   and it matches how .hdr-tel is sized — phone, CTA and burger all 44px, which
   is what puts the right-hand cluster on a single line. */
.hdr-cta .btn{height:44px; min-height:0}

/* My own `.formcard .fstep{display:grid}` above has the same specificity as
   `.fstep[hidden]{display:none}` but comes later, so it re-showed all four
   hidden steps — the form rendered every step at once and shoved the action
   row 605px down the card. [hidden] must always win. */
.formcard .fstep[hidden],.fstep[hidden]{display:none !important}

/* The form sits inside .final.inv, which colours headings white for the dark
   section — but the card itself is white, so the form's own heading was
   white-on-white and invisible. */
.formcard,.formcard h1,.formcard h2,.formcard h3,.formcard h4,
.inv .formcard h3,.inv .formcard label,.inv .formcard legend{color:var(--t-head)}
.formcard .t-cap{color:var(--t-muted)}
.formcard #quoteFormTitle{font-size:.75rem; letter-spacing:.1em; text-transform:uppercase;
  color:var(--t-muted); font-family:var(--f-mono); font-weight:600}

/* Breadcrumb: the current-page link was 4.48:1, a hair under AA, and the "/"
   separator was near-invisible. */
.crumbs a{color:var(--navy-600)}
.crumbs span[aria-hidden]{color:var(--n-400)}

/* Any author `display` declaration beats the UA's [hidden]{display:none}, so
   `.btn{display:inline-flex}` kept the hidden Back button laid out and offset
   the Continue button. This bit me twice (fieldsets, then buttons) — guard the
   whole class of bug once. */
[hidden]{display:none !important}

/* Full-width primary action on narrow screens: easier to hit, and it reads as
   the obvious next step. */
@media (max-width:700px){
  .formact{flex-direction:column-reverse}
  .formact .btn-primary,.formact .btn-ghost{width:100%; flex:none}
}

/* ============================================================
   FLOATING CONTACT ACTIONS
   Call bottom-left, WhatsApp bottom-right.

   Built from this site's own vocabulary rather than generic widget shapes:
   the frosted surface of .hdr, the hairline border and amber dot of .chip,
   the mono uppercase label voice, and the skewed amber wipe that .nav a
   already uses on hover. The result should look like it grew here.

   Resting state is a light frosted disc, not a solid slab — a dark filled pill
   sitting over the page is the thing that dates these.
   ============================================================ */
.fab{
  position:fixed; bottom:var(--sp-6); z-index:75;
  display:inline-flex; align-items:center; justify-content:center;
  /* Fixed square box: nothing expands these any more, so width can be stated
     outright rather than defended with min-width and zero padding. */
  width:56px; height:56px; padding:0;
  /* overflow:hidden still earns its place: it clips the skewed amber wipe to
     the circle. */
  border-radius:var(--r-pill); overflow:hidden; isolation:isolate;
  background:rgba(255,255,255,.78);
  -webkit-backdrop-filter:blur(14px) saturate(1.5);
  backdrop-filter:blur(14px) saturate(1.5);
  border:1px solid var(--navy-200);
  color:var(--t-head);
  /* Two-part shadow: a tight contact edge plus a wide ambient pool, which
     reads as depth instead of the single grey haze of a stock FAB. */
  box-shadow:0 1px 2px rgba(20,28,63,.06),
             0 12px 30px -12px rgba(20,28,63,.34),
             inset 0 1px 0 rgba(255,255,255,.85);
  transition:bottom var(--d-slow) var(--ease-std),
             transform var(--d-base) var(--ease-over),
             box-shadow var(--d-base) var(--ease-std),
             background-color var(--d-base) var(--ease-std),
             border-color var(--d-base) var(--ease-std);
  animation:fab-in var(--d-slower) var(--ease-over) backwards;
}
.fab-call{left:var(--sp-6); animation-delay:.35s}
.fab-wa{right:var(--sp-6); flex-direction:row-reverse; animation-delay:.45s}

.fab svg{flex:none; position:relative; z-index:2;
  transition:transform var(--d-slow) var(--ease-over)}
.fab-call svg{color:var(--navy-900)}
/* WhatsApp's dark brand green: the familiar #25D366 is 2.0:1 on white, under
   the 3:1 WCAG 1.4.11 wants of a graphic carrying the control's meaning.
   #128C7E measures 4.1:1 and is still unmistakable. */
.fab-wa svg{color:#128C7E}

/* The signature: the same skewed amber wipe as .nav a:hover::after, scaled up
   to fill the disc. Sits behind the content, so navy text stays 9.4:1 on it. */
.fab::before{
  content:""; position:absolute; z-index:1;
  /* Horizontal overhang, not a uniform -1px inset. skewX(-20deg) slides the top
     edge sideways by half the height x tan(20deg) — about 10px on a 56px disc —
     which left an uncovered wedge in the corner the wipe starts from. 16px of
     overhang clears that with margin to spare, and overflow:hidden on .fab
     trims whatever spills past the circle. */
  inset:-1px -16px;
  background:var(--amber-400);
  transform:skewX(-20deg) scaleX(0); transform-origin:left center;
  transition:transform var(--d-slow) var(--ease-over);
}
.fab-wa::before{transform-origin:right center}

/* Availability dot, borrowed from .chip .dot. Tucked against the rim so it
   reads as a status light rather than a notification badge. */
.fab::after{
  content:""; position:absolute; top:11px; right:11px; z-index:3;
  width:6px; height:6px; border-radius:50%; background:var(--amber-500);
  box-shadow:0 0 0 2px rgba(255,255,255,.9);
  transition:opacity var(--d-fast) var(--ease-std);
}
.fab-wa::after{right:auto; left:11px}

.fab:hover,.fab:focus-visible{
  transform:translateY(-4px);
  border-color:var(--amber-500);
  background:rgba(255,255,255,.92);
  box-shadow:0 2px 4px rgba(20,28,63,.08),
             0 20px 40px -14px rgba(20,28,63,.42),
             inset 0 1px 0 rgba(255,255,255,.9);
}
.fab:hover::before,.fab:focus-visible::before{transform:skewX(-20deg) scaleX(1.4)}
/* The status light would sit on amber once the wipe lands — drop it there. */
.fab:hover::after,.fab:focus-visible::after{opacity:0}
.fab:hover svg,.fab:focus-visible svg{transform:scale(1.06)}
/* Navy on amber-400 is 9.4:1; the green glyph goes navy so both read cleanly. */
.fab-wa:hover svg,.fab-wa:focus-visible svg{color:var(--navy-900)}
.fab:active{transform:translateY(-1px) scale(.97)}

@keyframes fab-in{
  from{opacity:0; transform:translateY(14px) scale(.92)}
  to{opacity:1; transform:none}
}

@media (max-width:999px){
  .fab{width:52px; height:52px; bottom:var(--sp-4)}
  .fab-call{left:var(--sp-4)}
  .fab-wa{right:var(--sp-4)}
  .fab::after{top:9px; right:9px}
  .fab-wa::after{right:auto; left:9px}
  /* Clear the sticky call/quote bar once it slides up, so the two never
     overlap. 72px = its 46px button + 12px padding either side + 1px border. */
  .sticky.up ~ .fab{bottom:calc(var(--sp-4) + 72px)}
}

@media (prefers-reduced-motion:reduce){
  .fab{animation:none}
  .fab,.fab svg,.fab::before{transition:background-color var(--d-base) linear,
                                                   color var(--d-base) linear}
  .fab:hover,.fab:focus-visible,.fab:active{transform:none}
  .fab:hover::before,.fab:focus-visible::before{transform:skewX(-20deg) scaleX(1.4)}
  .fab:hover svg,.fab:focus-visible svg{transform:none}
}

/* ============================================================
   PROCESS  ·  two-column stepped list
   Left column states the section and stays put; the stages run down the
   right as full-width rows. Replaces the four-across grid, where four
   short columns made the sequence read as a menu rather than an order.

   Built from existing tokens: mono numerals in a chip (the site's data
   voice), hairline rules between rows, and an amber corner bracket that
   picks up the skewed-amber accent used on nav hover and .slash.
   ============================================================ */
.process{display:grid; gap:var(--sp-12)}

.process-intro{display:flex; flex-direction:column; align-items:flex-start; gap:var(--sp-5)}
.process-intro .t-lead{color:var(--t-muted); max-width:34ch}

/* Arrow sits in its own box so the hover nudge cannot reflow the label. */
.process-cta{display:inline-flex; align-items:center; gap:var(--sp-2);
  font-family:var(--f-mono); font-size:.75rem; font-weight:600; letter-spacing:.08em;
  text-transform:uppercase; color:var(--navy-900);
  border-bottom:2px solid var(--amber-400); padding-bottom:2px;
  transition:color var(--d-fast) var(--ease-std)}
.process-cta::after{content:"→"; display:inline-block; transition:transform var(--d-base) var(--ease-over)}
.process-cta:hover{color:var(--navy-600)}
.process-cta:hover::after{transform:translateX(4px)}

.process-steps{display:flex; flex-direction:column; margin:0; padding:0; list-style:none}

.process-step{position:relative; display:grid; grid-template-columns:auto minmax(0,1fr);
  gap:var(--sp-5); padding-block:var(--sp-8); border-top:1px solid var(--n-200);
  transition:border-color var(--d-base) linear}
.process-step:last-child{border-bottom:1px solid var(--n-200)}
.process-step:hover{border-top-color:var(--navy-300)}

/* The numeral as a chip, not loose text: it anchors the row and keeps the
   headings on one optical line down the column. */
.process-num{display:inline-grid; place-items:center; flex:none;
  width:2.75rem; height:2.75rem; border-radius:var(--r-chip);
  background:var(--n-0); border:1px solid var(--n-200);
  font-family:var(--f-mono); font-variant-numeric:tabular-nums;
  font-size:.8125rem; font-weight:600; color:var(--t-muted);
  transition:background-color var(--d-base) var(--ease-std),
             color var(--d-base) var(--ease-std),
             border-color var(--d-base) var(--ease-std)}
/* Amber fill with navy on top: 9.4:1, the sanctioned pairing. */
.process-step:hover .process-num{background:var(--amber-400); border-color:var(--amber-400);
  color:var(--t-on-amber)}

.process-body{display:flex; flex-direction:column; gap:var(--sp-2)}
.process-body p{color:var(--t-muted); font-size:.9375rem; line-height:1.65; max-width:52ch}

/* Corner bracket, drawn with two borders rather than an asset. */
.process-step::after{
  content:""; position:absolute; top:var(--sp-6); right:0; width:12px; height:12px;
  border-top:2px solid var(--amber-400); border-right:2px solid var(--amber-400);
  opacity:.55; transition:opacity var(--d-base) var(--ease-std),
                         transform var(--d-base) var(--ease-over)}
.process-step:hover::after{opacity:1; transform:translate(3px,-3px)}

@media (min-width:1000px){
  .process{grid-template-columns:minmax(0,20rem) minmax(0,1fr); gap:var(--sp-16); align-items:start}
  /* The intro holds its place while the stages scroll past it. --hdr-h is
     measured in site.js, so the sticky offset follows the real header. */
  .process-intro{position:sticky; top:calc(var(--hdr-h, 5rem) + var(--sp-8))}
}
@media (max-width:700px){
  .process-step{grid-template-columns:1fr; gap:var(--sp-3); padding-block:var(--sp-6)}
  .process-step::after{top:var(--sp-5)}
}
@media (prefers-reduced-motion:reduce){
  .process-cta::after,.process-step::after{transition:opacity var(--d-base) linear}
  .process-cta:hover::after{transform:none}
  .process-step:hover::after{transform:none}
}

/* ── "Why" cards ───────────────────────────────────────────────────────────
   Three-up card grid replacing the rule-separated two-column list. The cards
   themselves are the site's existing .card component; only the grid and the
   slash-in-the-icon-holder are new, so hover, radius, border and shadow stay
   identical to the device-category cards higher up the page. */
.whycards{display:grid; grid-template-columns:1fr; gap:var(--sp-4);
  margin:0; padding:0; list-style:none}
/* Roomier than the shared .card padding: these carry a heading and a short
   paragraph and nothing else, so 24px read tight once the icon holder came out. */
.whycards .card{padding:var(--sp-8); gap:var(--sp-4)}
/* .card sets a flex column; the heading should sit tight under the icon and
   the paragraph take the remaining height so card bottoms line up. */
.whycards .card-ico{margin-bottom:var(--sp-1)}
/* Icon slot inside the 40px holder. Sized now so dropping an <img class="why-ico">
   in place of the .slash needs no CSS change, matching how .trust-ico works. */
/* object-fit:contain because the box is square but the artwork is not: the
   default `fill` stretched a 24x18 eye to 24x24 and a 20x24 one out to square.
   The box stays 24px so every icon still sits on the same rhythm. */
.why-ico{width:24px; height:24px; display:block; flex:none; object-fit:contain}
.whycards p{margin:0; flex:1}
@media (min-width:700px){ .whycards{grid-template-columns:1fr 1fr; gap:var(--sp-5)} }
@media (min-width:1000px){ .whycards{grid-template-columns:repeat(3,1fr); gap:var(--sp-6)} }

/* About: mission and vision, two cards on one row. Same .card component and the
   same roomy padding as .whycards, because they carry the same shape — one
   heading, one paragraph, nothing else. Stops at two columns: these are wide
   cards by design, and a third would leave a hole. */
.aboutcards{display:grid; grid-template-columns:1fr; gap:var(--sp-4);
  margin:0; padding:0; list-style:none}
.aboutcards .card{padding:var(--sp-8); gap:var(--sp-4)}
/* A shade larger than the .card default: only two of these carry the section,
   so the copy is doing more work here than in a three-up grid. */
.aboutcards p{margin:0; font-size:1rem; line-height:1.6}
@media (min-width:700px){ .aboutcards{grid-template-columns:1fr 1fr; gap:var(--sp-6)} }

/* ── Card media slot ──────────────────────────────────────────────────────
   Image area at the head of each device card. Until photography exists the
   card's own icon stands in, on the same pending-photo treatment .photoslot
   uses elsewhere (warm gradient, blueprint grid, mono tag) so it reads as a
   deliberate placeholder rather than a broken image.
   To go live: replace the .cardmedia-ph span with <img src alt>. No CSS change
   needed, .cardmedia img already covers the slot. */
.cardmedia{display:block; position:relative; aspect-ratio:4/3; overflow:hidden;
  border-radius:var(--r-card); border:1px solid var(--n-200);
  background:linear-gradient(135deg,var(--s-warm),var(--s-tint));
  margin-bottom:var(--sp-2); transition:border-color var(--d-fast) var(--ease-std)}
/* <picture> needs to fill the slot too, or the img sizes against the span. */
.cardmedia picture{display:block; width:100%; height:100%}
.cardmedia img{width:100%; height:100%; object-fit:contain; display:block}
.card:hover .cardmedia{border-color:var(--navy-200)}

.cardmedia-ph{position:absolute; inset:0; display:grid; place-content:center;
  justify-items:center; gap:var(--sp-2); color:var(--navy-400); text-align:center;
  padding:var(--sp-4)}
/* Blueprint grid behind the mark, matching .photoslot-grid. */
.cardmedia-ph::before{content:""; position:absolute; inset:0; z-index:0; opacity:.45;
  background-image:linear-gradient(var(--n-200) 1px,transparent 1px),
                   linear-gradient(90deg,var(--n-200) 1px,transparent 1px);
  background-size:28px 28px}
.cardmedia-ph svg{position:relative; z-index:1; width:28px; height:28px}
.cardmedia-ph .mono{position:relative; z-index:1; font-size:.5625rem; letter-spacing:.1em;
  color:var(--t-muted); background:var(--n-0); border:1px solid var(--n-200);
  border-radius:var(--r-pill); padding:.25rem .5rem}
