/* ==========================================================================
   1. DESIGN TOKENS
   Pulled straight from the Figma file so every value has a single source of
   truth. Change a token here and it cascades through the whole page.
   ========================================================================== */
:root {
  /* ---- Colour ---------------------------------------------------------- */
  --c-navy:          #071e42;   /* hero + primary brand navy               */
  --c-navy-mid:      #02142c;   /* about statement band                    */
  --c-navy-deep:     #070b17;   /* footer background                       */
  --c-lavender:      #cad0e3;   /* light information band                  */
  --c-card:          #19202f;   /* solution card surface                   */
  --c-black:         #000000;
  --c-white:         #ffffff;
  --c-ink:           #0d0d0d;   /* page background                         */
  --c-surface-veil:  rgba(217, 217, 217, 0.1);
  --c-hairline:      rgba(0, 0, 0, 0.25);
  --c-white-70:      rgba(255, 255, 255, 0.7);
  --c-white-50:      rgba(255, 255, 255, 0.5);
  --c-white-15:      rgba(255, 255, 255, 0.15);
  /* Mirror of the white scale, for the light surfaces (Solutions, Careers):
     secondary text, icon rest state and card edges on white.              */
  --c-black-70:      rgba(0, 0, 0, 0.7);
  --c-black-12:      rgba(0, 0, 0, 0.12);

  /* ---- Typography ------------------------------------------------------ */
  --font-body:  'Figtree', system-ui, -apple-system, sans-serif;
  --font-ui:    'Inter', system-ui, -apple-system, sans-serif;

  /* Fluid type: min value on a 360px viewport, max on 1440px.             */
  --fs-display: clamp(2.5rem, 1.15rem + 6.0vw, 6rem);       /* 40 -> 96px  */
  --fs-h1:      clamp(1.875rem, 1.1rem + 3.4vw, 3rem);      /* 30 -> 48px  */
  --fs-h2:      clamp(1.75rem, 1.25rem + 2.2vw, 2.5rem);    /* 28 -> 40px  */
  --fs-h3:      clamp(1.5rem, 1.05rem + 2.0vw, 2.25rem);    /* 24 -> 36px  */
  --fs-h4:      clamp(1.375rem, 1.15rem + 1.0vw, 1.75rem);  /* 22 -> 28px  */
  --fs-step:    clamp(1.5rem, 1.1rem + 1.8vw, 2rem);        /* 24 -> 32px  */
  --fs-lead:    clamp(1.0625rem, 0.95rem + 0.5vw, 1.5rem);  /* 17 -> 24px  */
  --fs-body:    clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem);/* 15 -> 17px  */
  --fs-small:   0.875rem;                                    /* 14px       */
  --fs-xs:      0.8125rem;                                   /* 13px       */
  --fs-label:   1.25rem;                                     /* 20px       */
  --fs-eyebrow: 0.9375rem;                                   /* 15px       */

  /* ---- Spacing scale --------------------------------------------------- */
  --sp-2xs: 0.5rem;
  --sp-xs:  0.75rem;
  --sp-sm:  1rem;
  --sp-md:  1.5rem;
  --sp-lg:  2rem;
  --sp-xl:  3rem;
  --sp-2xl: clamp(3rem, 1.5rem + 6vw, 5.625rem);  /* section rhythm 48->90 */

  /* Page gutter grows with the viewport, capped at the Figma 36px inset.  */
  --gutter: clamp(1.25rem, 0.7rem + 2.5vw, 2.25rem);
  --maxw:   1440px;

  /* Height the floating header occupies, so any hero underneath can clear
     it by exactly the same amount on every page.                          */
  --header-h: clamp(5rem, 3rem + 8vw, 8.5rem);

  /* Single gap between a section heading block and the content below it.  */
  --head-gap: clamp(2rem, 1rem + 4vw, 3.5rem);

  /* ---- Radii ----------------------------------------------------------- */
  --radius:    10px;
  --radius-sm: 4px;

  /* ---- MOTION  (see section 19 — everything animated reads from here) --- */
  --dur-fast:  160ms;
  --dur-base:  280ms;
  --dur-slow:  600ms;
  --dur-xslow: 900ms;
  --ease-out:     cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-in-out:  cubic-bezier(0.65, 0.05, 0.36, 1);
  --ease-spring:  cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Reusable transition shorthands so new animations stay consistent.     */
  --t-colour:    color var(--dur-fast) var(--ease-out),
                 background-color var(--dur-fast) var(--ease-out),
                 border-color var(--dur-fast) var(--ease-out);
  --t-transform: transform var(--dur-base) var(--ease-out);
  --t-all:       all var(--dur-base) var(--ease-out);

  /* ---- Asset paths — localised into assets/img/ on 2026-08-17. These were
     hotlinked to figma.com/api/mcp/asset/... which is a Figma session
     endpoint, not a CDN; the originals totalled ~51MB. Do not point these
     back at a remote URL. ------------------------------------------------ */
  --asset-hero-bg:    url("img/hero-bg.jpg");
  --asset-about-bg:   url("img/about-bg.jpg");
  --asset-careers-hero: url("img/careers-hero-tower.jpg");
  --asset-contact-hero: url("img/contact-hero-forest.jpg");

  /* Logo sprite sheets — see ASSET REGISTRY note above. */
  --sprite-1: url("img/sprite-1.png");
  --sprite-2: url("img/sprite-2.png");
  --sprite-3: url("img/sprite-3.png");
  --sprite-4: url("img/sprite-4.png");
  --sprite-5: url("img/sprite-5.png");
  --sprite-6: url("img/sprite-6.png");
}


/* ==========================================================================
   2. RESET / BASE
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Keeps anchor targets clear of the overlaying header. */
  scroll-padding-top: calc(var(--header-h) + var(--sp-sm));
}

body {
  margin: 0;
  background-color: var(--c-ink);
  color: var(--c-white);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }

a {
  color: inherit;
  text-decoration: none;
  transition: var(--t-colour);
}

button { font: inherit; }

/* Keyboard focus ring — visible on every interactive element. */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}


/* ==========================================================================
   3. LAYOUT PRIMITIVES
   Small composable helpers. Sections stay dumb; the container owns width.
   ========================================================================== */
.container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--sp-2xl);
  /* `clip` (not `hidden`) so the off-screen start position of a left/right
     scroll-reveal never creates a horizontal scrollbar, while still allowing
     position: sticky inside a section later. */
  overflow-x: clip;
}

/* Light surfaces flip the text colour wholesale. */
.section--light { background-color: var(--c-lavender); color: var(--c-navy); }
.section--navy  { background-color: var(--c-navy);     color: var(--c-white); }

/* Small uppercase-ish eyebrow used above every major heading in the design. */
.eyebrow {
  font-size: var(--fs-eyebrow);
  font-weight: 300;
  line-height: 1.4;
}

.section-label {
  font-size: var(--fs-label);
  font-weight: 500;
}

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


/* ==========================================================================
   4. COMPONENT — BUTTON
   .btn--outline-dark   1px black outline, for light surfaces
   .btn--outline-light  1px white outline, for dark surfaces
   ========================================================================== */
.btn {
  --btn-ink: currentColor;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 43px;
  padding: var(--sp-2xs) var(--sp-md);
  border: 1px solid var(--btn-ink);
  background: transparent;
  color: var(--btn-ink);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 400;
  text-align: center;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  /* Explicit list — keeps a future ::before wipe animation cheap to add. */
  transition: color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

.btn--outline-dark  { --btn-ink: var(--c-black); }
.btn--outline-light { --btn-ink: var(--c-white); }

.btn:hover  { background-color: var(--btn-ink); color: var(--c-white); }
.btn--outline-light:hover { color: var(--c-navy); }
.btn:active { transform: translateY(1px); }

.btn { width: 100%; }
@media (min-width: 480px) {
  .btn { width: auto; min-width: 216px; }
}


/* ==========================================================================
   5. COMPONENT — TEXT LINK WITH CHEVRON
   ========================================================================== */
.link-more {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2xs);
  width: fit-content;
  font-weight: 300;
  font-size: 1rem;
}

.link-more__icon {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  transition: var(--t-transform);
}

.link-more:hover .link-more__icon { transform: translateX(4px); }


/* ==========================================================================
   6. BLOCK — SITE HEADER / NAVIGATION
   Overlays the hero on desktop. On mobile the links collapse into a panel
   toggled by the hamburger button.
   ========================================================================== */
.site-header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 100;
  border-block-end: 1px solid transparent;
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}

/* Scrolled state — main.js (section i) adds .is-stuck past 24px. Transparent
   at the top so each hero reads full-bleed, opaque navy once the page moves,
   or the nav sits unreadable over photography. The open mobile drawer gets
   the same treatment: its panel hangs off a bar that must not be see-through.
   The lightbox is a <dialog> opened with showModal(), so it renders in the
   browser's top layer and is unaffected by this z-index. */
.site-header.is-stuck,
.site-header.is-open {
  background-color: rgba(7, 30, 66, 0.92);   /* --c-navy at 92% */
  border-block-end-color: var(--c-white-15);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding-block: clamp(1rem, 0.4rem + 2.2vw, 2.4375rem);
  transition: padding-block var(--dur-base) var(--ease-out);
}

/* Tighten the bar once it is a persistent fixture rather than part of the
   hero, so it gives back most of the vertical space it takes.

   Excludes the solid variant deliberately. That one is `sticky`, so it stays
   in normal flow and its height is page layout — shrinking it at the 24px
   threshold would yank every section below it up by ~35px mid-scroll. The
   fixed variant is out of flow, so there it costs nothing. */
.site-header.is-stuck:not(.site-header--solid) .site-header__inner {
  padding-block: clamp(0.625rem, 0.35rem + 1vw, 1.0625rem);
}

@media (prefers-reduced-motion: reduce) {
  .site-header,
  .site-header__inner { transition: none; }
}

.site-header__logo {
  width: clamp(104px, 8vw + 4rem, 153px);
  height: auto;
  transition: opacity var(--dur-base) var(--ease-out);
}
.site-header__logo:hover { opacity: 0.8; }

/* ---- Hamburger ---------------------------------------------------------- */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(40px, 3.9vw, 56px);
  height: clamp(40px, 3.9vw, 56px);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--c-white);
  cursor: pointer;
  z-index: 2;
}

.nav-toggle__bars { display: flex; flex-direction: column; gap: 6px; }

.nav-toggle__bar {
  display: block;
  width: 24px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform var(--dur-base) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}

/* Hamburger -> X */
.site-header.is-open .nav-toggle__bar:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.site-header.is-open .nav-toggle__bar:nth-child(2) { opacity: 0; }
.site-header.is-open .nav-toggle__bar:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* ---- Nav panel ---------------------------------------------------------- */
.site-nav {
  position: absolute;
  inset-inline: var(--gutter);
  top: calc(100% - var(--sp-sm));
  background: var(--c-black);
  border: 0.3px solid var(--c-white-15);
  /* Collapsed state. Animating max-height keeps this dependency-free. */
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transition: max-height var(--dur-base) var(--ease-in-out),
              opacity var(--dur-fast) var(--ease-out),
              visibility 0s linear var(--dur-base);
}

.site-header.is-open .site-nav {
  max-height: 26rem;
  opacity: 1;
  visibility: visible;
  transition: max-height var(--dur-base) var(--ease-in-out),
              opacity var(--dur-base) var(--ease-out),
              visibility 0s;
}

.site-nav__list { display: flex; flex-direction: column; padding: var(--sp-sm); }

.site-nav__link {
  display: block;
  padding: var(--sp-xs) var(--sp-sm);
  font-family: var(--font-ui);
  font-size: 1rem;
  letter-spacing: 0.02em;
  color: var(--c-white);
}
.site-nav__link:hover,
.site-nav__link[aria-current="page"] { font-weight: 500; }

@media (min-width: 900px) {
  .site-header__inner { position: relative; }
  .nav-toggle { display: none; }

  .site-nav {
    position: static;
    max-height: none;
    opacity: 1;
    visibility: visible;
    overflow: visible;
    background: transparent;
    border: 0;
    inset-inline: auto;
  }

  .site-nav__list {
    flex-direction: row;
    align-items: center;
    gap: clamp(1rem, 2.8vw, 2.5rem);
    padding: 0;
  }

  .site-nav__link { position: relative; padding: var(--sp-2xs) 0; }

  /* Underline sweep — the hook most hover animations will build on. */
  .site-nav__link::after {
    content: "";
    position: absolute;
    left: 0; bottom: 0;
    width: 100%;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right;
    transition: transform var(--dur-base) var(--ease-out);
  }
  .site-nav__link:hover::after,
  .site-nav__link[aria-current="page"]::after {
    transform: scaleX(1);
    transform-origin: left;
  }
}


/* ==========================================================================
   7. BLOCK — HERO
   ========================================================================== */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  isolation: isolate;
  /* Full viewport so the info band below sits entirely under the fold. */
  min-height: 100vh;
  min-height: 100svh;
  padding-block: calc(var(--header-h) + var(--sp-xl)) var(--sp-2xl);
  background-color: var(--c-navy);
  overflow: hidden;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--asset-hero-bg);
  background-size: cover;
  background-position: center 30%;
  opacity: 0.32;
  /* Pre-set so a parallax / slow-zoom is a one-line change later. */
  will-change: transform;
  transition: transform var(--dur-xslow) var(--ease-out);
}

.hero__inner {
  display: flex;
  flex-direction: column;
  gap: clamp(1.75rem, 1rem + 3vw, 3.75rem);
}

.hero__title {
  /* 1030px is the exact text-frame width in Figma — lets the line sit
     unbroken at 1440 and wrap naturally below that. */
  max-width: 1030px;
  font-size: var(--fs-display);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.03em;
}

.hero__subtitle {
  max-width: 34ch;
  font-size: var(--fs-lead);
  line-height: 1.38;
}


/* ==========================================================================
   8. BLOCK — INFO BAND  (intro + certifications, light lavender)
   ========================================================================== */
.info-band__rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xl);
}

.info-row { display: grid; gap: var(--sp-md); }

.info-row__label {
  font-size: var(--fs-label);
  font-weight: 400;
  line-height: 1.3;
}

.info-row__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-lg);
  max-width: 64ch;
}

.info-row__text {
  font-size: var(--fs-lead);
  font-weight: 300;
  line-height: 1.42;
}

@media (min-width: 768px) {
  .info-row {
    grid-template-columns: minmax(9rem, 190px) minmax(0, 1fr);
    column-gap: clamp(2rem, 4.5vw, 4.0625rem);
  }
}


/* ==========================================================================
   9. BLOCK — SOLUTIONS OVERVIEW  (two media/text cards)
   ========================================================================== */
.solutions-overview { background-color: var(--c-surface-veil); }

.solutions-overview__intro {
  display: grid;
  gap: var(--sp-md);
  margin-block-end: clamp(2.5rem, 1.5rem + 4vw, 4.375rem);
}

.solutions-overview__heading {
  max-width: 26ch;
  font-size: var(--fs-h2);
  font-weight: 300;
  line-height: 1.15;
}

.solutions-overview__lead {
  max-width: 56ch;
  font-size: 1rem;
  font-weight: 300;
  line-height: 1.5;
}

@media (min-width: 900px) {
  .solutions-overview__intro {
    grid-template-columns: minmax(0, 1fr) minmax(0, 523px);
    align-items: start;
    column-gap: var(--sp-xl);
  }
}

.solutions-overview__list { display: flex; flex-direction: column; gap: var(--sp-sm); }

.solution-card {
  display: grid;
  gap: var(--sp-sm);
  transition: var(--t-transform);
}

.solution-card__media {
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 426 / 227;
  /* Holds the shape while the photo loads (or if an asset URL expired). */
  background-color: var(--c-card);
}

.solution-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.solution-card:hover .solution-card__img { transform: scale(1.04); }

.solution-card__body {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--sp-xs);
  min-height: 227px;
  padding: clamp(1.5rem, 1rem + 2vw, 2.375rem) clamp(1.5rem, 1rem + 2.4vw, 2.8125rem);
  border-radius: var(--radius);
  background-color: var(--c-navy);
  /* A hairline all the way round, not the 4px accent slab this carried until
     2026-08-24 — that read as a stray line down one side of the panel rather
     than as an edge. --c-white-15 is the same hairline the role and office
     cards use, so all the cards on the site share one edge weight. */
  border: 1px solid var(--c-white-15);
  overflow: hidden;
}

.solution-card__title { font-size: var(--fs-h4); font-weight: 300; }

.solution-card__text {
  max-width: 62ch;
  font-weight: 300;
  line-height: 1.5;
}

@media (min-width: 900px) {
  .solution-card {
    grid-template-columns: minmax(0, 426fr) minmax(0, 742fr);
    align-items: stretch;
  }
  .solution-card__media { aspect-ratio: auto; }
}


/* ==========================================================================
   10. BLOCK — ABOUT HERO
   --------------------------------------------------------------------------
   One continuous aerial photograph sits behind BOTH the centred statement and
   the Story / Mission panel beneath it. The panel is see-through, so the
   photograph reads as a single uninterrupted image down the whole block.

   ONE knob controls how see-through the old "white box" is:

     --about-panel-bg
        rgba(255,255,255,0.85)  ← SHIPPED (2026-08-20, Janice). Composites to
                                  rgb(214,215,212) over the aerial; the navy
                                  copy measures 11.41:1, well past WCAG AAA.
        rgba(255,255,255,0.80)  ← 10.40:1. Photo still clearly readable.
        rgba(255,255,255,0.70)  ← 6.8:1 era value. Closest to the Figma box.
        rgba(255,255,255,0.55)  ← original build. 6.8:1, AA but not AAA.
        transparent             ← literal zero. Measured 1.4:1 against the
                                  aerial, below the 4.5:1 WCAG AA floor, so
                                  the body copy becomes unreadable. If you
                                  want it anyway, also set --about-panel-ink
                                  to var(--c-white) and expect the light road
                                  running through the frame to still fight it.

     --about-panel-ink   panel text colour, if you flip the type instead.
   ========================================================================== */
.about-hero {
  --about-panel-bg:  rgba(255, 255, 255, 0.85);
  --about-panel-ink: var(--c-navy);

  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--c-navy-mid);   /* shows only until the photo loads */
}

.about-hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: var(--asset-about-bg);
  background-size: cover;
  background-position: center top;
  /* Pre-set so a parallax / slow-zoom is a one-line change later. */
  will-change: transform;
  transition: transform var(--dur-xslow) var(--ease-out);
}

/* ---- Centred statement -------------------------------------------------- */
.about-hero__statement {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 100vh;
  min-height: 100svh;
  padding-block: calc(var(--header-h) + var(--sp-lg)) var(--sp-2xl);
}

/* Scrim confined to the statement only — keeps the white line legible
   without tinting the panel below it. */
.about-hero__statement::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    180deg,
    rgba(2, 20, 44, 0.72) 0%,
    rgba(2, 20, 44, 0.42) 55%,
    rgba(2, 20, 44, 0.12) 100%
  );
  pointer-events: none;
}

.statement__text {
  margin-inline: auto;
  max-width: 1097px;
  font-size: var(--fs-h2);
  font-weight: 300;
  line-height: 1.25;
  text-align: center;
  color: var(--c-white);
}

/* ---- Story / Mission panel ---------------------------------------------- */
.about-hero__panel {
  position: relative;
  padding-block: var(--sp-2xl);
  background-color: var(--about-panel-bg);
  color: var(--about-panel-ink);
  border-start-start-radius: var(--radius);
  border-start-end-radius: var(--radius);
}

.about-hero__entries {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xl);
}

.about-entry {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xs);
  max-width: 118ch;
}

.about-entry__heading {
  margin-block-end: var(--sp-2xs);
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.2;
}

.about-entry__prose {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  font-weight: 300;
  line-height: 1.55;
}


/* ==========================================================================
   11. BLOCK — ABOUT: FACTS, CLIENTS, CERTIFICATIONS
   Three stacked label + heading + prose groups on the lavender surface.
   ========================================================================== */
.about__groups {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xl);
}

.about-group { display: grid; gap: var(--sp-sm); }

.about-group__heading {
  max-width: 24ch;
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.2;
}

.about-group__prose {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  max-width: 100ch;
  font-weight: 300;
  line-height: 1.55;
}

.about-group__actions { margin-block-start: var(--sp-sm); }


/* ==========================================================================
   12. BLOCK — LOGO STRIP  (clients / certifications)  —  INFINITE MARQUEE
   --------------------------------------------------------------------------
   Structure:  .logo-strip > .logo-strip__viewport > .logo-strip__track
                                                       > .logo-strip__group (xN)

   The track is a single flex row holding N identical groups. JS (assets/main.js)
   clones the authored group until the row is at least twice the viewport
   width, then sets --marquee-shift to the width of exactly one half. The
   keyframe slides the track left by that distance and loops — because the
   second half is pixel-identical to the first, the reset is invisible.

   Speed is constant regardless of how many logos there are: JS derives the
   duration from distance ÷ --marquee-speed (px per second).

   Each logo is currently a crop out of a shared Figma sprite sheet, so every
   item carries custom properties describing its window:
       --sprite  which sheet             --img-w / --img-h  image size (%)
       --box-w / --box-h  the visible window   --img-x / --img-y  offsets (%)
   Once individual logos are exported these can all be deleted and replaced
   with a plain `height: 100%; width: auto` image.
   ========================================================================== */
.logo-strip {
  --marquee-speed: 60;      /* pixels travelled per second — tune freely */
  --marquee-gap: var(--sp-xl);
  background-color: var(--c-navy);
}

/* Scrollable by default so every logo stays reachable when the marquee is
   NOT running (no JS, or prefers-reduced-motion). JS adds .is-animated once
   the loop is live, at which point the row is clipped instead. */
.logo-strip__viewport {
  overflow-x: auto;
  padding-block: var(--sp-2xs);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.logo-strip__viewport::-webkit-scrollbar { display: none; }

.logo-strip.is-animated .logo-strip__viewport { overflow: hidden; }

.logo-strip__track {
  display: flex;
  align-items: center;
  gap: var(--marquee-gap);
  width: max-content;       /* let the row grow past the viewport */
  will-change: transform;
}

/* The animation only starts once JS has cloned enough groups and measured
   the shift distance — otherwise the strip would slide into empty space. */
.logo-strip__track.is-running {
  animation: marquee var(--marquee-duration, 30s) linear infinite;
}

@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(var(--marquee-shift, 0px) * -1), 0, 0); }
}

/* Courtesy pause so a logo can actually be read. */
.logo-strip:hover .logo-strip__track,
.logo-strip:focus-within .logo-strip__track { animation-play-state: paused; }

.logo-strip__group {
  display: flex;
  align-items: center;
  gap: var(--marquee-gap);
  flex: 0 0 auto;
}

.logo-strip__item {
  position: relative;
  flex: 0 0 auto;
  width: var(--box-w);
  height: var(--box-h);
  overflow: hidden;
  opacity: 0.9;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.logo-strip__item:hover { opacity: 1; transform: translateY(-2px); }

.logo-strip__item::before {
  content: "";
  position: absolute;
  left: var(--img-x, 0);
  top: var(--img-y, 0);
  width: var(--img-w, 100%);
  height: var(--img-h, 100%);
  background-image: var(--sprite);
  background-size: 100% 100%;
  background-repeat: no-repeat;
}


/* ==========================================================================
   13. BLOCK — SOLUTIONS DETAIL: TABS
   Two service lines share one page; only the panel below the tab list swaps.
   ========================================================================== */
.solutions-detail {
  background-color: var(--c-white);
  color: var(--c-black);
  /* Header is in normal flow on this page, so the usual --sp-2xl top
     padding would read as a large empty band. */
  padding-block-start: var(--sp-xl);
}

.solutions-detail__title {
  max-width: none;
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.2;
}

.solutions-detail__intro {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  margin-block-start: var(--sp-md);
  max-width: none;
  font-weight: 300;
  line-height: 1.55;
}

.tabs { margin-block-start: var(--sp-xl); }

.tabs__list {
  display: flex;
  gap: var(--sp-xs);
  border-bottom: 1px solid var(--c-hairline);
  /* Scroll rather than wrap: a wrapped tab row breaks the underline rule. */
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs__list::-webkit-scrollbar { display: none; }

.tabs__btn { white-space: nowrap; }

.tabs__btn {
  position: relative;
  padding: var(--sp-2xs) var(--sp-sm);
  border: 0;
  background: transparent;
  color: var(--c-black);
  font-size: var(--fs-eyebrow);
  font-weight: 300;
  cursor: pointer;
  transition: var(--t-colour);
}

/* Active indicator rides on ::after so it can slide/scale in later. */
.tabs__btn::after {
  content: "";
  position: absolute;
  left: 0; bottom: -1px;
  width: 100%;
  height: 2px;
  background: var(--c-navy);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--dur-base) var(--ease-out);
}

.tabs__btn:hover { color: var(--c-navy); }
.tabs__btn[aria-selected="true"] { font-weight: 500; }
.tabs__btn[aria-selected="true"]::after { transform: scaleX(1); }

.tabs__panel { padding-block-start: var(--sp-xl); }
.tabs__panel[hidden] { display: none; }

/* Panel content fades up whenever it becomes the active tab. */
.tabs__panel.is-entering { animation: tab-in var(--dur-slow) var(--ease-out) both; }

@keyframes tab-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}


/* ==========================================================================
   14. BLOCK — SOLUTIONS DETAIL: SPEC LAYOUT
   Three columns at desktop: summary | photo | what-we-provide + why-it-matters
   ========================================================================== */
.spec { display: grid; gap: var(--sp-lg); }

.spec__summary { display: flex; flex-direction: column; gap: var(--sp-sm); }

.spec__title {
  font-size: var(--fs-h3);
  font-weight: 300;
  line-height: 1.196;
}

.spec__blurb { font-size: var(--fs-eyebrow); font-weight: 300; line-height: 1.5; }

.spec__media {
  aspect-ratio: 361 / 280;
  overflow: hidden;
  background-color: var(--c-lavender);
}
.spec__media img { width: 100%; height: 100%; object-fit: cover; }

/* Hairline-separated detail stack on the right. */
.spec__details { display: flex; flex-direction: column; }

.spec-item {
  padding-block: var(--sp-sm);
  border-top: 1px solid var(--c-hairline);
}
.spec__details .spec-item:last-child { border-bottom: 1px solid var(--c-hairline); }

.spec-item__heading {
  margin-block-end: var(--sp-2xs);
  font-size: var(--fs-eyebrow);
  font-weight: 400;
}

.spec-item__text { font-size: var(--fs-small); font-weight: 300; line-height: 1.5; }

.spec-item__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-inline-start: 1.3rem;
  list-style: disc;
  font-size: var(--fs-small);
  font-weight: 300;
}
.spec-item__list li { display: list-item; }

@media (min-width: 900px) {
  .spec {
    grid-template-columns: minmax(0, 327fr) minmax(0, 361fr) minmax(0, 529fr);
    column-gap: clamp(1.5rem, 3vw, 3.5rem);
    align-items: start;
  }
  .spec__media { margin-block-start: 0; }
}


/* ==========================================================================
   15. BLOCK — PROCESS  (four steps, navy band, centre photo)
   ========================================================================== */
.process__heading {
  max-width: 20ch;
  font-size: var(--fs-h1);
  font-weight: 300;
  line-height: 1.15;
  margin-block-end: clamp(2rem, 1rem + 4vw, 3.5rem);
}

.process__grid { display: grid; gap: var(--sp-xl); }

.process__col { display: flex; flex-direction: column; gap: var(--sp-xl); }

.process__media {
  order: -1;                 /* photo first on mobile, centre on desktop */
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 272 / 470;
  max-width: 320px;
  margin-inline: auto;
  background-color: var(--c-navy-deep);
}
.process__media img { width: 100%; height: 100%; object-fit: cover; }

.step { display: flex; flex-direction: column; gap: var(--sp-2xs); }

.step__number {
  font-size: 1rem;
  font-weight: 300;
  color: var(--c-white-70);
}

.step__title {
  font-size: var(--fs-step);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.step__text { font-weight: 300; line-height: 1.5; max-width: 46ch; }

@media (min-width: 900px) {
  .process__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 272px) minmax(0, 1fr);
    column-gap: clamp(2rem, 4vw, 4rem);
    align-items: start;
  }
  .process__media { order: 0; max-width: none; }
}


/* ==========================================================================
   16. BLOCK — REASONS  ("The Clear Choice For Your Business")
   ========================================================================== */
.reasons { background-color: var(--c-white); color: var(--c-black); }

.reasons__intro { display: grid; gap: var(--sp-sm); margin-block-end: var(--sp-2xl); }

.reasons__heading {
  max-width: 14ch;
  font-size: var(--fs-h1);
  font-weight: 300;
  line-height: 1.196;
}

.reasons__lead { max-width: 62ch; font-weight: 300; line-height: 1.5; }

@media (min-width: 900px) {
  .reasons__intro {
    grid-template-columns: minmax(0, 155px) minmax(0, 552px) minmax(0, 656px);
    column-gap: clamp(1.5rem, 3vw, 3rem);
    align-items: start;
  }
}

.reasons__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-lg);
}

.reason { display: flex; flex-direction: column; gap: var(--sp-xs); }

.reason__media {
  aspect-ratio: 322 / 294;
  overflow: hidden;
  background-color: var(--c-lavender);
}

.reason__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.reason:hover .reason__img { transform: scale(1.05); }

.reason__title { font-size: var(--fs-label); font-weight: 400; }
.reason__text  { font-size: var(--fs-xs); font-weight: 300; line-height: 1.5; }

@media (min-width: 600px) {
  .reasons__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .reasons__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sp-md);
  }
  /* Vertical hairlines between columns, as drawn in Figma. Drawn as an
     absolutely-positioned rule inside the gutter so all four columns keep
     exactly the same content width. */
  .reason { position: relative; }
  .reason + .reason::before {
    content: "";
    position: absolute;
    left: calc(var(--sp-md) / -2);
    top: 0;
    bottom: 0;
    border-left: 0.3px solid var(--c-hairline);
  }
}


/* ==========================================================================
   17. BLOCK — PARTNERS
   ========================================================================== */
.partners { text-align: center; }

.partners__label {
  font-family: var(--font-ui);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: -0.06em;
  color: var(--c-white-70);
}

.partners__grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  margin-block-start: var(--sp-lg);
}

.partners__logo {
  height: clamp(28px, 4vw, 44px);
  width: auto;
  opacity: 0.65;
  filter: grayscale(1);
  transition: opacity var(--dur-base) var(--ease-out),
              filter var(--dur-base) var(--ease-out);
}
.partners__logo:hover { opacity: 1; filter: grayscale(0); }


/* ==========================================================================
   18. BLOCK — SITE FOOTER
   ========================================================================== */
.site-footer {
  background-color: var(--c-navy-deep);
  padding-block: clamp(2.5rem, 1.5rem + 4vw, 3.75rem);
}

.site-footer__inner { display: grid; gap: var(--sp-xl); }

.site-footer__brand { display: flex; flex-direction: column; gap: var(--sp-md); }

.site-footer__logo { width: clamp(120px, 14vw, 180px); height: auto; }

.site-footer__copyright {
  font-family: var(--font-ui);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: -0.04em;
  color: var(--c-white-50);
}

.footer-nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-md);
}

.footer-nav__col { display: flex; flex-direction: column; }

.footer-nav__link {
  font-family: var(--font-ui);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: -0.04em;
  line-height: 34px;
  color: var(--c-white);
}
.footer-nav__link:hover { color: var(--c-white-70); }

@media (min-width: 768px) { .footer-nav { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

@media (min-width: 900px) {
  .site-footer__inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 464px);
    align-items: start;
    column-gap: var(--sp-xl);
  }
}


/* ==========================================================================
   19. ANIMATION HOOKS  ← this is where the next steps plug in
   --------------------------------------------------------------------------
   [data-reveal]          element starts hidden, animates in when scrolled to
   [data-reveal="up"]     rises into place (default)
   [data-reveal="fade"]   opacity only
   [data-reveal="left"]   slides in from the left
   [data-reveal="right"]  slides in from the right
   [data-reveal="scale"]  scales up from 0.96
   [data-delay="1..5"]    staggers the start by 90ms increments
   .is-revealed           added by the IntersectionObserver in assets/main.js

   Everything here is scoped to `.js` — with JavaScript off, nothing hides.
   ========================================================================== */
.js [data-reveal] {
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

.js [data-reveal="up"],
.js [data-reveal=""]      { transform: translateY(28px); }
.js [data-reveal="fade"]  { transform: none; }
.js [data-reveal="left"]  { transform: translateX(-32px); }
.js [data-reveal="right"] { transform: translateX(32px); }
.js [data-reveal="scale"] { transform: scale(0.96); }

.js .is-revealed { opacity: 1; transform: none; }

[data-delay="1"] { --reveal-delay:  90ms; }
[data-delay="2"] { --reveal-delay: 180ms; }
[data-delay="3"] { --reveal-delay: 270ms; }
[data-delay="4"] { --reveal-delay: 360ms; }
[data-delay="5"] { --reveal-delay: 450ms; }


/* ==========================================================================
   20. REDUCED MOTION
   One global opt-out. Anything added later inherits this safety net as long
   as it animates transform/opacity or uses the tokens above.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .js [data-reveal] { opacity: 1; transform: none; }

  /* The marquee is never started by JS under reduced motion; belt-and-braces
     in case .is-running is ever applied some other way. The viewport stays
     scrollable (see section 12) so all logos remain reachable. */
  .logo-strip__track { animation: none !important; }
}

/* ==========================================================================
   21. BLOCK — SITE HEADER, SOLID VARIANT
   --------------------------------------------------------------------------
   The default header is absolutely positioned so it floats over a photographic
   hero (Home, About, Careers, Contact). Pages that open on a light surface —
   Solutions uses the "navbar black" component in Figma — add
   .site-header--solid.

   This is `sticky`, not `fixed`: sticky pins the bar once the page scrolls
   while leaving it in normal flow, so the top spacing these light pages were
   built around still works and nothing jumps at the moment it pins. It was
   `static` until 2026-08-24, which silently beat the fixed base rule in
   section 20 and left the bar scrolling away for good on Solutions.
   ========================================================================== */
.site-header--solid {
  position: sticky;
  top: 0;
  background-color: var(--c-white);
  color: var(--c-black);
}

/* Stays white when stuck — the navy .is-stuck fill would swallow the dark
   logo. It takes a hairline and a soft lift instead, to separate the bar from
   whatever white section is sliding underneath it. */
.site-header--solid.is-stuck {
  background-color: var(--c-white);
  border-block-end-color: var(--c-black-12);
  box-shadow: 0 1px 12px rgba(0, 0, 0, 0.06);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.site-header--solid .nav-toggle     { color: var(--c-black); }
.site-header--solid .site-nav__link { color: var(--c-black); }

/* The mobile drop panel inverts too, so it reads against the white bar. */
@media (max-width: 899px) {
  .site-header--solid .site-nav {
    background: var(--c-white);
    border-color: rgba(0, 0, 0, 0.12);
  }
}


/* ==========================================================================
   22. BLOCK — PAGE HERO
   Compact dark banner opening the Careers and Contact pages, standing in for
   the full-height Home hero.
   ========================================================================== */
.page-hero {
  position: relative;
  isolation: isolate;
  /* Top padding clears the floating header by the same amount everywhere. */
  padding-block: calc(var(--header-h) + var(--sp-xl)) var(--sp-2xl);
  background-color: var(--c-navy);
  overflow: hidden;
}

/* IMPORTANT: never put a max-width on an element that also carries
   .container — .container sets `margin-inline: auto`, so a narrower
   max-width would centre the whole block and knock it out of alignment
   with every other section. Constrain the children instead. */
.page-hero__inner {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

.page-hero__title {
  /* Matches the 1061px hero text frame in Figma — wide enough to keep the
     headline to two lines on desktop, wraps naturally below that. */
  max-width: 66rem;
  font-size: var(--fs-h1);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.01em;
}

.page-hero__lead {
  max-width: 78ch;
  font-weight: 300;
  line-height: 1.6;
}


/* ==========================================================================
   23. SECTION HEADING
   One heading treatment shared by every section that isn't reproducing a
   bespoke Figma layout, so the type scale and the gap below it stay
   identical page to page.
   ========================================================================== */
.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  margin-block-end: var(--head-gap);
}

.section-head--center { align-items: center; text-align: center; }

.section-head__title {
  max-width: 22ch;
  font-size: var(--fs-h1);
  font-weight: 300;
  line-height: 1.15;
}

.section-head__lead {
  max-width: 70ch;
  font-weight: 300;
  line-height: 1.6;
  color: var(--c-white-70);
}


/* ==========================================================================
   24. BLOCK — CAREERS
   ========================================================================== */
.values { background-color: var(--c-surface-veil); }

.values__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-lg);
}

.value-card { display: flex; flex-direction: column; gap: var(--sp-xs); }

.value-card__media {
  aspect-ratio: 382 / 360;
  border-radius: var(--radius);
  overflow: hidden;
  background-color: var(--c-card);
}

.value-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.value-card:hover .value-card__img { transform: scale(1.05); }

.value-card__title { font-size: var(--fs-label); font-weight: 400; }
.value-card__text  { font-size: var(--fs-small); font-weight: 300; line-height: 1.55; }

@media (min-width: 700px)  { .values__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .values__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ---- Open roles --------------------------------------------------------- */
.roles__grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-lg); }

.role {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  padding: var(--sp-lg);
  border: 1px solid var(--c-white-15);
  border-radius: var(--radius);
  background-color: var(--c-card);
  transition: border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.role:hover { border-color: var(--c-white-50); transform: translateY(-3px); }

.role__title { font-size: var(--fs-h4); font-weight: 400; }
.role__text  { font-weight: 300; line-height: 1.55; }

@media (min-width: 800px) { .roles__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---- Apply call-to-action ----------------------------------------------- */
.cta-band { background-color: var(--c-navy); }

.cta-band__inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-md);
}

.cta-band__title { max-width: 20ch; font-size: var(--fs-h2); font-weight: 300; line-height: 1.2; }
.cta-band__text  { max-width: 62ch; font-weight: 300; line-height: 1.6; }

/* ---- Team --------------------------------------------------------------- */
/* Explicit column counts rather than auto-fit: auto-fit would silently
   create a fourth empty track at wide viewports and leave the three cards
   looking off-centre. */
.team__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-lg);
  max-width: 760px;
}

@media (min-width: 700px) {
  .team__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.member { display: flex; flex-direction: column; gap: var(--sp-2xs); }

.member__media {
  aspect-ratio: 1;
  border-radius: var(--radius);
  overflow: hidden;
  background-color: var(--c-card);
}
.member__media img { width: 100%; height: 100%; object-fit: cover; }

.member__name { font-weight: 400; }
.member__role { font-size: var(--fs-small); font-weight: 300; color: var(--c-white-70); }


/* ==========================================================================
   26. BLOCK — CONTACT DETAILS + FAQ
   ========================================================================== */
/* "Get in touch." is centred in the Figma frame; everything else is flush
   left. .section-head--center handles that without a bespoke rule. */

.details {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--sp-lg);
}

.detail { display: flex; flex-direction: column; gap: var(--sp-2xs); }

.detail__icon { width: 24px; height: 24px; opacity: 0.8; }

/* The LinkedIn mark is a filled glyph, not a stroked one. Without this it
   inherits the stroke-based sizing of its siblings and renders as a blob. */
.detail__icon--solid { stroke: none; }

.detail__text { font-weight: 300; line-height: 1.5; }

/* ---- Hover: weight change plus an underline sweep -----------------------
   font-weight cannot tween between the static Figtree instances the page
   loads, so it snaps. The sweep supplies the movement — same technique and
   the same easing as the nav underline in section 20, so hover behaves the
   same way everywhere on the site.
   ------------------------------------------------------------------------ */
.detail__text--link {
  position: relative;
  align-self: flex-start;   /* so the underline tracks the text, not the cell */
  transition: font-weight var(--dur-fast) var(--ease-out);
}

.detail__text--link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--dur-base) var(--ease-out);
}

.detail__text--link:hover {
  font-weight: 600;
  color: inherit;           /* the old rule dimmed on hover; bold is the cue now */
}

.detail__text--link:hover::after,
.detail__text--link:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* ---- Whole-tile links (the two office addresses) ------------------------
   The tile is the <a>, so the address and the cue move together. Only the
   office name changes weight: bolding three lines of address re-wraps them
   and the block visibly jumps. */
.detail--link {
  color: inherit;
  text-decoration: none;
}

.detail__label {
  font-weight: 500;
  transition: font-weight var(--dur-fast) var(--ease-out);
}
.detail--link:hover .detail__label { font-weight: 700; }

.detail__cue {
  position: relative;
  align-self: flex-start;
  font-size: var(--fs-paragraph);
  font-weight: 400;
  color: var(--c-white-70);
  transition: color var(--dur-base) var(--ease-out);
}

.detail__cue::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--dur-base) var(--ease-out);
}

.detail--link:hover .detail__cue,
.detail--link:focus-visible .detail__cue { color: var(--c-white); }

.detail--link:hover .detail__cue::after,
.detail--link:focus-visible .detail__cue::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* ---- FAQ accordion ------------------------------------------------------
   Built on native <details>/<summary>: opens without JavaScript and is
   keyboard-accessible for free. Only the chevron is animated.
   ------------------------------------------------------------------------ */


.faq__list { border-top: 1px solid var(--c-white-15); }
.faq__item { border-bottom: 1px solid var(--c-white-15); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding-block: var(--sp-md);
  font-size: var(--fs-lead);
  font-weight: 300;
  line-height: 1.35;
  cursor: pointer;
  list-style: none;
  transition: var(--t-colour);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--c-white-70); }

/* Chevron drawn in CSS so there is no icon asset to expire. */
.faq__chevron {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  margin-inline-end: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--dur-base) var(--ease-out);
}
.faq__item[open] .faq__chevron { transform: rotate(-135deg); }

.faq__a {
  padding-block-end: var(--sp-md);
  max-width: 110ch;
  font-weight: 300;
  line-height: 1.6;
  color: var(--c-white-70);
  animation: faq-in var(--dur-base) var(--ease-out) both;
}

@keyframes faq-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}


/* ==========================================================================
   27. FULL-VIEWPORT SECTIONS
   Home and About set min-height on their own hero blocks (sections 7 and 10).
   Careers uses the shared .page-hero, so it opts in with a modifier rather
   than making every page-hero full height — Contact must stay compact.
   ========================================================================== */
.page-hero--full {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100svh;   /* excludes mobile browser chrome where supported */
}


/* ==========================================================================
   28. PARALLAX
   assets/main.js (section f) writes translate3d on [data-parallax] every
   animation frame. The value of the attribute is the speed factor: 0.12 is
   subtle, 0.3 is pronounced. Negative values travel the other way.

   Absolutely-positioned media needs slack around the edges or the transform
   would drag the image off its own container and expose the backdrop, hence
   the inset override below.
   ========================================================================== */
[data-parallax] { will-change: transform; }

.hero__media[data-parallax],
.about-hero__media[data-parallax] { inset: -18% 0; }

/* JS clears the CSS transition on these so the per-frame writes are not
   smoothed into lag; this is the no-JS / reduced-motion resting state. */
@media (prefers-reduced-motion: reduce) {
  [data-parallax] { transform: none !important; }
}


/* ==========================================================================
   29. HOME — INFO BAND BALANCE
   The band read left-heavy: a 190px label column against a 64ch body left
   roughly 600px of dead space at 1440. Widen both so the row spans the
   container while keeping the measure readable.
   ========================================================================== */
@media (min-width: 768px) {
  .info-row {
    grid-template-columns: minmax(12rem, 260px) minmax(0, 1fr);
  }
  .info-row__body { max-width: min(100%, 86ch); }
  .info-row__text { max-width: 76ch; }
}


/* ==========================================================================
   30. ABOUT — SINGLE-LINE GROUP HEADINGS
   Both headings are authored to sit on one line on desktop. nowrap only
   above 1100px; below that they wrap normally rather than overflow.
   ========================================================================== */
@media (min-width: 1100px) {
  .about-group__heading { white-space: nowrap; }
}


/* ==========================================================================
   31. SOLUTIONS — LARGER SERVICE TABS
   ========================================================================== */
.tabs__btn {
  padding: var(--sp-sm) var(--sp-lg);
}

/* The process heading is authored to hold one line on desktop. */
@media (min-width: 1100px) {
  .process__heading { white-space: nowrap; }
}


/* ==========================================================================
   32. HOME — PARTNER LOGOS
   Reuses the sprite-window technique from section 12 (each logo is a crop
   out of a shared sheet) but laid out as a static wrapping grid rather than
   a marquee.
   ========================================================================== */
.partners__grid .logo-strip__item {
  opacity: 0.7;
  filter: grayscale(1) brightness(1.6);
  transition: opacity var(--dur-base) var(--ease-out),
              filter var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

.partners__grid .logo-strip__item:hover {
  opacity: 1;
  filter: grayscale(0) brightness(1);
  transform: translateY(-2px);
}


/* ==========================================================================
   34. CAREERS — ROLE ICONS
   ========================================================================== */
.role__icon {
  width: 40px;
  height: 40px;
  margin-block-end: var(--sp-2xs);
  color: var(--c-white-70);
  transition: color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

.role:hover .role__icon { color: var(--c-white); transform: translateY(-2px); }


/* ==========================================================================
   35. CAREERS — "OUR OFFICE" IMAGE CAROUSEL
   Same marquee engine as the logo strip (section 12) so there is one
   implementation to maintain: .logo-strip__track / __group / is-running are
   reused verbatim and main.js drives any track it finds.
   ========================================================================== */
.office-strip {
  --marquee-speed: 45;
  --marquee-gap: var(--sp-sm);
  background-color: transparent;
}

.office-strip .logo-strip__viewport { padding-block: 0; }

.office-strip__item {
  flex: 0 0 auto;
  width: clamp(240px, 34vw, 420px);
  aspect-ratio: 3 / 2;
  border-radius: var(--radius);
  overflow: hidden;
  background-color: var(--c-card);
}

.office-strip__item img { width: 100%; height: 100%; object-fit: cover; }


/* ==========================================================================
   36. CONTACT — FULL-WIDTH "GET IN TOUCH"
   The detail tiles previously collapsed to a narrow auto-fit cluster. Pin
   them to an even track count so the block spans the same width as the
   form grid above it.
   ========================================================================== */
.get-in-touch .details {
  width: 100%;
  /* One tile per row on phones. The inherited auto-fit/minmax(200px) put two
     cramped columns on a 600px screen, which wrapped each address to five or
     six lines. */
  grid-template-columns: 1fr;
  align-items: start;
}

/* Two up once there is room for a full address line beside another tile. */
@media (min-width: 620px) {
  .get-in-touch .details { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Four across, but only from 1200px. The two contact links need roughly half
   the width of an address, so the tracks are proportioned rather than equal.

   1200, not 1000: at 1024 the equivalent tracks came out 162/150/240/240,
   which wrapped "info@dynasynergy.my" onto two lines and each address onto
   five. Two balanced columns read better than four cramped ones.

   Proportioned tracks also replaced a pair of nth-child(4)/nth-child(5) span
   rules written for the old five-tile layout: removing the dynasynergy.my tile
   on 2026-08-25 shifted the addresses to positions 3 and 4, so those spans
   applied to the wrong tiles and orphaned Kota Kinabalu onto its own row.
   Proportions carry no positional dependency, so adding or removing a tile
   cannot break this the same way again. */
@media (min-width: 1200px) {
  .get-in-touch .details {
    grid-template-columns: 1fr 1fr 1.6fr 1.6fr;
    gap: var(--sp-xl);
  }
}


/* ==========================================================================
   37. COMPONENT — SOLID BUTTON  (primary call to action)
   The outline variants carry equal weight, which is fine inside a section
   but gives a hero no focal point. This is the one filled treatment, so
   there is never more than one primary action competing on a screen.
   Declared after section 4 so its :hover wins on equal specificity.
   ========================================================================== */
.btn--solid-light {
  --btn-ink: var(--c-white);
  background-color: var(--c-white);
  color: var(--c-navy);
}

.btn--solid-light:hover {
  background-color: var(--c-lavender);
  border-color: var(--c-lavender);
  color: var(--c-navy);
}


/* ==========================================================================
   38. HERO ACTIONS
   ========================================================================== */
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-sm);
}


/* ==========================================================================
   39. SOLUTION CARDS — PROMINENT CTA
   Both service lines share one surface: the navy panel and hairline edge set
   on .solution-card__body above. Card one's texture and the per-card accent
   edges were removed on Janice's instruction (2026-08-24) — two different
   surfaces read as an inconsistency, not a distinction — and the accent slab
   that replaced them came off the same day in favour of a thin grey outline.
   The "Learn More" text link stays promoted to a button so each card has an
   obvious action.
   ========================================================================== */
.solution-card__cta {
  align-self: flex-start;
  margin-block-start: var(--sp-2xs);
}

.solution-card__cta .link-more__icon { margin-inline-start: var(--sp-2xs); }
.solution-card__cta:hover .link-more__icon { transform: translateX(4px); }


/* ==========================================================================
   40. TRUST SIGNALS  (certifications + partner logos, paired)
   Certifications used to sit in the light info band, where it read as a
   second positioning statement. Pairing it with the client logos turns two
   weak signals into one proof section. Reuses .info-row for the credentials
   block and .partners__* for the logo wall, so no new type styles.
   ========================================================================== */
.trust-signals { background-color: var(--c-surface-veil); }

.trust-signals__partners {
  margin-block-start: var(--sp-2xl);
  padding-block-start: var(--sp-2xl);
  border-block-start: 1px solid var(--c-white-15);
  text-align: center;
}


/* ==========================================================================
   42. HOME PAGE — LAYOUT CONSISTENCY PASS  (2026-08-18)
   Six measured problems, no content changes. Recorded here so the reasoning
   survives: the page had four section-heading sizes (20/40/48/40px), four
   left edges (36/361/594/881px), four body sizes and two adjacent sections
   sharing one background.
   ========================================================================== */

/* ---- a) Separate "What" from "Proof" -----------------------------------
   Both carried --c-surface-veil, giving 1,936px of unbroken identical
   background from the solution cards through the logo wall. Dropping Trust
   to the plain page ink puts a real edge between the two sections. */
.trust-signals { background-color: transparent; }

/* ---- b) One section-heading treatment ----------------------------------
   "Powering Malaysia's most remote sites." was an <h2> at 20px, smaller
   than the 22.4px paragraph beside it, so it read as a caption. It now uses
   the same 40px/300 as every other section heading on the page.
   "Certifications" deliberately stays at label size: it sits underneath the
   Trust Signals heading, so it is a sub-label, not a section heading. */
.info-row__label--heading { max-width: 20ch; }

/* ---- c) Fix the Solutions intro ---------------------------------------
   The heading sat at x=36 and its lead at x=881, so the eye crossed an
   845px void to reach the smallest text in the section. Stacked left, the
   way the Trust Signals head already works. */
@media (min-width: 900px) {
  .solutions-overview__intro {
    grid-template-columns: minmax(0, 1fr);
    column-gap: 0;
  }
}

/* ---- d) Two text sizes, not four --------------------------------------
   Leads were 22.4 / 16 / 17px with no pattern. One lead size and one body
   size is enough to carry the hierarchy. */
.solutions-overview__lead,
.trust-signals .section-head__lead { max-width: 68ch; }

/* ---- e) Even out the logo wall ----------------------------------------
   The eight sprite crops ran 41px to 70px tall (a 70% spread) and wrapped
   7 + 1, orphaning CIDB alone on a centred second row. Boxes are now
   normalised to a 38px cap height in the markup (the --img-* offsets are
   percentages of the box, so they scale with it), and a fixed four-column
   grid removes the orphan. */
@media (min-width: 700px) {
  .partners__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    place-items: center;
    row-gap: var(--sp-xl);
  }
}

/* ---- f) Hero subtitle orphan ------------------------------------------
   A 34ch measure broke a 54-character line, leaving "energy." alone. */
.hero__subtitle { max-width: 58ch; }

/* ---- g) The promoted Why heading needs the full row --------------------
   At 20px it fitted the 260px label column. At 40px it wrapped to four
   lines in that column. Now that it is a section heading it behaves like
   every other one on the page: full width, content beneath. This also
   collapses the 361px body indent on this row, leaving one spine.
   The Certifications row keeps the two-column indent, because there the
   label genuinely is a sub-label under the Trust Signals heading. */
.info-row--stacked { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 768px) {
  .info-row--stacked { grid-template-columns: minmax(0, 1fr); }
  .info-row--stacked .info-row__body { max-width: 78ch; }
}

.info-row--stacked .info-row__label--heading {
  max-width: 30ch;
  margin-block-end: var(--sp-2xs);
}

/* ---- h) Optical sizing for square logo marks --------------------------
   Equal cap height makes a roundel read far smaller than a wordmark, so the
   two squarest marks (SEDA, Energy Commission of Sabah) are set slightly
   taller to balance visually rather than mathematically. Set in the markup
   alongside the other boxes. */


/* ==========================================================================
   43. RESPONSIVE FIXES  (2026-08-18)
   Audited at 320 / 375 / 414 / 768 / 1024 / 1280 / 1440 / 1920 plus three
   landscape phone sizes. Page-level horizontal overflow was already zero
   everywhere and no text fell below 14px, so the fluid type scale and
   existing breakpoints were sound. These are the four real failures found.
   ========================================================================== */

/* ---- a) Short viewports: the full-height heroes overflowed --------------
   On a landscape phone the hero was TALLER than the screen (463px in a
   390px viewport on an iPhone 14, 519px on Careers), because 100svh plus
   184px of header clearance plus 90px of bottom padding plus the content
   cannot fit. The primary CTA ended up below the fold, which defeats the
   point of putting it in the hero.

   A full-viewport hero is the wrong pattern on a 390px-tall screen anyway,
   so below 560px of height it becomes content-sized with tighter padding
   and a smaller headline. Targets height, not width, so it catches landscape
   phones and short desktop windows alike without touching tablets. */
@media (max-height: 560px) {
  .hero,
  .page-hero--full {
    min-height: 0;
    padding-block: calc(var(--header-h) + var(--sp-sm)) var(--sp-lg);
  }

  .about-hero__statement {
    min-height: 0;
    padding-block: calc(var(--header-h) + var(--sp-sm)) var(--sp-lg);
  }

  /* 69px of display type on a 390px-tall screen leaves no room for anything
     else. */
  .hero__title { font-size: clamp(2rem, 4.5vw, 2.75rem); }
  .hero__inner { gap: var(--sp-sm); }
  .statement__text { font-size: var(--fs-h3); }
}

/* ---- b) Partner logo wall was cramped between 700 and 900px ------------
   The four-column grid started at 700px, but the widest mark (Suruhanjaya
   Tenaga, 166px) is wider than the resulting 140-165px column, so logos
   collided. The grid now waits for 900px, where columns reach 181px; below
   that the original centred flex wrap packs them without collision. */
@media (min-width: 700px) and (max-width: 899px) {
  .partners__grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
  }
}

/* ---- c) Touch targets ---------------------------------------------------
   The contact detail links were 17-23px tall, well under the 44px comfort
   target for touch. Buttons sat at 43px, one pixel short. Footer links were
   34px. Applied only on coarse pointers so desktop density is unaffected.

   Scoped to --link deliberately. This targeted plain .detail__text until
   2026-08-25, which was safe while every .detail__text was a one-line link.
   It is not any more: the office tiles use .detail__text for a multi-line
   address, and inline-flex turned that <p> into a flex container, so the
   <strong> office name and the address became side-by-side flex items and the
   <br> between them was discarded. The two address tiles are whole-tile links
   and already far larger than 44px, so they need nothing here. */
@media (pointer: coarse) {
  .btn { min-height: 44px; }

  .detail__text--link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  .footer-nav__link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  .nav-toggle { min-height: 44px; min-width: 44px; }

  /* The compact small-screen tab scale (section 43d) lands at 39px, so the
     floor is restored here rather than by inflating the padding, which would
     push the second tab further off screen. */
  .tabs__btn { min-height: 44px; }
}

/* ---- d) Solution tabs overflowed their scroller on small screens -------
   Enlarging the tabs (section 31) pushed the second tab entirely off screen
   at 320-414px, where the scroll affordance is invisible. At small sizes
   they return to the compact scale so the second tab is partly visible and
   the row reads as scrollable. */
@media (max-width: 600px) {
  .tabs__btn { padding: var(--sp-2xs) var(--sp-sm); }
}


/* ==========================================================================
   44. TYPE SYSTEM — THREE TIERS  (2026-08-18)
   --------------------------------------------------------------------------
   The site had grown to 13 distinct font sizes (96/48/40/36/32/28/22.4/20/
   17/16/15/14/13) and four weights (300/400/500/600). Worse, two classes
   rendered at two different sizes depending on the page: .section-head__title
   was 40px on Home and 48px on Careers, .section-head__lead 20px vs 17px.

   Everything below now resolves to exactly three sizes:

     --fs-heading     28 -> 40px   page titles and section titles
     --fs-subheading  20 -> 24px   card titles, sub-labels, h3s
     --fs-paragraph   15 -> 17px   all body copy, leads, lists, controls

   Weights follow the same discipline: headings 300, subheadings 400,
   paragraphs 300, with 500 reserved for a proper name or a form label.
   Line heights are fixed per tier (1.15 / 1.3 / 1.6) so vertical rhythm is
   identical page to page.

   ONE documented exception: the Home hero H1 keeps --fs-display (96px). It
   is a single element on a single page carrying the brand statement over a
   full-bleed photograph; at 40px the home page loses its opening. Collapse
   it into the heading tier by deleting the .hero__title rule at the end of
   this section if a strict three-size rule is wanted.
   ========================================================================== */
:root {
  --fs-heading:     clamp(1.75rem, 1.1rem + 2.6vw, 2.5rem);      /* 28 -> 40 */
  --fs-subheading:  clamp(1.125rem, 1rem + 0.6vw, 1.3125rem);    /* 18 -> 21 */
  --fs-paragraph:   clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem); /* 15 -> 17 */
}

/* ---- Tier 1: HEADING ---------------------------------------------------- */
.page-hero__title,
.about-entry__heading,
.solutions-detail__title,
.section-head__title,
.cta-band__title,
.solutions-overview__heading,
.process__heading,
.reasons__heading,
.about-group__heading,
.spec__title,
.info-row__label--heading,
.statement__text {
  font-size: var(--fs-heading);
  font-weight: 300;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

/* ---- Tier 2: SUBHEADING ------------------------------------------------- */
.solution-card__title,
.role__title,
.step__title,
.reason__title,
.value-card__title,
.spec-item__heading,
.info-row__label,
.section-label,
.partners__label,
.hero__subtitle,
.faq__q {
  font-size: var(--fs-subheading);
  font-weight: 400;
  line-height: 1.3;
}

/* The Certifications sub-label sits under a tier-1 heading, so it must not
   inherit the heading treatment from the shared .info-row__label class. */
.info-row__label--heading { line-height: 1.15; }

/* ---- Tier 3: PARAGRAPH -------------------------------------------------- */
.info-row__text,
.solutions-overview__lead,
.section-head__lead,
.cta-band__text,
.page-hero__lead,
.role__text,
.step__text,
.step__number,
.reasons__lead,
.spec__blurb,
.spec-item__text,
.spec-item__list,
.value-card__text,
.member__name,
.member__role,
.detail__text,
.reason__text,
.solution-card__text,
.about-entry__prose,
.about-group__prose,
.eyebrow,
.faq__a,
.btn,
.tabs__btn,
.link-more {
  font-size: var(--fs-paragraph);
  line-height: 1.6;
}

/* Weight discipline inside the paragraph tier: light by default, 500 only
   where something genuinely needs to read as a label or a proper name. */
.info-row__text,
.solutions-overview__lead,
.section-head__lead,
.cta-band__text,
.page-hero__lead,
.role__text,
.step__text,
.reasons__lead,
.spec__blurb,
.spec-item__text,
.value-card__text,
.member__role,
.detail__text,
.reason__text,
.solution-card__text,
.eyebrow,
.faq__a { font-weight: 300; }

.member__name,
.btn,
.tabs__btn { font-weight: 400; }

.section-label,
.partners__label { font-weight: 500; }

/* Tighter leading for the short, wide lines that open a page or a section. */
.page-hero__lead,
.section-head__lead,
.solutions-overview__lead,
.info-row__text,
.cta-band__text { line-height: 1.55; }

/* ---- The one exception, kept deliberately ------------------------------- */
.hero__title {
  font-size: var(--fs-display);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.03em;
}


/* ==========================================================================
   45. SPACING RHYTHM
   Every section already shares --sp-2xl top and bottom padding. These pin
   the two gaps that were still set per block: the space under a heading
   before its content, and the gap between stacked prose paragraphs.
   ========================================================================== */
.section-head { margin-block-end: var(--head-gap); }

.solutions-overview__intro,
.reasons__intro { margin-block-end: var(--head-gap); }

.about-entry__prose,
.about-group__prose,
.solutions-detail__intro { gap: var(--sp-sm); }

.info-row__body { gap: var(--sp-md); }

/* Reserve two lines for a member's name so the job titles beneath stay on a
   shared baseline. "Ahmad Zamir Bin Ahmad Zamri" wraps where the other two
   names do not, which pushed its role 27px out of line with the row. */
.member__name { min-height: 3.2em; }


/* ==========================================================================
   46. ABOUT — CUSTOMERS AND PARTNERS  (grouped client logo boxes)
   From the 2026 company profile slide "Trusted by Malaysia's infrastructure
   operators". Three bordered boxes reusing the .role card treatment from the
   Careers page, so the box, radius, border and hover match the rest of the
   site rather than the slide.

   The logos are white-on-transparent sprite crops, so this section must sit
   on a dark surface. It is placed after the lavender band for that reason.
   Crop values were derived from the actual alpha bounds of each logo inside
   its sheet, not hand-tuned: for a logo at (lx,ly,lw,lh) in a 1080 sheet,
     --img-w = 1080/lw*100%    --img-x = -(lx/lw)*100%
     --img-h = 1080/lh*100%    --img-y = -(ly/lh)*100%
   and the box is sized to lw/lh so nothing distorts.
   ========================================================================== */
.client-groups {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-lg);
}

@media (min-width: 800px) {
  .client-groups { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.client-group {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
  padding: var(--sp-lg);
  border: 1px solid var(--c-white-15);
  border-radius: var(--radius);
  background-color: var(--c-card);
  transition: border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.client-group:hover { border-color: var(--c-white-50); transform: translateY(-3px); }

.client-group__label {
  font-size: var(--fs-subheading);
  font-weight: 400;
  line-height: 1.3;
  padding-block-end: var(--sp-xs);
  border-block-end: 1px solid var(--c-white-15);
}

/* Logos are centred in a wrapping row and vertically centred against each
   other, so a tall mark (U Mobile, the Navy crest) sits level with a
   wordmark instead of dragging the row. */
.client-group__logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md) var(--sp-lg);
  flex: 1;
  min-height: 96px;
}

.client-group .logo-strip__item {
  opacity: 0.9;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.client-group .logo-strip__item:hover { opacity: 1; transform: translateY(-2px); }

.client-groups__note {
  margin-block-start: var(--sp-xl);
  max-width: 78ch;
  font-size: var(--fs-paragraph);
  font-weight: 300;
  line-height: 1.55;
  color: var(--c-white-70);
}


/* ==========================================================================
   47. ABOUT — DEFINING PARTNERSHIPS  (three stat cards)
   From the 2026 company profile slide. Same card shell as .client-group and
   .role so all three card patterns on the site match. The figure is the
   card's headline, so it takes the heading tier rather than introducing a
   fourth size; the slide sets it at roughly the same scale as the section
   heading, so this is faithful to the design as well as to the type system.
   ========================================================================== */
.partnerships {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-lg);
}

@media (min-width: 860px) {
  .partnerships { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.partnership {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  padding: var(--sp-lg);
  border: 1px solid var(--c-white-15);
  border-radius: var(--radius);
  background-color: var(--c-card);
  transition: border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.partnership:hover { border-color: var(--c-white-50); transform: translateY(-3px); }

/* Region label left, partner logo right, on one baseline. */
.partnership__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-md);
  min-height: 34px;
}

.partnership__region {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-white-50);
}

.partnership__name {
  font-size: var(--fs-subheading);
  font-weight: 400;
  line-height: 1.3;
}

/* The figure is the card's focal point. */
.partnership__figure {
  margin-block: var(--sp-2xs) var(--sp-2xs);
  font-size: var(--fs-heading);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.partnership__text {
  font-size: var(--fs-paragraph);
  font-weight: 300;
  line-height: 1.6;
  color: var(--c-white-70);
}

.partnership__text strong { font-weight: 500; color: var(--c-white); }

.partnership .logo-strip__item { opacity: 0.85; flex: 0 0 auto; }
.partnership:hover .logo-strip__item { opacity: 1; }


/* ==========================================================================
   48. ABOUT — LICENCES  (six licensing-body cards)
   From the 2026 company profile slide. Fourth use of the shared card shell
   (.role, .client-group, .partnership, .licence) so every card on the site
   matches. Three columns at desktop, two at tablet, one on mobile.

   The body name and its logo sit on one row; the registration held sits
   beneath, pushed to the card's bottom edge with margin-block-start:auto so
   the descriptors align across a row despite very different lengths.
   Crop values are alpha-derived, as in section 46.
   ========================================================================== */
.licences {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-md);
}

@media (min-width: 620px)  { .licences { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .licences { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.licence {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-lg) var(--sp-lg);
  border: 1px solid var(--c-white-15);
  border-radius: var(--radius);
  background-color: var(--c-card);
  transition: border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.licence:hover { border-color: var(--c-white-50); transform: translateY(-3px); }

/* Wraps rather than overflows: between roughly 620px and 1000px the column
   is too narrow to hold a long body name beside its logo, and with the logo
   at flex:0 0 auto the mark was pushing past the card's padding. Wrapping
   drops it under the name instead. */
.licence__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-xs) var(--sp-md);
  min-height: 52px;
}

.licence__body {
  min-width: 0;
  font-size: var(--fs-subheading);
  font-weight: 400;
  line-height: 1.25;
  max-width: 18ch;
}

.licence__held {
  margin-block-start: auto;
  font-size: var(--fs-paragraph);
  font-weight: 300;
  line-height: 1.55;
  color: var(--c-white-70);
}

.licence .logo-strip__item { flex: 0 0 auto; opacity: 0.85; }
.licence:hover .logo-strip__item { opacity: 1; }


/* ==========================================================================
   49. CAREERS — OFFICE ADDRESSES
   Two columns under the carousel, each linking out to Google Maps. The whole
   card is the link so the tap target is the card, not just the street line.
   ========================================================================== */
.offices {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-md);
  margin-block-start: var(--sp-2xl);
}

@media (min-width: 700px) {
  .offices { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-lg); }
}

.office-card {
  display: flex;
  gap: var(--sp-md);
  padding: var(--sp-lg);
  border: 1px solid var(--c-white-15);
  border-radius: var(--radius);
  background-color: var(--c-card);
  color: inherit;
  transition: border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.office-card:hover { border-color: var(--c-white-50); transform: translateY(-3px); }

.office-card__icon { flex: 0 0 auto; width: 24px; height: 24px; opacity: 0.8; }

.office-card__name {
  font-size: var(--fs-subheading);
  font-weight: 400;
  line-height: 1.3;
  margin-block-end: var(--sp-2xs);
}

.office-card__address {
  font-size: var(--fs-paragraph);
  font-weight: 300;
  line-height: 1.6;
  color: var(--c-white-70);
}

.office-card__cue {
  display: inline-block;
  margin-block-start: var(--sp-2xs);
  font-size: var(--fs-paragraph);
  font-weight: 400;
  text-decoration: underline;
  text-underline-offset: 3px;
}


/* ==========================================================================
   50. CAREERS — ARROW-CONTROLLED OFFICE CAROUSEL
   The strip moves only when the reader asks it to. There is no auto-advance
   and no pointer dragging: main.js (section g) wires the two arrow buttons
   to scroll by exactly one tile, and scroll-snap makes each stop land clean.
   The viewport is still a native scroll container, so trackpad and touch
   scrolling keep working and the row stays reachable without JavaScript.
   ========================================================================== */
.office-carousel { position: relative; }

.office-strip .logo-strip__viewport {
  overflow-x: auto;
  scroll-behavior: smooth;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

/* start, not center: a centred snap point is unreachable for the first and
   last tile, which would leave the row unable to settle at either end. */
.office-strip__item { scroll-snap-align: start; }

.office-strip__item img { cursor: zoom-in; -webkit-user-drag: none; }

@media (prefers-reduced-motion: reduce) {
  .office-strip .logo-strip__viewport { scroll-behavior: auto; }
}

/* ---- Arrows ------------------------------------------------------------- */
.office-nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--c-white-50);
  border-radius: 50%;
  background: rgba(7, 11, 23, 0.72);
  color: var(--c-white);
  cursor: pointer;
  backdrop-filter: blur(4px);
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out);
}

.office-nav:hover { background: var(--c-white); border-color: var(--c-white); color: var(--c-navy); }

.office-nav--prev { left: clamp(0.5rem, 1.2vw, 1rem); }
.office-nav--next { right: clamp(0.5rem, 1.2vw, 1rem); }

.office-nav svg { width: 20px; height: 20px; }

/* At either end the arrow greys out rather than disappearing, so the control
   does not shift position as the reader moves through the row. */
.office-nav[disabled] { opacity: 0.3; cursor: default; }
.office-nav[disabled]:hover {
  background: rgba(7, 11, 23, 0.72);
  border-color: var(--c-white-50);
  color: var(--c-white);
}

@media (pointer: coarse) {
  .office-nav { width: 44px; height: 44px; }
}


/* ==========================================================================
   51. LIGHTBOX  (click an office photo to expand)
   Built on <dialog> so Esc, focus handling and the top layer come for free.
   ========================================================================== */
.lightbox {
  padding: 0;
  border: 0;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  background: transparent;
  overflow: hidden;
}

.lightbox::backdrop { background: rgba(7, 11, 23, 0.88); }

.lightbox__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: clamp(1rem, 4vw, 4rem);
}

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: var(--radius);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
  animation: lightbox-in var(--dur-base) var(--ease-out) both;
}

@keyframes lightbox-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: none; }
}

.lightbox__close {
  position: absolute;
  top: clamp(0.75rem, 2vw, 1.5rem);
  right: clamp(0.75rem, 2vw, 1.5rem);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--c-white-50);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.4);
  color: var(--c-white);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: var(--t-colour);
}
.lightbox__close:hover { background: var(--c-white); color: var(--c-navy); }

.lightbox__caption {
  position: absolute;
  inset-inline: 0;
  bottom: clamp(0.75rem, 3vw, 2rem);
  text-align: center;
  font-size: var(--fs-paragraph);
  font-weight: 300;
  color: var(--c-white-70);
  padding-inline: var(--sp-lg);
}

@media (prefers-reduced-motion: reduce) {
  .lightbox__img { animation: none; }
}


/* ==========================================================================
   52. ABOUT — CLOSING ACTION
   The "Explore our solutions" button moved out of the Certifications block to
   the foot of the page, centred and a step larger than a standard .btn.
   ========================================================================== */
.page-action {
  display: flex;
  justify-content: center;
  padding-block-end: var(--sp-2xl);
}

.btn--lg {
  min-height: 56px;
  padding-inline: var(--sp-xl);
  font-size: var(--fs-subheading);
}

@media (min-width: 480px) {
  .btn--lg { min-width: 280px; }
}


/* ==========================================================================
   53. CAREERS — LIGHT SURFACE
   --------------------------------------------------------------------------
   Careers was navy throughout. On Janice's instruction (2026-08-24) it now
   follows the Solutions page: white sections with dark ink, the navy kept for
   the closing "Interested in joining us?" band and the footer only.

   Everything here is careers-only. .page-hero--full, .roles, .values and
   .office appear on no other page, so these are plain overrides rather than
   new modifiers. .page-hero itself is NOT touched — Contact still opens on
   the navy banner and shares that class.

   The vocabulary is borrowed from Solutions rather than invented: white
   surface with --c-black ink, --c-lavender behind images, --c-hairline and
   --c-black-12 for edges.
   ========================================================================== */

/* ---- Opening section ---------------------------------------------------- */
/* The hero drops its 100vh height along with the navy. A full viewport of
   white behind three text blocks reads as a loading error, and Solutions
   opens on a normal-height section, which is the style being matched. The
   header is sticky (in flow) on this page now, so the hero must NOT also
   pad by --header-h or the clearance is counted twice. */
.page-hero--full {
  /* Full page: the header is sticky and therefore in normal flow on this
     page, so the hero takes the viewport MINUS the bar rather than a plain
     100svh, which would push its own base below the fold. --header-h (136px
     at 1440) runs 2px over the bar's real height there — close enough that
     header + hero fill the screen exactly. */
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  padding-block: var(--sp-xl) var(--sp-2xl);
  background-color: var(--c-white);   /* shows only until the photo loads */
  color: var(--c-black);
}

/* Section 47's short-screen rule wants a full-height hero to collapse on
   landscape phones. It sits earlier in the file, so restate it here or the
   min-height above silently wins and the hero squashes its own text. */
@media (max-height: 560px) {
  .page-hero--full { min-height: 0; }
}

/* ---- Hero photograph + scrim ------------------------------------------- */
/* Same layering as .about-hero (section 10): the photo sits at z-index -2,
   the scrim at -1, and the text keeps the default stacking above both. The
   section already carries position/isolation/overflow, so nothing else is
   needed to contain them. */
.page-hero--full .page-hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: var(--asset-careers-hero);
  background-size: cover;
  background-position: center;
  /* Photograph dropped to 60% over the white section so the hero text stays
     readable — worst measured contrast 5.64:1 against the 4.5:1 floor, where
     the full-strength photo gave 1.8:1. This opacity is the whole mechanism:
     a blur(8px) was tried alongside it and removed on Janice's instruction
     (2026-08-24), and it was never earning its place anyway — the tower is
     one large dark mass rather than fine detail, so blurring moved the worst
     case only 7.40 -> 7.45 at a fixed opacity. Lower this value to gain
     contrast; there is no other dial here. */
  opacity: 0.6;
}

/* The white rectangle Janice asked for, at 10%. It lightens the photograph
   only slightly — the ink over it stays --c-black, so if a future photograph
   is darker than this one the contrast has to be re-checked, not assumed. */
.page-hero--full::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(255, 255, 255, 0.1);
}

/* ---- Light sections ----------------------------------------------------- */
.roles,
.values,
.office {
  background-color: var(--c-white);
  color: var(--c-black);
}

/* Secondary text: the shared rule paints this --c-white-70, invisible here. */
.roles .section-head__lead,
.values .section-head__lead,
.office .section-head__lead { color: var(--c-black-70); }

/* ---- Cards stay dark --------------------------------------------------- */
/* The role and office boxes keep the dark --c-card surface they always had —
   they were briefly flipped to white on 2026-08-24 and Janice reverted it the
   same day. They are dark cards sitting on a white page, so the ink inside
   them has to be re-lit: the section sets color: --c-black and every child
   inherits it, which would otherwise put black text on a #19202f box.

   Only the box interiors are re-lit. Everything outside them — section
   headings, leads, the value-card captions — stays dark on white. */
.roles .role,
.office .office-card {
  color: var(--c-white);
}

.office .office-card__address { color: var(--c-white-70); }

/* ---- Value cards ------------------------------------------------------- */
/* --c-lavender is what Solutions puts behind an image while it loads; these
   are placeholders behind photos, not boxes in their own right. */
.values .value-card__media { background-color: var(--c-lavender); }

/* ---- Our Office -------------------------------------------------------- */
.office .office-strip__item { background-color: var(--c-lavender); }

/* Carousel arrows invert: a dark pill with a white glyph disappears against
   white, so they become white pills with navy glyphs and fill on hover. */
.office .office-nav {
  background: rgba(255, 255, 255, 0.86);
  border-color: var(--c-hairline);
  color: var(--c-navy);
}
.office .office-nav:hover {
  background: var(--c-navy);
  border-color: var(--c-navy);
  color: var(--c-white);
}

/* The shared rule greys the end-stop arrow with opacity: 0.3, which on a dark
   photo still leaves a visible pill but on white erases the control entirely —
   the opposite of what that rule is for. Mute the ink and edge instead, so the
   pill keeps its outline and holds its position. */
.office .office-nav[disabled],
.office .office-nav[disabled]:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.86);
  border-color: var(--c-black-12);
  color: var(--c-hairline);
}


/* ==========================================================================
   54. CONTACT — FEATURE HERO
   --------------------------------------------------------------------------
   Replaces the enquiry form removed on 2026-08-25. A full-viewport
   photographic hero: display headline, lead, one action. A "Get in touch"
   eyebrow sat above the headline until Janice removed it the same day.

   Contact keeps the default fixed header, which is OUT of flow, so this hero
   takes a plain 100svh and clears the bar with .page-hero's own --header-h
   padding. Do not copy the careers hero's calc() here — that page's header is
   sticky and in flow, and subtracting --header-h on this page would leave a
   header-sized gap at the bottom of the viewport.

   TYPE SYSTEM: this is the SECOND documented exception to the three-size rule
   in section 44, alongside the Home hero H1. Same justification — a single
   element on a single page carrying a statement over a full-bleed photograph,
   which the 40px heading tier cannot hold. It is deliberately smaller than
   --fs-display (72px against Home's 96px) so the home page keeps the largest
   type on the site. To collapse it back into the tier, delete the
   .page-hero__title--display rule below.
   ========================================================================== */
.page-hero--feature {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100svh;   /* excludes mobile browser chrome where supported */
}

/* Section 47's short-screen rule collapses full-height heroes on landscape
   phones. It sits earlier in the file, so restate it or the min-height above
   silently wins and the copy squashes. */
@media (max-height: 560px) {
  .page-hero--feature {
    min-height: 0;
    padding-block: calc(var(--header-h) + var(--sp-sm)) var(--sp-xl);
  }
}

.page-hero--feature .page-hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: var(--asset-contact-hero);
  background-size: cover;
  /* Centred. The previous photograph held 70% because its subject sat on the
     right of the frame; a landscape has no such subject. This one is a 2.22:1
     panorama against a 1.6:1 desktop hero and a 0.46:1 phone, so cover crops
     it hard horizontally on narrow screens — centre keeps the ridgeline and
     the mist, which read at any crop. */
  background-position: center;
}

/* Dark scrim, not the white one on careers. Contact is a dark-surface page
   with white ink, so the photograph is pushed DOWN rather than lightened.
   Weighted to the left, where the text sits, and eased off to the right so
   the far side of the photograph stays open. Measured against the composited
   pixels, worst contrast per element: headline 8.20:1, lead 12.69:1, button
   17.07:1, against the 4.5:1 AA floor.

   The strong region reaches 55% because the headline was unwrapped to a single
   line on 2026-08-25 and now runs to 896px of a 1425px hero. At the previous
   38% stop it fell to 4.14:1 — still over the 3:1 large-text threshold at
   72px, but with no margin. Re-measure on ANY change to the photograph, the
   gradient, or the width of the text column: all three have moved these
   numbers already. */
.page-hero--feature::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    90deg,
    rgba(7, 11, 23, 0.92) 0%,
    rgba(7, 11, 23, 0.85) 55%,
    rgba(7, 11, 23, 0.45) 85%,
    rgba(7, 11, 23, 0.38) 100%
  );
}

.page-hero__inner--feature {
  gap: var(--sp-md);
  /* 56rem (896px) so the headline clears the 844px it needs on one line at
     72px. The lead does not stretch with it — .page-hero__lead is capped at
     78ch, about 660px, so the measure stays readable. Not on the .container
     itself — see the max-width warning in the README. */
  max-width: 56rem;
}


.page-hero__title--display {
  /* No max-width: the headline is meant to sit on one line. It does so from
     roughly a 500px viewport upward, because the clamp below shrinks the type
     faster than the container narrows. Below that it wraps to two lines, which
     is correct — forcing nowrap would push it off the side of a phone. */
  max-width: none;
  font-size: clamp(2.25rem, 1.2rem + 4vw, 4.5rem);   /* 36 -> 72px */
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.02em;
}

/* .page-hero__inner is a flex column, so without this the button stretches to
   the full 56rem text column. Same fix as .solution-card__cta. */
.page-hero--feature .btn {
  align-self: flex-start;
  margin-block-start: var(--sp-sm);
}


/* ==========================================================================
   55. ABOUT — SINGLE-LINE SECTION HEADINGS
   --------------------------------------------------------------------------
   The three About section headings and the client-groups note sit on one line
   on Janice's instruction (2026-08-25).

   .section-head__title caps at 22ch (561px at 40px), which is what wrapped
   them. That cap is NOT changed globally — the class carries every section
   heading on Home, Careers and Contact too, and widening it there would undo
   deliberate two-line breaks. Scoped to the three About sections instead.

   Widths needed on one line at 40px, against a 1425px container:
     Fully licensed to meet your energy needs.        716px
     Trusted by Malaysia's infrastructure operators.  789px
     Some of our defining partnerships.               598px
   and the note at 17px: 892px.

   No white-space: nowrap. --fs-heading shrinks with the viewport, so these
   hold one line down to roughly 860px and wrap below it, which is correct;
   nowrap would push them off the side of a phone. The note is set in
   --fs-paragraph, which barely scales, so it re-wraps at about 960px.
   ========================================================================== */
.licences-section .section-head__title,
.customers .section-head__title,
.partnerships-section .section-head__title {
  max-width: none;
}

.client-groups__note {
  max-width: none;
}


/* ==========================================================================
   56. LEGAL PAGES — PRIVACY POLICY, TERMS OF USE
   --------------------------------------------------------------------------
   Plain long-form reading pages, same white surface and solid sticky header
   as Solutions (hence padding-block-start: --sp-xl, not the section default
   --sp-2xl — see §solutions-detail for why).

   .legal__body constrains line length to 68ch. It is a plain div, not the
   .container it sits inside, so this does not trip the "never max-width an
   element that also carries .container" rule (README) — the child is
   constrained, .container itself is untouched.
   ========================================================================== */
.legal {
  background-color: var(--c-white);
  color: var(--c-black);
  padding-block-start: var(--sp-xl);
}

.legal__title {
  max-width: none;
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.2;
}

.legal__meta {
  margin-block-start: var(--sp-2xs);
  font-size: var(--fs-small);
  font-weight: 300;
  line-height: 1.6;
  color: var(--c-black-70);
}

.legal__body {
  max-width: 68ch;
  margin-block-start: var(--sp-xl);
  font-weight: 300;
  line-height: 1.6;
}

.legal__body h2 {
  font-size: var(--fs-h4);
  font-weight: 500;
  margin-block-start: var(--sp-xl);
}

.legal__body h2:first-child { margin-block-start: 0; }

.legal__body p {
  margin-block-start: var(--sp-sm);
}

.legal__body ul,
.legal__body ol {
  margin-block-start: var(--sp-sm);
  padding-inline-start: 1.25em;
}

.legal__body ul { list-style: disc; }
.legal__body ol { list-style: decimal; }

.legal__body li + li { margin-block-start: var(--sp-2xs); }

.legal__body a { color: inherit; text-decoration: underline; }
