/* =========================================================================
   BlueHound Group — the whole design system, in numbered sections.

   01  Tokens
   02  Reset & base
   03  Layout primitives
   04  Typography
   05  Buttons & links
   06  Header & nav
   07  Hero
   08  Sections: index headings, sector rows, supply index, reasons
   09  Contact & forms
   10  Footer
   11  Utilities
   12  Responsive collapse (explicit per layout)

   DESIGN READ — "the procurement dossier".
   The client is a B2B procurement firm selling to engineers and buyers in
   oil & gas, mining and power. Those readers trust documents, not brochures,
   so the site is built like one: a fixed measure, hairline rules, numbered
   sections, monospace labels for anything that behaves like a reference, and
   flat fills. Credibility is carried by precision, not by decoration.

   LOCKS (audited before ship):
   - Shape lock ....... structural surfaces (sections, panels, rows, the logo
                        tile) are 0 radius. Only things you click or type into
                        get --radius-ui, and that is 2px. There are no pills
                        and no rounded cards anywhere.
   - Fill lock ........ flat colour only. No gradient is used as decoration.
                        The one gradient in the file is the hero band, and it
                        exists to separate the hero from the fact strip that
                        closes it — three stops of the same hue, no hue shift.
   - Shadow lock ...... exactly one shadow, --shadow-header, and it appears
                        only once the page has scrolled under the header.
                        Every other separation here is a 1px line.
   - Accent lock ...... --c-navy is the structure, --c-blue is the accent. The
                        accent marks one thing at a time: a live link, a focus
                        ring, a small label. It is never a band background.
   - Motion lock ...... colour and border transitions only. Nothing moves on
                        scroll, nothing lifts on hover, nothing autoplays.
   - Theme lock ....... light only. The dark bands (hero, footer, page heads)
                        are an inversion of this palette, not a second theme,
                        so there is no prefers-color-scheme branch to keep in
                        step.

   Tokens in 01 marked "verified" came out of the old WordPress site's
   Elementor kit and its form styling — see
   ../../../01_Discovery/audit/content-inventory.md, "Design tokens".
   Everything else in the palette is derived from them.
   ========================================================================= */


/* 01 — Tokens ============================================================ */

:root {
  color-scheme: light;

  /* Brand — verified against the old site */
  --c-navy:  #033A79;   /* kit colour "Bleue de Eric", confirmed by the Depicter hero CSS */
  --c-blue:  #1C74FF;   /* kit colour "New Blue" — the single accent */
  /* The same blue, darkened until it carries text. #1C74FF is 4.21:1 on
     paper, which fails AA for anything under 18.66px — and every text use of
     the accent on this site is a 13px label. This lands at 5.29:1 on paper
     and 4.84:1 on --c-surface. The lighter token stays for borders, focus
     rings and icons, where the requirement is 3:1. */
  --c-blue-ink: #1565E0;
  /* One step darker again, for the nav's own highlight: the current
     section's label and its underline, and the hover that previews them.
     6.37:1 on paper, so the 15px nav label clears AA outright, and it
     still sits 1.75:1 clear of --c-navy, which is what the other three
     labels are set in — the highlight has to read as a different colour,
     not just a darker one. */
  --c-blue-deep: #1059C9;
  --c-ink:   #0B133C;   /* the old form button background */

  /* Derived: the two darker steps the hero and footer need. Both are
     --c-navy pulled down in lightness only, so the whole dark half of the
     site stays one hue. */
  --c-navy-deep: #052A56;
  --c-navy-dark: #041D3B;

  /* Neutrals — a cool grey family, so nothing competes with the navy */
  --c-paper:   #FFFFFF;
  --c-surface: #F3F5F8;
  --c-line:    #D8DEE7;
  --c-line-strong: #A2AEC0;
  --c-text:    #151A24;
  --c-muted:   #535D72;

  /* Reversed neutrals, for the hero, footer and page heads */
  --c-on-dark:       #FFFFFF;
  --c-on-dark-muted: #BACAE0;
  --c-on-dark-line:  rgb(255 255 255 / 0.18);

  /* Intent */
  --c-focus: #5E9CFF;   /* the accent lifted so the ring clears 3:1 on navy too */
  --c-error: #B3261E;
  --c-ok:    #14603A;

  /* Type. Montserrat (headings) and Poppins (body) are the faces the old site
     actually used — 47 and 57 widget references. The monospace stack is new:
     it carries field labels and anything that reads as a reference
     rather than as prose. It is a system stack on purpose, so it costs no
     request. */
  --f-head: 'Montserrat', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --f-body: 'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --f-mono: ui-monospace, 'SFMono-Regular', 'Cascadia Mono', 'Segoe UI Mono',
            'Roboto Mono', Menlo, Consolas, monospace;

  --fs-xs:   0.8125rem;   /* 13px — mono labels, legal line */
  --fs-sm:   0.9375rem;   /* 15px — nav, buttons, captions */
  --fs-base: 1.0625rem;   /* 17px — body */
  --fs-lg:   1.1875rem;   /* 19px — lede */
  --fs-xl:   1.375rem;
  --fs-2xl:  clamp(1.5rem,   1.24rem + 1.1vw, 2rem);
  --fs-3xl:  clamp(1.875rem, 1.50rem + 1.7vw, 2.625rem);
  --fs-4xl:  clamp(2.25rem,  1.55rem + 3.1vw, 3.75rem);

  /* Space — a 4px scale. Nothing in this file uses a raw pixel gap. */
  --s-1: 0.25rem; --s-2: 0.5rem;  --s-3: 0.75rem; --s-4: 1rem;
  --s-5: 1.5rem;  --s-6: 2rem;    --s-7: 3rem;    --s-8: 4rem;
  --s-9: 5.5rem;  --s-10: 7rem;

  /* Layout */
  --shell:  1180px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --radius-ui: 2px;
  --header-h: 72px;

  --shadow-header: 0 1px 0 rgb(21 26 36 / 0.06), 0 6px 20px rgb(5 42 86 / 0.07);

  --ease: cubic-bezier(.2, .7, .3, 1);
  --dur: 180ms;
}


/* 02 — Reset & base ====================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The header is sticky, so an in-page anchor has to clear it or it lands
     underneath. A little larger than --header-h so the target heading is not
     flush against the header's bottom rule. */
  scroll-padding-top: calc(var(--header-h) + 1.5rem);
}

body {
  margin: 0;
  font-family: var(--f-body);
  font-size: var(--fs-base);
  line-height: 1.65;
  color: var(--c-text);
  background: var(--c-paper);
  -webkit-font-smoothing: antialiased;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--c-navy); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { color: var(--c-blue-ink); }

/* One focus ring for the whole site. 3px, so it stays visible over the navy
   bands as well as over paper. */
:focus-visible { outline: 3px solid var(--c-focus); outline-offset: 2px; border-radius: var(--radius-ui); }

::selection { background: var(--c-navy); color: var(--c-on-dark); }

hr { border: 0; border-top: 1px solid var(--c-line); margin: 0; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}


/* 03 — Layout primitives ================================================= */

.shell,
.wrap { width: min(var(--shell), 100%); margin-inline: auto; padding-inline: var(--gutter); }

/* Vertical rhythm. Anything that also carries .shell must set its rhythm with
   padding-block, never the padding shorthand — the shorthand resets the
   inline padding to 0 and the section loses its gutters entirely. */
.band { padding-block: var(--s-9); }
.band--tight { padding-block: var(--s-8); }
.band--tint { background: var(--c-surface); }

/* The head of an inner page. Flat navy, no photograph: none of the imagery on
   the old site can be carried over, so there is nothing to put behind it.
   See the README, "Photography". */
.page-head { background: var(--c-navy); color: var(--c-on-dark); padding-block: var(--s-8) var(--s-7); }
.page-head h1 { color: var(--c-on-dark); margin: 0; max-width: 18ch; }
.page-head .lede { color: var(--c-on-dark-muted); margin: var(--s-4) 0 0; }


/* 04 — Typography ======================================================== */

h1, h2, h3, h4 {
  font-family: var(--f-head);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--c-navy);
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: var(--fs-4xl); letter-spacing: -0.025em; }
h2 { font-size: var(--fs-3xl); }
h3 { font-size: var(--fs-xl); letter-spacing: -0.005em; }
h4 { font-size: var(--fs-base); }

p { margin: 0 0 var(--s-4); }
/* Trim the trailing margin off the last paragraph of a block. Wrapped in
   :where() so it carries no specificity of its own: as a bare `p:last-child`
   it out-ranked `.field` and silently removed the bottom margin from the
   second field of every two-column form row, which then stretched to match
   its neighbour and came out 16px taller. */
p:where(:last-child) { margin-bottom: 0; }

.lede { font-size: var(--fs-lg); color: var(--c-muted); max-width: 62ch; line-height: 1.6; }
/* A band heading's bottom margin is set for body copy; the lede is larger and
   needs a little more air under the heading than that margin gives it. */
.band h2 + .lede { padding-top: var(--s-4); }
.prose { max-width: 68ch; }


.band-intro { max-width: 34ch; }
.band-intro + .lede { margin-top: var(--s-4); }
/* The gap between a band's heading (or its lede) and the block of content
   under it. Without this the first row's top rule sits directly under the
   heading and reads as an underline of it. */
.band-intro + .rows,
.band-intro + .reasons,
.band-intro + .supply,
.band-intro + .split,
.band-intro + .contact-list,
.lede + .rows,
.lede + .reasons,
.lede + .supply,
.lede + .contact-list { margin-top: var(--s-6); }

/* A short label above a value — the fact strip and the contact list. */
.label {
  font-family: var(--f-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-muted);
}


/* 05 — Buttons & links =================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--f-head);
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: 1;
  padding: 0.95rem 1.5rem;
  border: 1px solid var(--c-navy);
  border-radius: var(--radius-ui);
  background: var(--c-navy);
  color: var(--c-on-dark);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.btn:hover { background: var(--c-ink); border-color: var(--c-ink); color: var(--c-on-dark); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.btn .icon { width: 1.15em; height: 1.15em; flex: 0 0 auto; }

/* Secondary. Only ever used beside a filled .btn: the filled one is always
   the ask, this one is the "read more first" half. */
.btn--line { background: transparent; color: var(--c-navy); border-color: var(--c-line-strong); }
.btn--line:hover { background: var(--c-surface); border-color: var(--c-navy); color: var(--c-navy); }

.btn--sm,
.btn-sm { padding: 0.65rem 1.05rem; font-size: var(--fs-xs); }

/* On the navy bands the pair inverts: the ask becomes white-on-navy. */
.on-dark .btn { background: var(--c-on-dark); border-color: var(--c-on-dark); color: var(--c-navy); }
.on-dark .btn:hover { background: var(--c-blue); border-color: var(--c-blue); color: var(--c-on-dark); }
.on-dark .btn--line { background: transparent; color: var(--c-on-dark); border-color: var(--c-on-dark-line); }
.on-dark .btn--line:hover { background: rgb(255 255 255 / 0.1); border-color: var(--c-on-dark); color: var(--c-on-dark); }

/* The text link. The underline is a bottom border so it can change colour and
   weight without the text reflowing, which a text-decoration change risks. */
.tlink {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--f-head);
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--c-navy);
  text-decoration: none;
  border-bottom: 1px solid var(--c-line-strong);
  padding-bottom: 2px;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tlink:hover { color: var(--c-blue-ink); border-bottom-color: var(--c-blue); }
.tlink .icon { width: 1.1em; height: 1.1em; }
.on-dark .tlink { color: var(--c-on-dark); border-bottom-color: var(--c-on-dark-line); }
.on-dark .tlink:hover { color: var(--c-on-dark); border-bottom-color: var(--c-on-dark); }

.actions { display: flex; flex-wrap: wrap; gap: var(--s-3); }
/* An action pair that closes a block of content rather than opening one. The
   spacing lives here rather than as an inline style on the four pages that
   need it. */
.checklist + .actions,
.rows + .actions,
.contact-list + .actions { margin-top: var(--s-6); }


/* 06 — Skip link ========================================================= */
/* The header itself is section 13. An earlier multi-page header lived here:
   a sticky white bar with `.header-bar`, `.header-cta` and `aria-current=
   "page"`, none of which the one-page markup emits. Its `.site-nav ul a`
   (0,1,2) out-ranked section 13's `.site-nav a` (0,1,1), so the nav rendered
   in body ink over the hero video instead of white. Deleted, not overridden. */

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--c-navy); color: var(--c-on-dark);
  padding: var(--s-3) var(--s-4); font-weight: 600;
}
.skip-link:focus { left: var(--s-3); top: var(--s-3); }


/* 08 — Sections ========================================================== */

/* --- sector rows: the three verified sector headlines and paragraphs ----- */
.rows { display: grid; }
.row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 22ch) minmax(0, 1fr);
  gap: var(--s-6);
  align-items: start;
  padding-block: var(--s-6);
  border-top: 1px solid var(--c-line);
}
.row:last-child { border-bottom: 1px solid var(--c-line); }
.row__icon { color: var(--c-navy); }
.row__icon .icon { width: 44px; height: 44px; stroke-width: 1.25; }
.row h3 { margin-bottom: var(--s-2); }
.row__meta {
  font-family: var(--f-mono); font-size: var(--fs-xs);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--c-blue-ink); margin-bottom: var(--s-3);
}
.row p { color: var(--c-muted); max-width: 62ch; }
.row .tlink { margin-top: var(--s-4); }

/* The four supply categories are a carousel now — section 17b. */

/* --- reasons: five verified points, numbered, rule-separated ------------- */
.reasons { display: grid; counter-reset: reason; margin: 0; }
.reasons > div {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 20ch) minmax(0, 1fr);
  gap: var(--s-5);
  align-items: baseline;
  padding-block: var(--s-5);
  border-top: 1px solid var(--c-line);
}
.reasons > div:last-child { border-bottom: 1px solid var(--c-line); }
.reasons > div::before {
  counter-increment: reason;
  content: counter(reason, decimal-leading-zero);
  font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: .08em;
  color: var(--c-blue-ink);
}
.reasons dt { font-family: var(--f-head); font-weight: 700; color: var(--c-navy); }
.reasons dd { margin: 0; color: var(--c-muted); }

/* --- service article: one sector, in depth, on /services/ ---------------
   Same two-column shape as .row on the homepage — name on the left, prose on
   the right — so a reader moving from one page to the other is reading the
   same object twice, not two different ones. */
.service {
  display: grid;
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
  gap: var(--s-7);
  align-items: start;
  padding-block: var(--s-7);
  border-top: 1px solid var(--c-line);
}
.service:last-of-type { border-bottom: 1px solid var(--c-line); }
.service__head { display: flex; align-items: center; gap: var(--s-4); }
.service__head .icon { width: 40px; height: 40px; color: var(--c-navy); flex: 0 0 auto; stroke-width: 1.3; }
.service p { color: var(--c-muted); max-width: 68ch; }

/* --- a plain two-column text block --------------------------------------
   The heading column is a fixed 20rem rather than a ch measure: at --fs-3xl
   a 26ch column is about 234px, and a three-word heading broke across three
   lines in it. Headings in a split also step down a size — beside body copy
   they are a label for the column, not the top of a page. */
.split { display: grid; grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); gap: var(--s-8); align-items: start; }
.split > h2 { font-size: var(--fs-2xl); }
.split + .split { margin-top: var(--s-7); padding-top: var(--s-7); border-top: 1px solid var(--c-line); }

/* --- the checklist on /services/: what to put in an enquiry ------------- */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.checklist li { display: grid; grid-template-columns: 1.5rem minmax(0, 1fr); gap: var(--s-3); align-items: start; color: var(--c-muted); }
.checklist .icon { width: 18px; height: 18px; color: var(--c-blue); margin-top: .45em; }

/* --- closing band ------------------------------------------------------- */
.closing { background: var(--c-navy); color: var(--c-on-dark); }
.closing .shell { display: flex; align-items: center; justify-content: space-between; gap: var(--s-7); flex-wrap: wrap; }
.closing h2 { color: var(--c-on-dark); max-width: 20ch; }
.closing p { color: var(--c-on-dark-muted); max-width: 48ch; margin: var(--s-3) 0 0; }


/* 09 — Contact & forms =================================================== */

.contact-grid { display: grid; grid-template-columns: minmax(0, 24rem) minmax(0, 1fr); gap: var(--s-8); align-items: start; }

/* dt and dd sit in column 2; the icon is told explicitly to take column 1 and
   span both rows, or it lands above the label instead of beside it. */
.contact-list { display: grid; margin: 0; }
.contact-list > div {
  display: grid; grid-template-columns: 1.75rem minmax(0, 1fr);
  gap: 0 var(--s-4);
  padding-block: var(--s-4);
  border-top: 1px solid var(--c-line);
}
.contact-list > div:last-child { border-bottom: 1px solid var(--c-line); }
.contact-list .icon { grid-column: 1; grid-row: span 2; width: 20px; height: 20px; color: var(--c-navy); margin-top: .35em; }
.contact-list dt { grid-column: 2; margin-bottom: var(--s-1); }
/* Fluid rather than a breakpoint: the longest value here is
   sales@bluehoundgroup.com, one unbreakable word about 264px wide at 19px, and
   the column on a 320px phone is 236px. The clamp lands it at 15px there and
   19px from about 620px up, so it never has to break mid-word — the
   overflow-wrap below is the guarantee, not the plan. */
.contact-list dd { grid-column: 2; margin: 0; font-size: clamp(0.9375rem, 2.2vw + 0.45rem, var(--fs-lg)); font-family: var(--f-head); font-weight: 600; color: var(--c-navy); line-height: 1.45; }
/* An email address is one unbreakable word. At 19px, sales@bluehoundgroup.com
   is about 264px, which with the icon column and the gutters overflows a 320px
   phone. Let it break rather than push the page sideways. */
.contact-list dd a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--c-line-strong); overflow-wrap: anywhere; }
.contact-list dd a:hover { color: var(--c-blue-ink); border-bottom-color: var(--c-blue); }

/* The form. A panel, not a card: square, one hairline border, flat tint. */
.contact-form { background: var(--c-surface); border: 1px solid var(--c-line); padding: var(--s-6); }
.contact-form h2 { font-size: var(--fs-2xl); margin-bottom: var(--s-2); }
.contact-form .form-intro { color: var(--c-muted); font-size: var(--fs-sm); margin-bottom: var(--s-6); }

/* align-items: start so a field can never be stretched to match a taller
   neighbour — that stretch is absorbed by the grid rows inside it and makes
   one input taller than the other. */
.field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--s-4); align-items: start; }
.field { display: grid; align-content: start; gap: var(--s-2); margin: 0 0 var(--s-4); }
.field label { font-family: var(--f-head); font-weight: 600; font-size: var(--fs-sm); color: var(--c-text); }
.field .req { color: var(--c-error); font-weight: 700; }
.field .hint { font-size: var(--fs-xs); color: var(--c-muted); }
.field input, .field textarea {
  font: inherit; font-size: var(--fs-base);
  width: 100%;
  padding: 0.8rem 0.9rem;
  color: var(--c-text);
  background: var(--c-paper);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--radius-ui);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.field input:hover, .field textarea:hover { border-color: var(--c-muted); }
.field input:focus, .field textarea:focus { border-color: var(--c-blue); box-shadow: inset 0 0 0 1px var(--c-blue); }
.field textarea { resize: vertical; min-height: 9rem; }

/* Error state. site.js writes the reason into .field-error and points the
   input at it with aria-describedby, so a screen reader hears why the field
   is invalid rather than just that it is. */
.field [aria-invalid="true"] { border-color: var(--c-error); box-shadow: inset 0 0 0 1px var(--c-error); }
.field-error { font-size: var(--fs-sm); font-weight: 600; color: var(--c-error); }
.field-error:empty { display: none; }

/* Honeypot. Positioned off-screen rather than display:none — some bots skip
   fields that are display:none, which would defeat the point of it. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-foot { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; margin-top: var(--s-5); }
.form-status { margin: 0; font-size: var(--fs-sm); font-weight: 600; }
.form-status:empty { display: none; }
.form-status[data-state="ok"]    { color: var(--c-ok); }
.form-status[data-state="error"] { color: var(--c-error); }
.form-status[data-state="busy"]  { color: var(--c-muted); }

/* The no-JavaScript confirmation. contact.php redirects back to
   /contact/?sent=1 (or ?error=…) when the request was not sent by fetch, and
   site.js unhides the matching note on arrival. */
.form-note { padding: var(--s-4); border-left: 3px solid var(--c-ok); background: var(--c-paper); margin-bottom: var(--s-5); font-size: var(--fs-sm); }
.form-note--error { border-left-color: var(--c-error); }


/* 10 — Footer ============================================================ */

.site-footer {
  background: var(--c-navy-dark);
  color: var(--c-on-dark-muted);
  padding-top: var(--s-8);
  padding-bottom: calc(var(--s-5) + env(safe-area-inset-bottom, 0px));
}
.site-footer h2 {
  font-family: var(--f-mono); font-size: var(--fs-xs); font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--c-on-dark); margin-bottom: var(--s-4);
}
/* break-word, not anywhere. Both break a string that genuinely cannot fit,
   but `anywhere` also drops the element's min-content width to one character
   — and since these lists are grids whose tracks are minmax(0, 1fr), nothing
   then reserves the address its natural width. The result was
   sales@bluehoundgroup.co / m wrapped across two lines in a column with room
   to spare. `break-word` leaves min-content alone, so the track is sized to
   hold the address and the long map link still wraps at its spaces. */
.site-footer a { color: var(--c-on-dark); text-decoration: none; border-bottom: 1px solid transparent; overflow-wrap: break-word; }

/* An email address or a phone number read as one thing; split across a line
   break it reads as neither. Neither is long enough to overflow the narrowest
   track the footer or the contact list ever produces. */
.site-footer a[href^="mailto:"],
.site-footer a[href^="tel:"],
.contact__list a[href^="mailto:"],
.contact__list a[href^="tel:"] { white-space: nowrap; }
.site-footer a:hover { color: var(--c-on-dark); border-bottom-color: var(--c-on-dark); }

.footer-grid {
  display: grid;
  /* Three tracks for three columns. This said repeat(3, …) against a footer
     that has only ever had a brand, Contact and Work hours — so a fourth
     track sat empty on the right at 209px while Contact was squeezed to 209px
     to make room for it, one pixel under the 208px the email address needs.
     That is what split the address mid-word. Three tracks give Contact 282px
     and spend the dead column on the columns that exist. */
  grid-template-columns: minmax(0, 1.5fr) repeat(2, minmax(0, 1fr));
  gap: var(--s-7);
  padding-bottom: var(--s-7);
}
.footer-grid ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); font-size: var(--fs-sm); }

/* The footer brand uses the same markup as the header, with the white mark —
   no tile is drawn behind it, so there is no .brand__mark rule here. */
.footer-brand .brand { margin: 0 0 var(--s-5); }
/* The footer takes the white mark, so no tile is drawn behind it. */
.footer-brand .brand__word b { color: var(--c-on-dark); }
.footer-brand .brand__word span { color: var(--c-on-dark-muted); }
.footer-brand .strapline {
  font-family: var(--f-head); font-weight: 700; font-size: var(--fs-sm);
  letter-spacing: .1em; color: var(--c-on-dark); margin-bottom: var(--s-2);
}
.footer-brand .tagline { font-size: var(--fs-sm); margin: 0; }

.footer-base {
  border-top: 1px solid var(--c-on-dark-line);
  padding-top: var(--s-5);
  display: flex; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap;
  font-size: var(--fs-xs);
}
.footer-base p { margin: 0; }


/* 11 — Utilities ========================================================= */

.plain { list-style: none; margin: 0; padding: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* The inline <symbol> sprite that build.py writes into every page. It already
   carries width/height 0; this takes it out of flow too, so it can never open
   a page with a stray blank line above the skip link. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Every icon on the site is a stroked 24-unit symbol drawn in currentColor,
   so an icon is always the colour of the text beside it. */
.icon {
  width: 1.25em; height: 1.25em;
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}

.nowrap { white-space: nowrap; }

/* 404 */
.notfound { text-align: left; }
.notfound .actions { margin-top: var(--s-6); }


/* A text link is ~28px tall at its natural size, which clears WCAG 2.5.8's 24px
   minimum but is tight under a thumb. Keyed to the input device rather than to a
   width: a 1024px tablet is touch and a 1280px laptop is not, and `any-pointer`
   also catches a laptop with a touchscreen. Vertical padding only, so nothing
   reflows when it applies. */
@media (any-pointer: coarse) {
  .tlink { padding-block: 6px 5px; }
}

/* 12 — Responsive collapse =============================================== */

@media (max-width: 1060px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .row { grid-template-columns: 4rem minmax(0, 18ch) minmax(0, 1fr); gap: var(--s-5); }
}

@media (max-width: 900px) {
  .hero__grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .hero__index { max-width: 26rem; }

  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .facts > div,
  .facts > div:first-child { padding-inline: var(--s-4); }

  .row { grid-template-columns: 3rem minmax(0, 1fr); row-gap: var(--s-3); gap: var(--s-4); }
  .row__icon { grid-row: span 2; }
  .row__icon .icon { width: 34px; height: 34px; }

  .reasons > div { grid-template-columns: 2.5rem minmax(0, 1fr); row-gap: var(--s-1); gap: var(--s-4); }
  .reasons > div::before { grid-row: span 2; }

  .split { grid-template-columns: 1fr; gap: var(--s-5); }
  .service { grid-template-columns: 1fr; gap: var(--s-4); padding-block: var(--s-6); }
  .contact-grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .footer-grid { gap: var(--s-6); }
}

/* Narrow phones — 320px is an iPhone SE (1st gen) and still around. */
@media (max-width: 400px) {
  /* Long button labels wrap rather than setting a min-content width the
     viewport cannot hold. Every button is full width at this size anyway. */
  .btn { white-space: normal; text-align: center; }
}

@media (max-width: 620px) {
  .band { padding-block: var(--s-8); }
  .hero { padding-top: var(--s-8); }
  .hero .actions { margin-top: var(--s-6); }
  .hero .actions .btn { flex: 1 1 100%; justify-content: center; }

  .hero__index { max-width: none; }

  .facts { grid-template-columns: 1fr; margin-top: var(--s-7); }
  .facts > div,
  .facts > div:first-child { padding-inline: 0; }

  .field-row { grid-template-columns: 1fr; gap: 0; }
  .contact-form { padding: var(--s-5) var(--s-4); }
  .form-foot .btn { flex: 1 1 100%; justify-content: center; }

  .service__head { align-items: flex-start; }

  .footer-grid { grid-template-columns: 1fr; }
  .footer-base { flex-direction: column; gap: var(--s-2); }
}


/* 13 — Header: transparent over the hero, solid once you leave it ========= */
/* The page is one document, so the header is the only persistent chrome.
   It starts transparent over the video and takes a background the moment the
   hero stops covering it. site.js sets [data-scrolled] from an
   IntersectionObserver on the hero, so nothing listens to the scroll event. */
.site-header {
  position: fixed; inset: 0 0 auto; z-index: 40;
  background: transparent; border-bottom: 1px solid transparent;
  padding-top: env(safe-area-inset-top, 0px);
  transition: background-color .28s ease, border-color .28s ease, box-shadow .28s ease;
}
.site-header[data-scrolled] {
  background: var(--c-paper);
  border-bottom-color: var(--c-line);
  box-shadow: var(--shadow-header);
}

.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4); min-height: 72px;
}

.brand { display: inline-flex; align-items: center; gap: .6rem; text-decoration: none; }
/* Two marks, one shown at a time. Over the hero the header is transparent and
   the mark is white; once the header takes its white background the mark
   swaps to the blue black, which is the only one that reads on it. Both files
   sit in the markup, stacked in a single grid cell, so the swap is an opacity
   change rather than a request mid-scroll. */
.brand__marks { display: grid; width: 36px; height: 36px; flex: 0 0 auto; }
.brand__mark {
  display: block; width: 36px; height: 36px;
  grid-area: 1 / 1;
  transition: opacity .28s ease;
}
.brand__mark--dark { opacity: 0; }
.site-header[data-scrolled] .brand__mark--light { opacity: 0; }
.site-header[data-scrolled] .brand__mark--dark  { opacity: 1; }
.brand__word { display: grid; line-height: 1; }
.brand__word b {
  font-family: var(--f-head); font-size: .95rem; letter-spacing: .06em;
  color: var(--c-on-dark);
}
.brand__word span {
  font-size: .62rem; letter-spacing: .34em; color: var(--c-on-dark-muted);
}
.site-header[data-scrolled] .brand__word b { color: var(--c-navy); }
.site-header[data-scrolled] .brand__word span { color: var(--c-muted); }

/* The nav sits over a photograph until the header solidifies, so its resting
   colour is the on-dark pair, not the ink pair. */
.site-nav ul { display: flex; gap: var(--s-5); list-style: none; margin: 0; padding: 0; }
.site-nav a {
  display: block; padding: .4rem 0; text-decoration: none;
  font-family: var(--f-head); font-weight: 600;
  font-size: var(--fs-sm); color: var(--c-on-dark);
  border-bottom: 2px solid transparent;
  transition: color .28s ease, border-color .28s ease;
}
.site-nav a:hover { border-bottom-color: var(--c-on-dark); }
.site-nav a[aria-current="true"] { border-bottom-color: var(--c-blue-deep); }
/* On the white header the links take the navy the mark takes, so the whole
   lockup crosses from white to blue on one curve instead of the nav landing
   on body ink while the logo lands on blue. */
.site-header[data-scrolled] .site-nav a { color: var(--c-navy); }
.site-header[data-scrolled] .site-nav a:hover { color: var(--c-blue-deep); border-bottom-color: var(--c-blue-deep); }
.site-header[data-scrolled] .site-nav a[aria-current="true"] { color: var(--c-blue-deep); border-bottom-color: var(--c-blue-deep); }

/* The toggle only exists below the nav breakpoint. */
.nav-toggle {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block; width: 22px; height: 2px; background: var(--c-on-dark);
  transition: transform .2s ease, opacity .2s ease, background-color .28s ease;
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before,
.nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -7px; }
.nav-toggle-bars::after  { top:  7px; }
.site-header[data-scrolled] .nav-toggle-bars,
.site-header[data-scrolled] .nav-toggle-bars::before,
.site-header[data-scrolled] .nav-toggle-bars::after { background: var(--c-navy); }
[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(7px) rotate(45deg); }
[aria-expanded="true"] .nav-toggle-bars::after  { transform: translateY(-7px) rotate(-45deg); }

/* Anchor navigation: every section has to clear the fixed header when the
   browser jumps to it. */
[id] { scroll-margin-top: 84px; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }


/* 14 — Hero =============================================================== */
/* Full-bleed video with the copy laid over it. The scrim is what makes the
   type legible: a flat wash plus a stronger foot, so the headline never sits
   on an unpredictable part of the frame. */
.hero { position: relative; display: grid; align-items: end; min-height: 100svh; isolation: isolate; }
.hero__media { position: absolute; inset: 0; z-index: -1; overflow: hidden; }
.hero__video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Two layers instead of one flat wash. The first is a short gradient that
   exists only behind the headline at the foot, so the text keeps its contrast;
   the second is a light overall tint that stops the video bleaching the header
   nav without draining the footage of colour. Splitting them is what lets the
   middle of the frame sit at ~0.18 alpha where it used to be 0.46. */
.hero__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to top,
      rgb(4 29 59 / .82) 0%,
      rgb(4 29 59 / .45) 26%,
      rgb(4 29 59 / 0)   52%),
    linear-gradient(to top,
      rgb(4 29 59 / .30) 0%,
      rgb(4 29 59 / .18) 60%,
      rgb(4 29 59 / .42) 100%);
}
.hero__inner { padding-block: var(--s-9); color: var(--c-on-dark); }
.hero h1 {
  color: var(--c-on-dark); max-width: 26ch;
  font-size: clamp(2rem, 1.1rem + 3.1vw, 3.4rem);
  text-wrap: balance;
}
.hero h1 + .lede { margin-top: var(--s-4); }
.hero .lede { color: var(--c-on-dark-muted); max-width: 46ch; }
.hero .actions { margin-top: var(--s-6); }
.hero .btn { background: var(--c-on-dark); border-color: var(--c-on-dark); color: var(--c-navy); }
.hero .btn:hover { background: var(--c-blue); border-color: var(--c-blue); color: var(--c-on-dark); }


/* 15 — Scroll reveal ====================================================== */
/* The raised-text entry. One class, driven by an IntersectionObserver, and it
   degrades to plain visible content when JavaScript or motion is absent. */
.reveal { opacity: 0; transform: translateY(1.5rem); }
.reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .6s ease, transform .6s cubic-bezier(.22,.61,.36,1);
  transition-delay: var(--d, 0s);
}
html:not(.js) .reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal.is-in { opacity: 1; transform: none; transition: none; }
}


/* 16 — About: the five reasons =========================================== */
/* Five items, five cells, no holes. Three columns cannot do that with five
   items, so the grid is two wide and the first item takes the full first row:
   1 across the top, then 2 and 2. */
.reasons {
  display: grid; gap: 1px; margin-top: var(--s-7);
  background: var(--c-line);
  border: 1px solid var(--c-line);
  grid-template-columns: repeat(2, 1fr);
}
.reasons li { background: var(--c-paper); padding: var(--s-6); }
.reasons li:first-child { grid-column: span 2; background: var(--c-surface); }
.reasons h3 { margin: 0 0 var(--s-2); font-size: var(--fs-md); color: var(--c-navy); }
.reasons p { margin: 0; color: var(--c-muted); }

/* The strapline set as a pull quote rather than a heading: it is the
   company's own line about itself, not a section title. */
.pull {
  margin-top: var(--s-8);
  font-family: var(--f-head); font-size: var(--fs-lg);
  letter-spacing: .04em; color: var(--c-navy);
}
.pull span {
  display: block; margin-top: var(--s-2);
  font-family: var(--f-body); font-size: var(--fs-sm);
  letter-spacing: 0; color: var(--c-muted);
}


/* 17 — Services: sectors ================================================== */
.sector { margin-top: var(--s-8); }
.sector__grid {
  display: grid; gap: var(--s-6) var(--s-7);
  grid-template-columns: 1fr 1fr; align-items: center;
}
.sector--flip .sector__media { order: 2; }
.sector__media { margin: 0; }
.sector__media img { display: block; width: 100%; height: auto; }
.sector__copy h3 { margin: 0 0 var(--s-3); }

/* The third sector breaks the split, so the page does not read as a third
   identical zigzag row. Full-bleed image, copy laid over its foot. */
.sector--wide { position: relative; margin-top: var(--s-8); isolation: isolate; }
.sector--wide img { display: block; width: 100%; height: clamp(22rem, 52vw, 34rem); object-fit: cover; }
.sector--wide::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgb(4 29 59 / .90), rgb(4 29 59 / .30) 70%);
}
.sector__over {
  position: absolute; inset: auto 0 0 0; z-index: 1;
  padding-bottom: var(--s-7); color: var(--c-on-dark);
}
.sector__over h3 { color: var(--c-on-dark); margin: 0 0 var(--s-3); }
.sector__over p { color: var(--c-on-dark-muted); max-width: 52ch; margin: 0; }


/* 17a — Industries we serve: the rotating list ============================ */
/* Seven names in one grid cell, one visible at a time. Each <li> runs the same
   keyframes over the full cycle but starts a slot later, so the fade walks
   down the list and wraps. Pure CSS - no timer to drift, nothing to tear down,
   and it animates only opacity and transform, which the compositor handles.

   The stack is what makes the rotation possible and it is also what makes it
   conditional: seven items in one cell are unreadable the moment the animation
   is not running. So the stack and the animation are introduced together in the
   no-preference branch below, and the default state - the one reduced-motion
   users and any browser that ignores that branch will see - is an ordinary
   wrapped row with all seven legible at once. */
.industries { text-align: center; }
.industries__head {
  margin: var(--s-9) 0 var(--s-5);
  font-family: var(--f-mono);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--c-muted);
}
.industries__list {
  display: flex; flex-wrap: wrap;
  justify-content: center; gap: var(--s-3) var(--s-5);
  margin-bottom: var(--s-9);
}
.industries__list li {
  font-family: var(--f-head);
  font-size: var(--fs-2xl);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--c-navy);
}

@media (prefers-reduced-motion: no-preference) {
  /* One cell, seven layers. min-height holds the row open at the tallest name
     so the carousel below does not shift as the cycle runs. */
  .industries__list {
    display: grid; gap: 0;
    place-items: center;
    min-height: calc(var(--fs-2xl) * 2.3);
  }
  .industries__list li {
    grid-area: 1 / 1;
    opacity: 0;
    animation: industries-rotate 16.8s linear infinite;
  }
  /* 16.8s over seven names is one slot every 2.4s. */
  .industries__list li:nth-child(2) { animation-delay:  2.4s; }
  .industries__list li:nth-child(3) { animation-delay:  4.8s; }
  .industries__list li:nth-child(4) { animation-delay:  7.2s; }
  .industries__list li:nth-child(5) { animation-delay:  9.6s; }
  .industries__list li:nth-child(6) { animation-delay: 12.0s; }
  .industries__list li:nth-child(7) { animation-delay: 14.4s; }
}

/* One slot is 100/7 = 14.285% of the cycle: up over the first ~0.4s, held for
   ~1.5s, out over the last ~0.5s, then out of the way for the other six. */
@keyframes industries-rotate {
  0%      { opacity: 0; transform: translateY(0.6rem); }
  2.5%    { opacity: 1; transform: none; }
  11.5%   { opacity: 1; transform: none; }
  14.285% { opacity: 0; transform: translateY(-0.6rem); }
  100%    { opacity: 0; transform: translateY(-0.6rem); }
}


/* 17b — What we supply: the carousel ====================================== */
/* Ten product categories, two to a page. The track is a scroll-snap strip,
   so the swipe is the browser's own and works with the script switched off;
   site.js only unhides the arrows and dots. Snap points sit on the ODD cards,
   which is what makes a page two cards wide rather than one. */

.supply__head { margin-top: var(--s-9); font-size: var(--fs-2xl); }
/* Headings carry no bottom margin, so without this the track sits flush under
   the heading. Same gap the lede gets before a block of content. */
.supply__head + .supply-carousel { margin-top: var(--s-6); }

.supply-track {
  display: flex; gap: var(--s-5);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* The bar is the arrows' job; hiding it keeps the off-white cards clean. */
  scrollbar-width: none;
}
.supply-track::-webkit-scrollbar { display: none; }
@media (prefers-reduced-motion: reduce) { .supply-track { scroll-behavior: auto; } }

/* Half the wrap, less half the gap, so two cards fill the row exactly. */
.supply-card {
  flex: 0 0 calc(50% - var(--s-5) / 2);
  display: flex; flex-direction: column;
  background: var(--c-paper);
  border: 1px solid var(--c-line);
  border-radius: var(--radius-ui);
  overflow: hidden;
}
.supply-card:nth-child(odd) { scroll-snap-align: start; }

.supply-card__img {
  display: block; width: 100%; aspect-ratio: 3 / 2;
  object-fit: cover; background: var(--c-surface);
}
/* The off-white foot that carries the same two lines the index carried. */
.supply-card__body {
  padding: var(--s-5);
  background: var(--c-paper);
  border-top: 1px solid var(--c-line);
}
.supply-card__body h4 { font-size: var(--fs-base); margin-bottom: var(--s-1); }
.supply-card__body p { font-size: var(--fs-sm); color: var(--c-muted); margin: 0; }

/* Controls. [hidden] in the markup; site.js removes it, so a scripted page
   gets arrows and dots and an unscripted one just gets the swipe. */
/* Dots left, the two arrows paired on the right. */
.supply-controls {
  display: flex; align-items: center;
  gap: var(--s-2); margin-top: var(--s-5);
}
.supply-controls[hidden] { display: none; }

.supply-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  background: var(--c-paper); color: var(--c-navy);
  border: 1px solid var(--c-line-strong); border-radius: var(--radius-ui);
  cursor: pointer;
  transition: color .18s ease, border-color .18s ease, background-color .18s ease;
}
.supply-arrow:hover:not(:disabled) { color: var(--c-blue); border-color: var(--c-blue); }
.supply-arrow:disabled { opacity: .38; cursor: default; }
.supply-arrow .icon { width: 20px; height: 20px; }
[data-carousel-prev] .icon { transform: rotate(180deg); }

.supply-dots { display: flex; align-items: center; gap: var(--s-2); margin-right: auto; }
.supply-dot {
  width: 30px; height: 4px; padding: 0;
  background: var(--c-line-strong); border: 0; border-radius: 0;
  cursor: pointer;
  transition: background-color .18s ease;
}
.supply-dot[aria-selected="true"] { background: var(--c-navy); }
.supply-dot:hover { background: var(--c-blue); }

/* One card per page below the nav breakpoint, so every card is a snap point
   and the dot count follows from the layout rather than being hard-coded. */
@media (max-width: 48em) {
  .supply-card { flex-basis: 100%; }
  .supply-card:nth-child(even) { scroll-snap-align: start; }
}


/* 18 — Contact =========================================================== */
.contact__grid {
  display: grid; gap: var(--s-7);
  grid-template-columns: 1.3fr 1fr; align-items: start;
  margin-top: var(--s-7);
}
.contact__list li {
  display: grid; grid-template-columns: 24px 1fr; gap: var(--s-3);
  align-items: start; padding-block: var(--s-3);
}
.contact__list li + li { border-top: 1px solid var(--c-line); }
.contact__list .icon { color: var(--c-blue); margin-top: .15rem; }

.map { margin-top: var(--s-8); line-height: 0; }
.map iframe { display: block; width: 100%; border: 0; }


/* 19 — Responsive ======================================================== */
@media (max-width: 60em) {
  .sector__grid { grid-template-columns: 1fr; }
  .sector--flip .sector__media { order: 0; }
  .contact__grid { grid-template-columns: 1fr; }
  .reasons { grid-template-columns: repeat(2, 1fr); }
  .reasons li:first-child { grid-column: span 2; }
}

@media (max-width: 48em) {
  .nav-toggle { display: inline-flex; }

  /* The nav becomes a panel under the header. It is always navy, because it
     opens over the hero as often as over the page. */
  .site-nav {
    position: fixed; inset: 72px 0 auto; z-index: 39;
    background: var(--c-navy-dark);
    border-bottom: 1px solid var(--c-on-dark-line);
    display: none;
  }
  .site-nav[data-open] { display: block; }
  .site-nav ul { flex-direction: column; gap: 0; padding: var(--s-3) var(--gutter) var(--s-5); }
  .site-nav li + li { border-top: 1px solid var(--c-on-dark-line); }
  .site-nav a,
  .site-header[data-scrolled] .site-nav a {
    color: var(--c-on-dark); padding: var(--s-4) 0; border-bottom: 0;
  }
  .site-nav a[aria-current="true"] { color: var(--c-focus); }

  .reasons { grid-template-columns: 1fr; }
  .reasons li:first-child { grid-column: span 1; }

  .hero { min-height: 92svh; }
  .hero h1 { max-width: 100%; }
  .map iframe { height: 340px; }
}

/* 20 — Contact form layout =============================================== */
/* The short fields pair up; the message runs full width under them. */
.form-grid {
  display: grid; gap: var(--s-4) var(--s-5);
  grid-template-columns: 1fr 1fr;
}
.form-grid .field { margin: 0; }
.field--wide { grid-column: 1 / -1; }

.opt {
  font-weight: 400; font-size: var(--fs-xs);
  color: var(--c-muted); letter-spacing: 0; text-transform: none;
}

@media (max-width: 40em) {
  .form-grid { grid-template-columns: 1fr; }
}

/* 21 — Contact details column ============================================ */
.contact__details { padding-top: var(--s-2); }
