/* ============================================================================
   HYDRO2050 - CHROME
   ----------------------------------------------------------------------------
   The header bar, the desktop nav and its dropdowns, the mobile drawer, and
   the footer. Everything that appears on every page. Written by js/chrome.js.
   ========================================================================== */

/* ===== HEADER ============================================================ */

/* The bar is a 1376 navy panel with an 8px radius floating inside a 32px page
   padding, so it does not touch any page edge. On Home it sits over the hero
   at top:15; on inner pages it sits in a 148px white band. One rule handles
   both: the hero pulls up under the header with a negative margin.

   STICKY. The header follows the page down. It keeps its place in normal flow,
   so the hero's negative margin still pulls up under it exactly as before and
   nothing below it moves. Two things had to be true for this to work at all:
   body had to stop being a scroll container (see css/base.css, `overflow-x:
   clip`), and the bar had to sit above every page layer, which it does at
   --z-chrome. */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-chrome);
  padding: 32px 32px 0;
  /* Only these two. Never height: animating the height of a sticky bar
     reflows every page below it on every frame of the transition, and the
     measured --header-h the anchor offsets depend on would be a moving
     target mid-scroll. */
  transition: padding var(--dur-hover) var(--ease-out),
              background-color var(--dur-hover) var(--ease-out);
}

/* Stuck state. The floating panel meets the top of the viewport instead of
   hanging below a 32px gap that shows page content sliding through it. The
   outer padding collapses, the panel squares off its top corners, and the
   header's own ground goes navy so the collapse cannot reveal a white seam.

   Set by js/motion.js from an IntersectionObserver on a 1px sentinel written
   immediately before the header, NOT from a scroll listener: Lenis drives
   scroll from the GSAP ticker, so a scroll listener here would run on every
   animation frame for a boolean the browser answers for free. */
.site-header.is-stuck {
  padding: 0;
  background: var(--brand-navy);
}
.site-header.is-stuck .site-header__bar {
  border-radius: 0;
  box-shadow: 0 1px 0 #23E1DC3D;   /* the turquoise hairline, at 24% */
}

/* ---- THE HEADER ACROSS A PAGE NAVIGATION --------------------------------
   The client reported the bar bugging and overlapping when switching pages.
   Reproduced on 2026-08-24 by sampling the view-transition pseudo-elements
   through a real cross-document navigation, at 1440, in headless Chrome 148.
   Measured, not inferred:

     about-us -> index      ::view-transition-old(h2050-header)      1440 x 116
                            stayed at opacity 1 for all 527ms of the
                            transition, while
                            ::view-transition-new(h2050-header-home) 1440 x 99
                            faded in underneath it.
     index -> about-us      ::view-transition-new(h2050-header)      1440 x 116
                            was at opacity 1 from the first frame, while
                            ::view-transition-old(h2050-header-home) 1440 x 99
                            faded out over it.
     about-us -> case-studies   one group, both snapshots 1440 x 116, identical,
                                so nothing is visible. This pair was always fine.

   So on every navigation into or out of Home, which is most of them, two bars
   of different heights are painted at once, and one of them does not fade at
   all. That is the overlap.

   The cause is a pair of rules in css/motion.css. It names Home's bar
   h2050-header-home and every other bar h2050-header, so the two never pair,
   and it then declares `animation: none` on old and new. `animation: none` is
   only ever safe on a name that is CERTAIN to pair: an unpaired old with no
   animation has nothing to take it off screen, so it holds at full opacity
   until the whole transition ends and then vanishes, and an unpaired new with
   no animation is at full opacity from the first frame. The intent of the two
   names was that the bars would never be on screen together. They always are.

   The fix is one name, so old and new always pair into ONE group, and no
   `animation: none`, so the browser's own cross-fade runs inside it. Between
   two inner pages the two snapshots are identical and the cross-fade is
   invisible, which is the stability the original rule wanted. Between Home and
   an inner page one box moves and resizes from 99 to 116 while its contents
   cross-fade, and there is never a second box. The default `plus-lighter`
   blend is left alone on purpose: it is what makes a cross-fade of two
   near-identical navy bars hold its opacity instead of letting the page show
   through the middle of it.

   Written here rather than in motion.css because the header is this file's,
   and it beats motion.css on specificity rather than on order: chrome.css
   loads first, `.page-home .site-header` is 0-2-0, so a bare `.site-header`
   could not win. The two rules in motion.css that name h2050-header and
   h2050-header-home now match no element and are dead; deleting them belongs
   to whoever owns that file. */
html body .site-header,
html body.page-home .site-header { view-transition-name: h2050-chrome; }

::view-transition-group(h2050-chrome),
::view-transition-old(h2050-chrome),
::view-transition-new(h2050-chrome) {
  animation-duration: var(--dur-hover);
  animation-timing-function: var(--ease-out);
}

/* Under reduced motion the bar is not captured separately at all. It goes back
   into the root snapshot, whose animations css/motion.css already zeroes, so
   the navigation cuts. Nothing is hidden and nothing is left mid-morph: this
   removes the animation, not the header. */
@media (prefers-reduced-motion: reduce) {
  html body .site-header,
  html body.page-home .site-header { view-transition-name: none; }
}

/* The sentinel. Zero-height, invisible, and it must sit OUTSIDE the header so
   the header sticking cannot move it. It is what tells the observer that the
   top of the document has left the viewport. */
.header-sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
  visibility: hidden;
}

.site-header__bar {
  display: flex;
  align-items: center;
  gap: var(--s-10);
  height: 84px;
  padding-inline: 36px;
  background: var(--brand-navy);
  border-radius: 8px;
  transition: border-radius var(--dur-hover) var(--ease-out);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 44px;              /* the project's stated tap floor */
  padding-block: 5px;
  text-decoration: none;
  flex-shrink: 0;
}
/* The mark is a stacking context of its own, and it is clipped twice.

   A <video> is promoted to its own compositor layer, and a composited layer is
   clipped by an ancestor's `overflow: hidden` plus `border-radius` only when
   that ancestor is itself a stacking context. Without `isolation: isolate` the
   keyed video painted as a full square, outside the disc, over whatever the
   bar was sitting on. `clip-path` is the belt to that braces: it applies to
   the composited layer directly rather than to the parent's paint. */
.brand__mark {
  position: relative;
  isolation: isolate;
  display: block;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  overflow: hidden;
  clip-path: circle(50%);
  background: var(--page);
}
.brand__still, .brand__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* The video sits over the still and is transparent until motion.js plays it,
   so a browser that never runs the script, or one that refuses to decode the
   file, shows the still and nothing else. Both files carry a real alpha plane,
   so the mark keys straight onto whatever is behind it.

   Resting state only. The cross-fade between the two, and the transition that
   carries it, live together in motion.css: this rule used to declare its own
   `transition: opacity 260ms var(--ease-out)` and motion.css declared
   `180ms linear` at the same specificity, so the 260ms here never once ran and
   read as the live value to anyone who found this line first. */
.brand__still { opacity: 1; }
.brand__video { opacity: 0; }
.brand__word {
  font-family: var(--font-display);
  font-weight: var(--wt-semi);
  letter-spacing: -0.02em;
  line-height: 24px;
  color: var(--on-navy);
  white-space: nowrap;
}
.plinth .brand__mark { width: 40px; height: 40px; }
.plinth .brand__word { line-height: 32px; letter-spacing: -0.01em; font-weight: var(--wt-medium); }

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

/* The nav runs the FULL HEIGHT of the bar, and the nav items with it. Nothing
   visible moves: the link inside each item is still centred, so every label
   and every rule lands exactly where it did. What changes is what `100%`
   means for the dropdown underneath.

   The panel is absolutely positioned against .nav__item, so `top: 100%` is the
   item's bottom edge. While the item was only as tall as its link, that edge
   sat 4.5px above the bar's bottom edge, and the panel's measured 8px offset
   was being counted from the wrong line, which is why it used to need a magic
   12px to land near the right place and still missed by 1.5. Stretched to the
   bar, `100% + 8px` is the design's own measurement, taken from the design's
   own edge, at every breakpoint and whatever the nav's height happens to be.

   It also closes the dead strip between the label and the panel: the pointer
   travelling down from the label stays inside .nav__item the whole way. */
.nav { flex: 1 1 auto; align-self: stretch; display: flex; align-items: stretch; }
.nav__list { display: flex; align-items: stretch; gap: 34px; }
.nav__item { position: relative; display: flex; align-items: center; }
/* A dropdown parent is TWO controls, a link and a chevron toggle, where it used
   to be one <button> holding both. The 7px that used to be the button's own
   flex gap is now the item's, so the chevron sits exactly where it did. See
   navList() in js/chrome.js for why the control was split. */
.nav__item--has-menu { gap: 7px; }

/* A nav label never wraps. Wrapping halves the label's line height and takes
   the link under the 44px tap floor without changing anything visible, so it
   reads as a spacing bug rather than as an overflow.

   Scoped to the bar's own links. The dropdown panel's items carry the same
   class and are 15px type in a 256px column, where "Aquaculture and food
   production" is one word away from needing two lines; forbidding the wrap
   there would push it out of the panel instead. */
.nav__link .nav__text { white-space: nowrap; }

.nav__link {
  display: flex;
  align-items: flex-start;      /* the chevron is set against the label, below */
  gap: 7px;
  padding-block: 20px;          /* 14px label in a 58px hit box, past 44px */
  /* Every nav label is now an <a>, dropdown parents included, but the zero
     stays and is still load-bearing. When the parents were <button> elements
     they carried the UA's own 6px of inline padding, which put the two kinds
     of nav item on different left edges: the 6px is what the dropdown panel
     was misaligned by, and it was also 12px of the rule's overhang. The
     chevron toggle is a <button> and zeroes it for the same reason. */
  padding-inline: 0;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: var(--wt-regular);
  line-height: 18px;
  color: var(--on-navy);        /* 9.60:1 on navy */
  text-decoration: none;
}
/* Label and rule as one column. See navLabel() in js/chrome.js for why the
   rule is a child of this rather than a sibling of the label. */
.nav__label { display: flex; flex-direction: column; min-width: 0; }
.nav__rule {
  height: 2px;
  margin-top: 8px;          /* the nav item column gap, as measured */
  border-radius: 1px;
  background: transparent;
  transition: background-color var(--dur-hover) var(--ease-out);
}
.nav__link:hover .nav__rule,
.nav__link[aria-current="page"] .nav__rule { background: var(--brand-turq); }
/* The label's rule used to light from aria-expanded on the same element,
   because the label WAS the button. The state now lives on a following
   sibling, and a sibling combinator only reaches forward, so the item is what
   is queried. Hovering the chevron lights the label's rule too, which is what
   hovering the old single button did. `> .nav__link` and not a bare descendant
   selector: the panel's own items carry .nav__rule as well, and without the
   child combinator opening the menu would light all five of them at once. */
.nav__item--has-menu:hover > .nav__link .nav__rule,
.nav__item--has-menu:has(> .nav__menu-toggle[aria-expanded="true"]) > .nav__link .nav__rule {
  background: var(--brand-turq);
}

/* The chevron toggle. The item centres its children, so the toggle has to be
   the SAME HEIGHT as the label link or the chevron centres on a shorter box and
   drops below the label's text line. Measured at 1440 before this padding was
   written: the chevron sat at y=74 against a text line centred on 69.

   The bottom padding is that difference spelled out rather than a constant.
   The link's box is 20px of padding, the label column, and 20px again, where
   the label column is an 18px line, the 8px gap and the 2px rule. The toggle's
   own column is the chevron's 6px top margin and the 6px mark. What the toggle
   is short by is the one minus the other, so that is what it adds back.

   The hit area is widened by a transparent overlay rather than by padding,
   because inline padding would push every nav item to its right along the
   bar. */
.nav__menu-toggle {
  position: relative;
  display: flex;
  align-items: flex-start;
  padding-inline: 0;
  padding-block: 20px calc(20px + (18px + 8px + 2px) - (6px + 6px));
  margin: 0;
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.nav__menu-toggle::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -6px;
  right: -10px;
}
.nav__chev {
  /* The link top-aligns its children so the label column starts at a known
     line. The chevron is 6px tall against an 18px label, so 6px of margin
     centres it on the label rather than on the label-plus-rule column. */
  margin-top: 6px;
  flex-shrink: 0;
  color: var(--on-navy-mute);
  transition: transform var(--dur-hover) var(--ease-out),
              color var(--dur-hover) var(--ease-out);
}
.nav__item--has-menu:hover .nav__chev,
.nav__menu-toggle[aria-expanded="true"] .nav__chev { color: var(--brand-turq); }
.nav__menu-toggle[aria-expanded="true"] .nav__chev { transform: rotate(180deg); }

/* The dropdown. It is a real list under a real button with aria-expanded, so
   it opens on click and on Enter as well as on hover. Hover as the ONLY route
   to a menu item locks out every keyboard and touch visitor, which is why the
   markup is not a :hover-only CSS menu. */
.nav__menu {
  position: absolute;
  /* 8px below the bar's bottom edge, which is what .nav__item's bottom edge
     now is. See the .nav rule above. */
  top: calc(100% + 8px);
  /* THE PANEL IS ALIGNED TO THE LABEL'S TEXT EDGE, NOT TO THE NAV ITEM BOX AND
     NOT TO ITS OWN EDGE. In the artboard the Industries panel starts at 172
     and its items' text at 192, and 192 is exactly where the "Industries"
     label starts. The panel carries 20px of inline padding, so pulling the
     panel 20px left of the item puts its text on the label. That only holds
     while the item's left edge IS the label's left edge, which is what
     zeroing the <button>'s UA padding above is for: before it, the panel's
     text sat 6px left of the label on both dropdowns.

     21 and not 20, because the panel also carries a 1px border and the box is
     border-box, so its text starts at edge + 1 + 20. The artboard's own
     arithmetic, 172 to 192, counts the padding and not the border; measured
     at 20 the text landed 1px right of the label. The stated intent is that
     the text lines up, so the text is what is lined up. */
  left: -21px;
  z-index: var(--z-content);
  width: 296px;
  padding: 14px 20px;
  background: var(--brand-navy);
  border: 1px solid #FFFFFF2E;
  border-radius: 8px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity var(--dur-hover) var(--ease-out),
              transform var(--dur-hover) var(--ease-out),
              visibility 0s linear var(--dur-hover);
}
.nav__item--has-menu:hover .nav__menu,
.nav__item--has-menu:focus-within .nav__menu,
.nav__menu-toggle[aria-expanded="true"] + .nav__menu {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}
/* The 8px between the bar and the panel is ground the panel does not cover, so
   a pointer travelling from the label to the first item leaves .nav__item and
   the panel closes under the cursor. The bridge is a transparent strip that
   belongs to the panel, so hovering it is still hovering the item that
   contains it. It exists only while the panel does, because a hidden panel
   takes its own descendants out of hit testing with it. */
.nav__menu::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -9px;                /* the 8px gap plus the panel's own 1px border */
  height: 9px;
}

.nav__menu-link {
  display: flex;
  padding-block: 9px;
  font-family: var(--font-display);
  font-size: 15px;
  line-height: 19px;
  color: var(--on-navy);
  text-decoration: none;
}
/* Same mechanic as the bar's own rule, at the panel's larger scale: 6px of gap
   rather than 8px, and the width is the item's label, not the 256px column. */
.nav__menu-link .nav__rule { margin-top: 6px; }
.nav__menu-link:hover .nav__rule { background: var(--brand-turq); }

/* ---- focus, on the navy bar ---------------------------------------------
   css/base.css already switches the ring to turquoise over every dark ground,
   this bar among them, and sets it 3px out. The design puts it 5px out on the
   header specifically, and that is the whole disagreement: 2px, on one bar.

   The nav links need more than an offset. Their focusable box is the 75px hit
   target the 44px floor requires, and a ring around that is a 89px box inside
   an 84px bar, so it would touch the bar's own edges. The design draws a
   105 x 32 ring against a 91 x 18 label, which is the LABEL with 7px of air on
   every side: 2px of ring outside a 5px offset. Moving the outline onto the
   label draws exactly that and leaves the hit target the size it has to be.
   Measured after this change at 1440: 104.3 x 32 against a 90.3 x 18 label.

   The turquoise underline is deliberately NOT added on focus. The focus
   artboard shows the rule with no background, so focus and hover do not stack;
   the ring is the focus affordance on its own. A dropdown parent is the one
   exception and it is not an exception to this rule: what lights its rule is
   aria-expanded, because its menu is open, not the fact that it has focus. */
.site-header :focus-visible { outline-offset: 5px; }
.site-header .nav__link:focus-visible { outline: none; }
.site-header .nav__link:focus-visible .nav__text {
  outline: 2px solid var(--brand-turq);
  outline-offset: 5px;
  border-radius: 3px;
}
/* Same reasoning for the chevron toggle: its focusable box is a 58px hit
   target, so the ring goes on the 9px mark inside it. */
.site-header .nav__menu-toggle:focus-visible { outline: none; }
.site-header .nav__menu-toggle:focus-visible .nav__chev {
  outline: 2px solid var(--brand-turq);
  outline-offset: 5px;
  border-radius: 3px;
}

.site-header__cta { margin-left: auto; flex-shrink: 0; }

/* THE SWEEP. At rest this is a blue lozenge with a 42px white square at its
   right end. On hover the white square grows LEFTWARD to 142px, travelling
   under the label, and the label flips from white to the pill's own blue. It
   is the only state in the design where something moves.

   The build used to do something else here, a darken to --brand-blue-cta-h
   plus a 3px nudge of the square. That is not a smaller version of the sweep,
   it is a different gesture, and the two do not layer: a pill that darkens
   while a white block crosses it reads as two things happening. The darken is
   gone and --brand-blue-cta-h goes unused in the header.

   The white block is taken out of flow so its width can animate without the
   pill resizing around it, so the pill's padding-right has to hold open the
   space the block used to occupy as a flex item: 5px inset + 42px block + the
   11px gap = 58px. The pill measures 161 x 52 at rest either way. */
.pill-cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 52px;
  padding: 0 58px 0 20px;
  background: var(--brand-blue-cta);   /* white on it = 6.16:1 */
  border-radius: 12px;
  text-decoration: none;
}
.pill-cta__label {
  /* Above the block, so the sweep passes underneath the words rather than
     over them. */
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: var(--wt-semi);
  line-height: 15px;
  color: var(--on-navy);
  /* THE COLOUR TRAVELS WITH THE BLOCK, on the block's duration and easing,
     not on the 140ms the hover-state note proposes. That note is explicit that
     its timings are a proposal and not read out of the design file, and 140ms
     against the block's 320ms leaves the label blue on blue, which is
     invisible, for the 180ms in between. Sharing the sweep's curve means the
     colour is always about as far across as the white is, so the label is
     legible at every frame of the gesture. */
  transition: color var(--dur-drawer) var(--ease-out);
}
.pill-cta__dot {
  position: absolute;
  right: 5px;
  top: 5px;
  display: flex;
  align-items: center;
  /* The arrow holds still. The block's right edge is what is pinned, so an
     arrow set against that edge does not move while the left edge travels;
     14px is the inset it already had when the block was a 42px square with
     the 14px arrow centred in it. The artboard puts the hovered arrow 4px
     from where the resting one is, and nothing else in the pill shifts, so
     that reads as hand placement rather than as intent. */
  justify-content: flex-end;
  padding-right: 14px;
  width: 42px;
  height: 42px;
  background: var(--page);
  border-radius: 8px;
  color: var(--brand-blue-cta);
  transition: width var(--dur-drawer) var(--ease-out);
}
/* :focus-visible, not only :hover. Every hover state on this bar has to have
   a keyboard equivalent or the pill simply has no focus affordance beyond the
   ring, and the colour inversion is half of what tells you the control is
   live. 142px covers the label completely (it spans 14 to 156 against a label
   at 20 to 103), so the hovered label is --brand-blue-cta on --page at
   6.16:1 rather than blue on blue. */
.pill-cta:hover .pill-cta__dot,
.pill-cta:focus-visible .pill-cta__dot { width: 142px; }
.pill-cta:hover .pill-cta__label,
.pill-cta:focus-visible .pill-cta__label { color: var(--brand-blue-cta); }
.pill-cta:active { transform: translateY(1px); }

/* Between 900 and 1099 the bar's own contents do not fit: the brand, the five
   nav labels at a 34px gap and the 161px Contact pill need about 900px inside
   a bar that has 764. Measured, the pill's right edge landed 15px past the
   viewport and "How it works" wrapped to three lines in a 41px column, under
   the 44px tap floor. The wordmark drops and the nav gap tightens; nothing
   else moves and above 1260 the measured values return.

   The band was 900 to 1099 and the labels still wrapped at 1100 and 1180,
   because the five labels plus the wordmark plus the pill need 1260 of bar,
   not 1100. Widened to the same 1260 the footer lanes already use, so the
   site has one tablet band rather than two. */
@media (min-width: 900px) and (max-width: 1259px) {
  .brand__word { display: none; }
  /* With the wordmark gone the link is the 34px mark, under the tap floor. */
  .brand { min-width: 44px; justify-content: center; }
  .nav__list { gap: 20px; }
  .site-header__bar { padding-inline: 24px; }
}

/* The four footer lanes are 168 / 240 / 216 / 192 with 24px gaps and 152px of
   padding, which needs 968px beside a 400px plinth. Below 1260 they wrap and
   the plinth takes the full width above them. */
@media (max-width: 1259px) and (min-width: 900px) {
  .directory { flex-direction: column; }
  .plinth { width: 100%; padding: 48px var(--gutter) 56px; }
  .directory__lanes { flex-wrap: wrap; padding: 48px var(--gutter) 64px; }
  .directory__lane { flex: 1 1 200px; }
}

.nav__toggle { display: none; }

/* ---- mobile drawer ------------------------------------------------------ */

/* Outside the media query on purpose. The drawer's own `hidden` rule lives
   inside the mobile block, so above the breakpoint the only thing closing it
   was the matchMedia handler in motion.js. A dropped change event, which a
   verifier reproduced once in seven resizes, then left a 1376 by 238 duplicate
   nav painted under the header. This makes the JS handler belt-and-braces
   rather than the thing holding it up. */
@media (min-width: 900px) { .nav-drawer { display: none; } }

@media (max-width: 899px) {
  .site-header { padding: 0; }
  .site-header__bar {
    height: 64px;
    padding-left: 20px;
    padding-right: 16px;
    border-radius: 0;
    gap: 0;
  }
  .nav, .site-header__cta { display: none; }

  .nav__toggle {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    margin-left: auto;
    color: var(--on-navy);
    border-radius: var(--r-input);
    transition: background-color var(--dur-fast) var(--ease-out);
  }
  .nav__toggle:active { background: #FFFFFF17; }
  .nav__toggle-close { display: none; }
  .nav__toggle[aria-expanded="true"] .nav__toggle-open { display: none; }
  .nav__toggle[aria-expanded="true"] .nav__toggle-close { display: block; }

  /* Full-screen overlay, as the artboard shows it: the drawer replaces the
     page rather than pushing it. Fixed, below the bar, and scrollable, because
     the expanded Industries state is 917px tall against an 844px screen. */
  .nav-drawer {
    position: fixed;
    inset: 64px 0 0;
    z-index: var(--z-drawer);
    display: flex;
    flex-direction: column;
    padding-top: 8px;
    background: var(--brand-navy);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .nav-drawer[hidden] { display: none; }

  .drawer__row { border-bottom: 1px solid #BFDCEA38; }
  .drawer__head { display: flex; align-items: center; }
  .drawer__link {
    display: flex;
    flex-direction: column;
    justify-content: center;
    flex: 1 1 auto;
    min-height: 60px;
    padding: 8px 8px 8px 20px;
    font-family: var(--font-display);
    font-size: 20px;
    font-weight: var(--wt-medium);
    line-height: 24px;
    letter-spacing: -0.01em;
    color: var(--on-navy);
    text-decoration: none;
  }
  .drawer__rule {
    width: 100%;
    height: 2px;
    margin-top: 6px;
    background: transparent;
  }
  .drawer__link.is-current .drawer__rule { background: var(--brand-turq); }
  /* :active, not :hover. A phone has no cursor, and a hover state on a touch
     device sticks after the tap. 140ms so the tap feels acknowledged. */
  .drawer__link:active,
  .drawer__sub-link:active { background: #FFFFFF17; }

  .drawer__row--group > .drawer__head > .drawer__link { flex: 1 1 auto; }
  .drawer__toggle {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    margin-right: 8px;
    border-radius: var(--r-input);
    color: var(--on-navy-mute);
    transition: color var(--dur-hover) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
  }
  .drawer__toggle:hover { color: var(--on-navy); }
  .drawer__toggle:active { background: #FFFFFF17; }
  .drawer__toggle svg { transition: transform var(--dur-drawer) var(--ease-out); }
  .drawer__toggle[aria-expanded="true"] { color: var(--brand-turq); }
  .drawer__toggle[aria-expanded="true"] svg { transform: rotate(180deg); }

  /* In place, not a second screen. The children push the rows below them down,
     which is what the Industries-expanded artboard shows. */
  .drawer__sub { background: #00000024; }
  .drawer__sub[hidden] { display: none; }
  .drawer__sub-link {
    display: flex;
    align-items: center;
    min-height: 50px;
    padding: 8px 20px 8px 36px;
    font-family: var(--font-body);
    font-size: 17px;
    line-height: 22px;
    color: var(--on-navy);
    text-decoration: none;
  }

  .drawer__cta {
    display: flex;
    flex-direction: column;
    gap: var(--s-5);
    padding: 32px 20px 28px;
  }
  .drawer__contact {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: 17px;
    line-height: 22px;
    color: var(--on-navy);
    text-decoration: none;
    transition: color var(--dur-hover) var(--ease-out);
  }
  .drawer__contact:hover { color: var(--brand-turq); }
  .drawer__legal {
    display: flex;
    gap: var(--s-6);
    align-items: center;
    margin-top: auto;
    min-height: 56px;
    padding-inline: 20px;
    border-top: 1px solid #BFDCEA38;
  }
  .drawer__legal a {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: 14px;
    line-height: 18px;
    color: var(--on-navy-mute);   /* 6.69:1 on navy */
    text-decoration: none;
    transition: color var(--dur-hover) var(--ease-out);
  }
  .drawer__legal a:hover { color: var(--on-navy); }
  body.is-locked { overflow: hidden; }
}

/* ===== FOOTER ============================================================ */

.site-footer { position: relative; background: var(--surface); }

/* isolation, so the lifted card's --z-lifted is a claim against this row and
   not against the document. .closer is position: relative with no z-index of
   its own, which is not a stacking context. It briefly looks like one while
   the reveal is mid-flight, because .reveal carries opacity, a transform and
   will-change, and then stops being one the moment .is-in lands and those come
   off. A containment that exists only during an animation is worse than none,
   because it hides the problem exactly when anyone would look for it. */
.closer { position: relative; isolation: isolate; }
.closer__inner {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: var(--s-20);
  /* 80 from the page edge on the artboard, which is 16px inside the content
     column at 1440. Held against the column so it stays 16px inside it at
     every width instead of drifting to the page edge above 1440. */
  padding: 56px var(--pad-column-in) 64px;
}
.closer__eyebrow { font-size: var(--text-caption); line-height: var(--lh-caption); color: var(--ink-muted); }
.closer__headline {
  font-family: var(--font-display);
  font-size: var(--text-closer);
  font-weight: var(--wt-medium);
  line-height: var(--lh-closer);
  letter-spacing: var(--track-band);
  color: var(--brand-navy);
}

/* The enquiry card, lifted 64px across the seam. Two variants, and the lift is
   the only difference: nine pages carry a section above the footer for the
   card to straddle, and Pages 10, 11 and 12 do not, so there the card sits in
   normal flow. */
.enquiry-card {
  /* 420 is the artboard's measurement against its 1312 column, so it is held
     as that fraction of the column rather than as a pixel. Frozen at 420 it
     was the same defect the Home hero panels had, on all thirteen pages at
     once: the card kept its width while the closer row grew, so every pixel
     the column gained went into the gap between the headline and the card.

     Written against --content and not as a percentage because this card has
     two containing blocks. In the flow variant it is a flex item and a
     percentage would resolve against .closer__inner's 1280 content box; in
     the lifted variant it is absolutely positioned against .closer, which is
     full-bleed, so the same percentage would resolve against the viewport and
     keep growing past 1440 while everything else on the page had stopped.
     --content is the column itself, so both variants land on one number.

     The floor stops it dropping under 300 in the last stretch above the 899
     breakpoint, where the column is narrow but the row is still horizontal. */
  width: max(300px, calc(var(--content) * 0.32012));   /* 420 of the 1312 column */
  flex-shrink: 0;
  padding: 36px;
  background: var(--page);
  border-top: 2px solid var(--brand-turq);
}
/* The lifted card needs an edge of its own, and the artboard does not give it
   one. Straddling a navy seam, the ground supplies the edge and the card reads
   fine. Home is the one lifted page with no navy band above the footer, so
   there the white card crossed onto white and only its 2px turquoise rule was
   visible: it read as a stray line with text under it, over the top of
   whatever the section above happened to be saying. A hairline and a soft lift
   make it a card on any ground. Decorative only, no text sits on either, so
   neither touches a contrast ratio.

   This comment used to name Case studies as the second such page. It is not
   one: it closes on a .band like the other nine. The three pages that genuinely
   have no band, Contact and the two legal pages, are also the three that are
   not lifted, so the case the comment describes arises on Home alone. */
.closer--lifted .enquiry-card {
  border: 1px solid var(--rule);
  border-top: 2px solid var(--brand-turq);
  box-shadow: 0 18px 40px -24px #00477B4A;
}
.closer--lifted .enquiry-card {
  position: absolute;
  /* The artboard's right:80 is 16px inside the content column at 1440. Held
     against the column, so the card keeps its place in the composition at
     every width rather than clinging to the viewport edge. */
  right: var(--pad-column-in);
  top: -64px;
  /* --z-lifted is the token minted for exactly this element: content that
     straddles a section seam. Without it the card relied on source order
     alone to sit above the section it overlaps, which is the kind of accident
     that survives until a sibling gains a z-index and then silently breaks. */
  z-index: var(--z-lifted);
}
.enquiry-card__link {
  display: inline-block;
  min-height: 44px;
  box-sizing: content-box;
  padding-top: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--brand-navy);
  font-size: var(--text-small);
  line-height: var(--lh-small);
  color: var(--brand-navy);      /* 9.60:1 on page */
  text-decoration: none;
  transition: color var(--dur-hover) var(--ease-out);
}
.enquiry-card__link:hover { color: var(--brand-blue-cta); }
.enquiry-card__note {
  padding-top: 8px;
  font-size: var(--text-caption);
  line-height: var(--lh-caption);
  color: var(--ink-muted);
}
/* The 375 artboard closes this card with a full-width button; the 1440 one
   does not, so it is mobile-only rather than added to both. */
.enquiry-card .enquiry-card__btn { display: none; }

.directory { display: flex; border-top: 1px solid #00477B24; }

/* The navy panel runs off the left page edge, so its LEFT PADDING is what puts
   its type on the content column, and the panel has to grow by the same amount
   or the type is pushed out the far side. width and padding move together:

     width         = 320px + --pad-column-in
     padding-left  =         --pad-column-in

   so the readable column inside it is 320 - 40 = 280px at every viewport, the
   measured value, and the type starts 16px inside the content column exactly
   as it does at 1440. Setting the padding alone was a real bug: percentage
   padding resolves against the flex CONTAINER, so at 2560 a 640px left padding
   landed inside a 400px box and took three footer links to zero width. The
   audit caught it at 2560 and nowhere else. */
.plinth {
  position: relative;
  width: calc(320px + var(--pad-column-in));
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 26px;
  padding: 56px 40px 102px var(--pad-column-in);
  background: var(--brand-navy);
  overflow: clip;
}
.plinth__brand { display: flex; flex-direction: column; gap: var(--s-3); }
.plinth__tag {
  max-width: 280px;
  font-size: var(--text-small);
  line-height: 23px;
  color: var(--on-navy-mute);
}
.plinth__contact {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding-top: 22px;
  border-top: 1px solid #FFFFFF2E;
}
.plinth__contact a {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-size: 19px;
  line-height: 28px;
  color: var(--on-navy);
  text-decoration: none;
  transition: color var(--dur-hover) var(--ease-out);
}
.plinth__contact a:hover { color: var(--brand-turq); }
.plinth__where { display: flex; flex-direction: column; gap: var(--s-3); max-width: 280px; }
.plinth__where p { font-size: var(--text-small); line-height: 23px; color: var(--on-navy-mute); }

/* Right padding on the column for the same reason the plinth's left is: the
   lanes are the last thing on the page and their right edge is the one a
   reader lines up against. This one is safe as a bare padding because the
   lanes flex-grow rather than carrying a fixed width. */
.directory__lanes { display: flex; flex-grow: 1; gap: var(--s-6); padding: 56px var(--pad-column-in) 88px 72px; }
/* The measured lane widths become flex bases so four fixed columns cannot
   push the footer wider than the viewport below 1440. */
.directory__lane { flex: 1 1 var(--lane); min-width: 0; display: flex; flex-direction: column; gap: 22px; }
.directory__heading {
  font-family: var(--font-display);
  font-size: var(--text-small);
  font-weight: var(--wt-medium);
  line-height: var(--lh-small);
  color: var(--ink-muted);       /* 5.06:1 on surface */
}
.directory__links { display: flex; flex-direction: column; gap: var(--s-2); }
.directory__links a {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--text-small);
  line-height: var(--lh-small);
  color: var(--ink);             /* 13.46:1 on surface */
  text-decoration: none;
  transition: color var(--dur-hover) var(--ease-out);
}
.directory__links a:hover { color: var(--brand-blue-cta); text-decoration: underline; }

.legal {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-8);
  padding: 22px var(--pad-column-in);
  background: var(--brand-navy);
}
.legal a { display: inline-flex; align-items: center; min-height: 44px; }
.legal p, .legal a {
  font-size: var(--text-caption);
  line-height: var(--lh-caption);
  color: var(--on-navy-mute);    /* 6.69:1 on navy */
  text-decoration: none;
}
.legal__links { display: flex; gap: var(--s-8); }
.legal a:hover { color: var(--on-navy); text-decoration: underline; }

@media (max-width: 899px) {
  .closer__inner { flex-direction: column; gap: 0; padding: 44px var(--gutter-mob) 0; }
  .enquiry-card {
    width: auto;
    margin: 24px calc(50% - 50vw) 0;
    padding: 24px var(--gutter-mob) 26px;
  }
  .closer--lifted .enquiry-card { position: static; margin-top: 24px; }
  .enquiry-card__link { font-size: 22px; line-height: 28px; letter-spacing: -0.02em;
    font-family: var(--font-display); font-weight: var(--wt-medium); border: 0; padding: 0; }
  .enquiry-card__note { font-size: var(--text-small); line-height: 23px; }
  .enquiry-card .enquiry-card__btn {
    display: flex;
    justify-content: center;
    width: 100%;
    height: 56px;
    margin-top: 6px;
    font-size: 17px;
    line-height: 22px;
  }

  .directory { flex-direction: column; border-top: 0; }
  .plinth { width: auto; order: 2; padding: 28px var(--gutter-mob) 32px; }
  /* Two columns, not accordions. Nineteen links, and every one of them stays a
     single tap away. An accordion here would hide eighteen of them behind a
     tap that tells you nothing about what is inside. */
  .directory__lanes {
    order: 1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 32px var(--s-5);
    padding: 40px var(--gutter-mob) 0;
    background: var(--brand-navy);
  }
  .directory__lane { width: auto; gap: var(--s-3); }
  .directory__heading {
    font-family: var(--font-body);
    font-size: var(--text-caption);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--on-navy-mute);
  }
  .directory__links { gap: 0; }
  .directory__links a {
    display: flex;
    align-items: center;
    min-height: 44px;             /* the tap target, measured on the artboard */
    line-height: 21px;
    color: var(--on-navy);
  }
  .directory__links a:hover { color: var(--brand-turq); }
  .plinth__ring { display: none; }

  .legal {
    order: 3;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
    padding: 20px var(--gutter-mob) 28px;
    border-top: 1px solid #BFDCEA4D;
  }
  .legal__links { gap: var(--s-6); }
  .legal a { display: flex; align-items: center; min-height: 44px; font-size: var(--text-small); }
}

/* ===== THE ENQUIRY CARD, HEAD SESSION 024 ===============================
   Client: "For the footer on all pages in hydro2050 I need the 'Send an
   enquiry' to be more pronouced visually. So users feel inticed to click it."

   What it was: a 16px underlined text link, a caption, and a primary button
   that the stylesheet hid on every screen wider than 899px. The 375 artboard
   draws that button; the 1440 one does not, and the earlier decision to follow
   the artboard exactly left desktop with no button at all.

   What changed, and deliberately nothing else. No new colour, no gradient, no
   badge, no icon row, no second accent: every part of this was already in the
   system. The card gets a title at a size the ramp already carries, the button
   the mobile artboard already draws, and the same whole-card click target the
   industry tiles use, so the two behave alike. Weight stays at 500. ====== */

.enquiry-card__title {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: var(--wt-medium);
  line-height: 32px;
  letter-spacing: -0.02em;
  color: var(--brand-navy);
}
.enquiry-card__note { padding-top: 10px; }

/* Shown at every width now, not mobile-only. Full card width, because a
   button that fills its card is the thing being asked for and a small pill
   floating in a 420px card is what it was competing with. */
.enquiry-card .enquiry-card__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  height: 54px;
  margin-top: 20px;
  font-size: 16px;
  line-height: 22px;
}
.enquiry-card__btn svg { transition: transform var(--dur-hover) var(--ease-out); }
.enquiry-card:hover .enquiry-card__btn svg { transform: translateX(4px); }

/* The whole card is the click target, same stretched-link pattern as the
   industry tiles: one real <a>, no overlay anchor, no handler on the div, so
   keyboard, middle click and copy link address all keep working. The card is
   already a containing block in both variants, static in flow and absolute
   when lifted. */
.enquiry-card { position: relative; cursor: pointer; }
.enquiry-card__btn::after { content: ''; position: absolute; inset: 0; z-index: 1; }
/* The title and note deliberately do NOT sit above the stretched hit area.
   Lifting them made the top third of the card unclickable, which is worse than
   losing text selection on a two line caption inside a call to action. */
.enquiry-card__btn:focus-visible::after {
  outline: 3px solid var(--brand-turq);
  outline-offset: 4px;
}

/* The lift is 3px, not the 6px the tiles use. This card is already raised off
   the seam by 64px and carries a shadow at rest, so it needs less travel to
   register than a tile sitting flat on the page does. */
.enquiry-card {
  transition: transform var(--dur-hover) var(--ease-out),
              box-shadow var(--dur-hover) var(--ease-out);
}
.closer--lifted .enquiry-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 26px 52px -24px #00477B66;
}
.closer:not(.closer--lifted) .enquiry-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 18px 40px -24px #00477B4A;
}

@media (max-width: 899px) {
  .enquiry-card__title { font-size: 22px; line-height: 28px; }
  .enquiry-card .enquiry-card__btn {
    height: 56px;
    margin-top: 16px;
    font-size: 17px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .enquiry-card,
  .enquiry-card__btn svg { transition: none; }
  .enquiry-card:hover { transform: none; }
}
