/* ==========================================================================
   Auto Trim Plus — concept build by Framed Media Works
   Design system. Derived from the live site's own language:
   Orbitron display / Exo 2 body / JetBrains Mono labels / the 5px-0 diagonal
   corner, evolved into a chamfer motif. Three colours, no fourth:
   #ece9e3 paper, #b9b1a6 taupe, #424242 ink.
   ========================================================================== */

/* ---------- fonts ---------- */
@font-face{font-family:'Orbitron';src:url('fonts/orbitron-600.woff2') format('woff2');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Orbitron';src:url('fonts/orbitron-700.woff2') format('woff2');font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:'Orbitron';src:url('fonts/orbitron-800.woff2') format('woff2');font-weight:800;font-style:normal;font-display:swap}
@font-face{font-family:'Exo 2';src:url('fonts/exo-2-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Exo 2';src:url('fonts/exo-2-600.woff2') format('woff2');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'JetBrains Mono';src:url('fonts/jetbrains-mono-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'JetBrains Mono';src:url('fonts/jetbrains-mono-500.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap}

/* Cross-document view transitions. Moving between pages cross-fades instead
   of hard-cutting, which is what makes a multi-page static site feel like one
   continuous piece rather than a stack of documents. Chrome and Safari honour
   it; Firefox does not implement cross-document transitions yet and ignores
   the whole at-rule, so navigation there is exactly as it was. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){
  animation-duration:.34s;animation-timing-function:cubic-bezier(.22,1,.36,1)}
@media (prefers-reduced-motion:reduce){
  @view-transition{navigation:none}
}

/* ---------- tokens ---------- */
:root{
  /* Light palette: #ece9e3 paper, #b9b1a6 taupe, #424242 ink.
     --ink..--ink-4 is the surface ladder (paper -> band -> raised card ->
     hover); --fg..--fg-4 the text ladder (ink -> body -> muted -> faint).
     The heroes and the fleet band keep a dark ground because the footage and
     the white Sheriff cut-out need one, so there is a mirrored on-dark
     ladder for the copy that sits inside them. */
  --ink:#ece9e3; --ink-2:#dfdcd5; --ink-3:#ffffff; --ink-4:#d5d1c9;
  --foot:#1c1c1a;
  --fg:#2c2c2b; --fg-2:#424242; --fg-3:#5a544b; --fg-4:#645d4f;
  --taupe:#b9b1a6;
  --dark:#262624; --dark-2:#424242; --dark-3:#141413;
  --on-dark:#f5f4f2; --on-dark-2:#d3cec7; --on-dark-3:#b4ada4;
  /* There is no fifth colour: the accent IS the palette's ink. Solid #424242
     carries every action on paper — buttons, active chips, the map pin, the
     draw-underlines — and inverts to the paper tone on the dark blocks, where a
     #424242 fill would disappear into the ground. The taupe takes the
     decorative marks the old brand orange used to carry: labels over photos,
     the hero strip, the quote glyphs.
     The accent is #b9b1a6 rather than the ink: a solid #424242 button flipped
     to bare paper on hover, and the whole page read as black-on-white. Type
     ON the accent is always --fg, since taupe is a fill, never a text tone. */
  --accent:#b9b1a6; --accent-d:#9c9384;
  --taupe-d:#7d7364;
  /* Beige copy on the black blocks. --taupe is the button beige and carries
     the headings at 8.7:1 on #141413; --warm-2 is the same hue stepped down
     for body copy, at 6.1:1, so the pair keeps a hierarchy instead of running
     flat the way one beige at one weight does. */
  --warm-2:#9a948b;
  /* every hairline on the site — section rules, card borders, spec lists,
     the FAQ dividers — is mixed from the taupe rather than neutral grey, so
     #b9b1a6 carries the warmth of the palette structurally instead of just
     appearing as a fill somewhere. Same lightness as a neutral line, warmer
     hue. */
  --line:rgba(122,112,98,.24); --line-2:rgba(122,112,98,.42);
  --line-d:rgba(255,255,255,.13); --line-d-2:rgba(255,255,255,.26);

  /* Orbitron has no ®, ™, · or arrows. Falling through to Exo 2 (already
     loaded) keeps those glyphs on-brand instead of landing on whatever
     system face the browser picks. */
  --display:'Orbitron','Exo 2',"Arial Narrow",sans-serif;
  --body:'Exo 2',system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  /* fluid type — tracking tightens as size grows (optical correction) */
  /* 'Transformed.' is one unbreakable 12-character word, so the floor has to
     be viewport-driven or it clips on a phone — it was overflowing its
     container by 22px at 390px. Measured: the word needs ~8.94em, so the
     usable ceiling is about 8.7vw once the page gutter is taken off. */
  --t-hero:clamp(1.95rem,8.6vw,6rem);
  --t-h2:clamp(1.9rem,3.9vw,3.4rem);
  --t-h3:clamp(1.15rem,1.7vw,1.55rem);
  --t-lead:clamp(1.02rem,1.25vw,1.2rem);
  --t-body:1rem;
  --t-label:.78rem;

  /* One page gutter. These used to be 1280 / 1600, which put the header and
     hero content 109px left of every text section on a 1512px screen — it
     read as the sections being mis-centred. Wide bands stay full-bleed via
     their own backgrounds, not a wider container. */
  --shell:1280px; --shell-wide:1280px;
  --pad:clamp(1.25rem,4vw,2.5rem);
  --sec:clamp(5.5rem,10vw,10rem);
  /* the gap between two major blocks inside one section (copy above, a
     photograph or a player below) — a step down from the section padding */
  --block:clamp(3rem,5vw,4.5rem);
  --chamfer:14px;
  /* set by scrollbar() in site.js; 0 on overlay-scrollbar platforms */
  --sbw:0px;
  /* full-bleed art stops this far short of the viewport edge, so it never
     runs under the scrollbar */
  --bleed-gut:clamp(.85rem,1.6vw,1.9rem);
  --ease:cubic-bezier(.22,.61,.36,1);
  --grain-o:.027;
}

/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box}
/* Rubber-band overscroll is switched off rather than coloured in. Painting the
   canvas charcoal fixed the white flash above the hero, but it only traded one
   band for another — pull past the top and a flat black strip still opened
   above a photographic hero. With overscroll-behavior the page simply stops at
   its own edge and no strip exists to colour. The charcoal stays as the
   fallback for anything that ignores the property, since both ends of every
   page are dark: heroes at the top, footer at the bottom. */
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;background:var(--dark-3);
  overscroll-behavior-y:none}
body{margin:0;background:var(--ink);color:var(--fg-2);
  font:400 var(--t-body)/1.68 var(--body);
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  font-feature-settings:"kern" 1,"liga" 1;text-rendering:optimizeLegibility;
  /* `overflow-x:hidden` coerces overflow-y to auto, which quietly makes the
     body a scroll container. Scroll-driven animations then resolve view()
     against the body rather than the document — and since the body's own
     scrollport never scrolls, every timeline sits pinned at "finished" and the
     line reveals silently do nothing. `clip` does the same clipping without
     creating a scroller. The hidden line stays first as the fallback. */
  overflow-x:hidden;overflow-x:clip}
/* height:auto is essential — width/height attributes are on every img for CLS,
   and without this a width-constrained image keeps its attribute height and distorts.
   Rules that need a fixed box (.tile, .scard__img, hero) set height themselves and win. */
img,video,svg{display:block;max-width:100%}
img{height:auto}
/* Every work-grid tile is a <figure>, which carries a UA default
   margin:1em 40px — so each tile sat 80px narrower than its grid cell and
   the gaps between them were uneven on every gallery on the site. */
figure,blockquote{margin:0}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
h1,h2,h3,h4{margin:0;color:var(--fg);font-family:var(--display);font-weight:700;
  text-transform:uppercase;line-height:1.04;letter-spacing:-.005em;
  text-wrap:balance}                 /* even rags instead of one orphan word */
p{margin:0;text-wrap:pretty}         /* no single-word last lines */

/* a fixed header would otherwise cover whatever an in-page link jumps to */
:target,[id]{scroll-margin-top:calc(var(--hdr-h,88px) + 2rem)}
/* Skip link. Parked off-screen but still in the tab order, so it is the first
   thing a keyboard user reaches and the whole fixed nav can be jumped in one
   press. <main> carries tabindex="-1" so focus genuinely moves there rather
   than the page merely scrolling. */
/* Available to assistive tech, invisible on screen. Used where a section needs
   a heading for correct document structure but the design does not show one. */
.vh{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.skip{position:fixed;top:-6rem;left:1rem;z-index:90;
  padding:.85rem 1.4rem;background:var(--fg);color:var(--ink);
  font:700 .82rem/1 var(--display);letter-spacing:.06em;text-transform:uppercase;
  clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);
  transition:top .25s var(--ease)}
.skip:focus{top:1rem}
main:focus{outline:none}
:focus-visible{outline:2px solid var(--fg);outline-offset:3px;border-radius:2px;
  box-shadow:0 0 0 6px rgba(66,66,66,.15)}
/* The ink outline above is 1.1:1 against #141413 — invisible on exactly the
   blocks that now carry the work grids, and those tiles are focusable. */
:is(.hero,.phero,.lp-hero,.band,.section--dark) :focus-visible{
  outline-color:var(--on-dark);box-shadow:0 0 0 6px rgba(255,255,255,.14)}
/* A taupe highlight is only 1.85:1 against paper, so a selection barely
   registered and the text under it went muddy. Invert instead: ink highlight
   with paper type on the light page, and the reverse inside the dark blocks,
   so a selection is unmistakable on either ground. */
::selection{background:var(--fg);color:var(--ink)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) ::selection{background:var(--ink);color:var(--fg)}
/* the landing form is a white panel inside the dark hero — it goes back */
.lp-form ::selection{background:var(--fg);color:var(--ink)}

/* ---------- layout ---------- */
.shell{width:100%;max-width:var(--shell);margin-inline:auto;padding-inline:var(--pad);
  position:relative;z-index:1}
.shell-wide{width:100%;max-width:var(--shell-wide);margin-inline:auto;padding-inline:var(--pad);
  position:relative;z-index:1}
.section{padding-block:var(--sec);position:relative}
.section--tight{padding-block:clamp(3.5rem,6vw,6rem)}
/* Continues the lighter block started by the section above, so it carries no
   top padding of its own — the gap above comes from that section's padding.
   The bottom pad therefore has to match --sec, not a tighter value, or the
   content sits low in the lighter area (it was 151px above / 91px below). */
/* A section that ends on a ruled list.
   Two things it must not do:
   1. Borrow its top space from the previous section (padding-top:0). That
      space scrolls away, so once the block fills the viewport the heading
      tucks under the 70px fixed header while the bottom still has its full
      padding — it reads as badly off-centre even though the document-space
      gaps measure equal. It owns its top padding.
   2. Use symmetric padding, because the list's trailing row padding hangs
      below the shell box; +1.15rem puts the closing rule the same distance
      from the edge as the heading is from the top. */
.section--ruled{padding-bottom:calc(var(--sec) + 1.15rem)}
/* a faint light-fall at the top of each alternating band — stops the flat
   dark/darker stripe reading as two solid rectangles butted together.
   Photo bands are left alone; a wash over the image only muddies it. */
/* The wash was tuned against the old, much darker band. Against the new one
   it washed the top 220px back to paper and the section boundary vanished, so
   it is lighter now and a hairline carries the edge instead. */
.section.bg-2::before{content:"";position:absolute;inset:0 0 auto;height:220px;
  background:linear-gradient(rgba(255,255,255,.26),transparent);
  pointer-events:none;z-index:0}
.section.bg-2{border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
/* two bands in a row would draw a double line where they meet */
.section.bg-2 + .section.bg-2{border-top:0}
/* The same 115° hatch the fleet band runs, at a whisper. The alternating
   sections then differ in texture as well as tone, so the page reads as two
   materials rather than one field with a tint change. Sits under .shell,
   which already carries z-index:1. */
.section.bg-2::after{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:repeating-linear-gradient(115deg,
    rgba(125,115,100,.07) 0 1px, transparent 1px 13px)}
.rule{height:1px;background:var(--line);border:0;margin:0}

/* ---------- type ---------- */
.eyebrow{display:flex;align-items:center;gap:.6rem;
  /* line-height was 1: fine on one line, but 102 of these sit across the site and
     the longer ones wrap on a phone, where two tracked uppercase lines at 1.0 sat
     on top of each other. 1.4 only changes the wrapped case in any visible way. */
  font:500 var(--t-label)/1.4 var(--mono);letter-spacing:.16em;text-transform:uppercase;
  color:var(--fg);margin:0 0 1.15rem}
.eyebrow::before{content:"";width:26px;height:1px;background:var(--taupe-d);flex:none}

.h-hero{font-size:var(--t-hero);letter-spacing:-.022em;line-height:.96;font-weight:800}
.h2{font-size:var(--t-h2);letter-spacing:-.014em;line-height:1.03}
.h3{font-size:var(--t-h3);letter-spacing:.005em;line-height:1.18;font-weight:600}
.lead{font-size:var(--t-lead);line-height:1.62;color:var(--fg-2);max-width:62ch}
.muted{color:var(--fg-3)}
.accent{color:var(--fg)}
.gold{color:var(--fg)}
.prose p+p{margin-top:1.05em}
.prose{color:var(--fg-3);max-width:68ch}
.prose strong{color:var(--fg-2);font-weight:600}

/* section index number */
.idx{font:400 var(--t-label)/1 var(--mono);color:var(--fg-4);letter-spacing:.1em}

/* ---------- chamfer motif (from their 5px-0 button radius) ---------- */
.cut{clip-path:polygon(var(--chamfer) 0,100% 0,100% calc(100% - var(--chamfer)),calc(100% - var(--chamfer)) 100%,0 100%,0 var(--chamfer))}
.cut-sm{--chamfer:8px}
.cut-lg{--chamfer:22px}

/* Transparent cut-out artwork (vehicles, film diagrams).
   It carries no background of its own, so a fixed-ratio cover box just slices
   the subject — the Jeep, Supra and BMW on /wraps were all cropped mid-body,
   and the ppf-* diagrams are 2.5:1 shapes that were being forced into 3:2.
   A chamfer is wrong here too: clipping a corner off an irregular silhouette
   reads as a rendering fault. Let it size to its own ratio on the page, the
   way the fleet band's Sheriff truck does. */
.cutout{display:block;width:100%;height:auto;object-fit:contain}

/* ---- shop map ----------------------------------------------------------
   Leaflet + CARTO dark tiles, so the map reads as part of the page rather
   than a bright rectangle pasted into it. Library and tiles are only
   fetched when the section scrolls into view — see map() in site.js. */
.map{position:relative;aspect-ratio:16/10;background:var(--ink-3);
  border:1px solid var(--line);overflow:hidden}
@media (min-width:900px){.map{aspect-ratio:4/3}}
.map__canvas{position:absolute;inset:0}
/* footer variant: small, no zoom controls, just enough to place the shop */
.map--sm{aspect-ratio:4/3;max-width:300px}
/* zoom buttons stay available on the footer map — scroll-wheel zoom stays
   off so the map never swallows page scrolling */
.map--sm .leaflet-control-zoom a{width:24px;height:24px;line-height:24px;font-size:14px}
.map--sm .leaflet-control-attribution{font-size:9px;padding:1px 4px}
.ftr__dir{margin-top:.9rem;display:inline-flex}
.map__ph{position:absolute;inset:0;display:grid;place-items:center;
  font:400 .74rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--fg-4)}
.map.is-live .map__ph{display:none}
/* tone the tiles into the site palette and kill Leaflet's light chrome */
.map .leaflet-container{background:var(--ink-3);font-family:var(--body)}
/* OSM's basemap ships close to the right value for a paper page — it only
   needs the colour pulled back so the roads and parks read as tone rather
   than competing with the pin. */
.map .leaflet-tile-pane{filter:saturate(.26) brightness(1.05) contrast(.93)}
.map .leaflet-control-attribution{background:rgba(236,233,227,.84);color:var(--fg-4);
  font:400 10px/1.5 var(--mono);padding:2px 6px}
.map .leaflet-control-attribution a{color:var(--fg-3)}
.map .leaflet-control-zoom a{background:rgba(255,255,255,.92);color:var(--fg-2);
  border-color:var(--line);width:28px;height:28px;line-height:28px;font-size:16px}
.map .leaflet-control-zoom a:hover{background:var(--ink-4);color:var(--fg)}
.map .leaflet-bar{border:1px solid var(--line);box-shadow:none}
/* brand pin */
/* the ATP mark in a chamfered ink tile, echoing the site's .cut motif —
   a plain coloured dot reads as a default Leaflet marker */
.map__pin{display:grid;place-items:center;width:34px;height:34px;
  background:var(--fg);
  clip-path:polygon(7px 0,100% 0,100% calc(100% - 7px),calc(100% - 7px) 100%,0 100%,0 7px);
  box-shadow:0 6px 16px rgba(66,66,66,.34)}
/* the marker also carries this inline — Leaflet's CSS is injected after
   this file and wins the cascade at equal specificity */
.map .map__pin img{display:block;width:19px;height:auto}

/* ---- 3M colour swatches ------------------------------------------------
   These are complete reference charts (chip + name + code) at three very
   different ratios — 0.84, 2.60 and 5.49. They were previously forced into
   equal 4:3 tiles with object-fit:cover, which cropped most of the colours
   away. Show each in full at its own ratio, stacked and labelled, the way
   the live site does. Capped at 1000px because that is the native width of
   the source files — beyond it the chip codes go soft. */
.swatches{max-width:1000px;margin-inline:auto}
.swatch{margin:0}
.swatch + .swatch{margin-top:clamp(2.6rem,5vw,4.5rem)}
.swatch__label{font:800 clamp(1.05rem,1.8vw,1.45rem)/1 var(--display);
  text-transform:uppercase;letter-spacing:.07em;color:var(--fg);
  text-align:center;position:relative;padding-bottom:.95rem;margin-bottom:1.4rem}
.swatch__label::after{content:"";position:absolute;left:50%;bottom:0;
  transform:translateX(-50%);width:88px;height:2px;background:var(--taupe-d)}
.swatch img{width:100%;height:auto}

/* ---- video plate --------------------------------------------------------
   Stand-in for the 3M corporate clips we cannot self-host (they are private
   / region-locked on YouTube). Used where the page's only photo is already
   the hero, so a still would just repeat it. Swap for a real <video> once
   the client supplies the source files. */
.vplate{position:relative;aspect-ratio:16/9;background:
    radial-gradient(120% 140% at 50% 0,rgba(66,66,66,.055),transparent 60%),
    linear-gradient(180deg,var(--ink-3),var(--ink-2));
  border:1px solid var(--line);display:grid;place-items:center;overflow:hidden}
.vplate::before{content:"";position:absolute;inset:0;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),
                   linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:64px 64px;opacity:.5;
  -webkit-mask-image:radial-gradient(70% 70% at 50% 50%,#000,transparent);
          mask-image:radial-gradient(70% 70% at 50% 50%,#000,transparent)}
.vplate__in{position:relative;text-align:center;padding:var(--pad)}
.vplate__play{width:74px;height:74px;margin:0 auto;border-radius:50%;
  border:1px solid var(--line-2);display:grid;place-items:center;
  background:rgba(66,66,66,.035)}
.vplate__play svg{width:20px;height:22px;fill:var(--fg-3);margin-left:4px}
.vplate__t{font-family:var(--display);font-weight:800;text-transform:uppercase;
  letter-spacing:.04em;font-size:var(--t-h3);margin-top:1.1rem}
.vplate__s{font-family:var(--mono);font-size:.74rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--fg-4);margin-top:.6rem}
/* beside a column of copy rather than full-bleed: a placeholder should never
   be the largest thing on the page */
.vplate--aside{aspect-ratio:4/3}
.vplate--aside .vplate__play{width:58px;height:58px}
.vplate--aside .vplate__t{font-size:1.02rem}
@media (prefers-reduced-motion:no-preference){
  .vplate__play{transition:transform .5s var(--ease),border-color .5s var(--ease)}
  .vplate:hover .vplate__play{transform:scale(1.06);border-color:var(--line-2)}
}

/* ---------- buttons ---------- */
.btn{position:relative;display:inline-flex;align-items:center;gap:.6rem;
  padding:.95rem 1.7rem;font:700 .92rem/1 var(--display);letter-spacing:.045em;
  text-transform:uppercase;color:var(--fg);border:1px solid var(--line-2);
  background:transparent;overflow:hidden;isolation:isolate;
  transition:color .4s var(--ease),border-color .4s var(--ease),transform .4s var(--ease);
  clip-path:polygon(10px 0,100% 0,100% calc(100% - 10px),calc(100% - 10px) 100%,0 100%,0 10px)}
.btn::before{content:"";position:absolute;inset:0;background:var(--accent);z-index:-1;
  transform:scaleX(0);transform-origin:left;transition:transform .45s var(--ease)}
.btn:hover{color:var(--fg);border-color:var(--accent)}
.btn:hover::before{transform:scaleX(1)}
.btn--solid{background:var(--accent);border-color:var(--taupe-d);color:var(--fg)}
/* #b9b1a6 on the paper is only 1.7:1, so a borderless taupe button dissolves
   into the page — the deeper taupe edge is what makes it read as a control.
   It deepens on hover rather than flipping to the ground colour, which used
   to leave a bare white button under white type mid-transition. */
.btn--solid::before{background:var(--accent-d)}
.btn--solid:hover{color:var(--fg);border-color:var(--taupe-d)}
.btn--ghost{border-color:var(--line-2);color:var(--fg-2)}
.btn--lg{padding:1.1rem 2.1rem;font-size:1rem}
.btn__arrow{transition:transform .4s var(--ease)}
.btn:hover .btn__arrow{transform:translateX(4px)}
/* No shipped face contains an arrow glyph, so &rarr; was falling through to a
   system font and varying by platform. Draw it instead: masked SVG inherits
   currentColor and renders identically everywhere. */
.btn__arrow,.tlink span:last-child{display:inline-block;
  /* keep font-size inherited so the em sizing below still resolves — hide the
     character by indenting it out of an overflow:hidden box instead */
  overflow:hidden;text-indent:-999px;line-height:0;
  width:1.05em;height:.62em;vertical-align:-.02em;background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 12'%3E%3Cpath d='M13.4 0 12 1.4 15.6 5H0v2h15.6L12 10.6 13.4 12 20 6z' fill='%23000'/%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 12'%3E%3Cpath d='M13.4 0 12 1.4 15.6 5H0v2h15.6L12 10.6 13.4 12 20 6z' fill='%23000'/%3E%3C/svg%3E") center/contain no-repeat}
.btn .btn__arrow{width:1.15em;height:.68em}

/* ---- touch targets -----------------------------------------------------
   Measured at 390px: primary buttons came out 37px tall, footer and drawer
   links 32-38px. Both Apple and Android ask for ~44px, and on a phone the
   difference is felt rather than seen. Standalone controls only — inline
   links inside a paragraph are left alone so text flow is not wrecked. */
@media (max-width:820px){
  .btn{min-height:46px;padding-block:1.05rem}
  .ftr__grid ul a{display:inline-block;padding-block:.5rem}
  .ftr__addr a{display:inline-block;padding-block:.35rem}
  .tlink{min-height:44px}
  .drawer a{padding-block:.55rem}
  .brand{display:inline-flex;align-items:center;min-height:44px}
  .drawer__grp{font-size:.78rem}
  .chip{min-height:44px;display:inline-flex;align-items:center}
}

/* ---- scrollbar -------------------------------------------------------
   The default OS bar reads as chrome bolted onto the page. This is the same
   idea as the site's frosted surfaces: a translucent thumb over a near-invisible
   track, so it sits on the page rather than beside it. Overlay-scrollbar
   platforms (macOS trackpads) ignore all of this and keep their own. */
:root{scrollbar-width:thin;scrollbar-color:rgba(185,177,166,.42) transparent}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,rgba(185,177,166,.5),rgba(185,177,166,.32));
  border:3px solid transparent;background-clip:padding-box;border-radius:99px;
  transition:background .3s var(--ease)}
::-webkit-scrollbar-thumb:hover{
  background:linear-gradient(180deg,rgba(185,177,166,.78),rgba(185,177,166,.55));
  background-clip:padding-box}
::-webkit-scrollbar-corner{background:transparent}

/* A full-bleed image whose own ground is black, dropped flush into the bottom
   of a section that is also black — the car appears to sit on the page rather
   than inside a picture box. */
.section--flush-b{padding-bottom:0}
.bleed-img{width:100%;margin-top:clamp(2.5rem,5vw,4.5rem)}
.bleed-img img{display:block;width:100%;height:auto}

/* ---- tint shade scale -------------------------------------------------
   Built as a drawn scene rather than a photograph: five films laid over one
   continuous landscape is the only way to read them against each other, and
   an SVG stays crisp at every width and costs nothing to load. */
.shades-scene{position:relative;isolation:isolate;overflow:hidden;
  border-radius:2px;margin-top:clamp(2rem,4vw,3.2rem)}
.shades-scene svg{display:block;width:100%;height:auto}
.shades-bar{position:absolute;inset:0;display:grid;grid-template-columns:repeat(5,1fr);
  z-index:2}
.shades-bar .sh{position:relative;display:flex;align-items:center;justify-content:center;
  background:rgba(10,7,4,var(--sh));
  box-shadow:inset 1px 0 0 rgba(255,255,255,.16),inset -1px 0 0 rgba(255,255,255,.16);
  transition:background .5s var(--ease)}
.shades-bar .sh b{font:800 clamp(1.5rem,3.2vw,2.6rem)/1 var(--display);color:#fff;
  letter-spacing:.01em;text-shadow:0 2px 14px rgba(0,0,0,.5)}
.shades-bar .sh em{position:absolute;bottom:clamp(.7rem,1.6vw,1.2rem);font-style:normal;
  font:400 .68rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;
  color:rgba(255,255,255,.74)}
@media (max-width:560px){
  .shades-bar .sh em{display:none}
  .shades-bar .sh b{font-size:1.1rem}
}
.btn-row{display:flex;flex-wrap:wrap;gap:.85rem;align-items:center}

/* text link with draw underline */
.tlink{position:relative;display:inline-flex;align-items:center;gap:.45rem;
  font:500 .82rem/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--fg)}
/* The underline hangs off the text, not the link box, so the box can be given
   a proper touch height on phones without the rule drifting away from it. */
.tlink span:first-child{position:relative}
.tlink span:first-child::after{content:"";position:absolute;left:0;bottom:-6px;
  height:1px;width:100%;background:var(--fg);transform:scaleX(0);
  transform-origin:right;transition:transform .45s var(--ease)}
.tlink:hover span:first-child::after{transform:scaleX(1);transform-origin:left}
.tlink span{transition:transform .4s var(--ease)}
.tlink:hover span:last-child{transform:translateX(4px)}

/* ---------- header ---------- */
/* The live site runs its bar at #11111169 — 41% opacity over blur(12px)
   saturate(1.8) — so it sits ON the hero rather than above it. Same idea
   here, inverted for a paper page: transparent while the hero is behind it,
   frosted paper once you scroll past it, and immediately frosted on the four
   pages that open on a light section instead of a dark block (build-pages.py
   stamps body.over-hero). The wordmark cross-fades between its two cuts, so
   there is no flash and no layout shift. */
.hdr{position:fixed;inset:0 0 auto;z-index:60;background:transparent;
  transition:background .45s var(--ease),box-shadow .45s var(--ease)}
.hdr::after{content:"";position:absolute;inset:auto 0 0;height:1px;background:transparent;
  transition:background .45s var(--ease)}
body:not(.over-hero) .hdr,.hdr.is-stuck,body.nav-open .hdr{
  background:rgba(236,233,227,.66);
  backdrop-filter:blur(16px) saturate(1.7);-webkit-backdrop-filter:blur(16px) saturate(1.7)}
body:not(.over-hero) .hdr::after,.hdr.is-stuck::after,body.nav-open .hdr::after{background:var(--line)}
.hdr.is-stuck{box-shadow:0 10px 30px rgba(66,66,66,.06)}

/* Chrome for the transparent state, where the ground is the footage.
   Scoped to `.nav > a` — the direct children — not `.nav a`, which also
   matches every link inside .nav__menu. That dropdown is an opaque white
   panel whatever the bar behind it is doing, so lightening its links put
   #d3cec7 type on white and the whole menu became unreadable. */
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) :is(.nav > a,.nav__top,.hdr__tel){color:var(--on-dark-2)}
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) :is(.nav > a:hover,.nav > a[aria-current],.nav__top[data-active],.hdr__tel:hover){color:var(--on-dark)}
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) :is(.nav > a,.nav__top)::after{background:var(--taupe)}
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) .burger span,
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) .burger span::before,
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) .burger span::after{background:var(--on-dark)}
.hdr__in{display:flex;align-items:center;justify-content:space-between;gap:2rem;
  height:var(--hdr-h,88px);transition:height .45s var(--ease)}
.hdr.is-stuck .hdr__in{--hdr-h:70px}
.brand{display:flex;align-items:center;flex:none}
/* Two cuts of the same wordmark stacked in one box: the dark one for the
   frosted bar, the light one for the transparent state over the hero. Both
   are in the DOM and cross-fade, so nothing reflows and nothing flashes. */
.brand__swap{position:relative;display:block;width:clamp(150px,15vw,200px)}
.brand__img{width:100%;height:auto;display:block;transition:opacity .4s var(--ease)}
.brand__img--lt{position:absolute;inset:0;opacity:0}
/* the footer ships the light cut alone, so it is the only image in the
   box and must sit in flow rather than absolutely on top of nothing. */
.brand__swap--lt .brand__img--lt{position:static;opacity:1}
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) .brand__img--lt{opacity:1}
body.over-hero:not(.nav-open) .hdr:not(.is-stuck) .brand__img--dk{opacity:0}
.nav{display:flex;align-items:center;gap:2rem}
.nav a{position:relative;font:600 .84rem/1 var(--body);letter-spacing:.09em;text-transform:uppercase;color:var(--fg-2);padding-block:.4rem;transition:color .3s}
.nav a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--fg);transform:scaleX(0);transform-origin:right;transition:transform .4s var(--ease)}
.nav a:hover{color:var(--fg)}
.nav a:hover::after,.nav a[aria-current]::after{transform:scaleX(1);transform-origin:left}
.nav a[aria-current]{color:var(--fg)}
/* dropdown groups */
.nav__group{position:relative}
.nav__top{display:inline-flex;align-items:center;gap:.42rem;position:relative;
  font:600 .84rem/1 var(--body);letter-spacing:.09em;text-transform:uppercase;
  color:var(--fg-2);padding-block:.4rem;transition:color .3s}
.nav__top svg{width:9px;height:6px;fill:none;stroke:currentColor;stroke-width:1.6;
  transition:transform .35s var(--ease);opacity:.65}
.nav__top::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:var(--fg);transform:scaleX(0);transform-origin:right;
  transition:transform .4s var(--ease)}
.nav__group:hover .nav__top,.nav__group:focus-within .nav__top,.nav__group.is-open .nav__top,
.nav__top[data-active]{color:var(--fg)}
.nav__group:hover .nav__top::after,.nav__group:focus-within .nav__top::after,
.nav__top[data-active]::after{transform:scaleX(1);transform-origin:left}
.nav__group:hover .nav__top svg,.nav__group:focus-within .nav__top svg{transform:rotate(180deg)}

.nav__menu{position:absolute;top:100%;left:50%;transform:translate(-50%,6px);
  margin-top:.85rem;min-width:248px;padding:.5rem;
  background:rgba(255,255,255,.98);backdrop-filter:blur(16px) saturate(1.2);
  -webkit-backdrop-filter:blur(16px) saturate(1.2);
  border:1px solid var(--line);box-shadow:0 20px 46px rgba(66,66,66,.14);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .3s var(--ease),transform .3s var(--ease),visibility .3s;
  clip-path:polygon(10px 0,100% 0,100% calc(100% - 10px),calc(100% - 10px) 100%,0 100%,0 10px)}
/* bridges the gap so the menu does not close while the cursor travels to it */
.nav__group::after{content:"";position:absolute;top:100%;left:0;right:0;height:1rem}
.nav__group:hover .nav__menu,.nav__group:focus-within .nav__menu,.nav__group.is-open .nav__menu{
  opacity:1;visibility:visible;pointer-events:auto;transform:translate(-50%,0)}
.nav__menu a{display:block;padding:.62rem .85rem;font:400 .88rem/1.3 var(--body);
  letter-spacing:.01em;text-transform:none;color:var(--fg-3);
  transition:color .25s,background .25s}
.nav__menu a::after{display:none}
.nav__menu a:hover{color:var(--fg);background:rgba(66,66,66,.055)}
.nav__menu a[aria-current]{color:var(--fg)}

.hdr__cta{display:flex;align-items:center;gap:1.1rem}
/* was JetBrains Mono, whose zero carries a centre dot — at 13px that reads
   as an 8. Exo 2 has an open zero and matches the nav links beside it. */
.hdr__tel{font:600 .88rem/1 var(--body);letter-spacing:.045em;color:var(--fg-2);white-space:nowrap;
  transition:color .3s}
.hdr__tel:hover{color:var(--fg)}
.burger{display:none;width:44px;height:44px;place-items:center;flex:none}
.burger span{display:block;width:22px;height:1.5px;background:var(--fg);position:relative;transition:background .3s}
.burger span::before,.burger span::after{content:"";position:absolute;left:0;width:22px;height:1.5px;background:var(--fg);transition:transform .4s var(--ease)}
.burger span::before{top:-7px}.burger span::after{top:7px}
body.nav-open .burger span{background:transparent}
body.nav-open .burger span::before{transform:translateY(7px) rotate(45deg)}
body.nav-open .burger span::after{transform:translateY(-7px) rotate(-45deg)}

/* mobile drawer */
/* the fixed header paints above the drawer, so the top padding has to clear
   it or the first menu item slides under the bar */
.drawer{position:fixed;inset:0;z-index:55;background:var(--ink);
  padding:calc(var(--hdr-h,88px) + 1.6rem) var(--pad) 3rem;
  display:flex;flex-direction:column;gap:.25rem;
  opacity:0;visibility:hidden;transform:translateY(-8px);
  transition:opacity .4s var(--ease),transform .4s var(--ease),visibility .4s}
body.nav-open .drawer{opacity:1;visibility:visible;transform:none}
.drawer{overflow-y:auto;-webkit-overflow-scrolling:touch}
.drawer a{font:700 1.05rem/1.5 var(--display);text-transform:uppercase;color:var(--fg);
  letter-spacing:.01em;border-bottom:1px solid var(--line);padding-block:.62rem}
.drawer a:hover,.drawer a:active{color:var(--fg-3)}
.drawer__grp{margin:1.5rem 0 .3rem;font:500 .68rem/1 var(--mono);letter-spacing:.2em;
  text-transform:uppercase;color:var(--fg-3)}
/* mobile menu is long now — the sticky CTA keeps the primary action reachable.
   `bottom` has to cancel .drawer's own padding-bottom (3rem): pinned at 0 the bar
   stopped 48px short of the drawer floor, so menu links scrolled visibly through
   the strip underneath it. Full-bleed sideways so nothing shows past its edges. */
.drawer__foot{position:sticky;bottom:-3rem;
  background:linear-gradient(transparent,var(--ink) 22%);
  margin-inline:calc(var(--pad) * -1);padding-inline:var(--pad);
  margin-top:auto;padding-top:2rem;padding-bottom:3rem;display:grid;gap:.8rem}

/* ---------- hero ---------- */
/* The 900px ceiling kept the hero from ever filling a tall monitor — on a
   1440px-tall screen it stopped well short and showed the section beneath.
   Full viewport height now, with a floor so short landscape windows still
   give the headline room. */
.hero{position:relative;min-height:max(100svh,620px);display:flex;align-items:flex-end;
  padding-block:calc(var(--hdr-h,88px) + 4rem) clamp(3.5rem,7vw,6rem);overflow:hidden;background:var(--dark)}
/* iOS Safari: 100svh is the viewport with the browser chrome fully EXPANDED, so
   once the chrome settles into its compact overlay the hero came up short and the
   next section showed through under the toolbar. dvh tracks the chrome, so the
   hero covers whatever is actually on screen. svh above stays as the fallback. */
@supports (height:100dvh){
  .hero{min-height:max(100dvh,620px)}
}
.hero__media{position:absolute;inset:0;z-index:0;background:var(--dark)}
.hero__media video,.hero__media img{position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover}
/* the poster is a real responsive <img> (so it can be preloaded per-device);
   the video fades over it once it actually starts */
.hero__media video{opacity:0;transition:opacity .9s var(--ease)}
.hero__media video.is-live{opacity:1}
.phero__media video{opacity:0;transition:opacity .9s var(--ease)}
.phero__media video.is-live{opacity:1}
.hero__scrim{position:absolute;inset:0;z-index:1;
  background:radial-gradient(120% 80% at 22% 62%,transparent 38%,rgba(24,24,22,.34) 100%),
             linear-gradient(180deg,rgba(24,24,22,.5) 0%,rgba(24,24,22,.14) 32%,rgba(24,24,22,.42) 72%,rgba(24,24,22,.72) 100%),
             linear-gradient(90deg,rgba(24,24,22,.68) 0%,rgba(24,24,22,.2) 55%,transparent 100%)}
.hero__in{position:relative;z-index:2;width:100%}
.hero .h-hero{max-width:16ch}
.hero__lead{margin-top:1.5rem;max-width:46ch;font-size:var(--t-lead);color:var(--on-dark-2)}
.hero__cta{margin-top:2.4rem}
.hero__strip{position:relative;z-index:2;margin-top:clamp(3rem,6vw,5rem);
  border-top:1px solid var(--line-d);padding-top:1.5rem;
  display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem}
.hero__strip div{display:flex;flex-direction:column;gap:.4rem}
.hero__strip dt{font:500 .74rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--taupe)}
.hero__strip dd{margin:0;font:600 .95rem/1.4 var(--body);color:var(--on-dark)}

/* page hero (interior) */
.phero{position:relative;padding-block:calc(var(--hdr-h,88px) + clamp(4.5rem,9vw,8rem)) clamp(3rem,6vw,5rem);
  overflow:hidden;background:var(--dark)}
.phero__media{position:absolute;inset:0;z-index:0}
.phero__media img,.phero__media video{width:100%;height:100%;object-fit:cover;opacity:.88}
.phero__media video{position:absolute;inset:0}
.phero__scrim{position:absolute;inset:0;z-index:1;background:
  linear-gradient(180deg,rgba(24,24,22,.42),rgba(24,24,22,.2) 45%,rgba(24,24,22,.72) 100%),
  linear-gradient(90deg,rgba(24,24,22,.6) 0%,rgba(24,24,22,.16) 58%,transparent 100%)}
/* keeps headline contrast while the photo stays readable */
.phero__in{text-shadow:0 2px 26px rgba(0,0,0,.55)}
.phero__in{position:relative;z-index:2}
.phero .h-hero{font-size:clamp(2.2rem,5.4vw,4.2rem);max-width:18ch}

/* ---------- landing-page hero (form above the fold) ---------- */
.lp-hero{position:relative;overflow:hidden;background:var(--dark);
  padding-block:calc(var(--hdr-h,88px) + clamp(3rem,6vw,5rem)) clamp(3rem,6vw,5rem)}
.lp-hero__media{position:absolute;inset:0;z-index:0}
.lp-hero__media img{width:100%;height:100%;object-fit:cover}
.lp-hero__scrim{position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg,rgba(24,24,22,.58),rgba(24,24,22,.46) 45%,rgba(24,24,22,.76) 100%),
             linear-gradient(90deg,rgba(24,24,22,.72) 0%,rgba(24,24,22,.34) 60%,rgba(24,24,22,.56) 100%)}
.lp-hero__in{position:relative;z-index:2;display:grid;grid-template-columns:1.1fr .9fr;
  gap:clamp(2rem,5vw,4.5rem);align-items:center}
.lp-form{background:rgba(255,255,255,.97);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  box-shadow:0 26px 60px rgba(20,20,18,.28)}
.lp-hero .hero__strip{margin-top:clamp(2rem,4vw,3rem)}
@media (max-width:980px){
  .lp-hero__in{grid-template-columns:1fr}
  .lp-hero .hero__strip{grid-template-columns:1fr}
  .lp-hero .hero__strip div{flex-direction:row;align-items:baseline;gap:.9rem}
}

/* ---------- grids & cards ---------- */
.grid{display:grid;gap:clamp(1rem,2vw,1.6rem)}
/* grid items default to min-width:auto, which lets long content blow out
   the track and push the page wider than the viewport */
.grid>*,.work>*,.steps>*,.stats>*,.split>*,.logos>*{min-width:0}
.g-1{grid-template-columns:1fr}
.g-2{grid-template-columns:repeat(2,1fr)}
.g-3{grid-template-columns:repeat(3,1fr)}
.g-4{grid-template-columns:repeat(4,1fr)}
/* Columns of heading + body where the headings wrap to different line counts:
   subgrid puts every heading in one shared row and every paragraph in the
   next, so the body copy starts on the same line across all columns. Degrades
   to normal grid cells where subgrid is unsupported. */
.grid--align{grid-template-rows:auto 1fr}
.grid--align > *{display:grid;grid-row:span 2;grid-template-rows:subgrid;gap:0}

/* Same idea one level deeper, for rows of .card. A title that breaks to two
   lines where its neighbour breaks to one pushed that card's meta rule and
   body copy a line lower than the rest of the row - measured on six card rows
   across the site, up to 58px out. Mapping each card's parts onto rows the
   whole row shares puts every rule and every first line of body copy on one
   baseline, however the titles wrap. Six rows covers the deepest card shape
   here. The count was six on the assumption that a shorter card leaves the
   trailing rows at zero height. It does not: each unused row still carries its
   share of the row gap, which hung 57-91px of dead space under the last line of
   every synced card on ten grids across the site. Four is the deepest card
   actually built (image, title, meta, body), measured across every page. */
.grid--sync{grid-template-rows:repeat(4,auto)}
.grid--sync > .card{display:grid;grid-template-rows:subgrid;grid-row:span 4;gap:0}
.split{display:grid;grid-template-columns:1fr 1fr;gap:clamp(2rem,5vw,5rem);align-items:center}
.split--top{align-items:start}
.split--40{grid-template-columns:.8fr 1.2fr}
/* copy-led split: enough width for a two-line display heading, with the
   image still large beside it */
.split--60{grid-template-columns:1.15fr .85fr}
@media (max-width:820px){.split--60{grid-template-columns:1fr}}

.card{position:relative;background:var(--ink-3);border:1px solid var(--line);
  padding:clamp(1.5rem,2.4vw,2.1rem);
  /* a hairline was enough separation against black; on paper a white card
     needs a shadow or it reads as a box drawn on the page */
  box-shadow:0 1px 2px rgba(66,66,66,.05),0 10px 26px rgba(66,66,66,.055);
  transition:border-color .45s var(--ease),transform .45s var(--ease),box-shadow .45s var(--ease)}
.card::before{content:"";position:absolute;inset:0 auto auto 0;height:2px;width:100%;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform .5s var(--ease)}
.card:hover{border-color:var(--line-2);transform:translateY(-4px);
  box-shadow:0 18px 40px rgba(66,66,66,.11)}
.card:hover::before{transform:scaleX(1)}
.card__n{font:400 .74rem/1 var(--mono);color:var(--fg-4);letter-spacing:.1em;margin-bottom:1.4rem;display:block}
.card .h3{margin-bottom:.7rem}
.card p{color:var(--fg-3);font-size:.95rem}
/* A card carrying only a number and a heading gets the same padding as one
   with a paragraph, so it reads as half-empty. Tighten it and let the heading
   sit on the baseline of the number. */
.card--tight{padding:clamp(1.15rem,1.9vw,1.5rem)}
.card--tight .card__n{margin-bottom:.7rem}
.card--tight .h3{margin-bottom:0;font-size:1.02rem}
/* no display override here — .tlink is inline-flex, and forcing
   inline-block dropped its gap so the arrow sat against the label */
.card__link{margin-top:1.4rem}

/* service card w/ image */
.scard{position:relative;overflow:hidden;background:var(--ink-3);border:1px solid var(--line);
  box-shadow:0 1px 2px rgba(66,66,66,.05),0 10px 26px rgba(66,66,66,.055);
  display:flex;flex-direction:column;
  transition:border-color .45s var(--ease),transform .45s var(--ease),box-shadow .45s var(--ease)}
.scard:hover{border-color:var(--line-2);transform:translateY(-4px);
  box-shadow:0 18px 40px rgba(66,66,66,.11)}
.scard__img{position:relative;aspect-ratio:16/10;overflow:hidden;background:var(--ink-4)}
.scard__img img{width:100%;height:100%;object-fit:cover;transition:transform 1.1s var(--ease),filter .6s}
.scard:hover .scard__img img{transform:scale(1.055)}
/* a light foot under the photo so it seats onto the white card body —
   the dark build's near-black fade reads as a bruise on paper */
.scard__img::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,transparent 62%,rgba(38,38,36,.3))}
.scard__body{padding:clamp(1.35rem,2.2vw,1.9rem);display:flex;flex-direction:column;flex:1}
.scard__body p{color:var(--fg-3);font-size:.95rem;flex:1}
.scard__body .h3{margin-bottom:.65rem}
.scard__body .tlink{margin-top:1.4rem}

/* ---------- compact service index ---------- */
.svc-index{display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(2rem,5vw,5rem);
  border-top:1px solid var(--line)}
.svc-index a{display:grid;grid-template-columns:auto 1fr auto;gap:1.1rem;align-items:baseline;
  padding-block:1.15rem;border-bottom:1px solid var(--line);
  transition:background .35s var(--ease),padding-inline .35s var(--ease)}
.svc-index a:hover{background:rgba(66,66,66,.045);padding-inline:.7rem}
.svc-index i{font:400 .72rem/1.6 var(--mono);font-style:normal;letter-spacing:.1em;color:var(--fg-4)}
.svc-index b{font:600 1rem/1.3 var(--display);text-transform:uppercase;letter-spacing:.01em;
  color:var(--fg);font-weight:600}
.svc-index em{font-style:normal;font-size:.88rem;color:var(--fg-3);display:block;margin-top:.32rem;
  font-family:var(--body)}
.svc-index span{color:var(--fg);opacity:0;transform:translateX(-5px);
  transition:opacity .35s var(--ease),transform .35s var(--ease);font-size:1.05rem}
.svc-index a:hover span{opacity:1;transform:none}
@media (max-width:760px){.svc-index{grid-template-columns:1fr}}

/* ---------- stats ---------- */
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(1.5rem,3vw,3rem)}
.stat{border-top:1px solid var(--line);padding-top:1.6rem;min-width:0}
.stat__n{font:800 clamp(2.6rem,5.4vw,4.4rem)/1 var(--display);color:var(--fg);letter-spacing:-.02em;
  display:block;margin-bottom:.7rem;overflow-wrap:break-word}
/* The numeral size is set for short values like "30+". A word at that size
   ("Certified", "Shop") is far wider than a quarter-width column and spilled
   over the neighbouring stat. Set the qualifier smaller — it fits, and it
   reads better with the number staying dominant. */
.stat__n em{font-style:normal;color:var(--fg-3);font-size:.56em;letter-spacing:0}
.stat__l{color:var(--fg-3);font-size:.94rem;max-width:26ch}
/* Four-up stats sit in ~280px cells — a third narrower than the 3-up `.stats`
   the 4.4rem numeral was sized for. At that size "3M Certified" wrapped to a
   second line, which dragged every label down and left dead space under the
   single-line numbers. Smaller numeral keeps all four on one line. */
.g-4 .stat__n{font-size:clamp(2rem,3.6vw,3.1rem)}
.g-4 .stat__n em{font-size:.6em}

/* ---------- full-bleed band ---------- */
.band{position:relative;min-height:clamp(360px,52vw,620px);display:flex;align-items:center;overflow:hidden}
.band__media{position:absolute;inset:0;z-index:0}
.band__media img{width:100%;height:100%;object-fit:cover}
.band__scrim{position:absolute;inset:0;z-index:1;
  background:linear-gradient(90deg,rgba(24,24,22,.8) 0%,rgba(24,24,22,.48) 48%,rgba(24,24,22,.18) 100%)}
.band__in{position:relative;z-index:2;padding-block:clamp(3.5rem,7vw,6rem)}
.band__in .h2{max-width:17ch}
.band__in .lead{margin-top:1.3rem;max-width:48ch}

/* ---- fleet CTA band ----------------------------------------------------
   The live site runs this as a cut-out truck on a diagonally striped ground
   rather than a photographic wash, so the vehicle reads as a silhouette.
   The source webp is RGBA — see has_alpha() in build-images.py. */
.band--cut{background:var(--dark-2)}
.band--cut .band__stripes{position:absolute;inset:0;z-index:0;
  background:repeating-linear-gradient(115deg,
    rgba(255,255,255,.045) 0 2px, transparent 2px 15px)}
.band--cut::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:radial-gradient(70% 120% at 78% 50%,rgba(240,239,237,.06),transparent 62%)}
.band--cut .band__in{display:grid;align-items:center;gap:clamp(2rem,4vw,3.5rem);
  grid-template-columns:1fr}
.band--cut .band__copy .h2{max-width:14ch}
.band--cut .band__copy .lead{max-width:46ch}
/* phones: keep the truck inside the gutters rather than bleeding edge to edge */
.band--cut .band__art{position:relative;width:92%;margin-inline:auto}
.band--cut .band__art img{width:100%;height:auto}
@media (min-width:900px){
  .band--cut .band__in{grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr)}
  /* Let the truck run past the shell toward the viewport edge, as on the live
     site — but measured against the VISIBLE viewport (100vw - --sbw), and
     stopping --bleed-gut short of it. Sized from bare 100vw the tail ran under
     the scrollbar at 1280 and was clipped outright above it. */
  .band--cut .band__art{width:auto;margin-inline:calc(var(--pad) * -1);
    margin-right:calc(var(--bleed-gut) - (
      (100vw - var(--sbw) - min(100vw - var(--sbw), var(--shell-wide))) / 2 + var(--pad)))}
}

/* ---------- dark section ------------------------------------------------
   #424242 put back into the body of the page. Between the dark hero and the
   footer every page was running eight or more light sections in a row; this
   gives each page a charcoal close, and a charcoal stats row where it has
   one, so the layout reads as bookended rather than as one long light field.
   Carries the same 115 degree hatch as the light bands, mirrored to light
   lines on dark, plus a light-fall from the top edge so the ground has depth
   rather than reading as a flat rectangle. */
/* .section.section--dark, not .section--dark: .bg-2 is a utility declared
   later in this file, so at equal specificity it won the background and
   left on-dark text sitting on the light band. */
.section.section--dark{background:var(--dark-2)}
/* The showcase sections carry a cut-out vehicle rather than copy, and a
   white truck or a silver Supra never quite seated on #424242 — too close
   in value to the paint. A near-black ground gives the silhouette something
   to sit against, and the same hatch reads better over it. */
.section.section--dark.section--showcase{background:var(--dark-3)}
/* The 3M colour charts are photographed on pure black and butt straight up
   against the section ground, so at #141413 every chart drew its own visible
   rectangle. This one section drops to true black and halves the hatch, so
   the image edge has nothing to draw against. */
.section.section--dark.section--showcase.section--void{background:#000}
.section--void::before{background:repeating-linear-gradient(115deg,
  rgba(255,255,255,.03) 0 1px, transparent 1px 13px)}
.section--void::after{height:210px;background:linear-gradient(rgba(255,255,255,.042),transparent)}
/* The brand-construction plate is the loudest object on the design page and
   it was floating on paper, so the beige fought the animation instead of
   framing it. The section now carries the plate's own ground; the plate reads
   as an inset frame on it rather than a card dropped onto a different colour. */
.section.section--dark.section--plate{background:#232321}
/* Two charcoal grounds touching would merge into one slab with no visible
   boundary — a hairline keeps them reading as separate panels. */
.section--dark + .section--dark,
:is(.hero,.phero,.lp-hero,.band) + .section--dark{border-top:1px solid var(--line-d)}
.section--dark::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:repeating-linear-gradient(115deg,
    rgba(255,255,255,.05) 0 1px, transparent 1px 13px)}
.section--dark::after{content:"";position:absolute;inset:0 0 auto;height:260px;z-index:0;
  pointer-events:none;background:linear-gradient(rgba(255,255,255,.055),transparent)}

/* ---------- work / gallery ---------- */
.filters{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:clamp(1.6rem,3vw,2.4rem)}
.chip{padding:.55rem 1.05rem;border:1px solid var(--line);
  font:500 .76rem/1 var(--mono);letter-spacing:.11em;text-transform:uppercase;color:var(--fg-3);
  transition:color .3s,border-color .3s,background .3s;
  clip-path:polygon(7px 0,100% 0,100% calc(100% - 7px),calc(100% - 7px) 100%,0 100%,0 7px)}
.chip:hover{color:var(--fg);border-color:var(--line-2)}
.chip[aria-pressed="true"]{background:var(--accent);border-color:var(--taupe-d);color:var(--fg)}
.chipwrap{display:flex;flex-wrap:wrap;gap:.5rem}
.chip--static{cursor:default}
.chip--static:hover{color:var(--fg-3);border-color:var(--line)}
.work{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(.7rem,1.4vw,1.1rem)}
/* No frame. The hairline was drawn for paper, and once the work grids moved
   onto the near-black it read as a white box around every photograph. The
   ground behind a loading tile follows its section instead of flashing
   white on a dark one. */
.tile{position:relative;overflow:hidden;background:var(--ink-4);aspect-ratio:4/3}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.tile,.vplayer,.shade){background:#232321}
.tile img{width:100%;height:100%;object-fit:cover;filter:saturate(.86);
  transition:transform 1.2s var(--ease),filter .7s var(--ease)}
:is(.tile:hover,.tile:focus-visible) img{filter:saturate(1.04)}
.tile::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,transparent 45%,rgba(24,24,22,.86));opacity:.8;transition:opacity .5s}
.tile figcaption{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:1.1rem 1.2rem;
  transform:translateY(6px);opacity:0;transition:transform .5s var(--ease),opacity .5s var(--ease)}
.tile figcaption b{display:block;font:600 .95rem/1.3 var(--display);text-transform:uppercase;color:#fff;letter-spacing:.01em}
.tile figcaption span{font:400 .72rem/1 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--taupe);display:block;margin-top:.45rem}
:is(.tile:hover,.tile:focus-visible) img{transform:scale(1.06)}
:is(.tile:hover,.tile:focus-visible)::after{opacity:1}
:is(.tile:hover,.tile:focus-visible) figcaption{transform:none;opacity:1}
/* Every caption was revealed on hover, which a touch screen never fires —
   so on a phone the whole set read as unlabelled photographs. Where there is
   no hover, the caption and its scrim simply stay on. */
@media (hover:none){
  .tile figcaption{transform:none;opacity:1}
  .tile::after{opacity:1}
}
.tile.is-hidden{display:none}
.tile--tall{aspect-ratio:4/5}
.tile--wide{grid-column:span 2;aspect-ratio:8/3}

/* Four tiles in a three-column grid leaves a hole in the bottom-right
   (span-2 + 3 singles = 5 cells of 6). Where a section has exactly four
   images, run them 2x2 so every cell is filled and each vehicle gets the
   same weight. 3/2 suits vehicles better than the default 4/3. */
.work--quad{grid-template-columns:repeat(2,1fr)}
.work--quad .tile{aspect-ratio:3/2}

/* Bento rhythm. The first tile takes a 2x2 block, the next two stack beside it
   in the third column, and everything after flows three-up as before.
   The 2x2 hero consumes six cells with three tiles, so the arithmetic only
   works out flush when the tile count divides by three — which is why this is
   an opt-in class rather than a blanket rule, and why the filtered gallery is
   deliberately left out: hiding tiles changes the count under the pattern.
   Scoped to 1025px up, because .work only runs three columns above that — at
   two columns a span-2 tile fills the row and at one column it would just be a
   double-height stretched image. */
@media (min-width:1025px){
  .work--bento .tile:first-child{grid-column:span 2;grid-row:span 2;aspect-ratio:auto}
}
/* An odd number of jobs leaves the last tile alone in a two-column row. Rather
   than let it sit against the left edge with a hole beside it, it centres at
   the width of its neighbours so the row closes deliberately. */
@media (min-width:641px){
  .work--quad > .tile:last-child:nth-child(odd){grid-column:1 / -1;justify-self:center;
    width:calc((100% - clamp(.7rem,1.4vw,1.1rem)) / 2)}
}
@media (max-width:640px){.work--quad{grid-template-columns:1fr}}

/* A wide cut-out reading as the section's closing image: centred, and never
   scaled past its native width. */
.cutout--lead{max-width:820px;margin-inline:auto}

/* A cut-out vehicle sitting in a column beside its copy, credited underneath.
   Distinct from .colorway, which bleeds to the viewport edge above 900px —
   that would pull this one straight out of its grid track. */
.vfig{margin:0}
.vfig img{width:100%;height:auto;display:block}
.vfig figcaption{margin-top:1.1rem;font:400 .78rem/1.5 var(--mono);letter-spacing:.11em;
  text-transform:uppercase;color:var(--fg-4)}

/* a named colourway shown beside its copy, as on the live page */
.colorway{margin:0}
/* the bleed below is measured from 100vw, which includes the scrollbar, so
   clip it at the section rather than letting it widen the document */
.clip{overflow:hidden}
.colorway__name{font:400 .95rem/1 var(--body);color:var(--fg-4);
  text-align:center;margin-bottom:clamp(.9rem,1.6vw,1.4rem)}
@media (min-width:900px){
  /* run the car out to the viewport edge, as on the live page, rather than
     stopping at the shell. body{overflow-x:hidden} already clips it, so this
     cannot introduce a horizontal scrollbar. */
  .colorway{margin-right:calc(var(--bleed-gut) - (
      (100vw - var(--sbw) - min(100vw - var(--sbw), var(--shell))) / 2 + var(--pad)))}
}

/* ---------- video player (click-to-play facade) ----------
   Nothing loads until the viewer asks for it: self-hosted clips use
   preload="none", and the one third-party clip only injects its iframe on click. */
.vplayer{position:relative;display:block;width:100%;aspect-ratio:16/9;overflow:hidden;
  background:var(--ink-4);cursor:pointer;
  transition:transform .45s var(--ease)}
.vplayer:hover{transform:translateY(-3px)}
.vplayer__poster{width:100%;height:100%;object-fit:cover;
  transition:transform 1.1s var(--ease),filter .6s var(--ease)}
.vplayer:hover .vplayer__poster{transform:scale(1.04);filter:brightness(.92)}
.vplayer::after{content:"";position:absolute;inset:0;
  /* the wash only has to seat the play button, so it stays almost off the
     picture — the caption carries its own scrim below */
  background:linear-gradient(180deg,rgba(24,24,22,.03),rgba(24,24,22,.14));
  pointer-events:none;transition:opacity .5s}
.vplayer__btn{position:absolute;inset:0;margin:auto;width:74px;height:74px;
  display:grid;place-items:center;border-radius:50%;
  background:var(--accent);color:var(--fg);z-index:2;
  box-shadow:0 10px 34px rgba(24,24,22,.4);
  transition:transform .45s var(--ease),background .45s var(--ease)}
.vplayer:hover .vplayer__btn{transform:scale(1.08)}
.vplayer__btn svg{width:24px;height:24px;fill:currentColor;margin-left:4px}
/* Dialling the overall wash back brightened the posters but left white
   captions floating over whatever the photo happened to be — a pale garage
   wall on one, sky on another. The caption carries its own gradient instead,
   so it reads on any frame without darkening the picture. */
.vplayer__label{position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:3rem 1.3rem 1.1rem;
  background:linear-gradient(transparent,rgba(24,24,22,.86));
  display:flex;flex-wrap:wrap;gap:.35rem 1rem;align-items:baseline;pointer-events:none}
.vplayer__label b{font:600 .96rem/1.3 var(--display);text-transform:uppercase;color:#fff;letter-spacing:.01em}
.vplayer__label span{font:400 .7rem/1 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--taupe)}
.vplayer.is-playing{cursor:default}
.vplayer.is-playing::after,.vplayer.is-playing .vplayer__btn,.vplayer.is-playing .vplayer__label{display:none}
.vplayer video,.vplayer iframe{position:absolute;inset:0;width:100%;height:100%;border:0;background:#000}
.vplayer__note{margin-top:.7rem;font:400 .74rem/1.5 var(--mono);letter-spacing:.06em;color:var(--fg-4)}
.vgrid{display:grid;gap:clamp(1rem,2vw,1.6rem);grid-template-columns:repeat(2,1fr)}
/* a single featured film: constrained so it reads as a feature rather than
   a full-bleed banner, and centred under the copy above it */
.vsolo{max-width:860px;margin-inline:auto}

/* A video that carries a section of its own. The old treatment centred an
   860px box inside a wider shell, so the heading above it sat on a different
   left edge from every other heading on the page — the block read as dropped
   in rather than laid out. This runs the player the full width of the shell
   and leaves the heading on the page's own margin. */
.vfeature{margin-top:clamp(1.5rem,2.8vw,2.4rem)}
/* Two cards stacked in one column of a split, so a capped-measure prose block
   has something to sit against instead of a void down its right side. */
.cardstack{display:grid;gap:clamp(.9rem,1.7vw,1.3rem);align-content:start}
.vfeature .vplayer{width:100%}

/* One shared metadata row across the coverage cards. The four blurbs are very
   different lengths, so without it the cards read as four loose paragraphs;
   the mono line gives them a common spine to align on. */
.card__meta{font:400 .7rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--fg-4);margin:.6rem 0 .95rem;padding-bottom:.85rem;
  border-bottom:1px solid var(--line)}
.section--dark .card .card__meta{color:var(--fg-4);border-bottom-color:var(--line)}
@media (max-width:820px){.vgrid{grid-template-columns:1fr}}

/* ---------- tint shade swatches ---------- */
.g-5{grid-template-columns:repeat(5,1fr)}
.shades{gap:clamp(.6rem,1.2vw,1rem)}
.shade{margin:0;position:relative;overflow:hidden;background:var(--ink-4)}
.shade span{display:block;aspect-ratio:3/4;
  background-image:linear-gradient(rgba(0,0,0,var(--sh)),rgba(0,0,0,var(--sh))),
    url('img/bg-ferrari-1280.webp');
  background-size:cover;background-position:58% 42%;
  transition:filter .5s var(--ease)}
.shade:hover span{filter:saturate(1.15)}
.shade figcaption{position:absolute;left:0;right:0;bottom:0;padding:.85rem .9rem;
  background:linear-gradient(transparent,rgba(24,24,22,.92));
  font:700 1.05rem/1 var(--display);color:#fff;letter-spacing:.01em}
.shade figcaption em{display:block;font:400 .66rem/1 var(--mono);font-style:normal;
  letter-spacing:.14em;text-transform:uppercase;color:var(--taupe);margin-top:.45rem}
@media (max-width:820px){.g-5{grid-template-columns:repeat(3,1fr)}}
@media (max-width:480px){.g-5{grid-template-columns:repeat(2,1fr)}}

/* ---------- process ---------- */
.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(1.4rem,3vw,2.6rem);counter-reset:s}
/* declared after .steps: at equal specificity source order decides, and
   the other way round this modifier never applied at all. */
.steps--2{grid-template-columns:repeat(2,1fr)}
.step{position:relative;padding-top:2.2rem;border-top:1px solid var(--line)}
.step::before{counter-increment:s;content:"0" counter(s);position:absolute;top:1.3rem;left:0;
  font:500 .78rem/1 var(--mono);color:var(--fg-3);letter-spacing:.12em}
.step .h3{margin-bottom:.7rem}
.step p{color:var(--fg-3);font-size:.95rem}

/* ---------- reviews ----------
   No cards. A wrap shop sells craft, and boxed testimonials read like a
   template — so the lead quote carries the section at display scale, the
   credential strip is a single hairline rule, and the supporting quotes are
   set as plain columns. Chrome removed rather than added. */
.testi{position:relative}
.testi__lead{margin:0;position:relative;padding-left:clamp(2.4rem,4.6vw,4.2rem)}
.testi__lead blockquote{margin:0;color:var(--fg);
  font:400 clamp(1.5rem,3.15vw,2.6rem)/1.34 var(--body);letter-spacing:-.017em}
/* balance only where the line is long enough to benefit — on a phone it just
   produces short ragged lines */
@media (min-width:900px){
  .testi__lead{max-width:30ch}
  .testi__lead blockquote{text-wrap:balance}
}
/* opening mark set as a graphic, optically hung off the text edge */
.testi__lead::before{content:"\201C";position:absolute;left:0;top:-.22em;
  font:800 clamp(4.6rem,10vw,8rem)/1 var(--display);color:var(--taupe);
  opacity:1;pointer-events:none}
.testi__by{margin-top:clamp(1.4rem,2.4vw,2rem);display:flex;flex-wrap:wrap;
  align-items:baseline;gap:.4rem .9rem}
.testi__by b{font:600 .95rem/1 var(--display);text-transform:uppercase;
  letter-spacing:.02em;color:var(--fg)}
.testi__by span{font:400 .71rem/1 var(--mono);letter-spacing:.13em;
  text-transform:uppercase;color:var(--fg-4)}

/* credential strip — one rule, not a panel */
.testi__cred{display:flex;flex-wrap:wrap;align-items:center;
  gap:clamp(1rem,3vw,2.6rem);margin-top:clamp(2.6rem,5vw,4rem);
  padding-top:clamp(1.5rem,2.6vw,2rem);border-top:1px solid var(--line)}
.testi__score{display:flex;align-items:baseline;gap:.6rem}
.testi__score b{font:800 clamp(1.7rem,3vw,2.3rem)/1 var(--display);
  color:var(--fg);letter-spacing:-.02em}
.testi__cred .stars{margin:0}
.testi__cred .stars svg{width:16px;height:16px}
.testi__cred p{margin:0;font:400 .8rem/1.6 var(--mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--fg-4)}
.testi__cred p b{color:var(--fg-2);font-weight:400}
.testi__cred .testi__src{margin-left:auto}

.testi__more{display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(1.8rem,4vw,3.5rem);margin-top:clamp(2.2rem,4vw,3.2rem)}
.testi__more figure{margin:0}
.testi__more blockquote{margin:0;color:var(--fg-3);font-size:.99rem;line-height:1.75}
@media (min-width:900px){
  .testi__head{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);
    gap:clamp(2rem,5vw,5rem);align-items:end}
  .testi__lead{max-width:none}
}
@media (max-width:640px){
  .testi__more{grid-template-columns:1fr;gap:2rem}
  .testi__cred .testi__src{margin-left:0;width:100%}
}

@media (max-width:900px){
  .reviews__top{grid-template-columns:1fr;gap:2.4rem}
  .rating{text-align:left}
  .rating .stars{justify-content:flex-start}
}
@media (max-width:640px){
  .reviews__more{grid-template-columns:1fr;gap:2.2rem}
}

/* ---------- quote / testimonial ---------- */
.quote{max-width:56ch}
.quote blockquote{margin:0;font:400 clamp(1.2rem,2.1vw,1.65rem)/1.5 var(--body);color:var(--fg);letter-spacing:-.005em}
.quote blockquote::before{content:"\201C";color:var(--taupe-d)}
.quote blockquote::after{content:"\201D";color:var(--taupe-d)}
.quote figcaption{margin-top:1.6rem;font:500 .78rem/1 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--fg-3)}
.stars{display:flex;gap:.22rem;margin-bottom:1.5rem;color:var(--fg)}
.stars svg{width:15px;height:15px;fill:currentColor}

/* ---------- logo strip ---------- */
.logos{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:clamp(1.6rem,4vw,3.4rem)}
/* Every mark now ships dark-on-transparent (.src/build-logos-light.py), so
   the strip needs no blend mode at all — the old screen blend existed only
   to knock the black plate off the Avery / Inozetek / STEK files, and on
   paper it would have erased the marks themselves. */
.logos img{height:clamp(26px,3.4vw,42px);width:auto;opacity:.64;
  filter:grayscale(1) contrast(1.04);transition:opacity .45s,filter .45s}
.logos img:hover{opacity:1;filter:grayscale(0) contrast(1)}
/* Those dark-on-transparent marks are correct on paper and invisible on a dark
   showcase — measured at luminance 26 against a 27 ground, i.e. the partner and
   warranty logos simply were not there. Knocking them out to white is the
   standard treatment for a credential strip on dark and needs no second set of
   files. */
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img{
  filter:brightness(0) invert(1);opacity:.52}
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img:hover{
  filter:brightness(0) invert(1);opacity:.88}
/* 3M is the one mark that never gets knocked out — the red letters are the
   brand and a licensed asset, not a monochrome partner logo. On the dark
   blocks it ships as the light-lettering cut, so there is nothing to invert. */
.logos img.logo--3m,
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img.logo--3m{
  filter:none;opacity:.92}
.logos img.logo--3m:hover,
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img.logo--3m:hover{
  filter:none;opacity:1}
/* Same exemption for the 3M warranty shield. Knocked out it lost the one thing
   it exists to say — the gold plate and the black "10" both flattened to grey,
   leaving a badge reading "YEAR WARRANTY" with no number in it. It also wants
   a little more height than a wordmark: the number has to stay legible. */
.logos img.logo--warranty,
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img.logo--warranty{
  filter:none;opacity:.95;height:clamp(34px,4.2vw,52px)}
.logos img.logo--warranty:hover,
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img.logo--warranty:hover{
  filter:none;opacity:1}

/* This band carries a headline over the bright end of a dusty road shot, where
   the shared scrim left it at 3.02:1 — over the 3:1 large-text floor by a
   rounding error. Deeper on the copy side, and it still opens up to the car. */
.band--films .band__scrim{
  background:linear-gradient(90deg,rgba(20,20,19,.93) 0%,rgba(20,20,19,.78) 38%,
    rgba(20,20,19,.42) 66%,rgba(20,20,19,.14) 100%)}

/* The credential strip when it sits inside a photographic band: left-aligned
   under the copy rather than centred across the full width, and a touch
   brighter, because it is reading against a photograph and not flat ink. */
.band .logos{justify-content:flex-start;gap:clamp(1.4rem,3.2vw,2.8rem)}
.band .logos img{opacity:.82}
@media (max-width:700px){.band .logos{justify-content:center}}

/* Any mark whose meaning lives in its colour — the gold 50-year medallion is
   the case that prompted this — has to sit out the knock-out, which turns a
   foil badge into a featureless grey disc. */
.logos img.logo--keep,
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img.logo--keep{
  filter:none;opacity:.95;height:clamp(38px,4.6vw,58px)}
.logos img.logo--keep:hover,
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logos img.logo--keep:hover{
  filter:none;opacity:1}

/* The 3M endorsement mark shown on its own, outside a .logos strip. */
.logo-3m{height:34px;width:auto;opacity:.62}
:is(.section--dark,.band,.hero,.phero,.lp-hero) .logo-3m{opacity:.9}

/* ---------- spec list ---------- */
.specs{display:grid;gap:0;border-top:1px solid var(--line)}
.spec{display:grid;grid-template-columns:auto 1fr;gap:clamp(1rem,3vw,3rem);
  padding-block:1.35rem;border-bottom:1px solid var(--line);align-items:baseline}
.spec dt{font:500 .78rem/1.5 var(--mono);letter-spacing:.11em;text-transform:uppercase;color:var(--fg-3);min-width:8ch}
.spec dd{margin:0;color:var(--fg-3);font-size:.96rem}
/* A closing rule finishes a standalone spec list, but inside a card it floats
   above the card's own bottom padding and reads as an unfinished row. */
.card .spec:last-child{border-bottom:0;padding-bottom:0}

/* ---------- faq ---------- */
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{list-style:none;cursor:pointer;padding-block:1.4rem;display:flex;gap:1.5rem;align-items:center;
  font:600 clamp(1rem,1.5vw,1.14rem)/1.4 var(--display);text-transform:uppercase;color:var(--fg);
  letter-spacing:.005em;transition:color .3s}
.faq summary::-webkit-details-marker{display:none}
.faq summary:hover{color:var(--fg-3)}
.faq summary::after{content:"";margin-left:auto;flex:none;width:11px;height:11px;
  border-right:1.5px solid var(--fg-3);border-bottom:1.5px solid var(--fg-3);
  transform:rotate(45deg) translateY(-3px);transition:transform .4s var(--ease)}
.faq details[open] summary::after{transform:rotate(-135deg) translateY(-3px)}
.faq details p{padding-bottom:1.5rem;color:var(--fg-3);max-width:72ch}

/* ---------- brand plate (design page) ----------
   Ported from the live design-branding page: a concept-proof panel whose
   cover wipes away while a light blade sweeps across, revealing the
   wordmark construction diagram. Fires once when scrolled into view. */
/* stays dark on the light page: the wordmark construction diagram inside is
   white line art, and a proof plate reading as a dark panel is the point */
.brand-plate{position:relative;background:#232321;border:1px solid rgba(255,255,255,.12);
  padding:clamp(2rem,3.4vw,2.75rem) clamp(1.5rem,2.6vw,2.25rem) clamp(1.6rem,2.6vw,2.1rem);
  overflow:hidden}
.brand-plate-top,.brand-plate-bottom{display:flex;justify-content:space-between;align-items:center;
  font:400 .72rem/1 var(--mono);letter-spacing:.09em;text-transform:uppercase;color:var(--on-dark-3)}
.brand-plate-top{margin-bottom:1.9rem}
.brand-plate-top b{color:var(--taupe);font-weight:400}
.brand-plate-bottom{margin-top:1.9rem}
.brand-plate-bottom .dot{display:inline-block;width:7px;height:7px;border-radius:50%;
  background:var(--taupe);margin-right:.55rem;box-shadow:0 0 0 3px rgba(185,177,166,.22);
  animation:plate-pulse 2.2s var(--ease) infinite}
@keyframes plate-pulse{0%,100%{box-shadow:0 0 0 3px rgba(185,177,166,.22)}
  50%{box-shadow:0 0 0 7px rgba(185,177,166,.06)}}

/* ---------- before / after wipe ----------
   Two photographs of the same van from the same camera position — one in plain
   white vinyl, one in the finished wrap. The divider is dragged, so the visitor
   performs the reveal rather than watching it.

   The split is diagonal to echo the chamfer motif. Both the clipped overlay and
   the divider line are cut with the SAME polygon expressions, so they can never
   drift apart at different container widths — a skewed line would have had to
   track the aspect ratio to stay on the seam.

   Degrades honestly: --ba-pos is set here, not by script, so with no JS the
   figure is still a legible half-and-half comparison rather than a dead box. */
.ba{position:relative;--ba-pos:52%;--ba-skew:20px;
  background:var(--ink-4);overflow:hidden;
  touch-action:pan-y;-webkit-user-select:none;user-select:none}
.ba img{display:block;width:100%;height:auto;pointer-events:none}
.ba__before{position:absolute;inset:0;
  clip-path:polygon(0 0,calc(var(--ba-pos) + var(--ba-skew)) 0,
                    calc(var(--ba-pos) - var(--ba-skew)) 100%,0 100%)}
.ba__before img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.ba__line{position:absolute;inset:0;pointer-events:none;background:var(--on-dark);
  clip-path:polygon(calc(var(--ba-pos) + var(--ba-skew)) 0,
                    calc(var(--ba-pos) + var(--ba-skew) + 2px) 0,
                    calc(var(--ba-pos) - var(--ba-skew) + 2px) 100%,
                    calc(var(--ba-pos) - var(--ba-skew)) 100%)}
/* inset off the corners: .cut chamfers the top-LEFT but not the top-right, so
   flush tags would have had the Before label clipped and the After label not */
.ba__tag{position:absolute;top:14px;font:400 .7rem/1 var(--mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--on-dark);background:rgba(20,20,19,.72);
  backdrop-filter:blur(6px);padding:.5rem .7rem;pointer-events:none}
.ba__tag--before{left:14px}
.ba__tag--after{right:14px}
/* the knob sits at the vertical midpoint, where the diagonal crosses --ba-pos
   exactly, so it rides the seam without any angle maths */
.ba__handle{position:absolute;top:50%;left:var(--ba-pos);transform:translate(-50%,-50%);
  width:44px;height:44px;border-radius:50%;border:2px solid var(--on-dark);
  background:rgba(20,20,19,.55);backdrop-filter:blur(8px);cursor:ew-resize;
  display:grid;place-items:center;padding:0;
  box-shadow:0 2px 14px rgba(0,0,0,.35);transition:transform .25s var(--ease)}
.ba__handle::before{content:'';width:15px;height:9px;
  background:var(--on-dark);
  clip-path:polygon(0 50%,5px 0,5px 35%,10px 35%,10px 0,15px 50%,10px 100%,10px 65%,5px 65%,5px 100%)}
.ba:hover .ba__handle,.ba__handle:focus-visible{transform:translate(-50%,-50%) scale(1.08)}
.ba__handle:focus-visible{outline:2px solid var(--taupe);outline-offset:3px}
.ba.is-held .ba__handle{transform:translate(-50%,-50%) scale(.96)}
.ba__hint{position:absolute;left:50%;bottom:.9rem;transform:translateX(-50%);
  font:400 .68rem/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  color:var(--on-dark);background:rgba(20,20,19,.72);backdrop-filter:blur(6px);
  padding:.45rem .7rem;pointer-events:none;transition:opacity .35s var(--ease)}
.ba.is-touched .ba__hint{opacity:0}
@media (max-width:560px){
  .ba{--ba-skew:12px}
  .ba__handle{width:38px;height:38px}
  .ba__tag,.ba__hint{font-size:.62rem;padding:.4rem .55rem}
}
@media (prefers-reduced-motion:reduce){
  .ba__handle{transition:none}
}
/* its own caption class rather than reusing .vfig: that rule sets
   `img{width:100%;height:auto}` at the same specificity as .ba__before img and
   lands later in the file, which would beat the overlay's absolute fill. */
/* The proof plate sits under the two cards in "Two Ways to Get There". At the
   full shell width the wordmark banner outweighed the cards above it, so it is
   capped and centred — a proof card, not a slab. */
.plate-wrap{max-width:880px;margin-inline:auto}

.ba-fig{margin:0}
.ba-fig figcaption{margin-top:1.1rem;font:400 .78rem/1.5 var(--mono);letter-spacing:.11em;
  text-transform:uppercase;color:var(--on-dark-3)}
/* PPF finish comparison. Must come AFTER the rule above: same specificity, so
   source order decides, and the base is written for the design page's DARK
   section — its --on-dark-3 caption is near-invisible on this light one. This
   one also stands alone rather than beside a column of copy, so it is centred
   and capped, and its caption is a sentence, not a small mono credit line. */
.ba-fig--finish{max-width:900px;margin-inline:auto}
.ba-fig--finish figcaption{margin-top:1.4rem;text-align:center;text-transform:none;
  font:600 clamp(1rem,1.9vw,1.22rem)/1.45 var(--display);letter-spacing:.005em;color:var(--fg)}

.wipe{position:relative;overflow:hidden}
.wipe img{width:100%;height:auto}
/* The cover is RETRACTED by default and only covers the art once JS has armed
   it (image decoded, IO attached). A dead script, a missed observer or a
   throttled rAF therefore shows the diagram plainly instead of hiding it
   behind an opaque panel forever. */
.wipe .cover{position:absolute;inset:0;background:#232321;clip-path:inset(0 0 0 100%)}
.wipe.armed .cover{clip-path:inset(0 0 0 0)}
.wipe .blade{position:absolute;top:-10%;bottom:-10%;width:40px;left:-8%;opacity:0;
  background:linear-gradient(90deg,transparent,rgba(185,177,166,.85) 48%,rgba(255,255,255,.98) 51%,rgba(185,177,166,.85) 54%,transparent);
  box-shadow:0 0 34px 8px rgba(240,239,237,.26)}
.wipe.armed.play .cover{animation:wipe-go 2.2s cubic-bezier(.7,0,.2,1) .25s 1 both}
.wipe.armed.play .blade{opacity:1;animation:blade-go 2.2s cubic-bezier(.7,0,.2,1) .25s 1 both}
@keyframes wipe-go{from{clip-path:inset(0 0 0 0)}to{clip-path:inset(0 0 0 100%)}}
@keyframes blade-go{from{left:-8%}to{left:103%}}

/* the diagram must be visible even if the animation never runs */
@media (prefers-reduced-motion:reduce){
  .wipe .cover,.wipe .blade{display:none}
  .brand-plate-bottom .dot{animation:none}
}

/* ---------- legal / long-form ---------- */
/* A legal footnote under a full-bleed figure. Left to the shell it ran the
   whole 1200px — 150 characters a line, which nobody reads. Capped to a
   readable measure and centred on the figure above it. */
.fineprint{max-width:72ch;margin-inline:auto;text-align:center;
  font-size:.82rem;line-height:1.75;color:var(--fg-4)}

.legal{max-width:74ch}
.legal h2{margin:2.4rem 0 .75rem}
.legal h2:first-child{margin-top:0}
.legal p{color:var(--fg-3);line-height:1.8}
.legal p+p{margin-top:.9rem}
.legal a{color:var(--fg-2);text-decoration:underline;text-underline-offset:3px}
.legal a:hover{color:var(--fg)}

/* ---------- form ---------- */
.form{display:grid;gap:1.1rem}
.field{display:grid;gap:.5rem}
.field label{font:500 .74rem/1 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--fg-3)}
/* Fields were filled with the band beige, which put six tan rectangles on a
   white card — the single most dated-looking thing on the site. They are now
   white with a real border and a hairline inset, so they read as fields by
   structure rather than by tint, and the taupe stays on the buttons. */
.field input,.field select,.field textarea{
  width:100%;padding:.92rem 1rem;background:var(--ink-3);color:var(--fg);
  border:1px solid var(--line-2);font:400 .98rem/1.4 var(--body);
  box-shadow:inset 0 1px 2px rgba(66,66,66,.05);
  transition:border-color .35s,box-shadow .35s}
.field textarea{min-height:130px;resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--fg);
  box-shadow:0 0 0 3px rgba(66,66,66,.11)}
/* a required field left empty on submit — see forms() in site.js */
.field :is(input,select,textarea).is-bad{border-color:#a8422f;
  box-shadow:0 0 0 3px rgba(168,66,47,.14)}
.field input::placeholder,.field textarea::placeholder{color:var(--fg-4)}
.field select{appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--fg-3) 50%),linear-gradient(135deg,var(--fg-3) 50%,transparent 50%);
  background-position:calc(100% - 20px) calc(1.35rem),calc(100% - 15px) calc(1.35rem);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.form__row{display:grid;grid-template-columns:1fr 1fr;gap:1.1rem}
.form__note{font-size:.82rem;color:var(--fg-4);line-height:1.6}
.form__note a{color:var(--fg-3);text-decoration:underline;text-underline-offset:2px}

/* ---------- footer ---------- */
/* The footer was a full-width slab of the band beige on all 21 pages — the
   biggest single field of it anywhere, and the last thing every visitor saw.
   Charcoal anchors the page bottom, matches the dark blocks the site already
   runs, and takes roughly a fifth of the beige out of the design in one move. */
.ftr{background:var(--foot);padding-block:clamp(3.5rem,6vw,5.5rem) 2rem;color:var(--on-dark-2)}
.ftr::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:repeating-linear-gradient(115deg,
    rgba(255,255,255,.035) 0 1px, transparent 1px 13px)}
.ftr{position:relative}
.ftr > .shell{position:relative;z-index:1}
.ftr__grid{display:grid;grid-template-columns:1.4fr 1fr 1.15fr 1fr;gap:clamp(2rem,4vw,3.5rem)}
.ftr__legal a{text-decoration:underline;text-underline-offset:2px}
.ftr h3{font:500 .8rem/1 var(--mono);letter-spacing:.16em;color:var(--taupe);margin-bottom:1.3rem;text-transform:uppercase}
.ftr ul{list-style:none;margin:0;padding:0;display:grid;gap:.7rem}
.ftr a{color:var(--on-dark-2);font-size:.93rem;transition:color .3s}
.ftr a:hover{color:var(--on-dark)}
.ftr__bot{margin-top:clamp(2.5rem,5vw,4rem);padding-top:1.8rem;border-top:1px solid var(--line-d);
  display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;align-items:center;
  font-size:.82rem;color:var(--on-dark-3)}
.ftr__addr{color:var(--on-dark-2);font-size:.93rem;line-height:1.8;font-style:normal}
.ftr__blurb{color:var(--on-dark-3);max-width:34ch;font-size:.93rem}
.ftr .tlink{color:var(--on-dark)}
.ftr .map{background:var(--dark-3);border-color:var(--line-d)}
.ftr .map__ph{color:var(--on-dark-3)}

/* ---- touch targets ------------------------------------------------------
   Text links sit at 13-19px tall, which is fine with a mouse and awkward with
   a thumb. On touch widths the hit area is padded out toward the 44px
   guideline without changing the type or the visual rhythm much. */
@media (max-width:820px){
  .ftr ul{gap:.35rem}
  .ftr ul a{display:inline-block;padding-block:.62rem}   /* 43px -> 44px */
  .ftr__legal a{display:inline-block;padding-block:.65rem}   /* 38px -> 44px */
  .ftr__addr a{display:inline-block;padding-block:.35rem}
  .tlink{padding-block:.6rem}
  .prose a,.lead a,.spec a{padding-block:.3rem;display:inline-block}
  .brand{padding-block:.45rem}
  /* Tap-to-call is the primary action on a phone and these measured 35-38px,
     under the 44px both platforms ask for. The hit area is grown with a
     pseudo-element rather than padding so the footer address and the spec
     rows keep their rhythm — the target gets bigger, nothing moves. */
  a[href^="tel:"]:not(.btn){position:relative}
  a[href^="tel:"]:not(.btn)::after{content:"";position:absolute;left:0;right:0;
    top:50%;transform:translateY(-50%);height:44px}
}

/* ---------- warm type on the black ----------
   Where the black is the subject rather than a backdrop, the copy runs in the
   palette's own beige instead of the neutral on-dark ladder, and a card on it
   carries the same ground as the section — the photo and a hairline give the
   card its edge, and the section's hatch stopping at that edge does the rest.
   Specificity carries .section--dark so these win over the white-card block
   further down, which assumes a paper ground inside the card. */
.section--dark.section--warm{color:var(--warm-2)}
.section--dark.section--warm :is(h1,h2,h3,h4,.h3){color:var(--taupe)}
.section--dark.section--warm :is(p,.lead,dd,li,blockquote,.prose){color:var(--warm-2)}
.section--dark.section--warm .tlink{color:var(--taupe)}
.section--dark.section--warm .tlink span:first-child::after{background:var(--taupe)}
.section--dark.section--warm .btn:not(.btn--solid){color:var(--taupe);border-color:var(--line-d-2)}
.section--dark.section--warm .btn:not(.btn--solid):hover{border-color:var(--taupe)}
/* the eyebrow is a label, not body copy — it keeps the accent weight the rest
   of the site gives it, rather than being caught by the paragraph rule above */
.section--dark.section--warm .eyebrow{color:var(--taupe)}

/* A service card carrying the ink ground instead of paper. Written against the
   card alone, not the section, so the band behind it is free to stay light —
   the point is the card, not the strip it sits in. Selectors reach through
   .scard__body because the paper defaults there are set at the same
   specificity and would otherwise win on source order. */
.scard--ink{background:var(--dark-3);border-color:rgba(255,255,255,.16);
  box-shadow:0 1px 2px rgba(66,66,66,.08),0 14px 32px rgba(66,66,66,.16)}
.scard--ink:hover{border-color:rgba(255,255,255,.3);
  box-shadow:0 22px 48px rgba(66,66,66,.26)}
.scard--ink .scard__body :is(h1,h2,h3,h4,.h3){color:var(--taupe)}
.scard--ink .scard__body :is(p,.lead){color:var(--warm-2)}
.scard--ink .scard__body .tlink{color:var(--taupe)}
.scard--ink .scard__body .tlink span:first-child::after{background:var(--taupe)}
/* the photo's foot was drawn to seat onto a white card body; here it carries
   down into the card's own black instead */
.scard--ink .scard__img{background:var(--dark-2)}
.scard--ink .scard__img::after{
  background:linear-gradient(180deg,transparent 58%,rgba(20,20,19,.85))}

/* ---------- 3D wrap designer ----------
   A vendor embed (DreamRide). Its loader mounts an iframe inside a shadow root
   and hard-sets it to 100vh, which nothing out here can reach — so rather than
   fight it, the page gives it a band of its own: the section carries the
   heading, the frame runs full width beneath it, and the section's bottom
   padding is dropped so the tool sits flush against the block below.
   The widget's own UI is dark, which is why it lives on a dark section. */
/* Hidden until the vendor frame actually mounts — see designer() in site.js.
   On a domain DreamRide has not approved the section never appears at all,
   rather than showing their red error panel or an empty band. */
#designer{height:0;padding:0;overflow:hidden;opacity:0}
/* same section rhythm as everything around it — the tighter 86/50px it used
   to run made the white band look squeezed between the two dark blocks */
#designer.is-live{height:auto;opacity:1;overflow:visible;
  padding-top:var(--sec);padding-bottom:var(--sec)}
/* the heading's own margin sets the gap, as it does in every other section */
#designer .viz{margin-top:0}
.viz{position:relative;z-index:1;margin-top:clamp(1.6rem,3vw,2.4rem)}
.viz viz-anchor{display:block;width:100%}
/* a hairline top and bottom so the vendor frame reads as a panel set into the
   page rather than a hole cut in it */
.viz{border-block:1px solid var(--line)}
:is(.section--dark,.band,.hero,.phero,.lp-hero) .viz{border-color:var(--line-d)}

/* Scroll guard. The designer is a cross-origin iframe with scroll areas of its
   own — its page and its colour list — so a wheel or a swipe over it moved the
   designer instead of the page, and anyone scrolling down got stuck in it.
   Until someone clicks in, a transparent layer sits over the frame and the page
   scrolls straight past; clicking lifts it. It drops back into place when the
   pointer leaves the frame, when the frame scrolls out of view, or on Done —
   the only way out on a phone, where every drag belongs to the frame. */
.viz__guard{position:absolute;inset:0;z-index:3;display:block;width:100%;
  margin:0;padding:0;border:0;background:transparent;cursor:pointer}
.viz.is-on .viz__guard{display:none}
/* Start and Done share one spot — top centre, clear of the vendor's scene
   toggle and camera button — and it sticks just under the site header while
   the frame fills the screen, so on a phone the way out is always in reach. */
.viz__bar{position:sticky;top:var(--hdr-h,88px);z-index:4;height:0;
  display:flex;justify-content:center;pointer-events:none}
.viz__pill{display:inline-flex;align-items:center;gap:.7rem;margin-top:1rem;
  padding:.95rem 1.35rem;border:1px solid rgba(255,255,255,.2);pointer-events:auto;cursor:pointer;
  background:rgba(20,20,19,.74);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  font:500 .74rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:#fff;
  clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);
  transition:opacity .35s var(--ease),transform .35s var(--ease),background .3s var(--ease)}
.viz__pill:hover{background:rgba(20,20,19,.9)}
.viz__pill svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.6}
/* desktop: the prompt shows while the pointer is over the frame; touch: always */
.viz__start{opacity:0;transform:translateY(-6px)}
.viz:not(.is-on):hover .viz__start,.viz__start:focus-visible{opacity:1;transform:none}
@media (hover:none){.viz__start{opacity:1;transform:none}}
.viz.is-on .viz__start,.viz__done{display:none}
.viz.is-on .viz__done{display:inline-flex}

/* With the designer hidden (vendor not approved on this domain) the Color
   Series and the warranty panel end up touching — two #141413 grounds with no
   seam between them. */
#designer:not(.is-live) + .section--dark{border-top:1px solid var(--line-d)}
[data-viz-show][hidden]{display:none}

/* ---------- 3M Protection Wrap Film (pwf.html) ----------
   3M's brochure (75-5100-2980-6) rebuilt from its parts rather than pasted in
   as pages: the photographs were lifted out of the PDF at native size, the
   product lockup and the six feature icons were converted from its vector
   paths to inline SVG, and every line of copy is live text. The artwork draws
   in currentColor, so it takes the page's ink on paper and its beige on the
   dark blocks, and nothing carries the brochure's green. */
.pwf-lockup{display:block;width:clamp(210px,22vw,268px);height:auto;color:var(--fg);
  margin-bottom:1.35rem}
:is(.section--dark,.band,.hero,.phero) .pwf-lockup{color:var(--on-dark)}

/* phones: headline, photograph, then the six features.
   desktop: headline beside the car, and the six features run the full width
   beneath both as one strip — stacked under the headline they stood 250px
   taller than the photograph. */
.pwf-intro{display:grid;gap:clamp(2rem,4vw,3rem);
  grid-template-areas:"head" "fig" "perks"}
.pwf-intro__head{grid-area:head}
.pwf-intro__fig{grid-area:fig;aspect-ratio:4/3}
.pwf-perks{grid-area:perks}
/* stacked, the photograph-to-strip gap is a block gap like the others on the
   page, not the tighter heading gap the grid runs at. Scoped to .pwf-intro so
   the strip's own margin:0 further down does not cancel it. */
.pwf-intro .pwf-perks{margin-top:calc(var(--block) - clamp(2rem,4vw,3rem))}
@media (min-width:1025px){
  .pwf-intro{grid-template-columns:minmax(0,5fr) minmax(0,7fr);
    grid-template-areas:"head fig" "perks perks";column-gap:clamp(3rem,5vw,4.8rem);
    row-gap:var(--block)}
  .pwf-intro__head{align-self:center}
  .pwf-intro .pwf-perks{margin-top:0}
}

.pwf-fig{position:relative;margin:0;overflow:hidden;background:var(--dark-3)}
.pwf-fig img{display:block;width:100%;height:100%;object-fit:cover;
  transition:transform 1.4s var(--ease)}
@media (hover:hover){.pwf-fig:hover img{transform:scale(1.025)}}
/* the named 3M colour, set on the photograph the way the brochure labels its
   cars — over the darkest corner, with a marker pointing down at the paint */
.pwf-fig .finish{position:absolute;left:clamp(1.2rem,2.2vw,1.8rem);top:clamp(1.1rem,2vw,1.6rem);z-index:2;
  display:flex;flex-direction:column;align-items:flex-start;gap:.55rem;margin:0;
  font:500 .72rem/1.4 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:#fff;
  text-shadow:0 1px 14px rgba(0,0,0,.6)}
.pwf-fig .finish::after{content:"";border:5px solid transparent;border-top:6px solid var(--taupe);
  border-bottom:0;margin-left:1px}

/* six features on a hairline grid, two across on a phone and three above.
   The left column carries no rule so the icons align with the headline. */
.pwf-perks{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));border-top:1px solid var(--line)}
.pwf-perks li{display:flex;flex-direction:column;gap:1rem;min-width:0;
  padding:1.45rem 1rem 1.45rem 1.2rem;border-bottom:1px solid var(--line);border-left:1px solid var(--line)}
.pwf-perks li:nth-child(2n+1){border-left:0;padding-left:0}
@media (min-width:560px){
  .pwf-perks{grid-template-columns:repeat(3,minmax(0,1fr))}
  .pwf-perks li:nth-child(2n+1){border-left:1px solid var(--line);padding-left:1.2rem}
  .pwf-perks li:nth-child(3n+1){border-left:0;padding-left:0}
}
@media (min-width:1025px){
  .pwf-perks{grid-template-columns:repeat(6,minmax(0,1fr))}
  .pwf-perks li:nth-child(n){border-left:1px solid var(--line);padding-left:1.3rem}
  .pwf-perks li:first-child{border-left:0;padding-left:0}
}
.pwf-perks svg{width:40px;height:40px;color:var(--fg);flex:none}
.pwf-perks span{font:500 .72rem/1.5 var(--mono);letter-spacing:.11em;text-transform:uppercase;color:var(--fg-3)}

/* The Color Series — the brochure's inside spread: text and a tall photograph
   across the top, a letterbox photograph and the colour count beneath. */
.pwf-colors{display:grid;gap:clamp(1.4rem,2.6vw,2.2rem);grid-template-columns:minmax(0,1fr)}
.pwf-colors__a{aspect-ratio:2110/1667}
/* the e-tron shot is a 2.7:1 letterbox — full width on a phone that leaves
   the car 125px tall, so below the desktop grid it is cropped to 16:9 and
   held toward the headlight end */
.pwf-colors__b{aspect-ratio:16/9}
.pwf-colors__b img{object-position:72% 50%}
@media (min-width:1025px){.pwf-colors__b{aspect-ratio:2142/802}}
.pwf-colors__stat{border-top:1px solid var(--line-d);padding-top:1.5rem}
.pwf-colors__stat .stat__l{max-width:24ch}
/* This block used to be a big numeral ("30"). Its replacement is a phrase, and
   .stat__n's clamp tops out at 4.4rem — sized for two digits, and already known
   to spill a narrow column when given words (see the .stat__n notes above). This
   cell is 4 of 12 columns, so the heading is set between .h3 and .h2: enough
   weight to still anchor the corner, small enough to sit inside the column. */
.pwf-colors__title{font-size:clamp(1.35rem,2.3vw,2.05rem);line-height:1.12;
  letter-spacing:-.005em;margin-bottom:.2rem}
/* for display headings in a narrow column, so no word is left alone on a line */
.balance{text-wrap:balance}
@media (min-width:1025px){
  .pwf-colors{grid-template-columns:repeat(12,minmax(0,1fr));row-gap:clamp(1.4rem,2.6vw,2.2rem)}
  .pwf-colors__head{grid-column:1/6;align-self:center;padding-right:clamp(0rem,1.5vw,1.5rem)}
  .pwf-colors__a{grid-column:6/13}
  .pwf-colors__b{grid-column:1/9}
  /* Stretch, don't bottom-hang. The block used to be ~357px tall (eyebrow +
     a 4.4rem numeral) against a 295px photo, so its top rule sat 62px ABOVE the
     photo; without the numeral it is 253px and the rule fell to 42px BELOW it.
     Neither is a deliberate line. Stretching puts the rule exactly on the
     photo's top edge so it reads as one row, and justify-content keeps the copy
     bottom-aligned with the photo the way it was. */
  .pwf-colors__stat{grid-column:9/13;align-self:stretch;
    display:flex;flex-direction:column;justify-content:flex-end}
}

/* the brochure's "10 Year" box, drawn in the site's beige with the same
   fading gradient stroke 3M gives it in green */
.pwf-badge{display:inline-flex;align-items:center;gap:1.1rem;padding:1rem 1.7rem 1rem 1.3rem;
  border:1px solid;border-image:linear-gradient(120deg,var(--taupe),rgba(185,177,166,.22) 70%) 1}
.pwf-badge b{font:800 clamp(3rem,5.6vw,4.4rem)/.9 var(--display);letter-spacing:-.03em;color:var(--on-dark)}
.pwf-badge span{font:500 .78rem/1.4 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--taupe)}
.pwf-notes{font-size:.8rem;line-height:1.6;color:var(--on-dark-3)}
.pwf-notes a{color:var(--on-dark-2);text-decoration:underline;text-decoration-color:var(--line-d-2);
  text-underline-offset:3px}
.pwf-notes a:hover{text-decoration-color:currentColor}

/* Warranty panel. The Cybertruck install film sits beside the headline as the
   section's picture — parked under the footnotes it read as an afterthought —
   and the six points run beneath both in two columns, 01-03 down the left and
   04-06 down the right, so a numbered list still reads top to bottom. */
@media (min-width:821px){
  .pwf-warranty{grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr)}
}
.pwf-protected{margin-top:var(--block)}
@media (min-width:900px){
  .pwf-specs{grid-template-columns:1fr 1fr;grid-template-rows:repeat(3,auto);
    grid-auto-flow:column;column-gap:clamp(2rem,4vw,4rem)}
}


/* ---------- lightbox ----------
   Work photographs open full size on click. Written here rather than pulled
   from a CDN: the site ships one stylesheet and one script with no
   third-party dependencies, and a viewer is a few dozen lines. The tile
   itself is the trigger — see lightbox() in site.js, which also collects the
   sequence at open time so the gallery's filters are respected. */
.tile[role="button"],.vfig[role="button"]{cursor:zoom-in}
.lb{position:fixed;inset:0;z-index:120;
  display:grid;place-items:center;
  padding:clamp(4.2rem,7vw,5.5rem) clamp(1rem,6vw,5.5rem) clamp(3.4rem,5vw,4.2rem);
  background:rgba(10,10,9,.94);
  opacity:0;transition:opacity .3s var(--ease)}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .lb{background:rgba(10,10,9,.86);
    -webkit-backdrop-filter:blur(16px) saturate(.85);backdrop-filter:blur(16px) saturate(.85)}
}
.lb.is-open{opacity:1}
.lb__fig{margin:0;min-height:0;max-width:100%;max-height:100%;
  display:flex;flex-direction:column;align-items:center;gap:clamp(.85rem,1.6vw,1.25rem)}
.lb__img{display:block;min-height:0;max-width:100%;max-height:100%;width:auto;height:auto;
  object-fit:contain;
  opacity:.55;transform:scale(.99);
  transition:opacity .35s var(--ease),transform .35s var(--ease)}
.lb.is-open .lb__img{opacity:1;transform:none}
/* Only ever seen if a file is not in the cache yet — the frame already on
   screen is held and dimmed rather than blanked, so navigation never flashes
   an empty box. */
.lb.is-busy .lb__img{opacity:.42}
.lb__cap{margin:0;flex:none;text-align:center;max-width:60ch}
.lb__cap b{display:block;font:600 1rem/1.3 var(--display);text-transform:uppercase;
  letter-spacing:.01em;color:var(--on-dark)}
.lb__cap span{display:block;margin-top:.42rem;font:400 .72rem/1.5 var(--mono);
  letter-spacing:.13em;text-transform:uppercase;color:var(--taupe)}
.lb__idx{position:absolute;left:50%;bottom:clamp(1.1rem,2.4vw,1.7rem);transform:translateX(-50%);
  margin:0;font:400 .72rem/1 var(--mono);letter-spacing:.13em;color:var(--on-dark-3)}
.lb__x,.lb__nav{position:absolute;z-index:2;border:1px solid var(--line-d-2);
  background:rgba(255,255,255,.07);color:var(--on-dark);cursor:pointer;padding:0;
  display:grid;place-items:center;
  clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);
  transition:background .3s var(--ease),border-color .3s var(--ease)}
.lb__x:hover,.lb__nav:hover{background:rgba(255,255,255,.16);border-color:var(--on-dark-3)}
.lb__x:focus-visible,.lb__nav:focus-visible{outline:2px solid var(--on-dark);outline-offset:3px}
.lb__x{top:clamp(.9rem,2.4vw,1.6rem);right:clamp(.9rem,2.4vw,1.6rem);
  width:46px;height:46px;font:400 1.45rem/1 var(--body)}
.lb__nav{top:50%;margin-top:-27px;width:54px;height:54px;font:400 1.8rem/1 var(--body)}
.lb__nav--prev{left:clamp(.6rem,2vw,1.6rem)}
.lb__nav--next{right:clamp(.6rem,2vw,1.6rem)}
.lb__nav[hidden]{display:none}
/* On a phone the side arrows would sit over the photograph, so the whole
   control row drops to the bottom edge with the counter between them. */
@media (max-width:700px){
  .lb__nav{top:auto;bottom:clamp(.8rem,3vw,1.2rem);margin-top:0;width:48px;height:48px;
    font-size:1.6rem}
  .lb__nav--prev{left:clamp(.8rem,4vw,1.6rem)}
  .lb__nav--next{right:clamp(.8rem,4vw,1.6rem)}
  .lb__idx{bottom:calc(clamp(.8rem,3vw,1.2rem) + 17px)}
}
@media (prefers-reduced-motion:reduce){
  .lb{transition:none}
  .lb__img{opacity:1;transform:none;transition:none}
}

/* ---------- film grain (house premium layer) ---------- */
/* inset:0 — an oversized fixed layer can extend the scroll area on mobile,
   so the drift is animated via background-position, not transform. */
.grain{position:fixed;inset:0;z-index:100;pointer-events:none;opacity:var(--grain-o);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grain 7s steps(6) infinite;will-change:background-position}
@keyframes grain{0%{background-position:0 0}20%{background-position:-30px 20px}40%{background-position:20px -25px}60%{background-position:-15px 30px}80%{background-position:30px 15px}100%{background-position:0 0}}

/* ---------- reveal on scroll ---------- */
[data-rv]{opacity:0;transform:translateY(18px);
  transition:opacity .85s var(--ease),transform .85s var(--ease)}
[data-rv].rv-in{opacity:1;transform:none}
[data-rv-d="1"]{transition-delay:.09s}
[data-rv-d="2"]{transition-delay:.18s}
[data-rv-d="3"]{transition-delay:.27s}
[data-rv-d="4"]{transition-delay:.36s}
[data-rv="line"]{opacity:1;transform:none;position:relative;overflow:hidden}
[data-rv="line"]::after{content:"";position:absolute;inset:0;background:var(--ink);transform-origin:right;transition:transform 1s var(--ease)}
[data-rv="line"].rv-in::after{transform:scaleX(0)}

/* ---- scroll-linked line reveal -------------------------------------------
   Section headings rise line by line from behind their own mask as they cross
   the viewport. The animation is driven by the heading's own view() timeline,
   so its position maps to scroll position — it tracks the scrollbar both ways
   rather than firing once on entry, which is what separates this from the
   IntersectionObserver fade the rest of the page uses.

   The line spans are written by site.js because CSS cannot address a rendered
   line box. Everything that hides text lives inside @supports, so a browser
   without scroll-driven animation renders the heading as ordinary text. */
.ln{display:block}
.ln > i{display:block;font-style:normal}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    /* The timeline is named on the heading rather than left to view() on the
       moving line. `overflow:hidden` on the mask makes it a scroll container,
       so a bare view() would resolve against the mask — inside which the line
       is always fully visible, pinning every timeline at finished. Naming it
       on the heading, which sits outside any clipper, measures against the
       document as intended, and all the lines share one timeline. */
    .hl{view-timeline-name:--hl-line;view-timeline-axis:block}
    /* Orbitron sits tight at line-height 1.03 and the mask would shave the
       cap edges, so each line carries slack that is pulled back out again. */
    .hl .ln{overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em}
    /* longhands on purpose: the `animation` shorthand sets duration to 0s,
       and a scroll-driven animation needs `auto` to stretch across its range.
       With 0s it completes instantly and sits at the end state, so the whole
       effect silently does nothing. */
    /* Every range ends by `entry 100%` — the moment the heading is wholly on
       screen — so a heading you can see all of is always fully legible. They
       used to run to cover 26-44%, which left a heading resting in the lower
       third of the window half-masked: "Request a quote." sits there beside
       its form on most laptops. */
    .hl .ln > i{animation-name:ln-rise;animation-duration:auto;
      animation-timing-function:linear;animation-fill-mode:both;
      animation-timeline:--hl-line;animation-range:entry 0% entry 70%}
    .hl .ln:nth-child(2) > i{animation-range:entry 10% entry 85%}
    .hl .ln:nth-child(3) > i{animation-range:entry 20% entry 100%}
    .hl .ln:nth-child(n+4) > i{animation-range:entry 25% entry 100%}
    /* the heading opts out of the generic fade so the two do not stack */
    [data-rv].hl{opacity:1;transform:none;transition:none}
  }
}
@keyframes ln-rise{from{transform:translateY(105%)}to{transform:translateY(0)}}

/* ---------- dark surfaces on a light page ---------------------------------
   Four blocks keep a dark ground: the home hero and the interior page heroes
   (dark automotive footage with copy laid over it), the landing-page hero,
   and the two full-bleed bands — the fleet band's Sheriff truck is a white
   cut-out that would disappear on the paper. They read as deliberate anchors
   in a paper layout, and everything inside them switches to the on-dark
   text ladder. Grouped with :is() so a new page picks the treatment up from
   its section class alone, with no per-page markup. */
:is(.hero,.phero,.lp-hero,.band,.section--dark){color:var(--on-dark-2)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(h1,h2,h3,h4){color:var(--on-dark)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.lead,blockquote,dd){color:var(--on-dark-2)}
/* the hero strip's values stay at full strength */
:is(.hero,.phero,.lp-hero,.band,.section--dark) .hero__strip dd{color:var(--on-dark)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.muted,.prose,figcaption){color:var(--on-dark-3)}
/* components that carry their own light-ground colour and can now land on
   a charcoal section: the stats row, the process steps, the FAQ, chips */
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.stat__n,.testi__score b,.faq summary,.svc-index b,.h3){color:var(--on-dark)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.stat__l,.step p,.faq details p,.chip,.card p,.svc-index em){color:var(--on-dark-2)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.card__n,.idx,.colorway__name,.vplayer__note,.svc-index i){color:var(--on-dark-3)}
/* Three more that still carried a paper-ground tone once the work grids,
   the reviews and the colour chart moved onto the dark blocks. Measured on
   #141413: .prose strong at 1.83, the review credit lines at 2.83, the colour
   chart's fineprint at 3.22 on true black — the first was effectively
   invisible. --on-dark-2 clears 4.5 on every dark ground the site uses. */
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.prose strong,.testi__cred p,.testi__cred p b,.testi__src,.fineprint){color:var(--on-dark-2)}
/* The attribution under each review kept the paper ladder: the reviewer's name
   at --fg measured 1.32 on #141413 — the name, the one part a visitor looks
   for, was the least readable thing in the block — and the source line 2.83. */
:is(.hero,.phero,.lp-hero,.band,.section--dark) .testi__by b{color:var(--on-dark)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) .testi__by span{color:var(--on-dark-3)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) .chip{border-color:var(--line-d-2)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.rule,hr){background:var(--line-d)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.specs,.spec,.stat,.step,.testi__cred,.faq,.faq details,.svc-index,.svc-index a){border-color:var(--line-d)}
/* the taupe fill carries dark type on either ground, so the dark blocks only
   need their outline button lightening */
:is(.hero,.phero,.lp-hero,.band,.section--dark) .btn{color:var(--on-dark);border-color:var(--line-d-2)}
/* A fully transparent outline button sitting on moving footage takes on
   whatever is behind it — on the phone the hero's red Corvette turned the
   phone-number button into a red box. Over the heroes only, the outline
   buttons get a dark glass pane, so they read as one consistent control no
   matter what frame is behind them. The accent fill still wipes across on
   hover: a negative z-index pseudo paints above its own element's background,
   just below the label. */
:is(.hero,.phero,.lp-hero) .btn:not(.btn--solid){
  background:rgba(18,18,17,.30);
  backdrop-filter:blur(10px) saturate(1.25);
  -webkit-backdrop-filter:blur(10px) saturate(1.25);
  border-color:rgba(255,255,255,.32)}
:is(.hero,.phero,.lp-hero) .btn:not(.btn--solid):hover{border-color:var(--accent)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) .btn:hover{color:var(--fg)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) .btn--solid{color:var(--fg)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) .tlink{color:var(--on-dark)}
/* the underline is painted separately and had stayed ink, so on the dark
   blocks it drew a line you could not see */
:is(.hero,.phero,.lp-hero,.band,.section--dark) .tlink span:first-child::after{background:var(--on-dark)}
/* the taupe is the legible accent on a dark ground, where the ink is not */
:is(.hero,.phero,.lp-hero,.band,.section--dark) :is(.eyebrow,.accent,.gold,.stars,.spec dt,.stat__n em,.quote blockquote::before,.quote blockquote::after){color:var(--taupe)}
:is(.hero,.phero,.lp-hero,.band,.section--dark) .eyebrow::before{background:var(--taupe)}

/* the landing-page quote form is a white panel floating on the dark hero, so
   its interior goes back to the light ladder */
.lp-hero .lp-form{color:var(--fg-2);border-color:rgba(66,66,66,.10)}
.lp-hero .lp-form :is(h1,h2,h3,h4){color:var(--fg)}
.lp-hero .lp-form .eyebrow{color:var(--fg-3)}
.lp-hero .lp-form .btn{color:var(--fg);border-color:var(--line-2)}
.lp-hero .lp-form label{color:var(--fg-3)}
.lp-hero .lp-form .form__note{color:var(--fg-4)}

/* A white card sitting on the charcoal — the quote form, the package cards —
   belongs to the card, not to the section. Every tone the dark cascade sets
   has to be handed back explicitly, or the section's light-on-dark ladder
   leaks onto white: the package spec rows were rendering taupe-on-white at
   1.56:1. Scoped one level deeper than the cascade so it always wins. */
/* .scard joins .card here now the home page's service row sits on the black:
   its ground is still a white card, so everything inside it has to keep the
   paper text ladder or the headings would have gone white on white. */
.section--dark :is(.card,.scard){color:var(--fg-2)}
.section--dark :is(.card,.scard) :is(h1,h2,h3,h4,.h3,.eyebrow){color:var(--fg)}
.section--dark :is(.card,.scard) :is(p,.lead,blockquote,dt,dd,.spec dt,.spec dd,label,.prose){color:var(--fg-3)}
.section--dark :is(.card,.scard) :is(.card__n,.idx,.muted,figcaption,.form__note){color:var(--fg-4)}
.section--dark :is(.card,.scard) :is(.specs,.spec,.stat,.step){border-color:var(--line)}
.section--dark :is(.card,.scard) .eyebrow::before{background:var(--taupe-d)}
.section--dark :is(.card,.scard) .btn{color:var(--fg);border-color:var(--line-2)}
.section--dark :is(.card,.scard) .btn--solid{color:var(--fg);border-color:var(--taupe-d)}
.section--dark :is(.card,.scard) .form__note a{color:var(--fg-3)}
.section--dark :is(.card,.scard) .tlink{color:var(--fg)}
.section--dark :is(.card,.scard) .tlink span:first-child::after{background:var(--fg)}

/* the design page's proof plate is the same idea at component scale */
.brand-plate :is(h1,h2,h3,h4){color:var(--on-dark)}
.brand-plate p{color:var(--on-dark-3)}

/* ---------- utilities ---------- */
.center{text-align:center}
.mx-auto{margin-inline:auto}
.mt-1{margin-top:.6rem}.mt-2{margin-top:1.2rem}.mt-3{margin-top:1.8rem}.mt-4{margin-top:2.6rem}.mt-5{margin-top:3.6rem}
/* sub-section break: a new labelled block inside an existing section needs
   more air than mt-5 gives, but less than a full --sec section gap */
.mt-6{margin-top:clamp(4rem,7vw,6.5rem)}
/* the gap between two major blocks inside one section */
.mt-block{margin-top:var(--block)}
.mb-3{margin-bottom:1.8rem}.mb-4{margin-bottom:2.6rem}.mb-5{margin-bottom:3.6rem}
.maxw-60{max-width:60ch}.maxw-70{max-width:70ch}
.head{margin-bottom:clamp(2.2rem,4vw,3.4rem)}
.head--split{display:grid;grid-template-columns:1fr auto;gap:2rem;align-items:end}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.bg-2{background:var(--ink-2)}
.ratio-4-3{aspect-ratio:4/3;object-fit:cover;width:100%}
.ratio-3-2{aspect-ratio:3/2;object-fit:cover;width:100%}
.ratio-1-1{aspect-ratio:1/1;object-fit:cover;width:100%}

/* ---------- responsive ---------- */
@media (max-width:1024px){
  .g-4{grid-template-columns:repeat(2,1fr)}
  .work{grid-template-columns:repeat(2,1fr)}
  .ftr__grid{grid-template-columns:1fr 1fr}
  .nav,.hdr__tel{display:none}
  .burger{display:grid}
}
@media (max-width:820px){
  .g-3,.steps,.stats{grid-template-columns:1fr}
  .split,.split--40,.form__row,.head--split{grid-template-columns:1fr}
  .head--split{align-items:start}
  .hero__strip{grid-template-columns:1fr;gap:1rem}
  .hero__strip div{flex-direction:row;align-items:baseline;gap:.9rem}
    .tile--wide{grid-column:span 1;aspect-ratio:4/3}
  .stat{padding-top:1.2rem}
}
@media (max-width:560px){
  .g-2,.g-3,.g-4,.work,.steps,.steps--2{grid-template-columns:1fr}
  .ftr__grid{grid-template-columns:1fr}
  .btn{width:100%;justify-content:center}
  .btn-row{gap:.7rem}
  /* logo + CTA + burger are tight at this width — shrink both, hide neither */
  .brand__swap{width:132px}
  .hdr__cta .btn{width:auto;padding:.72rem 1rem;font-size:.76rem;white-space:nowrap}
  .hdr__in{gap:1rem}
}

/* ---------- narrow phones: the header CTA no longer fits ----------
   Shrinking was tried above and is not enough: brand 132 + gap + button 136 +
   gap + burger 44 + 40px padding needs ~400px, so on every phone at or below
   414px the burger was pushed past the right padding — clipped off-screen
   entirely at 375px and narrower, which is the primary nav control. The button
   is the thing to drop, not the burger: "Get a Free Quote" and the phone number
   both sit in the drawer, and the hero repeats both CTAs immediately below. */
@media (max-width:414px){
  .hdr__cta .btn{display:none}
  .hdr__in{gap:1rem}
}

/* ---------- motion / contrast preferences ---------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important}
  .grain{animation:none}
  [data-rv]{opacity:1;transform:none}
  [data-rv="line"]::after{display:none}
  }
