/* ==========================================================================
   Brunco Insulation Ltd — homepage
   Dark, restrained, architectural. Charcoal ground, warm ivory relief,
   bronze used sparingly enough that it never reads as gold-on-black.
   ========================================================================== */

:root {
  /* Core palette */
  --charcoal: #171a1d;
  --graphite: #2a2e32;
  --ivory: #f3f0e9;
  --stone: #c8c1b5;
  --bronze: #9a7b52;
  --white: #fff;

  /* The base bronze only clears 4.43:1 on charcoal and 3.7:1 on ivory, so it
     is reserved for rules, buttons and display-size text. Small labels use a
     lifted or deepened member of the same family to reach AA. */
  --bronze-lite: #c0996a; /* small text on dark  — 6.3:1 on charcoal */
  --bronze-deep: #7a5e38; /* small text on light — 5.1:1 on ivory    */

  /* Derived surfaces */
  --charcoal-deep: #101315;
  --graphite-lift: #31363b;
  --hairline: rgba(200, 193, 181, 0.14);
  --hairline-strong: rgba(200, 193, 181, 0.26);
  --hairline-dark: rgba(23, 26, 29, 0.14);
  --bronze-soft: rgba(154, 123, 82, 0.18);
  --ink-soft: rgba(23, 26, 29, 0.74);

  /* Type */
  --font-display: "Newsreader", "Iowan Old Style", "Palatino Linotype", Georgia,
    serif;
  --font-sans: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --step-hero: clamp(2.9rem, 7.2vw, 6rem);
  --step-h2: clamp(2rem, 4.4vw, 3.5rem);
  --step-h3: clamp(1.25rem, 1.9vw, 1.6rem);
  --step-body: clamp(1.0625rem, 1.1vw, 1.125rem);
  --step-lead: clamp(1.0625rem, 1.35vw, 1.25rem);

  /* Geometry. One radius, deliberately tiny — enough to take the hard
     machined corner off a surface without reading as a consumer-app pill.
     At 3px it registers as a softened edge, not as a rounded one, which is
     why there is no scale: nothing on this site should be rounder. */
  --radius: 3px;
  --gutter: clamp(1.375rem, 4vw, 3rem);
  --measure: 1280px;
  --section-y: clamp(4rem, 7.5vw, 7rem);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --nav-h: 76px;
}

/* --------------------------------------------------------------- base --- */

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--charcoal);
  color: var(--stone);
  font-family: var(--font-sans);
  font-size: var(--step-body);
  line-height: 1.7;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h2 {
  font-size: var(--step-h2);
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.06;
  letter-spacing: -0.025em;
  color: var(--white);
  margin: 0;
  text-wrap: balance;
}

p {
  margin: 0;
  text-wrap: pretty;
}

a {
  color: inherit;
  text-decoration: none;
}

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

/* <picture> must not generate a box of its own: otherwise height:100% on the
   inner <img> resolves against an auto-height inline parent and collapses to
   the image's intrinsic ratio instead of filling its container. */
picture {
  display: contents;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

:focus-visible {
  outline: 2px solid var(--bronze);
  outline-offset: 3px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ivory);
  color: var(--charcoal);
  padding: 0.75rem 1.25rem;
  font-weight: 600;
}
.skip:focus {
  left: 0;
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------- primitives --- */

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.eyebrow {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
  display: flex;
  /* Top-aligned so the rule stays on the first line when the label wraps
     on narrow screens, instead of floating to the block's centre. */
  align-items: flex-start;
  gap: 0.75rem;
  margin: 0 0 1.5rem;
}
.eyebrow::before {
  content: "";
  width: 28px;
  height: 1px;
  margin-top: 0.85em;
  background: var(--bronze);
  flex: none;
}

/* Numbered section mark: a hairline aligned to the content column, then
   "01 / Services". Gives every section a visible top edge and makes the page
   scan like a set of drawings rather than a run of blocks. */
.sec-mark {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  padding-top: 1.5rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
  border-top: 1px solid var(--hairline);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.sec-mark__no {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--bronze-lite);
}
.sec-mark__no::after {
  content: " /";
  color: var(--stone);
  opacity: 0.45;
}
.sec-mark__name {
  color: var(--stone);
}
/* The ivory section needs the light-ground members of the palette. */
.work .sec-mark {
  border-top-color: var(--hairline-dark);
}
.work .sec-mark__no {
  color: var(--bronze-deep);
}
.work .sec-mark__name {
  color: var(--ink-soft);
}

.lead {
  font-size: var(--step-lead);
  line-height: 1.65;
  max-width: 56ch;
}

/* Section headers: title left, optional action right. */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
.section-head h2 {
  max-width: 18ch;
}
.section-head__copy {
  max-width: 62ch;
}

/* A section header with no supporting paragraph beside it: nothing is
   competing for the row, so the heading is allowed to run wider. */
.section-head--solo h2 {
  max-width: 28ch;
}

/* An accented word inside a display heading. Upright, not italic: the
   colour alone is enough to separate it, and the italic was the most
   ornamental gesture on the page. */
.accent {
  color: var(--bronze);
}

/* Buttons: square, no badge. A plain arrow that steps forward on hover. */
.btn {
  border-radius: var(--radius);
  --btn-fg: var(--charcoal);
  --btn-bg: var(--ivory);
  display: inline-flex;
  align-items: center;
  gap: 1.1rem;
  padding: 0.95rem 1.6rem;
  border: 1px solid transparent;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.35s var(--ease), color 0.35s var(--ease),
    border-color 0.35s var(--ease);
}
/* Restyled rather than removed, so the button markup needs no change. */
.btn__ico {
  flex: none;
  display: grid;
  place-items: center;
  color: currentColor;
  transition: transform 0.4s var(--ease);
}
.btn__ico svg {
  width: 15px;
  height: 15px;
}
.btn:hover {
  --btn-bg: var(--bronze);
  --btn-fg: var(--charcoal);
}
.btn:hover .btn__ico {
  transform: translateX(4px);
}

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ivory);
  border-color: rgba(200, 193, 181, 0.45);
}
.btn--ghost:hover {
  --btn-bg: transparent;
  --btn-fg: var(--bronze);
  border-color: var(--bronze);
}


/* Quiet inline link with a moving rule under it. */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bronze-lite);
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--bronze-soft);
  transition: border-color 0.35s var(--ease);
}
.link svg {
  transition: transform 0.35s var(--ease);
}
.link:hover {
  border-color: var(--bronze);
}
.link:hover svg {
  transform: translateX(4px);
}

/* ------------------------------------------------------------- grading --- */
/* One mechanism, three intensities, so every image reads as one shoot.
   Layers rather than filter(): filter on a large image is expensive to
   composite during a scrub. */

.graded {
  border-radius: var(--radius);
  position: relative;
  overflow: hidden;
  background: var(--graphite);
  background-size: cover;
  background-position: center;
}
.graded > img,
.graded > picture > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.graded::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
      to bottom,
      rgba(23, 26, 29, 0.1),
      rgba(23, 26, 29, 0.72)
    ),
    linear-gradient(to top right, var(--bronze-soft), transparent 55%);
}

.graded--heavy::after {
  background: linear-gradient(
      to bottom,
      rgba(23, 26, 29, 0.55),
      rgba(23, 26, 29, 0.25) 35%,
      rgba(23, 26, 29, 0.88)
    ),
    radial-gradient(
      120% 80% at 50% 30%,
      transparent 30%,
      rgba(23, 26, 29, 0.7) 100%
    );
}

/* Grid cards: near-natural so clients recognize their building, under a
   veil that lifts on hover. */
.graded--veil::after {
  background: linear-gradient(
      to bottom,
      rgba(23, 26, 29, 0.1),
      rgba(23, 26, 29, 0.78)
    ),
    rgba(23, 26, 29, 0.34);
  transition: opacity 0.6s var(--ease);
}

/* Images fade up from their inlined placeholder. Gated on .js because the
   `is-loaded` class that reveals them is added by site.js — ungated, every
   graded image on the site stays at opacity 0 when scripting is off. */
.js .graded img {
  opacity: 0;
  transition: opacity 0.7s var(--ease);
}
.graded img.is-loaded {
  opacity: 1;
}

/* ------------------------------------------------------------------ nav --- */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  transition: background 0.45s var(--ease), box-shadow 0.45s var(--ease),
    border-color 0.45s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  background: rgba(16, 19, 21, 0.86);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--bronze-soft);
}
.nav__inner {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: 2rem;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-right: auto;
}
.brand img {
  height: 34px;
  width: auto;
  /* Pull the original lockup's yellow toward the muted bronze. */
  filter: saturate(0.72) brightness(0.96);
}
.brand__name {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
}
.brand__name b {
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ivory);
}
.brand__name span {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bronze-lite);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.2vw, 2rem);
}
.nav__links a {
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stone);
  padding: 0.35rem 0;
  position: relative;
  transition: color 0.3s var(--ease);
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 100%;
  background: var(--bronze);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease);
}
.nav__links a:hover {
  color: var(--ivory);
}
.nav__links a:hover::after {
  transform: scaleX(1);
}

.nav__cta {
  border-radius: var(--radius);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ivory);
  border: 1px solid var(--hairline-strong);
  padding: 0.7rem 1.35rem;
  white-space: nowrap;
  transition: border-color 0.35s var(--ease), color 0.35s var(--ease),
    background 0.35s var(--ease);
}
.nav__cta:hover {
  border-color: var(--bronze);
  color: var(--bronze);
}

.nav__burger {
  display: none;
  width: 44px;
  height: 44px;
  border: 1px solid var(--hairline-strong);
  background: transparent;
  cursor: pointer;
  padding: 0;
  place-items: center;
  /* Without this the three auto rows stretch to fill the 44px box and the
     bars sit 15.3px apart, so the open-state translateY(5.5px) leaves two
     separate diagonals instead of an X. align-content packs the rows to their
     own height, which is what the 5.5px is measured against. */
  align-content: center;
}
.nav__burger span {
  display: block;
  width: 17px;
  height: 1.5px;
  background: var(--ivory);
  transition: transform 0.35s var(--ease), opacity 0.25s var(--ease);
}
.nav__burger span + span {
  margin-top: 4px;
}
.nav.is-open .nav__burger span:nth-child(1) {
  transform: translateY(5.5px) rotate(45deg);
}
.nav.is-open .nav__burger span:nth-child(2) {
  opacity: 0;
}
.nav.is-open .nav__burger span:nth-child(3) {
  transform: translateY(-5.5px) rotate(-45deg);
}

/* --------------------------------------------------------------- hero --- */
/* The track supplies scroll distance; the stage sticks inside it. Using
   position:sticky rather than a GSAP pin means the hero still behaves
   without JavaScript — it simply becomes a full-screen still. */

.hero__track {
  position: relative;
  /* Scroll distance for the ascent. Long enough to read as a rise, short
     enough that the next section is roughly two flicks away. */
  height: 175vh;
}

.hero__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--charcoal-deep);
  display: grid;
}

.hero__frame {
  position: absolute;
  inset: 0;
  perspective: 1200px;
  overflow: hidden;
}

.hero__img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* Travel is traded against sharpness. A tall box gives a long ascent but
     forces a bigger upscale, and the supplied hero art is only 1110px wide.
     112% keeps the frame wide and the side crop small, which is what makes
     the shot read as zoomed out; most of the motion is the slow push in.
     --hero-from is the single source of truth — site.js reads it back rather
     than keeping its own copy, so the two can never drift apart.
     It must equal -((height - 100) / height) * 100%. */
  --hero-from: -10.714%;
  height: 112%;
  will-change: transform;
  transform-origin: 50% 20%;
  transform: translate3d(0, var(--hero-from), 0) scale(1.02);
}
.hero__img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Bronze bloom that opens near the top of the ascent. */
.hero__glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
      90% 55% at 50% 4%,
      rgba(154, 123, 82, 0.5),
      transparent 70%
    ),
    radial-gradient(120% 70% at 50% 0%, rgba(243, 240, 233, 0.16), transparent 60%);
}

.hero__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Darken the side the headline sits on rather than the whole frame, so the
     type stays legible while the skyline keeps its golden-hour light. */
  background: linear-gradient(
      100deg,
      rgba(16, 19, 21, 0.93) 0%,
      rgba(16, 19, 21, 0.76) 30%,
      rgba(16, 19, 21, 0.3) 58%,
      rgba(16, 19, 21, 0.12) 100%
    ),
    linear-gradient(
      to bottom,
      rgba(16, 19, 21, 0.6),
      rgba(16, 19, 21, 0.05) 26%,
      rgba(16, 19, 21, 0.1) 58%,
      rgba(16, 19, 21, 0.92)
    );
}

.hero__content {
  position: relative;
  z-index: 2;
  align-self: center;
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-top: var(--nav-h);
  will-change: transform, opacity;
}
.hero h1 {
  font-size: var(--step-hero);
  max-width: 15ch;
  letter-spacing: -0.03em;
}
.hero__sub {
  margin-top: 1.75rem;
  max-width: 48ch;
  font-size: var(--step-lead);
  color: var(--stone);
}
.hero__actions {
  margin-top: 2.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
}

.hero__cue {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 2rem;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--stone);
}
.hero__rule {
  width: 1px;
  height: 56px;
  background: var(--hairline-strong);
  position: relative;
  overflow: hidden;
}
.hero__rule i {
  position: absolute;
  inset: 0;
  background: var(--bronze);
  transform: scaleY(0);
  transform-origin: top;
}

/* Names the building in the hero, drawing-sheet style. */
.hero__caption {
  position: absolute;
  z-index: 2;
  right: var(--gutter);
  /* Clears the stats plate, which rides 5rem up over the hero. */
  bottom: 6rem;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--stone);
}
.hero__caption::before {
  content: "";
  width: 34px;
  height: 1px;
  background: var(--bronze);
  flex: none;
}

/* ---------------------------------------------------------- stats bar --- */
/* Ivory plate lifted over the hero seam — introduces the light ground
   before the page commits to it further down. */

.stats {
  position: relative;
  z-index: 3;
  margin-top: clamp(-5rem, -6vw, -3.5rem);
}
.stats__plate {
  border-radius: var(--radius);
  background: var(--ivory);
  /* A hairline instead of a blurred drop shadow: the soft shadow read like
     an app card floating over the page. */
  border: 1px solid var(--hairline-dark);
  padding: clamp(2rem, 3.5vw, 3rem) clamp(1.5rem, 3vw, 3rem);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.stat + .stat {
  border-left: 1px solid var(--hairline-dark);
  padding-left: clamp(1.25rem, 2.5vw, 2.5rem);
}
.stat b {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 3.6vw, 3.1rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--charcoal);
}
.stat span {
  display: block;
  margin-top: 0.7rem;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* -------------------------------------------------------------- intro --- */

.intro {
  padding-block: var(--section-y);
}
.intro__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 5rem);
  /* The heading column is much shorter than the body; centring it keeps the
     section from reading as a large hole under the title. */
  align-items: center;
}
.intro h2 {
  font-size: var(--step-h2);
}
.intro__body > p + p {
  margin-top: 1.25rem;
}
.intro__body .link {
  margin-top: 2rem;
}

/* ----------------------------------------------------------- services --- */
/* The full service list as a numbered index. Multi-column so the numbering
   runs down each column the way a printed index does, rather than snaking
   left-to-right across a grid. */

.services {
  padding-block: var(--section-y);
  background: var(--charcoal-deep);
}

.index {
  /* Two columns, not three: eight items split 4/4 and each gets more
     presence, which suits capability statements better than a long
     trade list would. */
  columns: 2;
  column-gap: clamp(2rem, 5vw, 4.5rem);
  counter-reset: svc;
  list-style: none;
  margin: 0;
  padding: 0;
}
.index__item {
  break-inside: avoid;
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 1.05rem 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 1rem;
  line-height: 1.45;
  color: var(--stone);
  transition: color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.index__item::before {
  counter-increment: svc;
  content: counter(svc, decimal-leading-zero);
  flex: none;
  width: 1.6rem;
  font-family: var(--font-display);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--bronze-lite);
}
.index__item:hover {
  color: var(--ivory);
  border-bottom-color: var(--bronze);
}

/* ------------------------------------------------------------ feature --- */
/* Full-bleed band using real jobsite photography rather than a rendering.
   Unused on the homepage since the fire-wrap section was removed; kept
   because the Fire Wrap page reuses it verbatim and its images are built. */

.feature {
  position: relative;
  padding-block: clamp(6rem, 14vw, 11rem);
  overflow: hidden;
}
.feature__bg {
  position: absolute;
  inset: -12% 0;
  z-index: 0;
  will-change: transform;
}
.feature__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.feature__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    rgba(16, 19, 21, 0.95) 12%,
    rgba(16, 19, 21, 0.8) 46%,
    rgba(16, 19, 21, 0.34)
  );
}
.feature .wrap {
  position: relative;
  z-index: 1;
}
.feature__panel {
  max-width: 43rem;
}
.feature h2 {
  font-size: var(--step-h2);
}
.feature__panel > p {
  margin-top: 1.5rem;
}
.feature__meta {
  margin-top: 2.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 2.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--hairline);
}
.feature__meta div b {
  display: block;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--bronze-lite);
}
.feature__meta div span {
  font-size: 0.8125rem;
  color: var(--stone);
}
.feature__actions {
  margin-top: 2.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
}

/* --------------------------------------------------------------- work --- */
/* The one light section: buildings read cleanly against a warm ground. */

.work {
  background: var(--ivory);
  color: var(--ink-soft);
  padding-block: var(--section-y);
}
.work h2,
.work h3 {
  color: var(--charcoal);
}
.work .eyebrow {
  color: var(--bronze-deep);
}
.work .section-head__copy {
  color: var(--ink-soft);
}

.work__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
}

.tile {
  border-radius: var(--radius);
  display: block;
  overflow: hidden;
  background: var(--graphite);
  position: relative;
  aspect-ratio: 4 / 5;
  isolation: isolate;
}
.tile__media {
  position: absolute;
  inset: 0;
}
.tile__media img {
  transition: transform 0.9s var(--ease), opacity 0.7s var(--ease);
}
.tile:hover .tile__media img,
.tile:focus-visible .tile__media img {
  transform: scale(1.03);
}
.tile:hover .tile__media::after,
.tile:focus-visible .tile__media::after {
  opacity: 0.45;
}
.tile__body {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  padding: clamp(1.1rem, 1.8vw, 1.5rem);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
}
.tile__text h3 {
  color: var(--white);
  font-size: 1.1875rem;
  letter-spacing: -0.005em;
}
/* Scoped to .tile__text: a bare `.tile__body span` also matched the arrow
   badge and overrode its display:grid. */
.tile__text span {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--stone);
}
.tile__ico {
  width: 34px;
  height: 34px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid rgba(243, 240, 233, 0.35);
  color: var(--ivory);
  transition: background 0.4s var(--ease), color 0.4s var(--ease),
    border-color 0.4s var(--ease), transform 0.4s var(--ease);
}
.tile__ico svg {
  width: 13px;
  height: 13px;
}
.tile:hover .tile__ico {
  background: var(--bronze);
  border-color: var(--bronze);
  color: var(--charcoal);
  transform: translateX(2px);
}

.work__foot {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--hairline-dark);
}
.work__foot p {
  font-size: 0.9375rem;
  max-width: 46ch;
}

/* The default pill is ivory-on-dark, which vanishes on this section's ivory
   ground. Invert it here so it keeps its silhouette. */
.work .btn {
  --btn-bg: var(--charcoal);
  --btn-fg: var(--ivory);
}
.work .btn__ico {
  background: var(--ivory);
  color: var(--charcoal);
}
.work .btn:hover {
  --btn-bg: var(--bronze-deep);
  --btn-fg: var(--ivory);
}

/* --------------------------------------------------------- affiliates --- */

.affil {
  padding-block: clamp(3.5rem, 7vw, 6rem);
  background: var(--charcoal-deep);
  border-block: 1px solid var(--hairline);
}
.affil__row + .affil__row {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  padding-top: clamp(2.5rem, 4vw, 3.5rem);
  border-top: 1px solid var(--hairline);
}
.affil__label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
  margin-bottom: 2rem;
}
.affil__marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(2rem, 5vw, 4.5rem);
}
/* The marks arrive pre-normalized to white by tools/build-images.py, so no
   filter is needed here. They vary wildly in proportion — TIAC is 9:1, MIAO
   is square — so constrain both axes and let each settle inside the box. */
.affil__marks img {
  width: auto;
  height: 34px;
  opacity: 0.62;
  transition: opacity 0.4s var(--ease);
}
.affil__marks img:hover {
  opacity: 1;
}
/* Optical, not mathematical: a 9:1 lockup at the same pixel height as a
   square emblem overwhelms the row, and a hairline wordmark disappears. */
.affil__marks .m-wide {
  height: 26px;
}
.affil__marks .m-square {
  height: 44px;
}

/* ------------------------------------------------------------ contact --- */

.contact {
  padding-block: var(--section-y);
  background: var(--charcoal-deep);
}
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}
.contact h2 {
  font-size: var(--step-h2);
}
.contact__detail {
  margin-top: 2.5rem;
  display: grid;
  gap: 1.75rem;
}
.contact__detail dt {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
  margin-bottom: 0.4rem;
}
.contact__detail dd {
  margin: 0;
  font-size: 1.0625rem;
  color: var(--ivory);
  line-height: 1.5;
}
.contact__detail a:hover {
  color: var(--bronze);
}

.form {
  border-radius: var(--radius);
  background: var(--graphite);
  border: 1px solid var(--hairline);
  padding: clamp(1.75rem, 3.5vw, 2.75rem);
  display: grid;
  gap: 1.25rem;
}
.form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
}
.field {
  display: grid;
  gap: 0.5rem;
}
.field label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--stone);
}
.field input,
.field select,
.field textarea {
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.9375rem;
  color: var(--ivory);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hairline-strong);
  border-radius: var(--radius);
  padding: 0.6rem 0;
  transition: border-color 0.35s var(--ease);
}
.field textarea {
  resize: vertical;
  min-height: 92px;
}
.field select option {
  background: var(--graphite);
  color: var(--ivory);
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--bronze);
}
.field input::placeholder,
.field textarea::placeholder {
  color: rgba(200, 193, 181, 0.4);
}
.form__note {
  font-size: 0.8125rem;
  color: rgba(200, 193, 181, 0.7);
}
.form .btn {
  justify-self: start;
}

/* ------------------------------------------------------------- footer --- */

.footer {
  background: var(--charcoal);
  border-top: 1px solid var(--hairline);
  padding-block: clamp(3rem, 6vw, 5rem) 2rem;
}
.footer__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 3.5rem);
}
.footer__about p {
  margin-top: 1.5rem;
  font-size: 0.875rem;
  max-width: 34ch;
}
.footer__label {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
  margin-bottom: 1.25rem;
}
.footer li + li {
  margin-top: 0.7rem;
}
.footer li a,
.footer__col p {
  font-size: 0.875rem;
  color: var(--stone);
  transition: color 0.3s var(--ease);
}
.footer li a:hover {
  color: var(--ivory);
}
.footer__bar {
  margin-top: clamp(3rem, 5vw, 4rem);
  padding-top: 1.75rem;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  justify-content: space-between;
  font-size: 0.8125rem;
}

/* ----------------------------------------------------------- reveals --- */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
}
[data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}

/* -------------------------------------------------------- responsive --- */

@media (max-width: 1180px) {
  .index {
    columns: 2;
  }
}

@media (max-width: 1080px) {
  .stats__plate {
    grid-template-columns: repeat(2, 1fr);
  }
  .stat:nth-child(odd) {
    border-left: 0;
    padding-left: 0;
  }
  .stat:nth-child(3),
  .stat:nth-child(4) {
    border-top: 1px solid var(--hairline-dark);
    padding-top: 1.75rem;
  }
  .footer__grid {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 900px) {
  :root {
    --nav-h: 64px;
  }

  .nav__burger {
    display: grid;
  }

  /* The overlay below is a positioned child of .nav, which owns the stacking
     context, so it paints above its own non-positioned siblings. Without this
     the brand and the close control vanish the moment the menu opens and the
     only way out is Escape or a blind tap. */
  .nav .brand,
  .nav__burger {
    position: relative;
    z-index: 51;
  }

  /* Full-screen overlay menu. The links must NOT be display:none here —
     they live inside this panel, and hiding them leaves it blank. */
  .nav__menu {
    position: fixed;
    inset: 0;
    z-index: 49;
    background: var(--charcoal-deep);
    display: grid;
    align-content: start;
    justify-items: start;
    gap: 2rem;
    /* Starts below the header rather than centred in the viewport: centred,
       the links floated mid-screen with 220px of dead space above them. */
    padding: calc(var(--nav-h) + clamp(2rem, 8vw, 3.5rem)) var(--gutter)
      var(--gutter);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.4s var(--ease), visibility 0.4s;
  }
  /* The header sits above the overlay, so it needs its own ground. */
  .nav.is-open {
    background: var(--charcoal-deep);
    border-bottom-color: var(--hairline);
    /* And the blur has to go while the menu is open. backdrop-filter makes
       .nav a containing block for its position:fixed descendants, so the
       overlay's inset:0 resolved against the 64px header instead of the
       viewport: the panel collapsed to 118px and the page showed through
       behind the links. Only visible once .is-stuck had been applied, which
       is why it looked fine at scroll 0. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .nav.is-open .nav__menu {
    opacity: 1;
    visibility: visible;
  }
  .nav__links {
    display: grid;
    gap: 0;
    width: 100%;
  }
  .nav__links a {
    font-family: var(--font-display);
    font-size: clamp(1.625rem, 7vw, 2.5rem);
    font-weight: 400;
    letter-spacing: -0.01em;
    text-transform: none;
    color: var(--ivory);
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--hairline);
  }
  .nav__links a::after {
    display: none;
  }
  .nav__links a:last-child {
    border-bottom: 0;
  }
  .nav__cta {
    font-size: 0.9375rem;
    padding: 0.7rem 1.5rem;
  }

  /* Footer links were 15px-tall targets stacked 0.7rem apart — ten of them,
     against a 44px guideline. The padding is inside the breakpoint so the
     desktop footer's rhythm is untouched. */
  .footer li + li {
    margin-top: 0;
  }
  .footer li a {
    display: block;
    padding-block: 0.7rem;
  }
  .footer__col a,
  .footer__col p {
    font-size: 0.9375rem;
  }
  .link {
    padding-block: 0.6rem 0.5rem;
  }

  /* 16px is not a style choice: iOS zooms the whole page when a text field
     under 16px takes focus, and the user is then left scrolled sideways on a
     page that has no horizontal scroll. The extra padding takes the control
     to a 44px target at the same time. */
  .field input,
  .field select,
  .field textarea {
    font-size: 1rem;
    padding-block: 0.75rem;
  }
  .search {
    font-size: 1rem;
  }

  /* The remaining sub-44px targets, all of them dense link lists: the 51
     datasheet links in the fire-wrap matrix, the contact details and staff
     directory, and the 404's list. Padding rather than font size — these
     read fine, they were just too thin and too close together to hit. */
  .matrix__row li a,
  .taglist li a {
    display: block;
    padding-block: 0.75rem;
  }
  .matrix__row li + li {
    margin-top: 0;
  }
  .contact__detail a,
  .directory__person a {
    display: inline-block;
    padding-block: 0.7rem;
  }
  .chip {
    min-height: 44px;
  }
  .link {
    padding-top: 0.85rem;
  }
  /* The brand is the home link and appears twice on every page. 34px of
     lockup plus this clears 44 and still sits inside the 64px header. */
  .brand {
    padding-block: 0.35rem;
  }

  .intro__grid,
  .contact__grid {
    grid-template-columns: 1fr;
  }
  .index {
    columns: 2;
  }
  .section-head {
    align-items: flex-start;
  }
}

@media (min-width: 901px) {
  .nav__menu {
    display: contents;
  }
}

@media (max-width: 820px) {
  /* A long pinned hero feels broken on a phone: shorten the ascent and
     reduce the scale travel (the scrub reads --hero-scale). */
  .hero__track {
    height: 150vh;
  }
  /* svh, not vh: vh is the LARGEST viewport on a phone, so with the toolbar
     showing the foot of the hero — the scroll cue — sat behind the chrome.
     Not dvh either: on a sticky element a dynamic unit re-measures as the
     toolbar collapses and the hero jitters through the whole scrub. */
  .hero__stage {
    height: 100svh;
  }
  .hero__caption {
    display: none;
  }
  /* A landscape source on a portrait screen already crops hard sideways;
     the desktop box height suits it, so only the track length changes. */
  .stats {
    margin-top: -2.5rem;
  }
  .form__row {
    grid-template-columns: 1fr;
  }
  .feature__meta {
    gap: 1.5rem 2rem;
  }
}

@media (max-width: 560px) {
  .stats__plate {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
  .stat + .stat {
    border-left: 0;
    border-top: 1px solid var(--hairline-dark);
    padding: 1.5rem 0 0;
  }
  .footer__grid {
    grid-template-columns: 1fr;
  }
  .index {
    columns: 1;
  }
  /* The marks vary from 9:1 to square; a single flex gap wrapped them ragged
     and pushed the last one against the container edge. Two per row, each
     given its own cell, settles them. */
  .affil__marks {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.75rem 1.5rem;
    align-items: center;
  }
  .affil__marks img {
    max-width: 100%;
  }
  .form {
    padding: 1.25rem;
    gap: 1rem;
  }
  /* The twelve curated projects become one horizontally snapped row rather
     than a two-up grid of 168px thumbnails. :not(--dense) matters: the same
     class carries the 290-card portfolio grid, which stays a grid. */
  .work__grid:not(.work__grid--dense) {
    display: flex;
    grid-template-columns: none;
    gap: 0.75rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    /* Bleed past the wrap's gutter to both screen edges, then pad it back so
       the first card still lines up with the heading above it. */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .work__grid:not(.work__grid--dense)::-webkit-scrollbar {
    display: none;
  }
  .work__grid:not(.work__grid--dense) .tile {
    /* Under 100% so the next card peeks — that peek is the only affordance
       telling you the row scrolls, since the scrollbar is hidden. */
    flex: 0 0 76%;
    scroll-snap-align: start;
  }
  /* Cards in the row opt out of the scroll reveal. The observer's root is the
     viewport, so a card parked off to the right only fades in at the moment
     you swipe to it — a 0.9s fade arriving after the gesture, on every card.
     A grid reveals once on the way past; a scroller should already be there. */
  .work__grid:not(.work__grid--dense) .tile[data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* The dense portfolio grid keeps its two-up thumbnails, so it keeps the
     smaller card furniture that goes with them. The column count itself has
     to be set further down: .work__grid--dense is re-declared ~600 lines
     below this block and would outrank anything set here. */
  .work__grid--dense .tile__body {
    padding: 0.85rem;
  }
  .work__grid--dense .tile__body h3 {
    font-size: 0.9375rem;
  }
  .work__grid--dense .tile__ico {
    display: none;
  }
}

/* ------------------------------------------------- reduced motion --- */
/* Everything scroll-driven collapses to a static, fully legible page. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .hero__track {
    height: auto;
  }
  .hero__stage {
    position: relative;
    height: 100svh;
  }
  .hero__img {
    height: 100%;
    transform: none;
  }
  .hero__cue {
    display: none;
  }
  .stats {
    margin-top: -3rem;
  }
  [data-reveal] {
    opacity: 1;
    transform: none;
  }
  .graded img {
    opacity: 1;
  }
}

/* ======================================================== subpages === */
/* Everything below is shared by about / services / fire-wrap / portfolio /
   contact. The homepage components above are reused as-is. */

/* Compact page header. The homepage's sticky cinematic hero is wrong here —
   this is a banner, not a scene. */
.page-head {
  position: relative;
  display: flex;
  align-items: flex-end;
  /* A fixed height, not a floor. On min-height the banner was sized by its
     own headline: 387px on Our Work against 622px on Services, so every page
     change moved the fold. The h1 measure below is widened to match, so the
     longest headline on the site still fits this box. */
  height: clamp(23rem, 54vh, 34rem);
  padding-top: calc(var(--nav-h) + clamp(3rem, 6vw, 5rem));
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
  background: var(--charcoal-deep);
  overflow: hidden;
}
.page-head__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.page-head__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.page-head__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Same left-weighted scrim as the hero, so the type side stays legible. */
  background: linear-gradient(
      100deg,
      rgba(16, 19, 21, 0.92) 0%,
      rgba(16, 19, 21, 0.72) 34%,
      rgba(16, 19, 21, 0.2) 100%
    ),
    linear-gradient(to bottom, rgba(16, 19, 21, 0.45), rgba(16, 19, 21, 0.1) 40%,
      rgba(16, 19, 21, 0.75));
}
.page-head .wrap {
  position: relative;
  z-index: 1;
}
.page-head h1 {
  font-size: clamp(2.4rem, 5.2vw, 4rem);
  /* 17ch stacked the longest headline ("Delivering the highest quality and
     cost effective services.") four lines deep and burst the fixed box; 24ch
     still ran it three lines and clipped under the nav on a 1440x900 laptop.
     At 30ch the longest headline on the site is two lines at every width. */
  max-width: 30ch;
}
.page-head__lead {
  margin-top: 1.5rem;
  max-width: 58ch;
  font-size: var(--step-lead);
  line-height: 1.6;
  color: var(--stone);
}
/* No image: a plain charcoal band. Same height as every other banner — the
   point of the fixed height is that the fold does not move between pages. */
.page-head--plain {
  background: var(--charcoal);
}

/* Generic two-column section: heading left, body right. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}
.split h2 {
  font-size: var(--step-h2);
}

/* A tall frame in one column of a .split, the text in the other. The frame
   takes its height from the row rather than from a fixed ratio, so its top and
   foot land on the first and last line of the text beside it at every width —
   a locked 4:5 leaves the image hanging a few hundred pixels below the prose.
   object-fit on the inner img absorbs whatever crop that implies. */
.split--media {
  align-items: stretch;
}
.split__aside {
  margin: 0;                           /* figure carries a UA inline margin */
  max-width: 30rem;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
}
.split__aside .graded {
  display: block;
  height: 100%;
  min-height: 22rem;                   /* floor: short copy must not squash it */
}
/* Taking the image out of flow is what makes the alignment exact rather than
   lucky: left in flow its 4:5 intrinsic ratio drives the row height and the
   foot lands wherever it lands. Absolute, the row height comes from the text
   column alone and the frame fills it. */
.split--media .split__aside .graded > picture > img {
  position: absolute;
  inset: 0;
}
.split--media h2 + .prose {
  margin-top: clamp(1.5rem, 2.5vw, 2rem);
}
.split__aside figcaption {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--hairline);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bronze-lite);
}
.prose {
  max-width: 68ch;
}
.prose > p + p {
  margin-top: 1.25rem;
}
/* A body column that fills the container instead of sitting at a 68ch measure
   with empty space beside it. The line is roughly 115 characters at the 1280px
   wrap — well past the comfortable 45-75 — so the leading opens up and the
   paragraph spacing grows to match: on a long measure those are what keep the
   eye from losing its place on the return sweep. */
.prose--wide {
  max-width: none;
}
.prose--wide .lead {
  max-width: none;
}
.prose--wide > p {
  line-height: 1.85;
}
.prose--wide > p + p {
  margin-top: 1.6rem;
}
/* Sub-headings inside long body copy (the original site used these to break
   up the safety text). Deliberately small and lettered rather than a second
   display face, so they read as labels under the h2, not as rival titles. */
.prose h3 {
  margin-top: 2.5rem;
  margin-bottom: 0.9rem;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
}
.prose h3 + p {
  margin-top: 0;
}
/* Body links only. Without the :not() guards this rule outranks .btn and
   .link (one class each) and repaints a button's label ivory on ivory. */
.prose a:not(.btn):not(.link) {
  color: var(--ivory);
  border-bottom: 1px solid var(--bronze-soft);
}
.prose a:not(.btn):not(.link):hover {
  border-color: var(--bronze);
}

.section {
  padding-block: var(--section-y);
}
.section--deep {
  background: var(--charcoal-deep);
}

/* Pill-free tag list (charities, clubs). */
.taglist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.75rem;
}
.taglist li {
  border-radius: var(--radius);
  font-size: 0.8125rem;
  color: var(--stone);
  border: 1px solid var(--hairline);
  padding: 0.45rem 0.85rem;
}

/* ------------------------------------------------------------- credentials */
/* Association memberships as cards. Graphite is the palette's designated
   surface for exactly this and nothing else on the site was claiming it. */

.creds {
  /* Clips the corner cells: a 1px-gap grid squares off outside a rounded
     container otherwise. */
  overflow: hidden;
  border-radius: var(--radius);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* The stronger hairline: a 0.14 alpha rule over graphite is invisible and
     the three tiles read as one wide slab. */
  gap: 1px;
  background: var(--hairline-strong);
  border: 1px solid var(--hairline-strong);
}
.creds__item {
  background: var(--graphite);
  padding: clamp(1.75rem, 3vw, 2.5rem);
  display: grid;
  align-content: space-between;
  gap: clamp(1.75rem, 3vw, 2.5rem);
  transition: background 0.35s var(--ease);
}
.creds__item:hover {
  background: var(--graphite-lift);
}
/* A fixed box rather than a fixed width: the three marks have different
   aspect ratios and build_logo_marks() already normalised their polarity. */
.creds__mark {
  height: 60px;
  display: flex;
  align-items: center;
}
/* Optical heights, the same judgement as .affil__marks: a 9:1 wordmark set at
   the pixel height of a square emblem overwhelms the row, and vice versa. */
.creds__mark img {
  width: auto;
  height: 52px;
  max-width: 100%;
  opacity: 0.82;
  transition: opacity 0.4s var(--ease);
}
.creds__item:hover .creds__mark img {
  opacity: 1;
}
.creds__mark .m-wide {
  height: 30px;
}
.creds__mark .m-square {
  height: 56px;
}
.creds__name {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  line-height: 1.35;
  color: var(--ivory);
}
.creds__note {
  margin-top: 0.85rem;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bronze-lite);
}

/* ----------------------------------------------------------- listed groups */
/* Named organisations in labelled columns. Deliberately not cards: section 03
   sits right under the credential row and must not read as a second one. */

.groups {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Three named rows so the lists start on one line no matter how long each
     column's intro sentence runs. Without subgrid the columns simply flow. */
  grid-template-rows: auto auto auto;
  border-top: 1px solid var(--hairline);
}
.groups__col {
  padding: clamp(1.75rem, 3vw, 2.5rem) clamp(1.25rem, 2.5vw, 2rem);
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  align-content: start;
}
.groups__col:first-child {
  padding-left: 0;
}
.groups__col:last-child {
  padding-right: 0;
}
.groups__col + .groups__col {
  border-left: 1px solid var(--hairline);
}
.groups__label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bronze-lite);
}
.groups__intro {
  margin-top: 1rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--stone);
}
.groups__col ul {
  margin-top: 1.75rem;
  align-self: start;
}
.groups__col li {
  padding-block: 0.8rem;
  border-top: 1px solid var(--hairline);
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--ivory);
}

/* --------------------------------------------- fire-wrap application matrix */

.matrix {
  /* Clips the corner cells: a 1px-gap grid squares off outside a rounded
     container otherwise. */
  overflow: hidden;
  border-radius: var(--radius);
  display: grid;
  gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
  counter-reset: app;
}
/* One application per row, its systems in the column beside it. A two-up grid
   halves the height but breaks the pairing that makes this table readable —
   you scan down the applications and read across to the products. */
.matrix__row {
  background: var(--charcoal);
  display: grid;
  grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.25fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: clamp(1.25rem, 2.5vw, 1.9rem);
  counter-increment: app;
  transition: background 0.35s var(--ease);
}
.matrix__row:hover {
  background: var(--graphite);
}
.matrix__row h3 {
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
}
/* The same numeral rail as .sec-mark__no. Generated content, so it stays out
   of the heading's accessible name. */
.matrix__row h3::before {
  content: counter(app, decimal-leading-zero);
  display: block;
  margin-bottom: 0.55rem;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--bronze-lite);
}
.matrix__row li {
  font-size: 0.9375rem;
  line-height: 1.5;
}
.matrix__row li + li {
  margin-top: 0.6rem;
}
.matrix__row a {
  color: var(--stone);
  border-bottom: 1px solid var(--bronze-soft);
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.matrix__row a:hover {
  color: var(--ivory);
  border-bottom-color: var(--bronze);
}
.matrix__row a[target="_blank"]::after {
  content: " \2197";
  color: var(--bronze-lite);
}

/* ------------------------------------------------------ portfolio filters */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 2rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--hairline);
}
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.chip {
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--stone);
  background: transparent;
  border: 1px solid var(--hairline-strong);
  padding: 0.62rem 1rem;
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease),
    background 0.3s var(--ease);
}
.chip:hover {
  color: var(--ivory);
  border-color: var(--bronze);
}
.chip[aria-pressed="true"] {
  background: var(--ivory);
  border-color: var(--ivory);
  color: var(--charcoal);
}
.filters__right {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}
.search {
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.9375rem;
  color: var(--ivory);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hairline-strong);
  padding: 0.55rem 0;
  min-width: 15rem;
  transition: border-color 0.3s var(--ease);
}
.search::placeholder {
  color: rgba(200, 193, 181, 0.45);
}
.search:focus {
  outline: none;
  border-bottom-color: var(--bronze);
}
.count {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
  white-space: nowrap;
}
/* 290 cards want a denser grid than the homepage's curated twelve. */
.work__grid--dense {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: clamp(0.75rem, 1.4vw, 1.25rem);
}
.work__grid--dense .tile__text h3 {
  font-size: 1rem;
}
.empty {
  padding: 4rem 0;
  color: var(--stone);
}

/* ------------------------------------------------------- staff directory */

.directory {
  /* Clips the corner cells: a 1px-gap grid squares off outside a rounded
     container otherwise. */
  overflow: hidden;
  border-radius: var(--radius);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
}
.directory__person {
  background: var(--charcoal);
  padding: clamp(1.25rem, 2.2vw, 1.75rem);
}
.directory__role {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze-lite);
}
.directory__name {
  margin-top: 0.6rem;
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 500;
  color: var(--ivory);
}
.directory__person a {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  color: var(--stone);
  border-bottom: 1px solid var(--bronze-soft);
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.directory__person a:hover {
  color: var(--ivory);
  border-bottom-color: var(--bronze);
}

@media (max-width: 900px) {
  .split,
  .matrix__row {
    grid-template-columns: 1fr;
  }
  /* Straight to one column rather than two: three cards in a 2-up grid leave
     an empty fourth cell, which reads as a card that failed to load. */
  /* Stacked, the heading must still lead; the frame follows the text. */
  .split--media > div {
    order: 1;
  }
  .split--media .split__aside {
    order: 2;
    margin-top: clamp(2rem, 4vw, 3rem);
    max-width: none;
  }
  .split__aside .graded {
    aspect-ratio: 4 / 5;
    height: auto;
  }
  .creds {
    grid-template-columns: 1fr;
  }
  /* Stacked: the vertical rules become horizontal ones between groups. */
  .groups {
    grid-template-columns: 1fr;
    grid-template-rows: none;
  }
  .groups__col {
    padding-inline: 0;
    display: block;
  }
  .groups__col + .groups__col {
    border-left: 0;
    border-top: 1px solid var(--hairline);
  }
  .filters {
    align-items: flex-start;
  }
  .filters__right {
    width: 100%;
    justify-content: space-between;
  }
  .search {
    min-width: 0;
    flex: 1;
  }
}

@media (max-width: 560px) {
  /* Two-up thumbnails. This has to sit after the .work__grid--dense
     declaration above, not up in the main 560px block, or the auto-fill
     column rule wins and 290 cards stack one per row. */
  .work__grid--dense {
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
  }

  /* Six chips wrapped to three rows and pushed the first project ~130px down
     the page. One swipeable row instead, bleeding to both screen edges. */
  .filters {
    gap: 1.25rem;
  }
  .chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    width: 100%;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scrollbar-width: none;
    /* The hidden scrollbar leaves no hint that the row continues, so the
       right edge fades instead. */
    mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
  }
  .chips::-webkit-scrollbar {
    display: none;
  }
  .chip {
    flex: 0 0 auto;
  }
  /* Search gets its own line; the count sits under it rather than squeezed
     against it. */
  .filters__right {
    flex-wrap: wrap;
    width: 100%;
    gap: 0.75rem;
  }
  .filters__right .search {
    flex: 1 0 100%;
  }
}

/* Three columns for the longer lists (17 services, 7 products). */
.index--3 {
  columns: 3;
}
@media (max-width: 1080px) {
  .index--3 {
    columns: 2;
  }
}
@media (max-width: 640px) {
  .index--3 {
    columns: 1;
  }
}

/* .tile sets display:block, which beats the UA stylesheet's [hidden] rule —
   without this the portfolio filter would hide nothing. */
.tile[hidden] {
  display: none !important;
}
