/* ==========================================================================
   OUTLAW GRAPHICS — style.css
   Vehicle wraps, fire & law-enforcement graphics, signs. Great Falls, MT.

   Build order step 1: the complete token layer and every shared component.
   Every colour, size, duration, easing and z-index lives at :root and NOWHERE
   ELSE. If you find a hex outside :root, it is a bug.

   Signature: THE CUT LINE — a -18deg blade edge that reveals every image,
   drawn from the "Cut Vinyl" service this shop actually sells.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* --- Colour -------------------------------------------------------------
     ACCENT SAMPLED FROM THE REAL LOGO. #BE0000 is the dominant saturated red
     in the brand mark's own pixels (the outlaw and the crossed blades), taken
     from assets/logo/outlaw-graphics-logo.png. It is used unaltered.

     The brand red reads 2.85:1 on the field, under the 3.0 floor for non-text
     UI, so it is NOT used for the hairline blade or for text. --accent-ink is
     the same hue at a lighter value and carries both. The brand colour itself
     is never changed.

     Contrast-audited, all passing:
       paper/ink 14.71   steel/ink 4.97    accent-ink/ink 4.94
       paper/accent 5.16 accent-ink/haze 4.53                                */
  --ink:            #141110;   /* page field — warm off-black, never #000    */
  --haze:           #1E1A18;   /* elevated surface: cards, sticky header     */
  --paper:          #E8E3DB;   /* inverted field + primary type, never #FFF  */
  --steel:          #8A827A;   /* hairlines, borders, captions, spec rail    */
  --accent:         #BE0000;   /* BRAND RED, sampled. Fills only: CTA bg, stars */
  --accent-ink:     #FF2414;   /* blade, accent TEXT, focus ring — 4.94:1    */

  --ink-a92:        rgba(20, 17, 16, 0.92);
  --ink-a72:        rgba(20, 17, 16, 0.72);
  --ink-a55:        rgba(20, 17, 16, 0.55);
  --ink-a20:        rgba(20, 17, 16, 0.20);
  --ink-a78:        rgba(20, 17, 16, 0.78);
  --paper-a12:      rgba(232, 227, 219, 0.12);
  --paper-a06:      rgba(232, 227, 219, 0.06);

  /* --- Type --------------------------------------------------------------- */
  --font-display: "Archivo", "Archivo Fallback", ui-sans-serif, system-ui, sans-serif;
  --font-body:    "Archivo", "Archivo Fallback", ui-sans-serif, system-ui, sans-serif;
  --stretch-display: 125%;   /* the width axis IS the display face */
  --stretch-body:     100%;

  /* Display was 7vw / 6.5rem, landing at ~101px on a 1440 viewport and
     swallowing the hero. Dialled back roughly a third; still the largest thing
     on the page, no longer shouting over the photography. */
  --fs-display:   clamp(2.125rem, 4.6vw, 4.25rem);   /* 34 -> 68px */
  --fs-h2:        clamp(1.625rem, 3.1vw, 2.625rem);  /* 26 -> 42px */
  --fs-h3:        clamp(1.125rem, 1.5vw, 1.375rem);  /* 18 -> 22px */
  --fs-body:      1.125rem;          /* 18px */
  --fs-lead:      1.3125rem;         /* 21px */
  --fs-utility:   0.6875rem;         /* 11px */

  --lh-display:   1.06;
  --lh-body:      1.7;
  --tr-display:   -0.03em;
  --tr-utility:   0.18em;
  --measure:      68ch;

  /* --- Space -------------------------------------------------------------- */
  /* Section padding is paid TWICE between adjacent sections. At 168 the gap
     between content blocks was 336px on desktop, while several blocks are only
     ~300px tall — more empty than content. 112 gives a 224px gap: still slow
     and generous, no longer cavernous. */
  --pad-section:    112px;
  --pad-section-sm:  64px;
  --gutter:          56px;
  --gutter-sm:       24px;
  --container:     1440px;
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px;

  /* --- Corners: SHARP. Held with no exceptions. --------------------------- */
  --radius: 0px;

  /* --- Depth: two stacked shadows, always ---------------------------------
     A 1px tight contact shadow plus a wide soft ambient shadow. Never one.  */
  --shadow-1: 0 1px 0 rgba(0,0,0,.55), 0 8px 24px -12px rgba(0,0,0,.7);
  --shadow-2: 0 1px 0 rgba(0,0,0,.6),  0 28px 64px -28px rgba(0,0,0,.85);
  --shadow-3: 0 1px 0 rgba(0,0,0,.65), 0 48px 96px -40px rgba(0,0,0,.9);

  /* --- Motion: base unit 120ms, every duration a multiple ----------------- */
  --d-micro:      240ms;
  --d-exit:       720ms;
  --d-reveal:     960ms;
  --d-cut:       1200ms;
  --d-field:     1440ms;

  --e-micro:    cubic-bezier(.33, 1, .68, 1);
  --e-reveal:   cubic-bezier(.16, 1, .30, 1);
  --e-cut:      cubic-bezier(.76, 0, .24, 1);
  --e-exit:     cubic-bezier(.65, 0, .35, 1);

  --stagger-line: 80ms;
  --stagger-item: 110ms;
  --stagger-nav:  50ms;

  /* The cut axis. Everything enters along it. Nothing rises from below-centre. */
  --cut-angle: -18deg;
  --blade:      2px;

  /* --- Z-index scale. Never 9999. ----------------------------------------- */
  --print-bg: #FFFFFF;  /* print only — the one place pure white is correct */
  --print-fg: #000000;

  --z-base: 0; --z-dropdown: 100; --z-sticky: 200;
  --z-overlay: 300; --z-modal: 400; --z-toast: 500;

  /* --- Chrome ------------------------------------------------------------- */
  --header-h:      88px;
  --header-h-sm:   64px;
  --topbar-h:      40px;
  --topbar-h-sm:   32px;
  --head-h:        calc(var(--topbar-h) + var(--header-h));
  --mobilebar-h:   56px;
}

@media (max-width: 900px) {
  :root {
    --pad-section: var(--pad-section-sm);
    --gutter: var(--gutter-sm);
    --fs-body: 1.0625rem;
  }
}

/* --------------------------------------------------------------------------
   2. FONT FACES — self-hosted, size-adjust fallback so the swap cannot shift
   layout. Drop the woff2 files into /assets/fonts/.
   -------------------------------------------------------------------------- */
/* Archivo is a VARIABLE font with a width axis (font-stretch 62%-125%), so a
   single file provides both the body face and the "expanded" display face.
   There is no separate Archivo Expanded download; the display face is simply
   Archivo at font-stretch 125%. */
@font-face {
  font-family: "Archivo";
  src: url("../fonts/archivo-latin.woff2") format("woff2");
  font-weight: 300 700; font-stretch: 62% 125%;
  font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  src: url("../fonts/archivo-latin-ext.woff2") format("woff2");
  font-weight: 300 700; font-stretch: 62% 125%;
  font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Metric-matched fallback under its OWN family name, so it cannot shadow the
   real face while that is still loading. */
@font-face {
  font-family: "Archivo Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Liberation Sans");
  size-adjust: 97%; ascent-override: 92%; descent-override: 24%; line-gap-override: 0%;
}

/* --------------------------------------------------------------------------
   3. RESET + BASE
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchor targets must clear the sticky header. */
  scroll-padding-top: calc(var(--topbar-h) + var(--header-h) + var(--s-5));
}

body {
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;           /* no horizontal scroll at any width */
}

img, picture, svg, video, canvas {
  display: block;
  max-width: 100%;
  height: auto;
}
/* Every <img> in the markup carries explicit width/height. This keeps the
   aspect ratio while still honouring those attributes. */
img { background-color: var(--haze); }

/* Inline icons carry a 24x24 viewBox and no width/height attribute, so they
   must be sized in CSS or they render at their container's full size. */
.topbar svg, .btn svg, .mobilebar svg, .footer svg,
.spec-rail svg, .field__error svg, .plate svg {
  width: 16px; height: 16px; flex: none; fill: currentColor;
}
.topbar__group > a, .topbar__hours {
  display: inline-flex; align-items: center; gap: var(--s-2);
}

input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-stretch: var(--stretch-display);
  font-weight: 300;             /* light, never bold — heavy reads cheap large */
  letter-spacing: var(--tr-display);
  line-height: var(--lh-display);
  text-wrap: balance;
}
h1 { font-size: var(--fs-display); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); font-weight: 400; }

p { max-width: var(--measure); text-wrap: pretty; }

/* --- Focus: designed, never removed ------------------------------------- */
:focus { outline: none; }
:focus-visible {
  outline: var(--blade) solid var(--accent-ink);
  outline-offset: 3px;
}

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

/* Skip link — first tab stop on every page. */
.skip-link {
  position: absolute; left: var(--s-4); top: calc(-1 * var(--s-9));
  z-index: var(--z-toast);
  background: var(--accent); color: var(--paper);
  padding: var(--s-3) var(--s-5);
  transition: top var(--d-micro) var(--e-micro);
}
.skip-link:focus-visible { top: var(--s-4); }

/* --------------------------------------------------------------------------
   4. LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--pad-section); position: relative; }

/* The locked asymmetric 8/4 grid: content left, spec rail right. */
.grid-8-4 {
  display: grid;
  grid-template-columns: minmax(0, 8fr) minmax(0, 4fr);
  gap: var(--gutter);
  align-items: start;
}
@media (max-width: 900px) { .grid-8-4 { grid-template-columns: 1fr; } }

/* The spec rail — reads like a print job ticket, not a sidebar. */
.spec-rail {
  border-left: 1px solid var(--steel);
  padding-left: var(--s-5);
  font-size: var(--fs-utility);
  text-transform: uppercase;
  letter-spacing: var(--tr-utility);
  color: var(--steel);
}
.spec-rail dt { color: var(--steel); margin-top: var(--s-4); }
.spec-rail dd { color: var(--paper); margin: var(--s-1) 0 0; letter-spacing: normal; }

/* Registration crosshairs — mark the outer corners of the content grid.
   Decorative, so hidden from assistive tech. */
.reg-marks { position: absolute; inset: 0; pointer-events: none; }
.reg-marks::before, .reg-marks::after {
  content: ""; position: absolute; width: 14px; height: 14px;
  border: 1px solid var(--steel); opacity: .5;
}
.reg-marks::before { top: var(--s-5); left: var(--s-5);  border-right: 0; border-bottom: 0; }
.reg-marks::after  { bottom: var(--s-5); right: var(--s-5); border-left: 0; border-top: 0; }

/* Inverted field — used for the section-to-section colour transition. */
.field-paper {
  padding-block: calc(var(--pad-section) * 1.25);
  background: var(--paper); color: var(--ink);
  transition: background var(--d-field) var(--e-cut),
              color      var(--d-field) var(--e-cut);
}
.field-paper .spec-rail { color: var(--ink); border-color: var(--ink); }
.field-paper .eyebrow   { color: var(--ink); }

/* --------------------------------------------------------------------------
   5. TYPOGRAPHY HELPERS
   -------------------------------------------------------------------------- */
.eyebrow {
  font-size: var(--fs-utility);
  text-transform: uppercase;
  letter-spacing: var(--tr-utility);
  color: var(--accent-ink);
  display: flex; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-5);
}
.eyebrow::before {
  content: ""; width: 28px; height: var(--blade);
  background: var(--accent-ink); flex: none;
}
.lead { font-size: var(--fs-lead); color: var(--paper); }
.muted { color: var(--steel); }

/* --------------------------------------------------------------------------
   6. THE CUT LINE — the signature.
   An image is revealed by a -18deg edge travelling across it, led by a 2px
   accent blade. Never an opacity fade. The element is in the DOM and readable
   from first paint; only the mask animates.
   -------------------------------------------------------------------------- */
.cut {
  position: relative;
  overflow: hidden;
  /* Pre-reveal: fully clipped along the cut axis. */
  --cut-progress: 0;
}
.cut > picture { display: block; }
.cut > img, .cut > picture, .cut > video {
  clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
  transition: clip-path var(--d-cut) var(--e-cut),
              filter    var(--d-cut) var(--e-cut);
  filter: saturate(.4);
}
/* The travelling blade. */
.cut::after {
  content: ""; position: absolute; top: -20%; left: 0;
  width: var(--blade); height: 140%;
  background: var(--accent-ink);
  transform: rotate(var(--cut-angle)) translateX(-2px);
  transform-origin: top center;
  opacity: 0;
  transition: left var(--d-cut) var(--e-cut), opacity var(--d-cut) var(--e-cut);
  will-change: left;
}
.cut.is-revealed > img,
.cut.is-revealed > picture,
.cut.is-revealed > video {
  clip-path: polygon(0 0, 120% -20%, 120% 120%, 0 100%);
  filter: saturate(1);
}
.cut.is-revealed::after { left: 110%; opacity: 0; }
.cut.is-cutting::after  { opacity: 1; }

/* --------------------------------------------------------------------------
   7. ENTRANCE REVEALS — fire once at 82% viewport, never repeat.
   Elements exist and are readable in the DOM before the animation runs.
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition: opacity var(--d-reveal) var(--e-reveal),
              transform var(--d-reveal) var(--e-reveal);
}
.reveal.is-in { opacity: 1; transform: none; }

/* Line-by-line display reveal, masked overflow. Max 6 lines staggered. */
.line-mask { display: block; overflow: hidden; }
.line-mask > span {
  display: block;
  transform: translate3d(0, 100%, 0);
  transition: transform var(--d-reveal) var(--e-reveal);
}
.is-in .line-mask > span { transform: none; }
.line-mask:nth-child(1) > span { transition-delay: 0ms; }
.line-mask:nth-child(2) > span { transition-delay: var(--stagger-line); }
.line-mask:nth-child(3) > span { transition-delay: calc(var(--stagger-line) * 2); }
.line-mask:nth-child(4) > span { transition-delay: calc(var(--stagger-line) * 3); }
.line-mask:nth-child(5) > span { transition-delay: calc(var(--stagger-line) * 4); }
.line-mask:nth-child(6) > span { transition-delay: calc(var(--stagger-line) * 5); }

/* Grid stagger, capped at 6. */
.stagger > *:nth-child(1) { transition-delay: 0ms; }
.stagger > *:nth-child(2) { transition-delay: var(--stagger-item); }
.stagger > *:nth-child(3) { transition-delay: calc(var(--stagger-item) * 2); }
.stagger > *:nth-child(4) { transition-delay: calc(var(--stagger-item) * 3); }
.stagger > *:nth-child(5) { transition-delay: calc(var(--stagger-item) * 4); }
.stagger > *:nth-child(6) { transition-delay: calc(var(--stagger-item) * 5); }
.stagger > *:nth-child(n+7) { transition-delay: calc(var(--stagger-item) * 5); }

/* Rule that draws across a section on entry. */
.rule-draw {
  height: 1px; background: var(--steel);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--d-reveal) var(--e-cut);
}
.rule-draw.is-in { transform: scaleX(1); }

/* --------------------------------------------------------------------------
   8. BUTTONS — default / hover / focus-visible / active / disabled, all
   designed. Touch targets never below 44px.
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--accent);
  --btn-fg: var(--paper);
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-3);
  min-height: 48px; padding: var(--s-3) var(--s-6);
  background: var(--btn-bg); color: var(--btn-fg);
  border: 1px solid var(--btn-bg);
  border-radius: var(--radius);
  font-size: var(--fs-utility);
  text-transform: uppercase;
  letter-spacing: var(--tr-utility);
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-1);
  transition: background var(--d-micro) var(--e-micro),
              color      var(--d-micro) var(--e-micro),
              border-color var(--d-micro) var(--e-micro),
              box-shadow var(--d-micro) var(--e-micro);
}
/* Hover changes colour, never scale — outer scale shifts layout. */
.btn:hover  { --btn-bg: var(--accent-ink); box-shadow: var(--shadow-2); }
.btn:active { --btn-bg: var(--accent); box-shadow: var(--shadow-1); }
.btn[disabled], .btn[aria-disabled="true"] {
  --btn-bg: var(--haze); --btn-fg: var(--steel);
  cursor: not-allowed; box-shadow: none;
}
.btn--ghost {
  --btn-bg: transparent; --btn-fg: var(--paper);
  border-color: var(--steel); box-shadow: none;
}
.btn--ghost:hover { --btn-bg: var(--paper-a06); border-color: var(--paper); }
.field-paper .btn--ghost { --btn-fg: var(--ink); border-color: var(--ink); }

/* Secondary actions are text links, not buttons. */
.link-underline {
  color: var(--accent-ink);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  transition: text-decoration-color var(--d-micro) var(--e-micro);
}
.link-underline:hover { text-decoration-thickness: var(--blade); }

/* --------------------------------------------------------------------------
   9. TOP BAR
   -------------------------------------------------------------------------- */
.topbar {
  min-height: var(--topbar-h);
  background: var(--haze);
  border-bottom: 1px solid var(--paper-a12);
  font-size: var(--fs-utility);
  text-transform: uppercase;
  letter-spacing: var(--tr-utility);
  color: var(--steel);
}
.topbar .container {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5); min-height: var(--topbar-h);
}
.topbar a { text-decoration: none; transition: color var(--d-micro) var(--e-micro); }
.topbar a:hover { color: var(--accent-ink); }
.topbar__group { display: flex; align-items: center; gap: var(--s-5); }
.topbar__socials { display: flex; align-items: center; gap: var(--s-2); }
.topbar__socials a {
  display: grid; place-items: center;
  width: 44px; height: 44px;     /* 44px target even though the icon is 18px */
  margin: -13px 0;
}
.topbar__socials svg { width: 18px; height: 18px; fill: currentColor; }
@media (max-width: 900px) {
  .topbar__hours, .topbar__email { display: none; }
}

/* --------------------------------------------------------------------------
   10. HEADER + NAV
   The ONE glassmorphism instance in the build. Scoped deliberately: the
   sticky header and the mobile overlay that shares its treatment. Not a
   site-wide surface — stacked backdrop-filters wreck scroll perf on Android.
   -------------------------------------------------------------------------- */
/* Top bar and header are one sticky unit, so contact details stay reachable
   at any scroll depth instead of scrolling away. The wrapper is what sticks;
   the header itself is static inside it. */
.site-head {
  position: sticky; top: 0; z-index: var(--z-sticky);
  transition: box-shadow var(--d-micro) var(--e-micro);
}
.site-head.is-stuck { box-shadow: var(--shadow-2); }
.site-head.is-stuck .topbar {
  min-height: var(--topbar-h-sm);
  background: transparent;
  border-bottom-color: var(--paper-a12);
}
.site-head.is-stuck .topbar .container { min-height: var(--topbar-h-sm); }
/* One frosted surface across the whole head, not two stacked panels. */
.site-head.is-stuck { background: var(--ink-a92); }
@supports (backdrop-filter: blur(1px)) {
  .site-head.is-stuck { background: var(--ink-a72); backdrop-filter: blur(18px) saturate(1.2); }
}

.header {
  position: relative; z-index: 1;
  height: var(--header-h);
  display: flex; align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: height var(--d-micro) var(--e-micro),
              background var(--d-micro) var(--e-micro),
              border-color var(--d-micro) var(--e-micro);
}
.site-head.is-stuck .header {
  height: var(--header-h-sm);
  border-bottom-color: var(--steel);
}
.header .container {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gutter); width: 100%;
}

/* LOGO SLOT.
   Renders an <img> only. There is deliberately NO text-wordmark fallback and
   no ::before content here — the company name lives in the img's alt text.
   A hard-coded title in this slot is a silent failure that ships to the
   client as finished work. If the logo is missing, the slot must look broken. */
.logo { display: block; flex: none; }
.logo img {
  height: 46px; width: auto;      /* native aspect ratio, never stretched */
  transition: height var(--d-micro) var(--e-micro);
}
.site-head.is-stuck .logo img { height: 34px; }

.nav { display: flex; align-items: center; gap: var(--s-6); }
.nav__list { display: flex; align-items: center; gap: var(--s-6); list-style: none; padding: 0; }
.nav__link {
  position: relative;
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--fs-utility);
  text-transform: uppercase; letter-spacing: var(--tr-utility);
  text-decoration: none; color: var(--paper);
  transition: color var(--d-micro) var(--e-micro);
}
.nav__link::after {
  content: ""; position: absolute; left: 0; bottom: 10px;
  width: 100%; height: var(--blade); background: var(--accent-ink);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--d-micro) var(--e-micro);
}
.nav__link:hover::after,
.nav__link[aria-current="page"]::after { transform: scaleX(1); }
.nav__link[aria-current="page"] { color: var(--accent-ink); }

/* Services — the only dropdown, one level deep. */
.nav__item--has-menu { position: relative; }
.nav__menu {
  position: absolute; top: 100%; left: 0; z-index: var(--z-dropdown);
  min-width: 280px; padding: var(--s-3) 0;
  background: var(--haze);
  border: 1px solid var(--steel);
  box-shadow: var(--shadow-3);
  list-style: none;
  opacity: 0; visibility: hidden;
  transform: translate3d(0, -6px, 0);
  transition: opacity var(--d-micro) var(--e-micro),
              transform var(--d-micro) var(--e-micro),
              visibility var(--d-micro);
}
.nav__item--has-menu:hover > .nav__menu,
.nav__item--has-menu:focus-within > .nav__menu,
.nav__menu[data-open="true"] { opacity: 1; visibility: visible; transform: none; }
.nav__menu a {
  display: block; padding: var(--s-3) var(--s-5);
  min-height: 44px; text-decoration: none;
  font-size: var(--fs-utility); text-transform: uppercase;
  letter-spacing: var(--tr-utility);
  border-left: var(--blade) solid transparent;
  transition: background var(--d-micro) var(--e-micro),
              border-color var(--d-micro) var(--e-micro);
}
.nav__menu a:hover { background: var(--paper-a06); border-left-color: var(--accent); }

/* Mobile overlay menu — full screen, focus trapped, Escape closes. */
.nav__toggle { display: none; width: 44px; height: 44px; place-items: center; }
.nav__toggle svg { width: 24px; height: 24px; stroke: currentColor; fill: none; stroke-width: 1.5; }

@media (max-width: 1024px) {
  .nav__toggle { display: grid; }
  .nav__list {
    position: fixed; inset: 0; z-index: var(--z-overlay);
    flex-direction: column; align-items: flex-start; justify-content: center;
    gap: var(--s-4);
    padding: var(--s-9) var(--gutter);
    background: var(--ink-a78);
    overflow-y: auto;
    opacity: 0; visibility: hidden;
    transition: opacity var(--d-exit) var(--e-exit), visibility var(--d-exit);
  }
  @supports (backdrop-filter: blur(1px)) {
    .nav__list { backdrop-filter: blur(18px) saturate(1.2); }
  }
  .nav__list[data-open="true"] { opacity: 1; visibility: visible; }
  .nav__link { font-family: var(--font-display); font-size: var(--fs-h3);
               text-transform: none; letter-spacing: var(--tr-display);
               min-height: 48px; }
  .nav__menu { position: static; opacity: 1; visibility: visible; transform: none;
               background: none; border: 0; box-shadow: none; min-width: 0;
               padding-left: var(--s-4); }
}

/* The hero eyebrow sits over photography rather than a flat field, where a
   small 11px accent-red label is the first thing to lose legibility. It takes
   the paper colour here; the accent rule before it keeps the brand cue. */
.hero .eyebrow { color: var(--paper); }

/* --------------------------------------------------------------------------
   11. HERO — 92vh, content sitting low, three parallax planes under
   perspective. The LCP image is NEVER animated from opacity 0.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: calc(80vh - var(--head-h));
  display: flex; align-items: flex-end;   /* content low, not centred */
  padding-block: var(--s-8) var(--s-7);
  overflow: hidden;
  perspective: 1400px;
  perspective-origin: 50% 40%;
}
.hero__stage { position: absolute; inset: 0; transform-style: preserve-3d; }
.hero__plane { position: absolute; inset: -8%; will-change: transform; }
.hero__plane img { width: 100%; height: 100%; object-fit: cover;
                   filter: brightness(.68) saturate(.95); }
.hero__plane--back  { transform: translateZ(-120px) scale(1.12); }
.hero__plane--mid   { transform: translateZ(-40px)  scale(1.04); }
.hero__plane--front { transform: translateZ(0); }
/* Legibility scrim — a flat wash, not a decorative gradient background. */
.hero__scrim {
  position: absolute; inset: 0;
  /* The shorter hero puts the eyebrow higher up the frame, where the old ramp
     had already faded out and the 11px label washed into a bright ceiling.
     Stops raised so the whole content block sits on a dark enough ground. */
  background: linear-gradient(to top,
      var(--ink)     0%,
      var(--ink-a92) 48%,
      var(--ink-a72) 68%,
      var(--ink-a55) 82%,
      var(--ink-a20) 93%,
      transparent   100%);
}
.hero__content { position: relative; z-index: 1; width: 100%; }
.hero__title { max-width: 19ch; }
.hero__sub { margin-top: var(--s-4); max-width: 46ch; color: var(--paper); }
.hero__actions { margin-top: var(--s-6); display: flex; flex-wrap: wrap; gap: var(--s-4); }
@media (max-width: 900px) { .hero { min-height: calc(82vh - var(--head-h) - var(--mobilebar-h)); perspective: none; }
  .hero__plane--back, .hero__plane--mid { display: none; } }

/* On a short viewport the hero content alone is taller than the space left
   after the sticky head and the mobile call bar, which pushes the primary CTA
   out of sight. Tighten the hero's own rhythm rather than cutting content. */
@media (max-height: 700px) {
  .hero { min-height: auto; padding-block: var(--s-5) var(--s-5); }
  .hero__sub { margin-top: var(--s-3); }
  .hero__actions { margin-top: var(--s-4); }
}

/* --------------------------------------------------------------------------
   12. CARDS — overlap the section edge. Hover scales the INNER image, never
   the card, so nothing shifts.
   -------------------------------------------------------------------------- */
.cards {
  display: grid; gap: var(--gutter);
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.card {
  position: relative;
  background: var(--haze);
  border: 1px solid var(--paper-a12);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  cursor: pointer;                 /* interactive card — cursor must say so */
  transition: border-color var(--d-micro) var(--e-micro),
              box-shadow   var(--d-micro) var(--e-micro),
              transform    var(--d-reveal) var(--e-reveal);
  transform-style: preserve-3d;
}
.cards.stagger .card { transform: rotateX(4deg) translate3d(0, 18px, 0); opacity: 0; }
.cards.stagger .card.is-in { transform: none; opacity: 1; }
.card:hover { border-color: var(--steel); box-shadow: var(--shadow-3); }
.card__media { position: relative; overflow: hidden; aspect-ratio: 4 / 3;
                background: var(--ink); }
/* Cards align to the top so differing photo ratios do not stretch a row. */
.cards { align-items: start; }
.card__media picture { display: block; width: 100%; height: 100%; }
/* contain, not cover: the whole vehicle stays in frame. Letterboxing onto the
   ink field reads as deliberate on a dark ground, and nothing gets its roof
   or wheels cut off. */
.card__media img {
  width: 100%; height: 100%; object-fit: contain;
  transition: transform var(--d-exit) var(--e-reveal);
}
.card:hover .card__media img { transform: scale(1.04); }
.card__body { padding: var(--s-6); }
.card__title { margin-bottom: var(--s-3); }
/* Whole-card link without nesting interactive elements. */
.card__link::after { content: ""; position: absolute; inset: 0; }
.card:focus-within { border-color: var(--accent-ink); }

/* A card that breaks the section edge — the rhythm lever. */
.card--breakout { margin-block-start: calc(-1 * var(--s-9)); }
@media (max-width: 900px) { .card--breakout { margin-block-start: 0; } }


/* --------------------------------------------------------------------------
   12b. PLATES — the no-photograph fallback.
   NOT decoration standing in for a photo, and NOT stock imagery. These render
   the client's own product: reflective chevron striping, cut-vinyl bars and
   plotter registration grids, drawn at the same -18deg cut angle as the
   signature. They read as deliberate graphic panels, never as a missing image.

   build.py emits a <picture> instead of a .plate the moment a real photo file
   exists on disk for that slug, so this whole layer disappears on its own.
   -------------------------------------------------------------------------- */
.plate {
  position: relative;
  width: 100%;
  background: var(--haze);
  overflow: hidden;
  isolation: isolate;
}
.plate::before {
  content: "";
  position: absolute; inset: -30%;
  transform: rotate(var(--cut-angle));
}
.plate::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, var(--ink) 2%, transparent 60%);
}

/* Chevron — fire apparatus rear striping. Alternating accent and paper bars. */
.plate--chevron::before {
  background: repeating-linear-gradient(90deg,
    var(--accent) 0 34px, var(--paper) 34px 68px);
  opacity: .92;
}
/* Fleet — wide accent bands with a quiet field between. */
.plate--fleet::before {
  background: repeating-linear-gradient(90deg,
    var(--accent) 0 12px, transparent 12px 96px);
  opacity: .85;
}
/* Cut vinyl — thin plotted lines, close together. */
.plate--vinyl::before {
  background: repeating-linear-gradient(90deg,
    var(--paper) 0 2px, transparent 2px 22px);
  opacity: .55;
}
/* Signage — a broad two-tone split. */
.plate--sign::before {
  background: linear-gradient(90deg,
    var(--accent) 0 46%, var(--paper) 46% 54%, transparent 54%);
  opacity: .8;
}
/* Banner — hemmed edge repeat with grommet marks. */
.plate--banner::before {
  background:
    repeating-linear-gradient(90deg, var(--paper) 0 3px, transparent 3px 120px),
    repeating-linear-gradient(90deg, var(--accent) 0 60px, transparent 60px 240px);
  opacity: .7;
}
/* Registration grid — the plotter setup screen. */
.plate--reg::before {
  background:
    repeating-linear-gradient(90deg, var(--steel) 0 1px, transparent 1px 48px),
    repeating-linear-gradient(0deg,  var(--steel) 0 1px, transparent 1px 48px);
  opacity: .5;
}
/* Install — diagonal hazard bars. */
.plate--install::before {
  background: repeating-linear-gradient(90deg,
    var(--accent) 0 26px, var(--ink) 26px 52px);
  opacity: .9;
}

/* The label sits on the plate so a panel always says what it represents. */
.plate__label {
  position: absolute; left: var(--s-5); bottom: var(--s-5); z-index: 1;
  font-size: var(--fs-utility);
  text-transform: uppercase; letter-spacing: var(--tr-utility);
  color: var(--paper);
  display: flex; align-items: center; gap: var(--s-3);
}
.plate__label::before {
  content: ""; width: 22px; height: var(--blade); background: var(--paper);
}

/* Type-led hero, used while there is no hero photograph. */
.hero--typeled {
  min-height: calc(80vh - var(--head-h));
  background: var(--ink);
}
.hero--typeled .hero__stage { inset: 0; overflow: hidden; }
.hero--typeled .hero__chevron {
  position: absolute; inset: -30%;
  transform: rotate(var(--cut-angle));
  background: repeating-linear-gradient(90deg,
    var(--accent) 0 46px, var(--paper) 46px 92px);
  opacity: .14;
}
.hero--typeled .hero__scrim {
  background: linear-gradient(to top, var(--ink) 22%, var(--ink-a55) 60%, transparent 92%);
}

/* Full-width editorial figure — native aspect ratio, never cropped. */
/* Native ratio, edge to edge, no letterbox. Width is capped instead of height
   so a 4:3 photo does not tower over the page and nothing is cropped. */
.figure-full { width: 100%; max-width: 1080px; margin-inline: auto;
               background: var(--ink); }
/* A portrait source at full column width would run to ~1900px tall. Narrow the
   figure instead — still native ratio, still uncropped, just not towering. */
.figure-full[data-portrait="true"] { max-width: 560px; }
.figure-full picture { display: block; }
.figure-full img { width: 100%; height: auto; display: block; }
.figure-full figcaption {
  padding: var(--s-4) 0 0; font-size: var(--fs-utility);
  text-transform: uppercase; letter-spacing: var(--tr-utility); color: var(--steel);
}

/* --------------------------------------------------------------------------
   12c. GALLERY — masonry-ish columns so every native aspect ratio is honoured.
   -------------------------------------------------------------------------- */
.gallery { columns: 3; column-gap: var(--s-5); }
@media (max-width: 1024px) { .gallery { columns: 2; } }
@media (max-width: 640px)  { .gallery { columns: 1; } }
.gallery figure {
  break-inside: avoid; margin: 0 0 var(--s-5);
  background: var(--haze); border: 1px solid var(--paper-a12);
  box-shadow: var(--shadow-1);
}
.gallery img { width: 100%; height: auto; display: block; }
.gallery figcaption {
  padding: var(--s-3) var(--s-4); font-size: var(--fs-utility);
  color: var(--steel); line-height: 1.5;
}
.gallery-group { margin-top: var(--s-9); }
.gallery-group__label {
  font-size: var(--fs-utility); text-transform: uppercase;
  letter-spacing: var(--tr-utility); color: var(--accent-ink);
  margin-bottom: var(--s-6);
}

/* --------------------------------------------------------------------------
   13. MARQUEE — service names, skewed by scroll velocity.
   Note: this is scroll-DRIVEN, not idle-looping. It does not move at rest.
   -------------------------------------------------------------------------- */
.marquee {
  overflow: hidden; border-block: 1px solid var(--paper-a12);
  padding-block: var(--s-5);
  --skew: 0deg;
}
.marquee__track {
  display: flex; gap: var(--s-7); width: max-content;
  font-family: var(--font-display); font-stretch: var(--stretch-display);
  font-size: var(--fs-h3);
  color: var(--steel); white-space: nowrap;
  transform: translate3d(var(--mx, 0), 0, 0) skewX(var(--skew));
  transition: transform 120ms linear;
  will-change: transform;
}
.marquee__track span { display: inline-flex; align-items: center; gap: var(--s-7); }
.marquee__track span::after {
  content: ""; width: 6px; height: 6px; background: var(--accent); flex: none;
}

/* --------------------------------------------------------------------------
   14. REVIEWS — stars are accessible inline SVG, never emoji.
   Rendered from /assets/data/reviews.json. Empty categories render nothing.
   -------------------------------------------------------------------------- */
.reviews__summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-5);
  padding-bottom: var(--s-6); border-bottom: 1px solid var(--paper-a12);
}
.reviews__score { font-family: var(--font-display); font-stretch: var(--stretch-display);
                  font-size: var(--fs-h2); }
.review-group { margin-top: var(--s-8); }
.review-group__label {
  font-size: var(--fs-utility); text-transform: uppercase;
  letter-spacing: var(--tr-utility); color: var(--accent-ink);
  margin-bottom: var(--s-5);
}
.review {
  background: var(--haze); border: 1px solid var(--paper-a12);
  padding: var(--s-6); box-shadow: var(--shadow-1);
}
.review__head { display: flex; justify-content: space-between; gap: var(--s-4);
                align-items: baseline; margin-bottom: var(--s-4); }
.review__author { font-weight: 500; }
.review__date { font-size: var(--fs-utility); color: var(--steel);
                text-transform: uppercase; letter-spacing: var(--tr-utility); }
.review__text { color: var(--paper); }
.stars { display: inline-flex; gap: 2px; }
.stars svg { width: 16px; height: 16px; fill: var(--accent); }
.stars svg.is-empty { fill: var(--steel); opacity: .4; }

/* --------------------------------------------------------------------------
   15. FORMS — labels above fields, never placeholder-as-label.
   -------------------------------------------------------------------------- */
.form { display: grid; gap: var(--s-5); max-width: 640px; }
.field { display: grid; gap: var(--s-2); }
.field label {
  font-size: var(--fs-utility); text-transform: uppercase;
  letter-spacing: var(--tr-utility); color: var(--steel);
}
.field .req { color: var(--accent-ink); }
.field input, .field select, .field textarea {
  width: 100%; min-height: 48px; padding: var(--s-3) var(--s-4);
  background: var(--haze); color: var(--paper);
  border: 1px solid var(--steel); border-radius: var(--radius);
  transition: border-color var(--d-micro) var(--e-micro),
              background var(--d-micro) var(--e-micro);
}
.field textarea { min-height: 140px; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--steel); }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--paper); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  border-color: var(--accent-ink);
}
/* Error state: colour is never the only indicator — an icon and text carry it. */
.field[data-invalid="true"] input,
.field[data-invalid="true"] select,
.field[data-invalid="true"] textarea { border-color: var(--accent-ink); border-width: var(--blade); }
.field__error {
  display: none; align-items: center; gap: var(--s-2);
  font-size: var(--fs-utility); color: var(--accent-ink);
  text-transform: uppercase; letter-spacing: var(--tr-utility);
}
.field[data-invalid="true"] .field__error { display: flex; }
.field__error svg { width: 14px; height: 14px; fill: currentColor; flex: none; }

/* Honeypot — off-screen, not display:none, and hidden from AT. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form__status { font-size: var(--fs-utility); text-transform: uppercase;
                letter-spacing: var(--tr-utility); min-height: 1.2em; }
.form__status[data-state="success"] { color: var(--paper); }
.form__status[data-state="failed"]  { color: var(--accent-ink); }
.btn[data-state="submitting"] { pointer-events: none; opacity: .7; }

/* --------------------------------------------------------------------------
   15b. FAQ — native <details>. Works with JS disabled, keyboard-operable and
   announced correctly, which a div-and-click accordion is not.
   -------------------------------------------------------------------------- */
.faq { border-top: 1px solid var(--paper-a12); }
.faq__heading {
  font-family: var(--font-body); font-size: var(--fs-utility);
  text-transform: uppercase; letter-spacing: var(--tr-utility);
  color: var(--steel); font-weight: 400;
  margin: var(--s-5) 0 var(--s-4);
}
.faq__item { border-bottom: 1px solid var(--paper-a12); }
.faq__q {
  list-style: none; cursor: pointer;
  position: relative;
  padding: var(--s-4) 34px var(--s-4) 0; min-height: 44px;
  font-size: 1rem; line-height: 1.5; color: var(--paper);
  transition: color var(--d-micro) var(--e-micro);
}
.faq__q::-webkit-details-marker { display: none; }
/* A plus that becomes a minus: two crossed blades, one of which rotates flat.
   Absolutely positioned so they overlap exactly rather than sitting side by
   side in the flex flow. */
.faq__q::before, .faq__q::after {
  content: ""; position: absolute; right: 0; top: 1.55em;
  width: 12px; height: var(--blade); background: var(--accent-ink);
  transition: transform var(--d-micro) var(--e-micro);
}
.faq__q::after { transform: rotate(90deg); }
.faq__item[open] .faq__q::after { transform: rotate(0deg); }
.faq__q:hover { color: var(--accent-ink); }
.faq__q:focus-visible { outline: var(--blade) solid var(--accent-ink); outline-offset: 2px; }
.faq__a { padding: 0 var(--s-6) var(--s-5) 0; }
.faq__a p { color: var(--steel); font-size: 1rem; line-height: 1.65; max-width: 46ch; }
.faq__more { margin-top: var(--s-5); font-size: var(--fs-utility);
             text-transform: uppercase; letter-spacing: var(--tr-utility);
             color: var(--steel); }
@media (prefers-reduced-motion: no-preference) {
  .faq__item[open] .faq__a { animation: faq-open var(--d-micro) var(--e-micro); }
}
@keyframes faq-open { from { opacity: 0; transform: translate3d(0, -4px, 0); }
                      to   { opacity: 1; transform: none; } }

/* --------------------------------------------------------------------------
   16. BREADCRUMBS
   -------------------------------------------------------------------------- */
.crumbs { font-size: var(--fs-utility); text-transform: uppercase;
          letter-spacing: var(--tr-utility); color: var(--steel);
          padding-block: var(--s-5); }
.crumbs ol { display: flex; flex-wrap: wrap; gap: var(--s-3); list-style: none; padding: 0; }
.crumbs li + li::before { content: "/"; margin-right: var(--s-3); color: var(--steel); }
.crumbs a { text-decoration: none; }
.crumbs a:hover { color: var(--accent-ink); }
.crumbs [aria-current="page"] { color: var(--paper); }

/* --------------------------------------------------------------------------
   17. FOOTER
   -------------------------------------------------------------------------- */
.footer { background: var(--haze); border-top: 1px solid var(--paper-a12);
          padding-block: var(--pad-section-sm) var(--s-7); }
.footer__grid {
  display: grid; gap: var(--gutter);
  grid-template-columns: 2fr 1fr 1fr 1.4fr;
}
@media (max-width: 1024px) { .footer__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px)  { .footer__grid { grid-template-columns: 1fr; } }
.footer .footer-h { font-size: var(--fs-utility); text-transform: uppercase;
             letter-spacing: var(--tr-utility); color: var(--steel);
             font-family: var(--font-body); font-weight: 400;
             line-height: var(--lh-body); margin-bottom: var(--s-4); }
.footer ul { list-style: none; padding: 0; display: grid; gap: var(--s-3); }
.footer a { text-decoration: none; }
.footer a:hover { color: var(--accent-ink); }
.footer address { font-style: normal; }
.footer__map { aspect-ratio: 16 / 10; border: 1px solid var(--paper-a12); position: relative; }
.footer__map iframe { width: 100%; height: 100%; border: 0; }
.footer__bottom { margin-top: var(--s-8); padding-top: var(--s-5);
                  border-top: 1px solid var(--paper-a12); }
/* Agency credit: utility size, muted, still 4.5:1. A credit, not a second CTA. */
.footer-credit { font-size: var(--fs-utility); color: var(--steel);
                 text-transform: uppercase; letter-spacing: var(--tr-utility);
                 line-height: 2; }
.footer-credit a { color: var(--steel); text-decoration: underline;
                   text-underline-offset: 3px; }
.footer-credit a:hover { color: var(--accent-ink); }

/* --------------------------------------------------------------------------
   18. STICKY MOBILE BAR — under 768px only. Call + WhatsApp.
   Body gets padding-bottom in the same query so it never covers the footer.
   -------------------------------------------------------------------------- */
.mobilebar { display: none; }
@media (max-width: 767px) {
  body { padding-bottom: calc(var(--mobilebar-h) + env(safe-area-inset-bottom, 0px)); }
  .mobilebar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-sticky);
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    height: calc(var(--mobilebar-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--haze); border-top: 1px solid var(--steel);
    box-shadow: var(--shadow-3);
  }
  .mobilebar a {
    display: flex; align-items: center; justify-content: center; gap: var(--s-2);
    min-height: 44px; text-decoration: none;
    font-size: var(--fs-utility); text-transform: uppercase;
    letter-spacing: var(--tr-utility);
  }
  .mobilebar a + a { border-left: 1px solid var(--paper-a12); }
  .mobilebar svg { width: 18px; height: 18px; fill: currentColor; }
  .mobilebar__call { background: var(--accent); color: var(--paper); }
  /* If no WhatsApp number is confirmed, the Call button spans the full bar. */
  .mobilebar[data-single="true"] { grid-auto-flow: row; }
}

/* --------------------------------------------------------------------------
   18b. TOUCH TARGETS — link lists and phone links are 19px tall at their
   natural line height, well under the 44px floor. Padded only for coarse
   pointers so the desktop layout is untouched. Inline links inside a sentence
   are deliberately excluded: WCAG exempts them, and stretching them would
   wreck the line rhythm.
   -------------------------------------------------------------------------- */
@media (pointer: coarse), (max-width: 767px) {
  .footer ul { gap: 0; }
  .footer ul a,
  .footer address a,
  .topbar__group > a,
  .spec-rail dd a,
  .crumbs a {
    display: inline-flex; align-items: center;
    min-height: 44px;
  }
}

/* --------------------------------------------------------------------------
   19. POPUP — home page only. Enters with the cut language, not a fade.
   -------------------------------------------------------------------------- */
.popup {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: grid; place-items: center; padding: var(--s-5);
  background: var(--ink-a78);
  opacity: 0; visibility: hidden;
  transition: opacity var(--d-exit) var(--e-exit), visibility var(--d-exit);
}
.popup[data-open="true"] { opacity: 1; visibility: visible; }
.popup__panel {
  position: relative;
  width: min(560px, 100%);
  background: var(--haze);
  border: 1px solid var(--steel);
  border-top: var(--blade) solid var(--accent);
  box-shadow: var(--shadow-3);
  padding: var(--s-8) var(--s-7) var(--s-7);
  clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
  transition: clip-path var(--d-exit) var(--e-cut);
}
.popup[data-open="true"] .popup__panel {
  clip-path: polygon(0 0, 120% -20%, 120% 120%, 0 100%);
}
.popup__close {
  position: absolute; top: var(--s-3); right: var(--s-3);
  width: 44px; height: 44px; display: grid; place-items: center;
  color: var(--steel);
  transition: color var(--d-micro) var(--e-micro);
}
.popup__close:hover { color: var(--paper); }
.popup__close svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 1.5; fill: none; }
.popup__title { font-size: var(--fs-h3); margin-bottom: var(--s-4); }
.popup__body { color: var(--paper); margin-bottom: var(--s-6); }
.popup__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-5); }
/* The dismiss is a control, not a link inside a sentence, so it takes the full
   44px target rather than the inline-link exemption. */
.popup__actions .link-underline {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: var(--s-2);
}
/* Scroll lock without the scrollbar-disappearing layout shift. */
body.is-locked { overflow: hidden; padding-right: var(--sbw, 0px); }

/* --------------------------------------------------------------------------
   20. DEVICE TIERING + REDUCED MOTION
   main.js sets .tier-full / .tier-reduced / .tier-minimal on <html> once.
   -------------------------------------------------------------------------- */
.tier-reduced .hero__plane { transform: none !important; }
.tier-reduced .marquee__track { --skew: 0deg !important; }
.tier-reduced .field-paper { transition: none; }

/* MINIMAL: a full bypass. Final state immediately, not a shortened duration. */
.tier-minimal *, .tier-minimal *::before, .tier-minimal *::after {
  animation: none !important;
  transition: none !important;
}
.tier-minimal .reveal { opacity: 1 !important; transform: none !important; }
.tier-minimal .line-mask > span { transform: none !important; }
.tier-minimal .rule-draw { transform: scaleX(1) !important; }
.tier-minimal .cut > img, .tier-minimal .cut > picture, .tier-minimal .cut > video {
  clip-path: none !important; filter: none !important;
}
.tier-minimal .cut::after { display: none !important; }
.tier-minimal .cards.stagger .card { opacity: 1 !important; transform: none !important; }
.tier-minimal .popup__panel { clip-path: none !important; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .reveal { opacity: 1 !important; transform: none !important; }
  .line-mask > span { transform: none !important; }
  .rule-draw { transform: scaleX(1) !important; }
  .cut > picture { display: block; }
.cut > img, .cut > picture, .cut > video { clip-path: none !important; filter: none !important; }
  .cut::after { display: none !important; }
  .cards.stagger .card { opacity: 1 !important; transform: none !important; }
  .popup__panel { clip-path: none !important; }
  .hero__plane { transform: none !important; }
}

/* --------------------------------------------------------------------------
   21. NO-JS — content must be complete and readable without JavaScript.
   Reveals are opt-in via .js-on, added by main.js before first paint.
   -------------------------------------------------------------------------- */
html:not(.js-on) .reveal { opacity: 1; transform: none; }
html:not(.js-on) .line-mask > span { transform: none; }
html:not(.js-on) .rule-draw { transform: scaleX(1); }
html:not(.js-on) .cut > img,
html:not(.js-on) .cut > picture,
html:not(.js-on) .cut > video { clip-path: none; filter: none; }
html:not(.js-on) .cards.stagger .card { opacity: 1; transform: none; }
html:not(.js-on) .nav__list { position: static; opacity: 1; visibility: visible; }

/* --------------------------------------------------------------------------
   22. PRINT
   -------------------------------------------------------------------------- */
@media print {
  .topbar, .header, .mobilebar, .popup, .marquee { display: none !important; }
  body { background: var(--print-bg); color: var(--print-fg); }
}
