/* htsignscincy.com - one stylesheet for the whole site.
   Bump ASSET_VER in build.py whenever this file changes, BEFORE deploying:
   it is served with a 30-day max-age, so without the bump the browser keeps
   the old copy and a fix you have just shipped simply will not appear.

   Palette sampled out of the company's own logo (img/logo.png):
     plum  #5b1b4e  the word HIGHTECH
     teal  #006f73  the word SIGNS
   Everything else is derived from those two. Every foreground/background pair
   used here clears WCAG AA at its own size; ht_contrast.js walks the rendered
   DOM and asserts it, because the CMS's own palette did not - #999900 bullets
   and #33cc00 body text on white are 2.5:1 and 2.3:1. */

:root {
  --plum: #5b1b4e;
  --plum-mid: #7d3f68;
  --plum-soft: #f3ecf1;
  /* The footer used to be the same --plum as the "Have a project in mind?"
     band directly above it, so the two ran together as one unbroken block of
     purple half a screen tall. This is a shade down (1.4:1 against the band -
     enough to read as a separate surface, not enough to look like a different
     site) and the band keeps the brand plum. */
  --plum-deep: #340e2b;
  --teal: #006f73;
  --teal-dark: #005457;
  --ink: #24222a;
  --muted: #5b5763;
  --line: #ddd9de;
  --paper: #fff;
  --wash: #f6f4f6;

  --shell: 1220px;
  --gutter: clamp(1rem, 4vw, 2.5rem);
  --radius: 4px;
  --head-h: 92px;            /* kept in step by ht.js; see --head-real */
  /* The width at which the nav collapses. A media query cannot read a custom
     property, so the number below is repeated in the @media rule at the foot
     of this file - and ht_css_order.py FAILS THE BUILD if the two ever drift.
     The harnesses read it from here rather than restating it: on an earlier
     site a harness kept a stale breakpoint and ran its desktop assertions
     against a page that was correctly in drawer mode. */
  --nav-break: 999px;
  --sans: Nunito, "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
  --display: Montserrat, "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* `overflow-x: clip` has to be on html AS WELL AS body: on body alone the
   root still scrolls, and it must be `clip` rather than `hidden`, which
   would make body a scroll container and kill `position: sticky` on the
   masthead. */
html, body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 clamp(1rem, .96rem + .2vw, 1.0625rem)/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
}

img, svg, video { max-width: 100%; }
img { height: auto; border: 0; }

/* `display: contents` on every <picture>.
   A <picture> is `display: inline`, so it sits as an extra inline box between
   a cropping tile and the <img> that is meant to fill it. Percentages happen
   to resolve correctly through it, but it leaves the img's box governed by
   something the tile's own rules never mention, and any later change to
   `.card__media`'s display or line-height moves the picture rather than the
   picture's contents. `display: contents` removes that box, so the <img> is
   laid out as a direct child of the tile and object-fit is unambiguous.
   harness/probe.js asserts, on every card and every gallery tile, that the
   image's rect equals its cropping box's - the number that goes wrong when
   this is not true, and which `overflow: hidden` would otherwise hide. */
picture { display: contents; }

a { color: var(--teal-dark); }
a:hover { color: var(--plum); }

:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 2px;
  border-radius: 2px;
}

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -.01em;
  margin: 0 0 .5em;
  color: var(--plum);
}
h1 { font-size: clamp(1.9rem, 1.35rem + 2.2vw, 3rem); }
h2 { font-size: clamp(1.4rem, 1.15rem + 1.1vw, 2rem); }
h3 { font-size: clamp(1.15rem, 1.05rem + .5vw, 1.4rem); }

p { margin: 0 0 1.1em; }
ul, ol { margin: 0 0 1.1em; padding-left: 1.3em; }
li + li { margin-top: .45em; }

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.narrow { max-width: 76ch; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--plum); color: #fff; padding: .7rem 1.1rem;
}
.skip:focus { left: .5rem; top: .5rem; }

.vh {
  position: absolute !important; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ------------------------------------------------------------- masthead */
.masthead {
  position: sticky; top: 0;
  /* The masthead must outrank the drawer's own backdrop. The drawer is a
     DESCENDANT of this element, so its z-index is scoped inside this stacking
     context and can never lift it past a sibling of the masthead: with the
     backdrop above, every link in the open drawer is unclickable and a tap
     closes the menu instead of following it. Five sites in this family have
     now shipped that bug. */
  z-index: 80;
  background: var(--paper);
  border-bottom: 3px solid var(--teal);
  box-shadow: 0 1px 12px rgb(36 34 42 / .07);
}
.masthead__bar {
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2.5rem);
  min-height: 74px; padding-block: .7rem;
}
.brand { display: inline-flex; align-items: center; flex: 0 0 auto; }
/* THE LOGO SIZES ITSELF, AND KEEPS ITS SHAPE WHILE IT DOES.
   Fixed px on a flex child plus the base `img { max-width: 100% }` is a
   squash waiting to happen: whenever the bar runs short of room the cap wins
   on the WIDTH and the declared height stays, so the wordmark comes out
   flattened - measured at 180x41 against a natural 325x60. `aspect-ratio`
   with `height: auto` means the shape cannot be lost whatever the width ends
   up being, and the clamp means the width never has to be taken away: at the
   narrowest real viewport (305px content) the row is 150 + 48 + 48 + two
   8.8px gaps = 264 inside 273 of gutter-to-gutter. */
.brand img {
  display: block;
  width: clamp(150px, 34vw, 232px);
  height: auto;
  aspect-ratio: 325 / 60;
}

.nav { margin-left: auto; }
.nav__list {
  list-style: none; display: flex; align-items: center;
  gap: clamp(.35rem, 1.4vw, 1.35rem); margin: 0; padding: 0;
}
.nav__list li { margin: 0; position: relative; }
.nav__link {
  display: block; padding: .55rem .35rem;
  font: 700 .95rem/1 var(--display);
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--plum); text-decoration: none;
  border-bottom: 3px solid transparent;
}
.nav__link:hover, .nav__link:focus-visible { color: var(--teal-dark); border-bottom-color: var(--teal); }
.nav__link[aria-current="page"], .nav__link.is-active { color: var(--teal-dark); border-bottom-color: var(--teal); }

/* the Portfolio dropdown. It opens on hover AND on focus, and the button
   works on a tap: a tap leaves the element in :hover, so a hover-only menu
   opens and will not close on a touch screen. */
.nav__toggle {
  display: inline-flex; align-items: center; gap: .35rem;
  background: none; border: 0; cursor: pointer; font: inherit;
  padding: .55rem .35rem;
  font: 700 .95rem/1 var(--display);
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--plum); border-bottom: 3px solid transparent;
}
.nav__toggle:hover, .nav__toggle[aria-expanded="true"] {
  color: var(--teal-dark); border-bottom-color: var(--teal);
}
.nav__toggle svg { width: .6em; height: .6em; transition: transform .18s; }
.nav__toggle[aria-expanded="true"] svg { transform: rotate(180deg); }

.nav__menu {
  position: absolute; top: 100%; left: 0; min-width: 17rem;
  margin: 0; padding: .4rem; list-style: none;
  background: var(--paper); border: 1px solid var(--line);
  border-top: 3px solid var(--teal);
  box-shadow: 0 14px 34px rgb(36 34 42 / .16);
  display: none; z-index: 5;
}
/* the LAST dropdown in the bar has to open leftwards or it runs off screen */
.nav__item--end .nav__menu { left: auto; right: 0; }
.nav__toggle[aria-expanded="true"] + .nav__menu { display: block; }
.nav__menu a {
  display: block; padding: .55rem .7rem; text-decoration: none;
  color: var(--ink); font-size: .95rem; border-radius: 2px;
}
.nav__menu a:hover, .nav__menu a:focus-visible { background: var(--plum-soft); color: var(--plum); }
.nav__menu a[aria-current="page"] { color: var(--plum); font-weight: 700; }
.nav__menu-top { font-weight: 700; }

.callbar { display: flex; align-items: center; gap: .6rem; flex: 0 0 auto; }
.btn--call svg { width: 1.05em; height: 1.05em; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .7rem 1.25rem; border: 2px solid transparent; border-radius: var(--radius);
  font: 700 .95rem/1.1 var(--display); letter-spacing: .03em;
  text-decoration: none; cursor: pointer; text-align: center;
}
/* An inline <svg> with no width/height attributes and no CSS size is a
   replaced element with no intrinsic size: it fills its flex line. In the
   drawer's call button that drew a 720px telephone. Every icon in a button is
   sized here, not per button, so a new one cannot be missed. */
.btn svg { width: 1.15em; height: 1.15em; flex: 0 0 auto; }
.btn--primary { background: var(--teal); color: #fff; }
.btn--primary:hover { background: var(--teal-dark); color: #fff; }
.btn--plum { background: var(--plum); color: #fff; }
.btn--plum:hover { background: var(--plum-mid); color: #fff; }
.btn--ghost { border-color: currentColor; color: var(--plum); background: transparent; }
.btn--ghost:hover { background: var(--plum); color: #fff; border-color: var(--plum); }
.btn--onphoto { background: #fff; color: var(--plum); }
.btn--onphoto:hover { background: var(--plum-soft); color: var(--plum); }

/* the burger and the drawer are display:none at base level so nothing leaks
   under the desktop footer before ht.js runs */
.burger { display: none; }
.backdrop { display: none; }

/* ------------------------------------------------------------ tagline */
.tagline {
  background: var(--teal); color: #fff;
  font-family: var(--display); font-weight: 700; letter-spacing: .01em;
  text-align: center;
}
.tagline p { margin: 0; padding: .7rem 0; font-size: clamp(.9rem, .85rem + .3vw, 1.06rem); }

/* --------------------------------------------------------------- hero */
.hero { position: relative; background: var(--plum); color: #fff; isolation: isolate; }
.hero picture, .hero img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: -2;
}
/* THE SCRIM ALPHA IS SOLVED, NOT CHOSEN.
   A DOM contrast walk cannot see this: the text sits over a gradient, which is
   a background-IMAGE, so there is no colour to compare against and the walker
   reads the .hero element's own plum instead - which is BELOW the photograph
   and not what is painted behind the words at all. The arithmetic:

     scrim  rgb(43 10 36)   worst background  white
     composited channel  c = 43a + 255(1-a)   (and the same for g, b)
     white text at 4.5:1 (normal)  needs a >= 0.579
     white text at 3:1   (large)   needs a >= 0.451

   THE GRADIENT NEVER GOES BELOW .66 ANYWHERE.
   The first attempt faded to .48 past a stop at min(860px, 100%), on the
   reasoning that the copy is capped at 46rem and could not reach it. That is
   wrong, and harness/probe.js caught it: the copy sits inside a CENTRED,
   capped shell, so as the window grows the text column moves right with the
   shell while a stop measured from the viewport's left edge stays put. At
   1600px the lead paragraph ran to x=1168 - 300px past the stop - and at
   2200px it would have been well into the light end.
   Chasing the text column with the stop is the wrong fix: it makes the scrim
   depend on the shell's arithmetic, and the next change to either breaks it
   silently. The floor is simply held everywhere instead. .66 composited over
   a white sky is 5.9:1, and the .88 end is the only variation. */
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg,
    rgb(43 10 36 / .88) 0,
    rgb(43 10 36 / .66) 100%);
}
.hero__inner { padding-block: clamp(3rem, 9vw, 6.5rem); max-width: 46rem; }
.hero h1 { color: #fff; margin-bottom: .35em; text-wrap: balance; }
.hero__lead { font-size: clamp(1.05rem, 1rem + .5vw, 1.3rem); margin-bottom: 1.6rem; }
.hero__cta { display: flex; flex-wrap: wrap; gap: .8rem; }

/* --------------------------------------------------- interior page head */
.pagehead { background: var(--plum); color: #fff; }
.pagehead .shell { padding-block: clamp(1.6rem, 4vw, 2.9rem); }
.pagehead h1 { color: #fff; margin-bottom: 0; text-wrap: balance; }
.pagehead p { margin: .6rem 0 0; max-width: 60ch; color: #f0e2ec; }

.crumbs { font-size: .875rem; margin: 0 0 .6rem; }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: .45rem; margin: 0; padding: 0; }
.crumbs li { margin: 0; }
.crumbs li + li::before { content: "/"; margin-right: .45rem; opacity: .6; }
.crumbs a { color: #f0d7e6; }
.crumbs a:hover { color: #fff; }
.crumbs [aria-current] { color: #fff; }

/* ------------------------------------------------------------ sections */
main { display: block; }
.band { padding-block: clamp(2.4rem, 6vw, 4.2rem); }
.band--wash { background: var(--wash); }
.band--plum { background: var(--plum); color: #fff; }
.band--plum h2 { color: #fff; }
/* A link inside a dark band inherits the page's teal, which is 1.4:1 on plum.
   This turned up the moment bare URLs and phone numbers started being
   auto-linked - the GLOW panel is the only dark band with links in its prose,
   and there was nothing to catch it until the contrast sweep walked it. */
.band--plum a:not(.btn) { color: #fff; text-decoration: underline; }
.band--plum a:not(.btn):hover { color: #f0c9dd; }
.band--tight { padding-block: clamp(1.6rem, 4vw, 2.6rem); }

.section-head { margin-bottom: clamp(1.4rem, 3vw, 2.2rem); }
.section-head p { margin: .4rem 0 0; color: var(--muted); max-width: 62ch; }
.band--plum .section-head p { color: #eddfe8; }

.prose > :last-child { margin-bottom: 0; }
.prose h2 { margin-top: 1.6em; }
.prose h2:first-child { margin-top: 0; }

.split {
  display: grid; gap: clamp(1.6rem, 4vw, 3rem);
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  align-items: start;
}
.split > * { min-width: 0; }

/* ------------------------------------------------------------ cards */
.cards {
  display: grid; gap: clamp(1rem, 2.2vw, 1.6rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15.5rem), 1fr));
  list-style: none; margin: 0; padding: 0;
}
.cards li { margin: 0; }
.card {
  display: flex; flex-direction: column; height: 100%;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; text-decoration: none; color: inherit;
  transition: box-shadow .18s, transform .18s, border-color .18s;
}
.card:hover, .card:focus-within {
  border-color: var(--teal); box-shadow: 0 10px 26px rgb(36 34 42 / .13); transform: translateY(-2px);
}
.card__media { display: block; aspect-ratio: 4 / 3; background: var(--plum-soft); overflow: hidden; }
.card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card__body { padding: 1rem 1.1rem 1.2rem; display: flex; flex-direction: column; gap: .35rem; flex: 1; }
.card__title { font: 800 1.06rem/1.25 var(--display); color: var(--plum); margin: 0; }
.card:hover .card__title { color: var(--teal-dark); }
.card__meta { margin: 0; font-size: .9rem; color: var(--muted); }
.card__more { margin-top: auto; padding-top: .55rem; font: 700 .82rem/1 var(--display); letter-spacing: .07em; text-transform: uppercase; color: var(--teal-dark); }

/* --------------------------------------------------------- the gallery */
.gallery {
  display: grid; gap: clamp(.5rem, 1.2vw, .85rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  list-style: none; margin: 0; padding: 0;
}
.gallery li { margin: 0; min-width: 0; }
.gallery a {
  display: block; aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--plum-soft); border-radius: 2px; position: relative;
}
.gallery img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .3s; }
.gallery a:hover img, .gallery a:focus-visible img { transform: scale(1.05); }
.gallery a::after {
  content: ""; position: absolute; inset: 0;
  box-shadow: inset 0 0 0 0 var(--teal); transition: box-shadow .18s;
}
.gallery a:hover::after, .gallery a:focus-visible::after { box-shadow: inset 0 0 0 3px var(--teal); }

.gallery-count { color: var(--muted); font-size: .93rem; margin: 0 0 1rem; }

/* the lightbox. The grid works without it: every tile is a plain link to the
   full-size photograph, so no-JS and a blocked script both land somewhere
   useful. */
.lb { display: none; }
.lb[open] {
  display: flex; flex-direction: column;
  position: fixed; inset: 0; z-index: 120;
  background: rgb(18 8 16 / .94);
  border: 0; padding: 0; margin: 0; max-width: none; max-height: none;
  width: 100%; height: 100%;
}
.lb::backdrop { background: rgb(18 8 16 / .94); }
.lb__stage {
  flex: 1; display: flex; align-items: center; justify-content: center;
  padding: clamp(.5rem, 3vw, 2.5rem); min-height: 0;
}
.lb__stage img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain; border-radius: 2px;
}
.lb__bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .6rem clamp(.6rem, 3vw, 1.5rem); color: #fff;
  font: 700 .9rem/1 var(--display);
}
.lb__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.9rem; height: 2.9rem; border-radius: 50%;
  background: rgb(255 255 255 / .14); color: #fff; border: 0; cursor: pointer;
  font-size: 1.35rem; line-height: 1;
}
.lb__btn:hover { background: rgb(255 255 255 / .28); }
.lb__btn:disabled { opacity: .3; cursor: default; }
.lb__nav { display: flex; gap: .5rem; }

/* ------------------------------------------------------- featured card */
.feature {
  background: var(--paper); border: 1px solid var(--line);
  border-top: 4px solid var(--teal); border-radius: var(--radius); overflow: hidden;
}
.feature__label {
  font: 800 .8rem/1 var(--display); letter-spacing: .16em; text-transform: uppercase;
  color: var(--teal-dark); padding: 1rem 1.1rem .4rem; margin: 0;
}
.feature__media { display: block; aspect-ratio: 4 / 3; overflow: hidden; }
.feature__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.feature__body { padding: .9rem 1.1rem 1.2rem; }
.feature__name { font: 800 1.15rem/1.25 var(--display); color: var(--plum); margin: 0 0 .3rem; }
.feature__name a { color: inherit; text-decoration: none; }
.feature__name a:hover { color: var(--teal-dark); text-decoration: underline; }
.feature__thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; padding: 0 1.1rem; margin: .5rem 0 0; list-style: none; }
.feature__thumbs img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block; border-radius: 2px; }

/* ------------------------------------------------------------- process */
.steps {
  display: grid; gap: clamp(.9rem, 2vw, 1.4rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  list-style: none; margin: 0; padding: 0; counter-reset: step;
}
.steps li { margin: 0; }
.step {
  height: 100%; padding: 1.6rem 1.3rem; border-radius: var(--radius);
  background: var(--plum); color: #fff;
  display: flex; flex-direction: column; gap: .5rem;
}
.steps li:nth-child(even) .step { background: var(--plum-mid); }
.step__n {
  font: 800 .8rem/1 var(--display); letter-spacing: .18em;
  text-transform: uppercase; color: #efd7e7;
}
.step h3 { color: #fff; margin: 0; font-size: 1.15rem; }

/* ------------------------------------------------------------- contact */
.contact-grid {
  display: grid; gap: clamp(1.6rem, 4vw, 3rem);
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  align-items: start;
}
.contact-grid > * { min-width: 0; }
.factlist { list-style: none; margin: 0 0 1.4rem; padding: 0; }
.factlist li { margin: 0 0 .9rem; display: flex; gap: .7rem; align-items: flex-start; }
.factlist svg { flex: 0 0 auto; width: 1.25rem; height: 1.25rem; margin-top: .2rem; color: var(--teal-dark); }
.factlist b { display: block; font-family: var(--display); font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
/* an e-mail address is one unbreakable token; on a 320px screen it is what
   pushes the whole column off the right edge */
.factlist a { overflow-wrap: anywhere; }

/* .prose zeroes its last child's margin so a band ends flush; the contact
   page puts a form straight after it, and "as soon as possible!" was sitting
   on top of the first field label. */
.contact-grid .prose { margin-bottom: 1.5rem; }
.formwrap { display: grid; gap: 1.05rem; }
.field { display: grid; gap: .35rem; }
.field label, .fieldset legend {
  font: 700 .82rem/1.3 var(--display); letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.field input, .field textarea, .field select {
  font: inherit; color: var(--ink); background: var(--paper);
  border: 1px solid #b8b2bc; border-radius: var(--radius);
  padding: .68rem .8rem; width: 100%;
}
.field input:focus, .field textarea:focus { border-color: var(--teal); }
.field textarea { min-height: 9rem; resize: vertical; }
.field-err input, .field-err textarea { border-color: #b3261e; background: #fdf4f3; }
.req { color: #b3261e; }
.hint { font-size: .88rem; color: var(--muted); margin: 0; }
.ok, .err { border-radius: var(--radius); padding: 1rem 1.2rem; margin: 0 0 1.2rem; }
.ok { background: #e6f4ec; border-left: 5px solid #1c7a44; }
.err { background: #fdf1f0; border-left: 5px solid #b3261e; }
.ok p:last-child, .err ul { margin-bottom: 0; }
/* the honeypot. Positioned off-canvas rather than display:none so a bot that
   checks for visibility still fills it, and `left` rather than a negative
   margin so a rect walk does not read it as page overflow. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --------------------------------------------------------------- table */
.tablewrap { overflow-x: auto; }
table.data { border-collapse: collapse; width: 100%; }
table.data th, table.data td { border-bottom: 1px solid var(--line); padding: .6rem .7rem; text-align: left; }

/* --------------------------------------------------------------- footer */
/* THE FOOTER IS A DARKER SURFACE THAN THE CTA BAND ABOVE IT.
   Both were --plum, and with the band's own padding there was no edge between
   them at all. */
.footer { background: var(--plum-deep); color: #f2e7ee;
          border-top: 3px solid var(--teal); }
.footer a { color: #fff; }
.footer a:hover { color: #f0c9dd; }
.footer__grid {
  display: grid; gap: clamp(1.6rem, 4vw, 2.6rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  padding-block: clamp(2.2rem, 5vw, 3.4rem);
}
.footer h2 {
  color: #fff; font-size: .82rem; letter-spacing: .14em; text-transform: uppercase;
  margin: 0 0 .9rem;
}
.footer ul { list-style: none; margin: 0; padding: 0; }
.footer li { margin: 0 0 .5rem; }
/* The wordmark on a WHITE PLATE, not knocked out.
   The logo is two-colour - plum HIGHTECH, teal SIGNS - and a white knockout
   throws the teal away and prints the whole thing in one flat colour. On a
   plate it is the actual mark. (It also cannot repeat the earlier bug, where
   `filter: brightness(0) invert(1)` on artwork with an OPAQUE white
   background painted a solid white rectangle.) */
.footer__plate {
  display: inline-block; background: #fff; border-radius: 8px;
  padding: .8rem 1.1rem; margin-bottom: 1.1rem;
}
.footer__logo { display: block; width: min(224px, 100%); height: auto;
                aspect-ratio: 325 / 60; }
.footer__legal {
  border-top: 1px solid rgb(255 255 255 / .18);
  padding-block: 1.1rem; display: flex; flex-wrap: wrap; gap: .6rem 1.4rem;
  font-size: .88rem;
}
.social { display: flex; gap: .6rem; margin-top: .9rem; }
.social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem; border-radius: 50%;
  background: rgb(255 255 255 / .14); color: #fff;
}
.social a:hover { background: var(--teal); color: #fff; }
.social svg { width: 1.15rem; height: 1.15rem; }

.dl {
  display: inline-flex; align-items: center; gap: .6rem;
  padding: .6rem .9rem; border: 1px solid rgb(255 255 255 / .3);
  border-radius: var(--radius); text-decoration: none; font-size: .92rem;
}
.dl:hover { background: rgb(255 255 255 / .12); }
.dl svg { width: 1.1rem; height: 1.1rem; flex: 0 0 auto; }

/* ================================================================== */
/* The nav collapses at the width where the bar stops fitting. MEASURED, not
   chosen: the masthead was walked in 5px steps with the webfonts loaded.
   The row is logo 232 + menu 410 + call button 172 + two gaps, and the gaps
   and gutters are both vw-relative, so the fit solves as
       0.92C >= 814 + 0.06C   ->   C >= 947 of CONTENT width
   i.e. a viewport of about 962. 999/1000 leaves ~34px of slack at the
   boundary, which is the room a late-loading webfont needs.
   (It was 1039 with a 190px logo and a five-item menu; the logo grew to 232
   and the Process item went, and the number was re-measured rather than
   left.)
   Re-measure, do not adjust by eye, if the nav gains an item or the logo
   changes width. harness/sweep.html does it in one call. */
@media (max-width: 999px) {
  .nav__list { display: none; }

  /* The header below the breakpoint is logo + call + burger and nothing else,
     and all three have to fit at 305px - the narrowest real viewport once the
     scrollbar is taken off a 320px phone. The phone number's DIGITS are hidden
     rather than removed, because they are the button's accessible name: with
     `display: none` on them the call button is an anonymous icon.
     The call button is not hidden at any width. A phone header that drops the
     one action most of its visitors came for is a worse header than a wide
     one. */
  .masthead__bar { gap: .55rem; }
  /* the collapsed <nav> has no width, so the auto margin that pushed the menu
     right has to move to the first thing that is still in the bar */
  .callbar { margin-left: auto; }
  .callbar__num {
    position: absolute !important; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  .btn--call { width: 3rem; height: 3rem; padding: 0; flex: 0 0 auto; }
  .btn--call svg { width: 1.35rem; height: 1.35rem; }

  .burger {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; margin-left: 0; width: 3rem; height: 3rem;
    background: none; border: 2px solid var(--plum); border-radius: var(--radius);
    color: var(--plum); cursor: pointer; padding: 0;
  }
  .burger svg { width: 1.5rem; height: 1.5rem; }
  .burger[aria-expanded="true"] { background: var(--plum); color: #fff; }

  /* The drawer IS the same <nav> as the desktop bar - one menu in the markup.
     So the bar cannot be display:none'd below the breakpoint: that would hide
     the drawer with it and the burger would open a 0x0 element. Collapse the
     bar instead and let the fixed drawer inside it sit out of flow. */
  .nav { margin-left: 0; }

  .nav__drawer {
    position: fixed; left: 0; right: 0;
    top: var(--head-real, var(--head-h));
    bottom: 0;
    background: var(--paper);
    border-top: 3px solid var(--teal);
    overflow-y: auto; overscroll-behavior: contain;
    padding: 1rem var(--gutter) 3rem;
    transform: translateX(-100%);
    transition: transform .26s ease;
    z-index: 90;
    /* never transition `visibility` on the thing that reveals a drawer: the
       transition runs to completion before the property flips, so the first
       tap lands on nothing */
    visibility: hidden;
  }
  .nav__drawer.is-open { transform: none; visibility: visible; }

  .backdrop {
    display: block; position: fixed; inset: 0;
    top: var(--head-real, var(--head-h));
    background: rgb(18 8 16 / .5);
    opacity: 0; pointer-events: none; transition: opacity .26s;
    z-index: 70;                    /* under the masthead (80), over the page */
  }
  .backdrop.is-open { opacity: 1; pointer-events: auto; }

  .nav__drawer .nav__list {
    display: block; margin: 0;
  }
  .nav__drawer .nav__list li { border-bottom: 1px solid var(--line); }
  .nav__drawer .nav__link,
  .nav__drawer .nav__toggle {
    display: flex; width: 100%; justify-content: space-between;
    padding: .95rem .2rem; font-size: 1rem; border-bottom: 0;
  }
  .nav__drawer .nav__menu {
    position: static; display: none; min-width: 0; box-shadow: none;
    border: 0; border-top: 0; padding: 0 0 .6rem .8rem; background: transparent;
  }
  .nav__drawer .nav__toggle[aria-expanded="true"] + .nav__menu { display: block; }
  .nav__drawer .nav__menu a { padding: .6rem .4rem; }
  .nav__drawer .drawer-cta { display: grid; gap: .7rem; margin-top: 1.4rem; }
  .nav__drawer .drawer-cta .btn { width: 100%; }

  /* THE SCROLL LOCK, AND WHAT IT DOES TO A STICKY HEADER.
     Fixing the body at `top: -scrollY` is the standard lock, and it takes the
     masthead with it: `position: sticky` sticks inside its own scroll
     container, and once the body is out of flow and shifted up by scrollY the
     header goes with it. Opening the menu 900px down the page put the header
     at top:-900 - gone - with the drawer still starting at the height the
     header used to occupy, so the menu opened under a strip of the page
     behind it. It is invisible at the top of the page, where scrollY is 0 and
     nothing moves, which is exactly where it gets tested.
     While locked, the masthead is `fixed` instead, so it is positioned
     against the viewport and stays put. */
  body.is-locked { position: fixed; width: 100%; overflow: hidden; }
  body.is-locked .masthead { position: fixed; top: 0; left: 0; right: 0; }

  .split, .contact-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (min-width: 1000px) {
  .nav__drawer .drawer-cta { display: none; }
}

@media (max-width: 620px) {
  /* the clamp above already handles this tier; nothing to override */
  .hero__cta .btn { width: 100%; }
  .footer__grid { padding-block: 2rem; }
}

@media print {
  .masthead, .footer, .backdrop, .lb { display: none !important; }
  body { color: #000; }
}
