/* ==========================================================================
   Ulicny — components
   Each block below maps 1:1 to an ACF field group in docs/02-templates.md.
   Section comments name the group, so the port is mechanical.
   ========================================================================== */

/* --- Top banner (v3.0) ------------------------------ parts/top-banner.php --
   The 30-years strip above the header, on every page. Measured off the v3.0
   artboard as fractions of page width W: band 0.1094 W tall, ground #EFEFEF,
   content column 0.1375 -> 0.8625 W (narrower than the site wrap), the lockup
   at half its usual size, and the standard pill.

   It sits in normal flow, so it scrolls away; the fixed header rests below it
   (--banner-h) and rises to the top edge once stuck. */

.top-banner {
  background: #EFEFEF;
  /* grey strip 0.1042 W on the artboard, the white rule below it 0.0052 W */
  padding-block: clamp(0.7rem, 2.18vw, 3rem);
  /* The artboard leaves a white rule between the grey strip and the navy
     header — measured 0.0052 W. */
  border-bottom: clamp(4px, 0.52vw, 12px) solid var(--c-white);
}
.top-banner__inner {
  display: flex;
  align-items: center;
  /* The artboard sets the three as one centred group — lockup, message, then
     the pill immediately after the text — not spread edge to edge. */
  justify-content: center;
  /* artboard gaps are uneven: 69px after the lockup, 15px before the pill */
  gap: clamp(0.5rem, 0.8vw, 1rem);
  /* The artboard centres the group in 1392px at 1920. Manrope needs about
     40px more than Proxima Nova for the same sentence, and the lockup keeps
     its 69px right margin, so the group runs a little wider than drawn —
     which is what keeps the message on ONE line, as the artboard sets it. */
  max-width: min(1664px, 100%);
}
.top-banner__message {
  flex: 0 1 auto;
  color: var(--c-navy);
  font-weight: 600;
  /* comp ink 0.0154 W */
  font-size: clamp(0.8125rem, 1.5625vw, 1.875rem);   /* artboard: one line, 1018px wide at 1920 */
  line-height: 1.25;
  text-wrap: balance;
}
/* Half-size mark: the banner carries its own message, so the lockup is the
   mark alone — no tagline, no pill. */
/* Higher specificity than the 900px rule that gives .anniversary an auto
   right margin — in a flex row that margin ate the free space and pushed the
   message away from the mark. */
.top-banner .anniversary--compact {
  flex: 0 0 auto;
  margin-inline: 0 clamp(1rem, 3.6vw, 4.5rem);
}
.anniversary--compact .anniversary__label { font-size: clamp(0.5rem, 0.72vw, 0.75rem); }
.anniversary--compact .anniversary__number { font-size: clamp(2rem, 5.1vw, 5.5rem); }
.anniversary--compact .anniversary__years { font-size: clamp(0.7rem, 1.5vw, 1.6rem); }

@media (max-width: 700px) {
  /* Stacked, and the mark leads. */
  .top-banner__inner { flex-wrap: wrap; justify-content: center; text-align: center; }
  .top-banner__message { flex-basis: 100%; order: 3; }
}

/* --- Header ------------------------------------------------------ header.php */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  top: 0;
  /* The header travels WITH the banner: it starts one banner-height down and
     is pulled up as the banner scrolls away, reaching the top edge exactly as
     the banner leaves (motion.js sets the transform per frame). Doing this
     with `top` alone left the header hanging in mid-air over the hero until
     it snapped to the top. --banner-h holds the resting offset so the first
     paint is already correct, before any script runs. */
  transform: translateY(var(--header-y, var(--banner-h, 0px)));
  /* the comp leaves ~3.5% of page width above the logo */
  padding-block: clamp(1rem, 3vw, 3rem);
  transition: background var(--dur-fast) var(--ease-soft),
              padding    var(--dur-fast) var(--ease-soft);
}
/* No hairline under the stuck header. It used to carry
   box-shadow: 0 1px 0 rgba(255,255,255,.12) - a 1px row of navy + 12% white
   (rgb 47,100,142 against 18,78,126) drawn straight across the hero and the
   page heads, which the client read as a seam where two navy areas meet. At
   90% / 110% browser zoom it smeared across two rows. The header is the same
   token navy as the band under it, so without the hairline the top of every
   page is one solid colour, at page top and once stuck. */
.site-header.is-stuck {
  background: var(--c-navy);
  padding-block: var(--sp-3);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-8);
}

/* Real logo, recoloured white. The comp sets it at ~0.10 of page width. */
.site-logo { display: block; line-height: 0; }
.site-logo img {

  width: clamp(122px, 10vw, 168px);
  height: auto;
}

.site-nav ul {
  display: flex;
  /* The .xd leaves 96, 85 and 89px between the four items at 1920 — about
     4.7% of the page. We were at 36px, which read as one dense block. The
     floor keeps 24px at the 900px breakpoint, where the row is tightest. */
  gap: clamp(1.5rem, 4.69vw, 5.625rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.site-nav a {
  color: var(--c-white);
  font-family: var(--ff-display);
  /* 1rem = 16px. rem rather than a hard px so it still respects a browser
     font-size preference. The mobile overlay overrides this below. */
  font-size: clamp(0.9375rem, 1.354vw, 1.625rem);   /* .xd nav: 26px */
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  text-decoration: none;
  padding-block: var(--sp-2);
  transition: color var(--dur-fast) var(--ease-out);
}
/* Colour alone marks the current page — the comp has NO underline. A lime rule
   under the active item sat at a different height from the header's own bottom
   edge, which read as two misaligned lines. */
.site-nav a:hover,
.site-nav a[aria-current="page"] { color: var(--c-lime); }

.nav-toggle { display: none; }
/* Drawer chrome only exists below the desktop breakpoint. */
.site-nav__head,
.site-nav__contact,
.nav-backdrop { display: none; }

@media (max-width: 899px) {
  /* No transform here. It would make the header the containing block for the
     drawer and the backdrop — both position:fixed — and collapse them to the
     header's own height. Any transform does this, including an identity one,
     so it cannot be left on for the pages that have no banner. */
  .site-header {
    transform: none;
    top: var(--header-y, var(--banner-h, 0px));
  }

  .nav-toggle {
    position: relative;
    display: block;
    width: 44px; height: 44px;
    margin-right: -10px;              /* bars' ink on the gutter, not the box */
    background: none; border: 0; cursor: pointer;
    color: var(--c-white);
  }

  /* Three drawn bars, not the ☰ character. The glyph's weight and bar spacing
     are whatever the font decides, and Manrope's is thin and cramped at this
     size; these are 22x2 with a real 7px rhythm. The middle bar IS the
     element; the outer two are its pseudo-elements. The toggle no longer
     folds into a cross: the drawer carries its own close button, top-right,
     and the toggle sits under the backdrop while the drawer is open. */
  .nav-toggle__bars,
  .nav-toggle__bars::before,
  .nav-toggle__bars::after {
    position: absolute;
    left: 50%;
    width: 22px;
    height: 2px;
    margin-left: -11px;
    border-radius: 2px;
    background: currentColor;
  }
  /* Hidden while the drawer is open. It sits under the backdrop, but the
     backdrop is only 60% opaque, so the bars still read through it next to
     the drawer's own close button — two menu controls at once. Opacity, not
     display, so the header row does not reflow, and so returning focus to it
     on close still works. */
  .nav-toggle[aria-expanded="true"] { opacity: 0; pointer-events: none; }

  .nav-toggle__bars { top: 50%; margin-top: -1px; }
  .nav-toggle__bars::before { content: ""; top: -7px; }
  .nav-toggle__bars::after  { content: ""; top:  7px; }

  /* --- Drawer --- */
  /* Slides in from the left and stops short of the full width, so a strip of
     the page stays visible behind the backdrop - it reads as a layer over the
     site rather than a new page, and tapping that strip closes it. */
  .site-nav {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 3;
    width: min(86vw, 400px);
    display: flex;
    flex-direction: column;
    padding: clamp(1rem, 3vw, 1.5rem) clamp(1.5rem, 5vw, 2.5rem) clamp(1.5rem, 5vw, 2.5rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--c-navy);
    box-shadow: 24px 0 64px rgba(3, 18, 40, 0.45);
    transform: translateX(-100%);
    visibility: hidden;
    /* visibility steps at the END of a transition, so a plain
       `visibility var(--dur-base)` left the panel unfocusable for 600ms after
       opening - and .focus() on a hidden element silently does nothing. It
       flips instantly on the way in, and waits for the slide on the way out. */
    transition: transform var(--dur-base) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .site-nav.is-open {
    transform: none;
    visibility: visible;
    transition: transform var(--dur-base) var(--ease-out),
                visibility 0s linear 0s;
  }

  .site-nav__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 44px;
    margin-bottom: clamp(2rem, 7vh, 3.5rem);
  }
  .site-nav__logo { display: block; line-height: 0; }
  .site-nav__logo img { width: 122px; height: auto; }

  /* Drawn cross, same 22x2 stroke as the hamburger bars. */
  .site-nav__close {
    position: relative;
    flex: none;
    width: 44px; height: 44px;
    margin-right: -10px;
    background: none; border: 0; cursor: pointer;
    color: var(--c-white);
    transition: color var(--dur-fast) var(--ease-out);
  }
  .site-nav__close::before,
  .site-nav__close::after {
    content: "";
    position: absolute;
    left: 50%; top: 50%;
    width: 22px; height: 2px;
    margin: -1px 0 0 -11px;
    border-radius: 2px;
    background: currentColor;
    transform: rotate(45deg);
  }
  .site-nav__close::after { transform: rotate(-45deg); }
  .site-nav__close:hover { color: var(--c-lime); }

  .site-nav ul { flex-direction: column; gap: 0; }
  .site-nav li + li { border-top: 1px solid rgba(255, 255, 255, 0.12); }
  .site-nav a { font-size: var(--fs-md); }
  .site-nav ul a { display: block; padding-block: 1rem; }

  /* Links follow the drawer in, a beat behind it and one after another. */
  .site-nav li {
    opacity: 0;
    transform: translateX(-16px);
    transition: opacity var(--dur-fast) var(--ease-out),
                transform var(--dur-base) var(--ease-out);
  }
  .site-nav.is-open li { opacity: 1; transform: none; }
  .site-nav.is-open li:nth-child(1) { transition-delay: 120ms; }
  .site-nav.is-open li:nth-child(2) { transition-delay: 170ms; }
  .site-nav.is-open li:nth-child(3) { transition-delay: 220ms; }
  .site-nav.is-open li:nth-child(4) { transition-delay: 270ms; }

  /* Contact pinned to the foot of the drawer - the same two lines the footer
     carries, so nothing new is being said. */
  .site-nav__contact {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin-top: auto;
    padding-top: var(--sp-8);
  }
  .site-nav .site-nav__contact a {
    font-family: var(--ff-body, inherit);
    font-size: 0.9375rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--c-panel-copy);
    padding-block: 0.6rem;
  }
  .site-nav .site-nav__contact a:hover { color: var(--c-lime); }

  .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 2;
    background: rgba(6, 24, 46, 0.6);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur-base) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .nav-backdrop.is-open {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--dur-base) var(--ease-out),
                visibility 0s linear 0s;
  }
}

/* Phones held sideways are ~390px tall, and at the portrait spacing the two
   contact links fell below the fold. The drawer does scroll, but the whole
   menu should be visible at once, so the rhythm tightens on short screens. */
@media (max-width: 899px) and (max-height: 500px) {
  .site-nav { padding-block: 0.75rem 1rem; }
  .site-nav__head { margin-bottom: 0.75rem; }
  .site-nav ul a { padding-block: 0.55rem; }
  .site-nav__contact { flex-direction: row; gap: var(--sp-6); padding-top: var(--sp-4); }
  .site-nav .site-nav__contact a { padding-block: 0.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  .site-nav,
  .site-nav li,
  .nav-backdrop { transition: none !important; }
}

/* --- Hero -------------------------------------------------- ACF: `hero` */

.hero {
  display: flex;
  align-items: flex-end;
  min-height: var(--hero-h);
  padding-top: var(--sp-24);
  padding-bottom: var(--hero-pb);
  background: var(--c-navy);
  color: var(--c-white);
}
/* The comp sets the hero eyebrow in SENTENCE case - only the parallax panels
   use uppercase. .u-eyebrow uppercases by default, so undo it here. */
.hero__eyebrow {
  color: var(--c-white);
  font-weight: 500;   /* client note: 500, not the eyebrow default */
  /* containing block for the typewriter caret */
  position: relative;
  /* 23ch = ~452px at this size. "…and design" measures 429px and "…design by"
     469px, so the break lands after "design", as it does in the comp.
     Manrope’s 0 advance is 0.658em, so ch is wider here than it looks. */
  /* v3.0 sets this line unbroken at 1920; it still wraps naturally when narrow */
  max-width: 58ch;
  text-transform: none;
  letter-spacing: 0;
}
/* The caret is created and removed by motion.js, never authored in the
   markup. Absolute, so it has zero effect on line breaking - the point of
   the whole approach is that the eyebrow's two lines never move. Cyan
   because that is the colour of the answer it is waiting for. */
.type-caret {
  position: absolute;
  top: 0;
  left: 0;
  width: max(2px, 0.075em);
  background: var(--c-cyan);
  pointer-events: none;
  will-change: transform;
}

.hero__title {
  margin-top: var(--sp-4);
  color: var(--c-cyan);
  max-width: 24ch;   /* "INSTITUTIONS, ADVISORS" sets on one line, as drawn */
}

/* --- Intro + 30th lockup ------------------------------ ACF: `intro` block */

/* Measured off the comp, as fractions of CONTENT width:
     body column 0.62 | gap 0.15 | lockup column 0.23
   The lead is NOT inside the left column - it spans the full width above
   both, which is why it sits in its own element. */
/* The comp wraps the lead at 0.766 W (≈1103px at a 1440 viewport), breaking
   after "…of the ways we". Measured at this size: that line is 48.4ch and
   adding "interact," pushes it to 55.1ch, so 51ch puts the break where the
   comp puts it. */
.intro__lead { max-width: 51ch; }

.intro__grid {
  display: grid;
  gap: var(--sp-12);
  align-items: start;
  margin-top: clamp(2rem, 5.5vw, 5rem);
}
@media (min-width: 900px) {
  .intro__grid {
    grid-template-columns: 62.1% 22.9%;   /* the remaining 15% is the gap */
    justify-content: space-between;
    gap: var(--sp-8);
  }
}
.intro__body { max-width: var(--measure); }
.intro__body p + p { margin-top: var(--sp-4); }

/* The comp centres the lockup at 0.807 W. Left-aligned inside its column and
   capped, so its own centre lands there — centring it in the full column put
   it at 0.847 W. This affects the homepage intro too, which shares the part. */
.anniversary {
  text-align: center;
  /* Sized to its own widest line rather than a fixed cap. A hard max-width
     was overflowed above 1680px, where the type keeps growing to its clamp
     maxima - and text overflowing a centred block spills right, dragging its
     visual centre off the numeral. max-content can never overflow. */
  width: max-content;
  max-width: 100%;
  margin-inline: auto;              /* centred when the columns stack */
}
@media (min-width: 900px) {
  .anniversary { margin-inline: 0 auto; }   /* column start once side by side */
}
/* 30th lockup — every value measured off the comp (p.3) as a fraction of
   page width W. "CELEBRATING" and "YEARS" are a SLAB serif; the "30" is a
   geometric sans (same treatment as the Who-we-are stat numerals), NOT a
   serif. All three are tracked to roughly equal width (~0.12 W). */

/* No letter-spacing compensation on these two: measured, the centring is
   already correct without it. margin-right:-0.2em pushed them ~5px right and
   text-indent:0.2em ~13px right. */
.anniversary__label {
  font-family: var(--ff-slab);
  font-weight: 700;
  font-size: clamp(min(0.8rem, 5vw), 1.69vw, 1.75rem);   /* comp cap 0.0104 W */
  line-height: 0.8;                           /* box hugs the caps */
  color: var(--c-lockup-label);
  text-transform: uppercase;
  /* comp width 0.1218 W. 0.2em overflowed the 185px lockup box, and text that
     overflows a centred block spills right, shifting its visual centre. */
  letter-spacing: 0.08em;
}

.anniversary__number {
  display: block;
  font-family: var(--ff-display);              /* geometric sans, not serif */
  font-weight: 400;
  font-size: clamp(min(4rem, 22vw), 10.68vw, 12.8rem);    /* .xd cap: 148 at 1920 */
  line-height: 0.8;
  letter-spacing: 0.01em;
  margin-right: -0.01em;
  margin-top: 0.05em;                          /* comp gap 0.177 x cap30 */
  color: var(--c-lockup-num);
}

.anniversary__years {
  font-family: var(--ff-slab);
  font-weight: 700;
  font-size: clamp(min(1.4rem, 9vw), 3.5vw, 3.79rem);      /* comp cap 0.0216 W */
  line-height: 0.8;
  color: var(--c-lockup-years);
  text-transform: uppercase;
  letter-spacing: 0.13em;                      /* comp width 0.1250 W */
  margin-top: 0.42em;                          /* comp gap 0.198 x cap30 */
}

.anniversary__tag {
  /* Sans and UPRIGHT in the comp - it was italic serif here. */
  font-family: var(--ff-body);
  font-style: normal;
  font-size: clamp(0.875rem, 1.49vw, 1.46rem);
  line-height: 1.3;
  color: var(--c-ink);
  margin-top: 1.15em;                          /* more air than the comp (0.344 x cap30) */
}

.anniversary .u-pill { margin-top: 1.25em; }   /* more air than the comp (0.240 x cap30) */

/* --- Parallax panels ------------------------ ACF: `parallax_panels` repeater */

.panels { background: var(--c-navy); }

.panel {
  position: relative;
  overflow: hidden;                 /* required: contains the parallax layer */
  isolation: isolate;
  padding-block: clamp(4rem, 10vw, 9rem);
  color: var(--c-white);
}
/* No dividing border - the panels are solid navy. The comp also puts a short
   white tick at the bottom-left of each; removed at the client's request. */

/* Optional per-panel background image, parallaxed at 0.88.
   Oversized so the translate never exposes an edge. */
.panel__bg {
  position: absolute;
  inset: -12% 0;
  z-index: -1;
  background-size: cover;
  background-position: center;
  opacity: 0.28;
  will-change: transform;
}

/* The comp breaks this after "DESIGN". Constrain so the break lands there
   rather than carrying "BY" up onto the first line. */
.panel__eyebrow { color: var(--c-white); max-width: 29ch; }
.panel__title   { margin-top: var(--sp-3); color: var(--c-cyan); font-size: var(--fs-3xl); }
/* The comp sets the panel body in a PALE CYAN (#C1EBF8), not white — measured
   from the stroke cores, b-r = 68 against the eyebrow's 14. This is specific
   to the parallax panels: the navy page-heads on Who we are / How we work
   measure pure white. */
.panel__body {
  margin-top: var(--sp-6);
  max-width: 58ch;
  line-height: var(--lh-copy);
  color: var(--c-panel-copy);
}
.panel .u-pill  { margin-top: var(--sp-8); }

/* --- Conversation band ------------------- ACF (Theme Options): `conversation` */

/* Every value measured off the comp (p.3) as a fraction of page width W.
   The band is asymmetric: much more air below than above. */
.conversation {
  /* The full-bleed CYAN bar belongs to THIS section, not to the panels above
     it. Confirmed in the comp: all four page mocks carry it, including the
     three that have no parallax panels. Measured 0.0045–0.0059 W tall. */
  border-top: clamp(4px, 0.52vw, 10px) solid var(--c-cyan);
  /* v3.0: shorter than the 2.0 comp — 0.2682 W on the home artboard and
     0.2625 W on the Approach one. */
  padding-top: clamp(1.5rem, 4.9vw, 6rem);
  padding-bottom: clamp(2.5rem, 6vw, 7.5rem);
  /* v3.0: the band is navy, not white (markup comment 9, "change background
     to Ulicny dark blue"). The cyan rule stays as its top edge. */
  background: var(--c-navy);
  color: var(--c-white);
}
.conversation__title {
  color: var(--c-white);
  /* v3.0 runs larger: ink 37px at 1920 against 27px before */
  font-size: clamp(1.25rem, 2.604vw, 3.125rem);
  font-weight: 700;
  line-height: 1.2;
}
.conversation__body {
  margin-top: clamp(1.5rem, 2.95vw, 2.75rem); /* comp gap 0.0433 W */
  /* comp body column 0.5224 W. ch scales with the font; vw would over-widen
     on large screens. */
  max-width: 70ch;   /* v3.0 sets this on one line at 1920 */
  /* marketing leading, not the long-form 1.7 it was inheriting */
  font-size: clamp(1rem, 1.875vw, 2.25rem);   /* .xd: 36px */
  line-height: var(--lh-copy);
}
.conversation .u-pill { margin-top: clamp(1.25rem, 3.44vw, 4.1rem); }  /* v3.0 artboard 0.0344 W; the 2.0 comp had 0.0297 */

/* --- Footer ------------------------------------------------------ footer.php */

.site-footer {
  background: var(--c-charcoal);
  color: var(--c-white);
  /* The footer runs SMALLER than body copy. Measured off the comp: text is
     0.0132 W (~19px at 1440), not the 0.016 W of --fs-base. It was
     inheriting --fs-base and reading far too large. */
  font-size: clamp(0.875rem, 1.354vw, 1.625rem);   /* .xd footer: 26px */
  line-height: 1.4;
}
.site-footer a { color: var(--c-white); text-decoration: none; }
.site-footer a:hover { color: var(--c-lime); }

.site-footer__grid {
  display: grid;
  gap: var(--sp-12);
}
@media (min-width: 900px) {
  /* comp column starts: 0.059 / 0.355 / 0.664 W - i.e. equal thirds, not a
     wider third column. */
  .site-footer__grid { grid-template-columns: repeat(3, 1fr); gap: var(--sp-8); }
}

.footer-phonetic {
  font-family: var(--ff-serif);
  font-size: var(--fs-md);
  margin-bottom: clamp(1.25rem, 3vw, 2.5rem);   /* comp 0.0545 W */
}
/* It works as the footer's logo. --fs-md bottoms out at its 20px floor on
   every screen under ~820px, so on phones and tablets it read as a caption.
   Below the desktop breakpoint it gets its own size: 26px on a phone, 28px
   on a tablet. The desktop size is the comp's and stays. min() keeps the
   floor from doubling under browser text zoom. */
@media (max-width: 899px) {
  .footer-phonetic { font-size: clamp(min(1.625rem, 9vw), 3.65vw, 1.75rem); }
}

.site-footer ul { margin: 0; padding: 0; list-style: none; }
.site-footer li + li { margin-top: 0.25rem; }   /* comp pitch 0.0213 W */

.footer-heading {
  font-family: var(--ff-display);
  font-size: inherit;                 /* comp: same size as the column text */
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  margin-bottom: 0.15rem;
}
/* FAQ is the one heading that is also a link. It inherits the heading's look
   rather than the footer link's, so the column still reads as headings and
   items — the underline on hover is the only thing that gives it away. */
.footer-heading a {
  color: inherit;
  text-decoration: none;
  padding-block: var(--sp-2);          /* keeps the 24px target of its siblings */
}
.footer-heading a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
.footer-col + .footer-col { margin-top: 0.5rem; }   /* comp pitch 0.0240 W */

/* The comp breaks this after "…delivered to your". Measured at this size that
   line is 29.3ch and adding "inbox," takes it to 34.2ch, so 31ch puts the
   break where the comp puts it. */
.subscribe__intro { max-width: 31ch; }

.subscribe__form {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(0.5rem, 1.04vw, 1rem);   /* comp 0.0104 W */
  margin-top: var(--sp-4);
}
/* comp: 103x45 at 1440 (0.0713 W x 0.0313 W). The global pill padding made
   it ~130px, which pushed it onto its own line. */
.subscribe__form .u-pill {
  min-height: clamp(38px, 3.13vw, 46px);
  padding-inline: clamp(0.875rem, 2.2vw, 2rem);
}

.subscribe__input {
  /* comp: 292x44 at 1440 = 0.2027 W. A large flex-basis makes the row WRAP
     (wrapping is decided on the basis, before shrinking), so keep the basis
     small and cap the grown width instead. */
  flex: 1 1 170px;
  max-width: clamp(180px, 19.8vw, 285px);
  min-height: clamp(38px, 3.3vw, 48px);   /* comp 0.0329 W */
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 999px;
  background: var(--c-white);
  color: var(--c-ink);
  font: inherit;
  font-size: var(--fs-sm);
}

.site-footer__legal {
  /* No divider rule: the comp has none. This border was invented. */
  margin-top: clamp(2rem, 6.2vw, 5.5rem);   /* comp 0.0617 W */
  font-size: clamp(0.75rem, 1.042vw, 1.25rem);   /* .xd: 20px */   /* comp 0.0103 W */
  opacity: 0.7;
}

/* --- Social — X removed, LinkedIn retained per the appendix ---------------- */
.social { display: flex; gap: var(--sp-4); margin-top: var(--sp-6); }
.social a {
  display: grid; place-items: center;
  width: 36px; height: 36px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 50%;
}
.social a:hover { border-color: var(--c-lime); }

/* Helpers referenced by index.html (kept out of inline styles so they port
   cleanly into template files). */
.intro__paras { margin-top: var(--sp-8); }
.site-footer__inner {
  /* comp: 0.0385 W top, 0.0256 W bottom - it was --section-y (128px) both
     sides, which left a large dead area under the copyright. */
  padding-top: clamp(1.5rem, 3.2vw, 4rem);   /* measured ink lands at 0.0385 W */
  padding-bottom: clamp(1.5rem, 2.56vw, 3.25rem);
}

/* --- Pill chevron -------------------------------------------------------- */
.pill__chev { display: inline-block; }

/* --- Panel depth layer --------------------------------------------------- */
/* The comp's panels are flat navy with no photography, so there is nothing
   for "parallax panels of information" to act on. This is a soft tonal wash
   that trails the scroll — it reads as depth without inventing imagery.
   ⚠️ DESIGN DECISION, not in the comp. Flag it on the 17th.
   If a `background_image` is supplied per row, it layers behind this. */
.panel__glow {
  position: absolute;
  inset: -25% -10%;
  z-index: -1;
  pointer-events: none;
  /* Removed: the radial wash read as a gradient, and the client wants plain
     navy only. The layer stays so an optional background_image still
     parallaxes behind it. */
  background: none;
  will-change: transform;
}

/* Panels need a stacking context for the negative z-index layers to sit
   behind the copy but in front of the section ground. */
.panel > .u-wrap { position: relative; z-index: 1; }

/* --- Page head --------------------------------- shared: parts/page-head.php */
/* The comp uses this same band on Who we are, Our clients, How we work and
   Our thinking: navy, heading bottom-left, intro copy bottom-right. Shorter
   than the homepage hero. Build it once as a template part. */

.page-head {
  display: flex;
  align-items: flex-end;
  /* The comp makes this the SAME height as the homepage hero (0.56 W), not a
     shorter band. It was 52vh - viewport-HEIGHT relative - which gave 468px at
     1440x900 against the comp’s ~806px. */
  min-height: var(--hero-h);
  padding-top: var(--sp-24);
  /* NOT --hero-pb. The band is the same height as the homepage hero, but the
     comp sets its heading 0.0500 W above the bottom edge against the hero's
     0.0569 W - the smaller type sits closer to the floor. */
  padding-bottom: clamp(1.75rem, 4.18vw, 4.25rem);
  background: var(--c-navy);
  color: var(--c-white);
}
.page-head__grid {
  display: grid;
  gap: var(--sp-8);
  align-items: end;
}
@media (min-width: 900px) {
  /* .xd: the title box runs to 0.48 W, the intro column to 0.50 W. */
  .page-head__grid {
    grid-template-columns: 48% 50%;
    justify-content: space-between;
    gap: var(--sp-6);
  }
}
.page-head__title {
  color: var(--c-cyan);
  /* comp cap 0.0283 W -> ~54px at 1440 */
  /* One step DOWN from the homepage hero. The hero stays the largest type on
     the site; section heads sit under it. The width still lets the comp break
     land - "TACTICAL SUPPORT" fits one line, "STRATEGIC & TACTICAL" does not. */
  font-size: clamp(min(1.75rem, 13vw), 4.167vw, 5rem);   /* cap raised so 1920 is not clipped */
  /* Measured, not inherited: the comp sets the two lines 0.0462 W apart, and
     --lh-snug gave 0.0389 - tight enough that the two lines read as one mass. */
  line-height: 1.22;
  max-width: 20ch;
  text-wrap: wrap;
}
.page-head__intro {
  /* Every intro in the v3.0 .xd — Team, Services and Clients — is 36px
     ProximaNova-Semibold on a 48px pitch. This was 26px regular at 1.29. */
  font-size: clamp(min(1rem, 5vw), 1.875vw, 2.25rem);
  font-weight: 600;
  line-height: 1.333;
}
/* Below 900 the head is a single column and nothing constrains the intro —
   86 characters a line at 768, 95 in landscape. The grid track is the measure
   above that, so this is scoped to where there is no track. */
@media (max-width: 899px) {
  .page-head__intro { max-width: 36em; }
}
/* comp: 0.0432 W between the last line of one paragraph and the first of the
   next, against a 0.0224 W line pitch - so the gap itself is 0.0208 W, not the
   16px of --sp-4. */
.page-head__intro > p + p { margin-top: 1.2em; }

/* --- FAQ ----------------------------------------------- page-faq.php ---- */
/* The comp does not design this page — p.5 col.5 is the 30th mock with an
   annotation box on it. Built from the brief instead: a long-form page whose
   VALUE IS ITS STRUCTURE, because it is aimed at AI retrieval. So the styling
   does one job — make a deep heading hierarchy legible at a glance:
   section title > question > answer, each a clear step apart. */

/* Constrain the CHILDREN, not the wrap. A max-width on `.u-wrap` centres the
   whole column, which puts the text a third of the way across the page — the
   post template left-aligns its long-form to the content edge and this should
   match it. */
.faq__col > * { max-width: 46rem; }

.faq__intro {
  margin: 0;
  max-width: 62ch;
  color: var(--c-blue);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-heading);
}
.faq__intro a { color: inherit; text-underline-offset: 0.18em; }

/* Contents, deliberately NOT a sticky rail — the post template dropped its
   left rail at the client's request, and a sidebar here would read as putting
   it back. A plain ruled list at the top of the column does the same job. */
.faq__contents {
  margin-top: clamp(2rem, 4vw, 3.5rem);
  padding: clamp(1.25rem, 2vw, 1.75rem) 0;
  border-top: 1px solid var(--c-rule);
  border-bottom: 1px solid var(--c-rule);
}
.faq__contents-title {
  margin: 0 0 var(--sp-4);
  color: var(--c-ink-soft);
  font-family: var(--ff-display);
  font-size: clamp(0.75rem, 0.9vw, 0.875rem);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.faq__contents ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-8);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: faq;
}
.faq__contents li { counter-increment: faq; }
.faq__contents a {
  /* inline-block + padding, because these are NAVIGATION. An inline link's box
     is its font's content area — 21px at this size — regardless of
     line-height, which is under the 24px minimum. The links inside the answer
     prose below are exempt: WCAG 2.5.8 excludes targets in a sentence. */
  display: inline-block;
  padding-block: var(--sp-2);
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(0.9375rem, 1.2vw, 1.125rem);
  font-weight: 600;
  text-decoration: none;
}
.faq__contents a::before {
  content: counter(faq) ".";
  margin-right: 0.45em;
  color: var(--c-ink-soft);
  font-weight: 400;
}
.faq__contents a:hover { color: var(--c-navy); text-decoration: underline; text-underline-offset: 0.18em; }

.faq__section {
  /* clears the fixed header when a contents link jumps here */
  scroll-margin-top: clamp(5rem, 9vw, 8rem);
  margin-top: clamp(3rem, 6.5vw, 6rem);
}
.faq__section-title {
  margin: 0 0 clamp(1.5rem, 2.6vw, 2.5rem);
  padding-bottom: var(--sp-4);
  border-bottom: 2px solid var(--c-blue);
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(1.375rem, 2.2vw, 2.1rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--ls-heading);
}

.faq__item + .faq__item {
  margin-top: clamp(1.75rem, 3vw, 2.75rem);
  padding-top: clamp(1.75rem, 3vw, 2.75rem);
  border-top: 1px solid var(--c-rule);
}
.faq__q {
  margin: 0 0 var(--sp-4);
  color: var(--c-navy);
  font-family: var(--ff-display);
  font-size: clamp(1.0625rem, 1.5vw, 1.4375rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}
.faq__a p,
.faq__a ul {
  margin: 0;
  font-size: clamp(0.9375rem, 1.32vw, 1.25rem);
  line-height: var(--lh-body);
  color: var(--c-ink);
}
/* `*` contributes nothing to specificity, so `.faq__a > * + *` is (0,1,0) and
   loses to `.faq__a p` at (0,1,1) — the reset above would win and the answers
   would run together with no gap. Each pair is named so it outranks it. */
.faq__a p + p,
.faq__a p + ul,
.faq__a ul + p { margin-top: var(--sp-4); }
.faq__a ul { padding-left: 1.2em; }
.faq__a li + li { margin-top: var(--sp-3); }
.faq__a a { color: var(--c-blue); text-underline-offset: 0.18em; }
/* A link that IS the whole sentence has no surrounding text to be exempted
   by, so it gets a real target. The links sitting inside a paragraph of prose
   do not — padding those would break the leading. */
.faq__more { display: inline-block; padding-block: var(--sp-2); }

/* An unanswered question, marked as such on the page rather than quietly
   filled with something plausible. */
.faq__todo {
  padding-left: var(--sp-4);
  border-left: 2px solid var(--c-rule);
  color: var(--c-ink-soft);
  font-style: italic;
}

.faq__note {
  margin-top: clamp(3rem, 6vw, 5.5rem);
  padding: clamp(1.25rem, 2.4vw, 2.25rem);
  background: var(--c-surface-alt);
  border-left: 3px solid var(--c-blue);
}
.faq__note p {
  margin: 0;
  color: var(--c-ink-soft);
  font-size: clamp(0.875rem, 1.1vw, 1.0625rem);
  line-height: 1.6;
}
.faq__note p + p { margin-top: var(--sp-4); }
/* `.faq__note p` is (0,2,1) because of the type selector, so a bare
   `.faq__note-title` at (0,1,0) loses the colour. Matched shape, not
   !important — the same trap the post body hit. */
.faq__note p.faq__note-title {
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-weight: 700;
}

/* --- Case studies ------------------------- page-case-study-*.php (PHASE 2) */
/* Comp p.5, columns 2-4. All three pages are identical but for one word, and
   all three reuse the HOMEPAGE PANEL treatment for the hero — which is the
   point of them: each panel lands on its own page and finishes its sentence.

   Measured off the comp, as fractions of page width W:
     hero band     0 -> 0.3146      eyebrow y 0.1525, pitch 0.0248
     headline      y 0.2145, cap 0.0543
     body band     0.3165 -> 1.0734,  lead ink 0.3651
     left column   0.0505 -> ~0.50   right column starts 0.577
     cards band    1.0763 -> 1.5033,  cards 0.2803 W at 9:8, gap 0.0285 */

.case-hero {
  display: flex;
  align-items: flex-end;
  min-height: clamp(13rem, 31.46vw, 34.8rem);
  padding-top: var(--sp-24);
  /* comp leaves 0.0458 W under the headline ink */
  padding-bottom: clamp(1.5rem, 3.9vw, 4rem);
  background: var(--c-navy);
  color: var(--c-white);
}
.case-hero__eyebrow {
  color: var(--c-white);
  /* SMALLER than --fs-eyebrow: the comp sets this at cap 0.0114 W (~23px at
     1440) against the eyebrow token's 30px, and tracks it wider. */
  font-size: clamp(0.8125rem, 1.583vw, 1.5rem);
  letter-spacing: 0.17em;
  /* comp line pitch 0.0248 W against a 0.0114 W cap — 1.57, much looser than
     --lh-snug. Two lines of tracked uppercase need the air. */
  line-height: 1.57;
  /* Two lines, breaking after "design" as the comp does. At 26ch it took
     three, which pushed the whole bottom-aligned block up 44px. */
  max-width: 62ch;
}
.case-hero__title {
  margin-top: var(--sp-4);
  color: var(--c-cyan);
  /* comp cap 0.0543 W — the panel size, not the page-head size */
  font-size: var(--fs-3xl);
}

/* The comp's white band runs to 0.7579 W — a third of it empty below the
   copy, because the aside content that would fill it does not exist yet.
   Matched at first; the client asked for it balanced instead, so the band
   keeps --u-band's symmetric padding and will grow on its own once the aside
   is written. */
.case-body__grid {
  display: grid;
  gap: clamp(2rem, 5vw, 4.5rem);
}
@media (min-width: 900px) {
  /* comp: the copy column ends at ~0.50 W and the aside starts at 0.577 W */
  .case-body__grid {
    grid-template-columns: 52% 41.4%;
    justify-content: space-between;
    align-items: start;
    gap: var(--sp-8);
  }
}
/* Grid children default to min-width:auto, which lets a long unbroken word
   push the column wider than its track. */
.case-body__main,
.case-aside { min-width: 0; }

.case-body__copy { margin-top: clamp(1.5rem, 3.2vw, 3rem); }
.case-body__copy p + p { margin-top: var(--sp-4); }

.case-aside__title {
  margin: 0;
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-heading);
}
/* A short rule over a tracked label — the same opening move the featured lead
   on the blog uses, so the two read as one system. */
.case-aside__eyebrow {
  position: relative;
  margin: 0 0 var(--sp-6);
  padding-top: var(--sp-5);
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(0.75rem, 0.95vw, 0.9375rem);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.case-aside__eyebrow::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: clamp(2.5rem, 4vw, 4rem);
  height: 3px;
  background: var(--c-blue);
}

.case-aside__lead,
.case-aside__note {
  max-width: 40ch;
  color: var(--c-ink-soft);
  font-size: clamp(0.875rem, 1.05vw, 1rem);
  line-height: 1.55;
}
.case-aside__lead { margin: var(--sp-5) 0 0; }
.case-aside__note { margin: var(--sp-5) 0 0; }

/* The brief's three lines, quoted, as a ruled list with a status column.
   The status is the point: two are built, one is the gap, and putting that in
   its own column makes it scannable instead of buried in a paragraph. */
.case-aside__list {
  margin: clamp(1.5rem, 2.4vw, 2.25rem) 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--c-rule);
}
.case-aside__list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: clamp(0.7rem, 1.1vw, 1rem);
  border-bottom: 1px solid var(--c-rule);
}
.case-aside__item {
  color: var(--c-ink);
  font-size: clamp(0.875rem, 1.05vw, 1rem);
  line-height: 1.4;
}
.case-aside__state {
  flex: none;
  color: var(--c-blue);
  font-family: var(--ff-display);
  /* floor was 0.625rem — 10px, under the 11px readability floor the audit
     enforces, and it hit that floor on every screen below 1080px. */
  font-size: clamp(min(0.75rem, 4vw), 0.85vw, 0.8125rem);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}
.case-aside__state--open { color: var(--c-ink-soft); }

.case-articles {
  background: var(--c-navy);
  color: var(--c-white);
  /* comp: heading ink 0.0438 W below the band top, 0.0877 W under the cards */
  padding-top: clamp(2rem, 5.2vw, 6rem);
  padding-bottom: clamp(2.5rem, 7.4vw, 8.5rem);
}
.case-articles__title {
  margin: 0 0 clamp(1.5rem, 3.4vw, 3.25rem);
  font-family: var(--ff-display);
  font-size: clamp(1.125rem, 1.9vw, 1.85rem);
  font-weight: 600;
  letter-spacing: var(--ls-heading);
}

/* --- 30 years: the lockup ------------------- page-templates/30-years.php --
   v3.0 keeps only the enlarged lockup on navy; the film and the timeline are
   gone, and their rules with them. The letter that replaces them is at the
   foot of this file with the rest of the v3.0 work. */

/* Inverted, and about 1.86x the homepage lockup — measured, not eyeballed:
   1x artboard ink at 1920: CELEBRATING 26, "30" 193, YEARS 54. Ours was
   30 / 240 / 67, so each size comes down by its own ratio. */
.anniversary--hero { margin-inline: auto; }
.anniversary--hero .anniversary__label {
  color: var(--c-white);
  font-size: clamp(min(1rem, 7vw), 1.99vw, 2.125rem);
  letter-spacing: 0.23em;
}
.anniversary--hero .anniversary__number {
  color: var(--c-white);
  font-size: clamp(min(5rem, 26vw), 15.36vw, 16rem);
  /* comp gap CELEBRATING ink -> 30 ink is 0.0257 W here, against 0.0194 at
     the shared rule's 0.05em. Scoped, so the homepage lockup is untouched. */
  margin-top: 0.083em;
  /* The shared rule cancels the numerals' trailing letter-space with
     margin-right:-0.01em. At 1.4px on the homepage that is invisible; at this
     size it is 2.75px, and it makes the glyphs overflow .anniversary's
     max-content box — a real 3px spill above 1280px. Dropping it moves the
     measured centre by about a pixel (0.4969 -> 0.4962 W against the comp's
     0.4993), which is well inside the noise. */
  margin-right: 0;
}
.anniversary--hero .anniversary__years {
  color: var(--c-white);
  font-size: clamp(min(1.4rem, 7vw), 4.05vw, 4.35rem);
  letter-spacing: 0.30em;
  margin-top: 0.56em;                          /* comp gap 0.0286 W */
}

/* --- Blog archive --------------------------- home.php / archive.php ----- */
/* ⚠️ The comp does not design this page. Page 4 gives the head and the word
   BLOG, then an annotation. Everything here is built from the written brief -
   "simplify, get rid of a bunch of stuff, the type hierarchy is kind of
   clunky" - in the language the rest of the site already establishes. */

/* The comp's head for this page is SHORTER than the others: 0.3147 W against
   0.5623 W, and it carries no intro column.
   .page-head--no-intro takes the same height: a head whose optional intro is
   empty is the same case as this one - a title alone - and the full 0.56 W
   band around a single heading is mostly empty navy. */
.page-head--short,
.page-head--no-intro {
  min-height: clamp(13rem, 31.51vw, 37.8rem);   /* .xd short head: 605 at 1920 */
  /* comp leaves 0.0440 W under the title ink here, against 0.0500 W on the
     tall heads - the band is shorter, so the type sits closer to its floor. */
  padding-bottom: clamp(1.5rem, 3.63vw, 3.7rem);
}

/* The comp puts a "BLOG" label here, 0.0783 W below the navy band. Dropped at
   the client's request — the page head already says "Our thinking" and the
   label repeated it. Without that anchor the band goes back to the site's
   standard section rhythm (--section-y) rather than the measured offset. */

/* Section labels — "All articles", "Keep reading". Sized from the comp's
   BLOG: cap 0.0179 W, in --c-blue. */
.section-label {
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(1.25rem, 2.604vw, 3.125rem);   /* .xd: 50px */
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  margin: 0 0 clamp(1.5rem, 3.5vw, 3.5rem);
}

/* --- Featured lead ------------------------------------------------------ */
/* An editorial split rather than a big card: photo on one side, copy on the
   other, set on the type scale the rest of the site uses and closed with the
   same lime pill. The hierarchy IS the feature — a featured section that lays
   out like the grid below it is just the top row again, and the new tag buys
   nothing. */

.flead {
  display: grid;
  gap: clamp(1.5rem, 3vw, 4rem);
  color: inherit;
  text-decoration: none;
}
@media (min-width: 900px) {
  .flead {
    grid-template-columns: 57% 1fr;
    align-items: center;
    gap: clamp(2rem, 5vw, 5.5rem);
  }
}
.flead__media {
  display: block;
  overflow: hidden;
  background: var(--c-navy);
  /* A real ratio, so the photo is composed rather than cropped to whatever
     height the column next to it happens to be. */
  aspect-ratio: 3 / 2;
}
.flead__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 700ms var(--ease-out);
}
.flead:hover .flead__media img,
.flead:focus-visible .flead__media img { transform: scale(1.03); }

.flead__body { display: block; }

/* A short rule above the label — the one piece of ornament on the page, and
   it is what makes the block read as a feature rather than a first row. */
.flead__eyebrow {
  display: block;
  position: relative;
  padding-top: var(--sp-6);
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(0.75rem, 0.95vw, 0.9375rem);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.flead__eyebrow::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: clamp(2.5rem, 4vw, 4rem);
  height: 3px;
  background: var(--c-blue);
}
.flead__title {
  margin: var(--sp-4) 0 0;
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(1.5rem, 2.85vw, 2.75rem);
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}
.flead:hover .flead__title,
.flead:focus-visible .flead__title { color: var(--c-navy); }
.flead__meta {
  display: block;
  margin-top: var(--sp-4);
  color: var(--c-ink-muted);
  font-size: clamp(0.8125rem, 1.02vw, 1rem);
}
.flead .u-pill { margin-top: clamp(1.5rem, 2.4vw, 2.25rem); }

/* --- Featured pair, and the related row on a post ----------------------- */

.featured {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.75rem);
}
/* Under a lead the pair needs room from it. When two are featured the pair is
   the whole band and follows its heading, which carries its own space. */
.flead + .featured--pair { margin-top: clamp(2.5rem, 5.5vw, 6rem); }
@media (min-width: 700px) {
  .featured--pair { grid-template-columns: repeat(2, 1fr); gap: clamp(1.5rem, 3.4vw, 3.5rem); }
}

.fcard {
  display: block;
  color: inherit;
  text-decoration: none;
  min-width: 0;
}
.fcard__media {
  display: block;
  overflow: hidden;              /* clips the hover scale */
  background: var(--c-navy);     /* holds the space before the image decodes */
  aspect-ratio: 16 / 9;
}
.fcard__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow, 600ms) var(--ease-out);
}
.fcard:hover .fcard__media img,
.fcard:focus-visible .fcard__media img { transform: scale(1.03); }

.fcard__title {
  margin: var(--sp-3) 0 0;
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(1.125rem, 1.65vw, 1.6rem);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}
.fcard:hover .fcard__title,
.fcard:focus-visible .fcard__title { color: var(--c-navy); }

.fcard__meta {
  margin: var(--sp-3) 0 0;
  color: var(--c-ink-muted);
  font-size: clamp(0.8125rem, 1.02vw, 1rem);
  line-height: 1.4;
}
.fcard__meta > span,
.fcard__meta > time { white-space: nowrap; }

/* --- Archive tools: category filter + search ---------------------------- */
/* Filters left, search right, on one row that wraps to two on a phone. */

.archive-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-8);
  margin-bottom: clamp(1.75rem, 3.2vw, 3rem);
}
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.filter {
  min-height: 40px;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--c-rule);
  border-radius: 999px;
  background: transparent;
  color: var(--c-ink-muted);
  font-family: var(--ff-display);
  font-size: clamp(0.8125rem, 0.95vw, 0.9375rem);
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.filter:hover { color: var(--c-blue); border-color: var(--c-blue); }
/* The active filter is filled, not outlined — the one state that has to read
   at a glance. Lime stays reserved for the CTA pill. */
.filter.is-active,
.filter[aria-pressed="true"] {
  background: var(--c-navy);
  border-color: var(--c-navy);
  color: var(--c-white);
}

.archive-search { flex: 0 1 16rem; }
.archive-search__input {
  width: 100%;
  min-height: 40px;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--c-rule);
  border-radius: 999px;
  background: var(--c-surface);
  color: var(--c-ink);
  font-family: var(--ff-body);
  font-size: clamp(0.875rem, 1vw, 1rem);
}
.archive-search__input::placeholder { color: var(--c-ink-muted); }
.archive-search__input:focus-visible { border-color: var(--c-blue); outline-offset: 1px; }

/* The empty state is a real answer, not an apology squeezed onto one line:
   the message, a line telling the reader what to try, and the way out on its
   own. It is the only thing on screen when it shows, so it carries the
   generous rhythm the rest of the page uses rather than sitting flush under
   the filters. */
.archive-empty {
  margin: clamp(2.5rem, 6vw, 5rem) 0 clamp(2rem, 5vw, 4rem);
  max-width: 52ch;
}
.archive-empty__title {
  margin: 0;
  color: var(--c-navy);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: var(--ls-heading);
}
.archive-empty__hint {
  margin: clamp(0.5rem, 1vw, 0.875rem) 0 0;
  color: var(--c-ink-muted);
  font-size: var(--fs-base);
  line-height: var(--lh-copy);
}
.archive-empty__action { margin-top: clamp(1.25rem, 2.4vw, 2rem); }
.archive-empty__action { display: inline-flex; }

/* --- Archive tiles ------------------------------------------------------ */
/* The live site's card layout, toned down: ONE navy scrim on every tile
   instead of a different colour per card, a single type size, and no author
   line (every post is Jeanie's). The gradient is stronger at the foot, where
   the type sits, and lighter at the head so the photograph still reads. */

.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
  gap: clamp(1rem, 1.7vw, 1.75rem);
  list-style: none;
  margin: 0;
  padding: 0;
}
@media (min-width: 900px) { .tile-grid { grid-template-columns: repeat(3, 1fr); } }

/* Explicit, because .tile carries its own display and would otherwise beat
   the UA sheet's [hidden] rule when the filter hides a card. */
.tile[hidden] { display: none; }

.tile a {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--c-navy);
  color: var(--c-white);
  text-decoration: none;
}
.tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 700ms var(--ease-out);
}
.tile a::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Flat navy, not a gradient: the client wants plain blue only. The navy
     comes from the token, so it follows --c-navy. */
  background: color-mix(in srgb, var(--c-navy) 82%, transparent);
  transition: opacity var(--dur-base) var(--ease-out);
}
.tile a:hover img,
.tile a:focus-visible img { transform: scale(1.04); }
.tile a:hover::after,
.tile a:focus-visible::after { opacity: 0.86; }

.tile__body {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  display: block;
  padding: clamp(1rem, 1.5vw, 1.5rem);
}
.tile__title {
  display: block;
  font-family: var(--ff-display);
  font-size: clamp(0.9375rem, 1.18vw, 1.1875rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: var(--ls-heading);
}
.tile__meta {
  display: block;
  margin-top: var(--sp-2);
  color: var(--c-panel-copy);
  font-size: clamp(0.75rem, 0.88vw, 0.875rem);
  line-height: 1.4;
}

/* The case-study variant: taller card (comp 9:8, against 16:9 on the
   archive), title sitting in the middle rather than at the foot, and a credit
   row pinned to the bottom — byline left, reading time in a chip on the
   right. Same tile, three declarations different. */
/* comp: cards 0.2803 W with a 0.0285 W gap, which is wider than the archive's */
.tile-grid--three {
  grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
  gap: clamp(1rem, 2.85vw, 3.1rem);
}
@media (min-width: 900px) { .tile-grid--three { grid-template-columns: repeat(3, 1fr); } }

.tile--credit a {
  display: flex;
  flex-direction: column;
  justify-content: center;
  aspect-ratio: 9 / 8;
}
.tile--credit .tile__body {
  position: static;
  /* Two rows: the title centres in whatever space is left, the credit line
     sits on the floor. Flex with margin-top:auto pinned the credit correctly
     but left the title jammed against the top edge — the comp has it around
     the middle. */
  display: grid;
  grid-template-rows: 1fr auto;
  flex: 1;
  padding: clamp(1.1rem, 1.8vw, 1.75rem);
}
.tile--credit .tile__title { align-self: center; }
.tile--credit .tile__title {
  font-size: clamp(1rem, 1.32vw, 1.375rem);
  line-height: 1.28;
}
.tile__credit {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: auto;
  padding-top: clamp(1rem, 2vw, 2rem);
}
.tile__by {
  color: var(--c-white);
  font-family: var(--ff-display);
  font-size: clamp(0.6875rem, 0.82vw, 0.8125rem);
  font-weight: 600;
}
.tile__read {
  flex: none;
  padding: 0.35em 0.85em;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.18);
  color: var(--c-white);
  font-family: var(--ff-display);
  font-size: clamp(0.6875rem, 0.82vw, 0.8125rem);
  font-weight: 600;
  white-space: nowrap;
}

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-top: clamp(2rem, 4vw, 4rem);
  font-family: var(--ff-display);
  font-weight: 600;
}
.pagination a { color: var(--c-blue); text-decoration: none; padding: var(--sp-2); }
.pagination a:hover { color: var(--c-navy); }
.pagination__current {
  color: var(--c-ink);
  padding: var(--sp-2);
  border-bottom: 2px solid var(--c-lime);
}
.pagination__next { margin-left: auto; letter-spacing: 0.06em; }

/* --- Single post ------------------------------------------- single.php -- */

.post-hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(20rem, 42vw, 46rem);
  padding-top: var(--sp-24);
  padding-bottom: clamp(2rem, 5vw, 5rem);
  /* The site navy's darker step, on client request: this hero should read
     darker than the flat navy bands, even under a bright photo. */
  background: var(--c-navy-deep);
  color: var(--c-white);
  overflow: hidden;
}
.post-hero__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* A FIXED scrim, not a tint baked into the photo. The live post darkens the
     image so far that the people in it are barely legible, and the amount
     varies per upload; a constant overlay keeps the type contrast predictable
     whatever the editor uploads.
     0.15 over --c-navy-deep (was 0.42 over --c-navy): the client asked for
     a darker hero, the same blue with the photo only as texture. */
  opacity: 0.15;
}
/* Centred, as the live article hero is. The title also drops its 20ch cap
   here: a centred line that is capped to 20ch but sits in a 1726px wrap looks
   like a column that lost its left edge rather than a centred title. */
.post-hero__inner { position: relative; z-index: 1; text-align: center; }
.post-hero__cat {
  color: var(--c-cyan);
  font-size: clamp(0.8125rem, 1.05vw, 1.0625rem);
  letter-spacing: 0.14em;
}
/* Wider than the article column, and on its own token: it was 24ch, a width
   that grows with the font — and the font grows with the viewport — so the hero
   spread to 1248px at 1920 and to 832px at 1280, which is a different shape at
   every screen size.

   --title-max is set per post by ulicny_title_fit(). min() takes whichever is
   smaller, so a short title keeps the drawn size and only a long one comes
   down far enough to land on two lines. */
.post-hero__title {
  margin: var(--sp-4) auto 0;
  max-width: var(--measure-hero);
  font-family: var(--ff-display);
  font-size: min(clamp(min(1.75rem, 13vw), 4.167vw, 5rem), var(--title-max, 5rem));
  font-weight: var(--wt-display);
  line-height: 1.15;
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}
/* The standfirst opens the article body — see .post-deck below. It used to sit
   in the hero between the title and the byline, where it competed with the
   title for the same centred column and, on a long title, pushed the byline off
   the bottom of the band. */
.post-hero__meta {
  margin: var(--sp-6) 0 0;
  color: var(--c-cyan);
  font-size: clamp(0.875rem, 1.1vw, 1.0625rem);
}

/* Post body. "Clunky" is fixed with FEWER sizes and more space, per the
   templates doc - three sizes total, one measure, generous top margins.

   Article type is set here rather than from --fs-base, which is 36px at 1920.
   That is the .xd's body size, and it is right where the .xd uses it: a short
   intro paragraph under a big heading on a marketing page, where 36px reads as
   a lead. The design has no artboard for an article, and a 1,500-word piece at
   36px runs to roughly 42 characters a line and scrolls forever. 20px body and
   28px headings is what the client asked for, and it is where long-form is
   normally set. The marketing pages keep the .xd sizes untouched. */
/* Centred in the wrap, every child sharing ONE column width — see
   --measure-post for why it is a fixed length rather than `ch`. */
.post-body .u-wrap > * { max-width: var(--measure-post); margin-inline: auto; }
.post-body p,
.post-body ul,
.post-body ol {
  font-size: var(--fs-post);
  line-height: var(--lh-body);
  margin-block: 0 var(--sp-6);
}
.post-body ul, .post-body ol { padding-left: 1.2em; }
.post-body li + li { margin-top: var(--sp-3); }
.post-body h2 {
  font-family: var(--ff-display);
  font-size: var(--fs-post-head);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--ls-heading);
  color: var(--c-blue);
  /* margin-block only: a `margin: x 0 y` shorthand would reset the
     margin-inline:auto that centres every child of the wrap, and the headings
     would pin to the left while the paragraphs stayed centred. */
  margin-block: var(--sp-16) var(--sp-6);
}
.post-body h3 {
  font-family: var(--ff-display);
  font-size: var(--fs-post-head);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: var(--ls-heading);
  color: var(--c-navy);
  margin-block: var(--sp-12) var(--sp-4);
}
.post-body > .u-wrap > *:first-child { margin-top: 0; }
.post-body a { color: var(--c-blue); }

/* An icon beside its passage: the strategies and principles the live articles
   set with a small picture at the left of each one. A grid rather than the
   float the live page used, so every line keeps one indent however far it
   wraps, and a short item cannot let the next icon ride up beside it. */
.post-body .icon-item {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  column-gap: var(--sp-5);
  align-items: start;
  margin-block: 0 var(--sp-6);
}
/* The icon has a box of its own because the body runs through wpautop: an
   <img> sitting straight inside the grid came out followed by a stray </p>,
   which the browser makes an empty paragraph, and that took the text's column. */
.post-body .icon-item__icon { margin-top: 0.25rem; }
.post-body .icon-item__icon img {
  display: block;
  width: 100%;
  height: auto;
}
.post-body .icon-item__body > :first-child { margin-top: 0; }
.post-body .icon-item__body > :last-child { margin-bottom: 0; }
/* A heading inside cannot push off the item above from in there — a grid keeps
   its children's margins to itself, and the icon would stay put while the
   heading dropped — so the item takes the heading's space instead. */
.post-body .icon-item:has(> .icon-item__body > :is(h2, h3):first-child) { margin-top: var(--sp-12); }

/* WordPress's own image alignment, carried through the migration. An author
   bio is written as a 200px portrait with alignleft and the paragraph after it,
   so the text sits beside the picture; without the float it stacks and the box
   stops reading as a bio. The paragraph is the containing block, so it also has
   to clear its own float. */
.post-body .alignleft,
.post-body .alignright {
  max-width: 40%;
  height: auto;
  margin-bottom: var(--sp-4);
}
.post-body .alignleft  { float: left;  margin-right: var(--sp-6); }
.post-body .alignright { float: right; margin-left: var(--sp-6); }
.post-body p:has(> .alignleft),
.post-body p:has(> .alignright) { overflow: hidden; }
.post-body .aligncenter { display: block; margin-inline: auto; height: auto; }
@media (max-width: 560px) {
  .post-body .alignleft,
  .post-body .alignright { float: none; max-width: 12rem; margin-inline: 0 auto; }
}

/* "Additional articles:" — Elementor renders these from a query, so they come
   across as the hand-picked links they are rather than as cards. Consecutive
   Posts widgets each emit their own list, so the lists are pulled together. */
.post-body .article-links {
  margin-block: 0 var(--sp-3);
  padding-left: 1.2em;
}
.post-body .article-links + .article-links { margin-top: calc(-1 * var(--sp-3)); }
.post-body .article-links a { font-weight: 500; }


/* Four articles paste a bare URL in as the link text — one of them runs 137
   characters with no space in it. A word that long cannot wrap, so at 360px it
   pushed the whole page sideways. break-word engages only when the word genuinely
   does not fit, so ordinary prose is untouched. */
.post-body { overflow-wrap: break-word; }

/* The standfirst, now the article's opening line. Blue and a size up from the
   body, so it reads as a lead-in rather than a first paragraph, and it shares
   the article column with everything else.

   Two classes, because `.post-body p` carries a type selector and would
   otherwise set this at the 20px body size. */
/* "How to get started:" — a single-item icon-list in the source, drawn on the
   live article as a chevron and a line of large primary-blue type introducing
   the paragraphs beneath it. The chevron is decorative, so it is a ::before
   rather than a character in the text. */
.post-body .lead-in {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  color: var(--c-blue);
  font-size: clamp(1.25rem, 1.6vw, 1.5rem);
  line-height: 1.3;
  font-weight: 500;
  margin-block: var(--sp-8) var(--sp-4);
}
.post-body .lead-in::before {
  content: "\203A";          /* › — the chevron, not an image */
  font-size: 1.5em;
  line-height: 1;
  font-weight: 400;
}
/* Inside a two-up panel it follows the panel's smaller type. */
.callout-row .callout .lead-in { font-size: 1.125rem; margin-block: var(--sp-5) var(--sp-3); }

.post-body .post-deck {
  color: var(--c-blue);
  font-size: clamp(1.25rem, 1.65vw, 1.625rem);
  line-height: 1.45;
  font-weight: 500;
  margin-block: 0 var(--sp-8);
}

/* Figures sit in the text column, not outside it.

   They used to be capped at 64rem against a 46rem text column, so every image
   overhung the prose by about 145px on each side and nothing lined up. An
   article reads better when the picture shares the column's edges — the eye
   keeps one left margin the whole way down. */
/* `.post-body .u-wrap > *` is (0,2,0) and would beat a plain `.post-body
   figure` (0,1,1) whatever the source order, so this has to match the same
   shape to win. */
.post-body .u-wrap > figure {
  max-width: var(--measure-post);
  margin: var(--sp-12) auto;
}
/* An image is never drawn larger than it was made. `width: 100%` stretched
   every one to the column, which upscaled the small ones badly: three toolkit
   icons 359px wide were painted at nearly twice that, and the logos and
   headshots in the older articles — some only 200px — were blown up to the
   full measure and went soft. 45 of the 109 images in the articles are
   narrower than the column, so they now sit at their own size, centred; the
   big ones still fill it. */
.post-body figure img {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}
.post-body figcaption {
  margin-top: var(--sp-3);
  color: var(--c-ink-muted);
  font-size: clamp(0.8125rem, 1.02vw, 1rem);
  line-height: 1.5;
}

/* The existing posts open with a boxed Overview. It is editor markup, not a
   widget, so it survives the simplification. */
/* Highlight panels.
 *
 * The live articles use three tints from the Elementor palette, and they are
 * not interchangeable: blue marks an aside the reader should act on, grey a
 * summary of what they just read. Flattening both to one colour loses that, so
 * the extractor carries the tone through as a modifier and these match the
 * live values — #0C70B829 and #81947B29 are the brand colours at 16% alpha,
 * which is where the pale blue and green come from. */
.callout {
  background: #DCEBF7;
  padding: clamp(1.25rem, 2.4vw, 2.25rem);
  margin-bottom: var(--sp-8);
}
.callout--blue  { background: color-mix(in srgb, #0C70B8 16%, #fff); }
.callout--green { background: color-mix(in srgb, #81947B 16%, #fff); }
/* Three panels use this one — a stat box and two pull-quotes. Same 16% alpha
   as the other two: the source stores #92788529, and 0x29 is 16% of 255. */
.callout--rust  { background: color-mix(in srgb, #927885 16%, #fff); }
.callout--grey  { background: #F7F7F7; }

/* Two panels side by side — "What firms can do" beside "How it helps advisors",
   "Key takeaway:" beside the takeaway. Elementor paints the band and lays the
   columns across it; this is that shape.

   auto-fit with a floor, so they drop to one column on a phone rather than
   squeezing to a four-word line. */
.callout-row {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  margin-bottom: var(--sp-8);
}
.callout-row .callout { margin-bottom: 0; height: 100%; }

/* The other shape: ONE panel whose columns are a layout inside it — a portrait
   beside its copy. data-cols carries Elementor's own split, so a 33/66 keeps
   the headshot column narrow instead of giving it half the box. */
.callout__cols {
  display: grid;
  gap: var(--sp-6);
  align-items: start;
}
/* Same reasoning as .callout-row: a 33/66 split leaves the text column around
   537px, where the article's 20px body and 28px headings run short lines. */
.callout__cols p,
.callout__cols li { font-size: 1.0625rem; line-height: 1.6; }
.callout__cols[data-cols="33-66"] { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.callout__cols[data-cols="66-33"] { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.callout__cols[data-cols="25-75"] { grid-template-columns: minmax(0, 1fr) minmax(0, 3fr); }
.callout__cols[data-cols="50-50"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.callout__col > *:first-child { margin-top: 0; }
/* The same, for a panel that is not in columns. Every Overview box opens
   with a heading, and `.post-body h3` sets a 3rem top margin for headings
   that separate sections of an article — inside a box that margin is just
   a gap above the first line. 57 panels across 25 articles open this way. */
.post-body .callout > *:first-child { margin-top: 0; }
.post-body .callout > *:last-child  { margin-bottom: 0; }
.callout__col > *:last-child  { margin-bottom: 0; }
/* The portrait fills its column and no more — at the article's figure width it
   would otherwise be wider than the panel holding it. */
.callout__cols figure { max-width: 100%; margin: 0 0 var(--sp-4); }
.callout__cols img { width: 100%; height: auto; display: block; }
/* A heading inside a column is the column's title, styled like the panel's. */
.post-body .callout__col h2,
.post-body .callout__col h3 {
  margin: 0 0 var(--sp-4);
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: 1.375rem;   /* 22px — the column is half a measure wide */
  font-weight: 600;
  letter-spacing: var(--ls-heading);
}
/* Below the measure the two stack, and the portrait stops being full width. */
@media (max-width: 640px) {
  .callout__cols { grid-template-columns: 1fr !important; }
  .callout__cols img { max-width: 14rem; }
}
/* A column is roughly half the measure, so the article's 20px body and 28px
   headings are too big for it — the type has to come down with the width or
   every line breaks after four words. */
.callout-row .callout p,
.callout-row .callout li { font-size: 1.0625rem; line-height: 1.6; }
.post-body .callout-row .callout__title { font-size: 1.375rem; }

/* The summary panels bullet with a filled cyan disc rather than a dot. */
.callout ul {
  margin: 0;
  padding-left: 0;
  list-style: none;
}
.callout ul > li {
  position: relative;
  padding-left: 2.25rem;
}
.callout ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.45em;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  background: var(--c-secondary, #33BFE3);
}
/* An ordered list keeps its numbers — those carry meaning the disc does not. */
.callout ol { margin: 0; }
/* A list that is followed by more panel copy needs the gap back: zeroing the
   margin above left the next paragraph butted against the last list item, so
   the two read as one run-on block. Last-child keeps the panel's own bottom
   padding doing the work when the list ends it. */
.callout ul:not(:last-child),
.callout ol:not(:last-child) { margin-bottom: var(--sp-5); }
/* Scoped, because `.post-body h2` carries a type selector and would otherwise
   outrank a bare `.callout__title` and push this heading 64px down inside its
   own box. */
/* No rule under the title. The live articles do not draw one — and on the pull
   quotes, where the quote itself IS the title, an underline reads as a
   correction mark through the attribution. */
.post-body .callout__title {
  margin: 0 0 var(--sp-4);
  color: var(--c-blue);
  font-family: var(--ff-display);
  /* 24px, under the article's 28px headings. A panel title is a label on an
     aside, not a step in the article's outline — and on the pull quotes, where
     the quote itself is the title, 28px over three lines shouted. */
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: var(--ls-heading);
}
.callout ul { margin: 0; }

.featured--related { grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }
@media (min-width: 800px) { .featured--related { grid-template-columns: repeat(3, 1fr); } }

/* --- Embedded video ------------------------------------------------------- *
 *
 * WordPress embeds a video URL that sits alone on its own line, and hands back
 * an iframe with the width and height YouTube suggested — 500x375 here, which
 * is both smaller than the column and the wrong shape. The aspect-ratio keeps
 * it 16:9 without the old padding-top trick, and the width makes it fill the
 * measure instead of sitting in a 500px box.
 *
 * .wp-embedded-content is the OTHER thing an embed can return: the little
 * preview card WordPress uses for a post it recognises as its own. */
.post-body iframe,
.post-body .wp-video,
.post-body video {
  display: block;
  width: 100%;
  max-width: var(--measure-post);
  margin-inline: auto;
  margin-block: var(--sp-8);
  border: 0;
}
.post-body iframe[src*="youtube"],
.post-body iframe[src*="youtu.be"],
.post-body iframe[src*="vimeo"] {
  aspect-ratio: 16 / 9;
  height: auto;
}

/* --- Article forms --------------------------- inc/forms.php: [ulicny_form] --
 *
 * The five migrated articles carry a Fluent Forms form — four reader surveys
 * and one "share your view".
 *
 * ONE inherited declaration caused most of what was wrong here. Fluent Forms
 * sizes almost nothing, so its markup inherited `body`: --fs-base, which is
 * 27px at 1440 and 36px at 1920, with --lh-body 1.7 for long-form prose. Labels
 * came out the size of section headings, and — less obviously — every option
 * row became a 46px line box around 22px of text, because the line box is built
 * from the BLOCK's inherited line-height, not from the 16px text inside it.
 * That, not any margin, was the gap between the radio buttons. So the container
 * resets both, and the rest of this section is detail.
 *
 * Specificity is deliberate. Fluent Forms' stylesheet loads AFTER the theme's,
 * so at equal weight the plugin wins; the rules it also sets are therefore
 * scoped through `.fluentform`, which its wrapper always carries, to sit one
 * class above. Where it is `.post-form .fluentform` rather than `.post-form`,
 * that is why. */
.post-form {
  /* The six custom properties the plugin's own stylesheet reads. Handing it
     these beats overriding its rules: a plugin update then changes the form,
     not the theme's idea of the form. */
  --fluentform-primary:       var(--c-blue);
  --fluentform-secondary:     var(--c-ink);
  --fluentform-border-color:  var(--c-rule);
  --fluentform-border-radius: 6px;

  /* The reset described above. Everything inside inherits from here. */
  font-size: 1rem;
  line-height: 1.5;

  /* A form is an object in the article, not a passage of it. */
  margin-block: var(--sp-10);
  padding: clamp(1.25rem, 3vw, 2rem);
  border: 1px solid var(--c-rule);
  border-radius: 10px;
  background: var(--c-surface-alt);
}

/* Between questions. Enough to separate, not enough to lose the set. */
.post-form .fluentform .ff-el-group { margin-bottom: var(--sp-6); }
.post-form .fluentform .ff-el-group:last-child { margin-bottom: 0; }

/* A name field is First and Last in two cells, each holding a lone
   .ff-el-group — which makes every one of them :last-child, so the rule above
   zeroes the margin under the whole row. The row carries it instead. Below the
   plugin's own 768px breakpoint the cells stack rather than sitting side by
   side, and with those margins gone the two boxes would touch. */
.post-form .fluentform .ff-name-field-wrapper { margin-bottom: var(--sp-6); }
@media (max-width: 767.98px) {
  .post-form .fluentform .ff-t-cell + .ff-t-cell { margin-top: var(--sp-3); }
}

/* The question, not a heading. On the surveys the label IS the question and
   runs to a full sentence, so it sits a step under the article's reading size:
   small enough to read as a prompt, large enough to read. Uppercasing it, which
   would suit a one-word "Name", would wreck those. */
.post-form .fluentform .ff-el-input--label { display: block; margin-bottom: var(--sp-2); }
.post-form .fluentform .ff-el-input--label label {
  display: block;
  margin-bottom: 0;
  color: var(--c-ink);
  font-family: var(--ff-display);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.35;
}

.post-form .fluentform .ff-el-form-control {
  width: 100%;
  padding: 0.7rem 0.9rem;
  /* The plugin sets line-height:1, which puts a textarea's second line on top
     of its first. */
  line-height: 1.5;
  background: var(--c-white);
  color: var(--c-ink);
  font-family: var(--ff-body);
  font-size: 1rem;
}
.post-form .fluentform textarea.ff-el-form-control { min-height: 7rem; resize: vertical; }
.post-form .fluentform .ff-el-form-control::placeholder { color: var(--c-ink-muted); opacity: 1; }
.post-form .fluentform .ff-el-form-control:focus {
  border-color: var(--c-blue);
  box-shadow: 0 0 0 3px rgba(15, 80, 128, 0.18);
}

/* Choice lists. The control and its text are one row: a flex label keeps a
   wrapped option indented under its own text rather than under the button, and
   gives the two the gap the plugin's bare `<input> <span>` never had. */
.post-form .fluentform .ff-el-form-check { margin-bottom: 0.35rem; }
.post-form .fluentform .ff-el-form-check:last-child { margin-bottom: 0; }
.post-form .fluentform .ff-el-form-check label.ff-el-form-check-label {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  margin-bottom: 0;
  font-family: var(--ff-body);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.45;
}
.post-form .fluentform .ff-el-form-check-input {
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
  /* Optically centred on the first line of the option, not on the whole box. */
  margin-top: 0.22em;
  accent-color: var(--c-blue);
}

/* The site has exactly one button shape. Its rules are repeated rather than the
   class reused, because the markup is the plugin's and cannot carry .u-pill.
   The :not() matches the plugin's own per-form inline rule, which is (0,3,0)
   and would otherwise keep the button its default blue. */
.post-form .fluentform .ff-btn-submit:not(.ff_btn_no_style) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: clamp(120px, 8.958vw, 172px);
  min-height: clamp(44px, 3.958vw, 60px);
  padding: var(--sp-2) clamp(1.5rem, 3vw, 2.75rem);
  border: 0;
  border-radius: 999px;
  background: var(--c-lime);
  color: var(--c-ink);
  font-family: var(--ff-display);
  font-size: clamp(0.875rem, 1.25vw, 1.25rem);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.post-form .fluentform .ff-btn-submit:not(.ff_btn_no_style):hover { background: var(--c-lime-hover); }
/* The plugin's `.ff-default .ff_btn_style:focus { outline: 0 }` outranks the
   site's :focus-visible ring, so a keyboard user lost the button. Navy rather
   than the usual lime: a lime ring round a lime pill barely shows. */
.post-form .fluentform .ff-btn-submit:not(.ff_btn_no_style):focus-visible {
  outline: 3px solid var(--c-navy);
  outline-offset: 3px;
}
/* The same plugin rule dims the button to opacity .8 on :hover and :focus,
   which drops ink on lime under 4.5:1 at the popup's 14px. The hover lime is
   change enough. */
.post-form .fluentform .ff-btn-submit:not(.ff_btn_no_style):hover,
.post-form .fluentform .ff-btn-submit:not(.ff_btn_no_style):focus { opacity: 1; }
.post-form .fluentform .ff_submit_btn_wrapper { margin-top: var(--sp-6); margin-bottom: 0; }

/* The validation messages, and the "* Required information." note the plugin
   writes in with JavaScript — all of it inherits the container's 1rem now, so
   only colour and a step down are left to say. */
.post-form .fluentform .error,
.post-form .fluentform .text-danger { font-size: var(--fs-sm); }
.post-form .fluentform .ff-el-is-required-message,
.post-form .fluentform .ff_t_c { font-size: var(--fs-sm); color: var(--c-ink-muted); }
.post-form .ff-message-success {
  padding: var(--sp-4) var(--sp-5);
  border-left: 3px solid var(--c-blue);
  background: var(--c-white);
}

/* --- Client logo grid ------------------- ACF: `client_logos` repeater (15) */
/* Measured off the comp at 4x, everything as a fraction of page width W:
     5 equal columns across the content width (0.0505 -> 0.9495 W)
     row centres 0.6868 / 0.8084 / 0.9322 W  ->  even pitch 0.1227 W
     band runs 0.5623 -> 1.0843 W
   Fixed row pitch with the logos centred in it, NOT auto rows: the three rows
   hold very different shapes (a square Schwab, a 7:1 BlackRock wordmark) and
   auto rows would put the pitch at the mercy of whichever logo is tallest. */

.client-logos {
  background: var(--c-white);
  /* 0.1245 W to the first row centre, 0.1521 W from the last centre to the
     end of the band, less half a row each. */
  padding-top: clamp(2rem, 6.32vw, 11.25rem);
  padding-bottom: clamp(2.75rem, 9.08vw, 16.15rem);
}
.logo-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-auto-rows: clamp(4.5rem, 12.27vw, 13.6rem);
  align-items: center;
  justify-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
.logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* --logo-w is per-logo, set in the markup from the comp - see the note
     there. The fallback keeps a logo visible if the field is ever empty.
     No padding here: --logo-w is a percentage of the COLUMN, and padding on
     the cell silently shrank every logo by the same 6%. */
}
.logo img {
  display: block;
  width: var(--logo-w, 70%);
  height: auto;
  /* Some of these are 15-year-old raster files. Cap the height so a tall mark
     cannot break the row pitch even if its width field is wrong. */
  max-height: 100%;
  object-fit: contain;
}

/* Five across needs roughly 900px to stay legible; below that the logos would
   be smaller than the type around them. */
@media (max-width: 899px) {
  .logo-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 559px) {
  .logo-grid { grid-template-columns: repeat(2, 1fr); }
}

/* --- Testimonial --------------------------- ACF: `testimonial` group ---- */
/* Navy band. Comp, as fractions of W from the top of the band:
     quote mark ink 0.0813, body first line 0.1238, attribution 0.2193,
     band ends 0.3304. */

.testimonial {
  background: var(--c-navy);
  color: var(--c-white);
  padding-top: clamp(2.4rem, 9.85vw, 17.5rem);
  padding-bottom: clamp(2.5rem, 9.15vw, 16.3rem);
}
.testimonial__quote { margin: 0; }
.testimonial__mark {
  /* The opening quote of the pull quote, not a decorative flourish: it is the
     only cyan on the band and it marks where the quote starts. */
  color: var(--c-cyan);
  font-family: var(--ff-display);
  font-weight: 800;
  /* Calibrated to ink: the comp mark measures 0.0254 W wide, 0.0194 W tall.
     Sized by measuring the rendered glyph, not by reasoning from an em value
     - a quote mark's ink is a small and very font-specific slice of its
     font-size. */
  font-size: clamp(3.25rem, 7.93vw, 8.8rem);
  /* The glyph sits high in its em box, so most of a normal line box is empty
     space UNDER the ink. 0.25 collapses that dead space; the gap to the quote
     below is then set by the margin, where it can be measured. */
  line-height: 0.25;
  /* comp: 0.0239 W from the bottom of the mark's ink to the top of the quote */
  margin: 0 0 0.03em;
}
.testimonial__body {
  /* Pale cyan, the same body colour the panels use - not white. */
  color: var(--c-panel-copy);
  /* comp 0.0189 W ≈ 27px at 1440, with a line pitch of 0.0246 W. The comp
     runs body copy at ~1.3 leading; --lh-copy (1.45) is looser than the comp
     everywhere it appears, and at 1.45 this quote fitted on two lines where
     the comp takes three. */
  font-size: clamp(1.25rem, 1.945vw, 2.16rem);
  line-height: 1.30;
  /* The comp's measure: its lines rag at 0.9195 W, inside the 0.9495 W content
     edge, which is what puts "incididunt" at the end of the first line. */
  max-width: 97.5%;
  margin: 0;
}
.testimonial__cite {
  margin-top: 1.2em;
  color: var(--c-white);
  font-weight: 700;
  /* Tracked to WIDTH: the comp line measures 0.2550 W ink-to-ink. Size alone
     reached that width only by overshooting the cap height. */
  font-size: clamp(0.9375rem, 1.40vw, 1.55rem);
  letter-spacing: 0.03em;
  font-style: normal;
}

/* --- Capability matrix ----------------- ACF: `approach_steps` / `capabilities` */
/* Three groups, each a list of terms. Strategy is terms only; Content and
   Design are term + description. One repeater handles both — leave the
   description empty and it renders as a plain term. */

.capability__grid {
  display: grid;
  gap: var(--sp-12);
}
@media (min-width: 900px) {
  /* comp: columns start at 0.0507 / 0.3572 / 0.6629 W and run to 0.9485 — so
     0.2856 W wide with a 0.0209 W gutter. --sp-16 (64px) was more than twice
     the comp's gutter, which narrowed every column and made the Design list
     wrap a line early on nearly every entry. */
  .capability__grid {
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(1.5rem, 2.09vw, 2.3rem);
  }
}

.cap-group__title {
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--c-blue);
  /* comp: 0.0552 W from the heading's ink to the first item's, against a
     0.0239 W cap — --sp-6 left it 19px tight. */
  margin-bottom: clamp(1.5rem, 3.0vw, 3.1rem);
}
.cap-group ul { margin: 0; padding: 0; list-style: none; }

/* The LI carries the type, not the spans inside it. An li left at --fs-base
   with --lh-body sets a 39px strut that every line box in it inherits — so
   the description was led at 39px however small its own font-size was. Comp
   pitch is 0.0179 W (25.8px at 1440); this was 51% looser than the comp.

   comp: item text 0.01375 W (~19.8px at 1440), pitch 0.0179 W -> 1.30 */
.cap-group li {
  font-size: clamp(0.9375rem, 1.375vw, 1.3rem);
  line-height: 1.30;
}
/* comp: 0.0350 W between the first lines of consecutive entries, against a
   0.0179 W pitch — so ~0.0171 W of clear space, near enough --sp-6. */
.cap-group li + li { margin-top: var(--sp-6); }

.cap-term {
  font-weight: 600;
  color: var(--c-ink);
}
/* Same size as the term, and the comp is explicit about it — bold and regular
   of ONE size, not two sizes. Only the weight and the colour change. */
.cap-desc { color: var(--c-ink-soft); }

/* Terms with no description sit on their own line at body weight. */
.cap-group--terms li { font-weight: 600; }
/* comp: 0.0302 W item to item, tighter than the described entries above. */
.cap-group--terms li + li { margin-top: 1.1rem; }

/* --- Diagram slot ------------------------------- ACF: `diagrams` (flexible) */
/* The comp marks this area "Content to come. Diagrams/text/graphics", and the
   4-D method is where the absorbed UTools material lands (`utools_content`).
   Rendered as a visible placeholder so the gap is obvious rather than hidden. */

.diagram-slot {
  display: grid;
  place-items: center;
  min-height: clamp(220px, 34vw, 380px);
  padding: var(--sp-8);
  border: 1px solid rgba(255, 255, 255, 0.5);
  text-align: center;
  color: var(--c-white);
}
.diagram-slot p {
  font-family: var(--ff-display);
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  max-width: 20ch;
}
.method__label {
  margin-bottom: var(--sp-8);
  color: var(--c-white);
}

/* --- 4-D method block ---------------------------------------------------- */
/* Deliberately NOT reusing .panel: that carries the homepage's decorative
   left tick, which would be unexplained here. Same ground, same depth layer,
   no borrowed ornament. */
.method {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  padding-block: clamp(4rem, 10vw, 9rem);
  background: var(--c-navy);
  color: var(--c-white);
}
.method > .u-wrap { position: relative; z-index: 1; }

/* --- Pill glyph ---------------------------------------------------------- */
/* The comp's CTA is three chevrons, letter-spaced, on the lime pill. */
.pill__chev {
  /* Negative: the three chevrons should read as ONE arrow, not three marks.
     Manrope’s side bearings on › leave a visible gap even at 0. */
  letter-spacing: -0.08em;
  /* negative tracking removes trailing space, so the nudge flips sign */
  margin-right: 0.08em;
  /* The comp sets these at 0.143 of the pill height, which is weak as an
     affordance - deliberately larger here. */
  font-size: clamp(1.125rem, 1.75vw, 1.75rem);
  /* Light, not bold. The size carries the affordance; the thin stroke keeps
     it elegant, and the comp’s own chevrons are thin. 700 read as blunt. */
  font-weight: 400;
  line-height: 1;
}

/* --- Stat row ----------------------------------- ACF: `stats` repeater (3) */
/* Sits inside the navy page-head band on Who we are. Numerals are LIGHT
   weight in the comp - the contrast against the heavy display face is the
   point, so do not bold these. */

.stats {
  display: grid;
  gap: var(--sp-8);
  margin-top: clamp(3rem, 8vw, 7rem);
}
@media (min-width: 700px) {
  /* Solved from the comp: three equal columns of 0.2484 W with 0.0741 W gaps.
     That is a much larger gap than it looks - it is what pushes column 3 to
     start at 0.697 W and finish flush with the content right edge (0.951 W),
     aligned with the header. A smaller gap leaves it short of the margin. */
  .stats {
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(1.5rem, 7.41vw, 8rem);
  }
}
.stat__num {
  font-family: var(--ff-display);
  font-size: clamp(2.75rem, 5vw, 4rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--c-cyan);
}
.stat__desc {
  margin-top: var(--sp-4);
  /* comp: text spans 0.223 W and the copy sets in three lines, breaking after
     "…veteran writers,". At --fs-sm (15px) the text was far too small and
     stopped 0.055 W short of where the comp puts it. */
  max-width: 24ch;
  font-size: clamp(0.9375rem, 1.46vw, 1.375rem);
  font-weight: 600;          /* semibold in the comp, not regular */
  line-height: 1.4;
}

/* --- Office map ------------------------------------- ACF: `offices_map` */
/* ⚠️ PLACEHOLDER ARTWORK, extracted from the PDF comp at native resolution.
   It is a designed asset and should be supplied as SVG - see README. */

.offices {
  background: var(--c-navy);
  /* The page-head above is also navy, so the two bands read as one. Its
     bottom padding already provides the separation - doubling it here just
     opens dead space. */
  padding-top: 0;
}
.offices__figure { margin: 0; }
.offices__figure img {
  width: 100%;
  max-width: 980px;
  margin-inline: auto;
}
.offices__caption {
  margin-top: var(--sp-6);
  text-align: center;
  font-size: var(--fs-sm);
  color: rgba(255, 255, 255, 0.7);
}

/* --- Leadership bio ------------------------------------ ACF: `leadership` */

.bio__name {
  /* comp line pitch 37px at 1440 -> ~30px at --lh-snug, not --fs-md (35px) */
  font-size: clamp(1.25rem, 2.1vw, 2rem);
  font-weight: 700;
  color: var(--c-blue);
  line-height: var(--lh-snug);
  /* no margin-bottom: the grid row-gap owns the space below. Having both gave
     64px where the comp wants ~36px. */
  margin-bottom: 0;
}
.bio__body {
  /* Long-form copy runs SMALLER than the marketing blocks: the comp line pitch
     is 0.0224 W (32px at 1440), which is ~19px at --lh-body 1.7. Inheriting
     --fs-base (23px) made it 22% loose. */
  font-size: clamp(0.9375rem, 1.32vw, 1.25rem);
  max-width: var(--measure);
}
.bio__body p + p { margin-top: var(--sp-4); }

/* Structural BEM blocks. No styling of their own - they exist so the markup
   reads as one unit per ACF row, and so the class audit stays clean. */
.stat { }
.bio  { }


/* --- Pinned panels ------------------------------------------------------- */
/* The section PINS in place and the content swaps between INSTITUTIONS /
   ADVISORS / INVESTORS as you scroll — the viewport holds still while the
   panel changes, rather than panels sliding over one another.

   Default (no JS, reduced motion) is plain stacked sections in normal flow.
   The pinned layout is applied by motion.js via .is-pinned, so a script
   failure can never leave three panels piled on one another.
   ?panels=classic keeps the trailing-background reading.                     */

.panels {
  position: relative;
  background: var(--c-navy);
}

.panels .panel {
  position: relative;
  background: var(--c-navy);
  padding-block: clamp(4rem, 10vw, 9rem);
}

/* --- Pinned mode (JS only) --- */
.panels.is-pinned {
  height: 100vh;
  overflow: hidden;
}
.panels.is-pinned .panel {
  position: absolute;
  inset: 0;
  min-height: 0;
  display: flex;
  align-items: center;
  padding-block: 0;
}
/* All three occupy the same space, so the stacking scrim has nothing to mark. */
.panels.is-pinned .panel__scrim { display: none; }

.panel__scrim { display: none; }

/* Fallback reading — plain flow, background layer trails the scroll. */
.panels--classic .panel {
  position: relative;
  min-height: 0;
  display: block;
  padding-block: clamp(4rem, 10vw, 9rem);
}


/* --- Touch targets -------------------------------------------------------- */
/* The comp is desktop-only, so mobile sizing is our call. Below the tablet
   breakpoint every interactive element gets the 44px minimum from the
   accessibility floor in docs/03-design-tokens.md — footer links measured
   19px tall, which is not tappable.

   Padding does the work rather than font-size, so the type scale is untouched.
   The list margin is dropped because the padding now provides the rhythm. */

/* At every width, footer links clear the 24px WCAG 2.5.8 minimum. The padding
   replaces the list margin so the comp's 0.0213 W pitch is preserved rather
   than added to. */
.site-footer a {
  display: inline-block;
  padding-block: 0.16rem;
}
.site-footer li + li { margin-top: 0; }

/* A coarse pointer, not just a narrow one. Gating this on width alone meant
   the whole floor fell away at exactly 900px — a real tablet width — where
   the footer links went back to 25px tall. A touchscreen needs the 44px
   however wide it is, and a mouse at 1920 still matches neither condition,
   so the approved desktop rendering is untouched. */
@media (max-width: 899px), (pointer: coarse) {
  /* touch: the full 44px floor */
  .site-footer a {
    padding-block: 0.8rem;
    /* Several footer links are short — "FAQ", the phone number — and measured
       29-35px wide against the 44 floor. The box grows to the right of the
       text, so the target passes without anything appearing to move. */
    min-width: 44px;
  }

  /* The blog filter chips and the pagination links measured 40px and 43px
     tall at every width — they were sized before this floor existed. */
  .filter,
  .pagination a,
  .pagination__current {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    /* page numbers are a character or two wide on their own */
    min-width: 44px;
  }

  .archive-search__input { min-height: 44px; }

  .subscribe__input,
  .subscribe__form .u-pill { min-height: 44px; }

  .site-logo { padding-block: 0.35rem; }
}

/* Pointer devices do not need the 44px floor, but the nav still benefits from
   a comfortable hit area. */
@media (min-width: 900px) {
  .site-nav a { padding-block: var(--sp-3); }
}

/* --- Stats band ----------------------------- ACF: `stats` repeater (Who we are) */
/* A separate navy band BELOW the page head, not part of it. Measured off the
   comp: the hero band ends at 0.5617 W and the stat numerals begin at
   0.6957 W, so the gap above is ~0.134 W. The map then starts at ~0.99 W,
   which is the ~0.172 W below.

   Keeping the stats inside .page-head made its min-height cover both blocks
   and pushed the heading off the hero line. */
.stats-band {
  background: var(--c-navy);
  color: var(--c-white);
  padding-top: clamp(3rem, 13.4vw, 12rem);
  padding-bottom: clamp(3rem, 20.5vw, 18rem);   /* map top lands at 0.994 W */
  /* The page head and this band are two navy sections that meet at a
     fractional y at 90% / 110% zoom: each background is anti-aliased there,
     so white showed through as one lighter row (a visible seam). Painting 1px
     of navy upward over the join closes it. A shadow, not a margin, so no
     layout moves. */
  box-shadow: 0 -1px 0 var(--c-navy);
}
.stats-band .stats { margin-top: 0; }

/* --- Office map (interactive) --------------------- ACF: `offices` repeater */
/* The dotted silhouette is a real SVG (assets/img/us-map-dots.svg, ~5.5kb
   gzipped) traced from the comp artwork — 2184 dots on the original grid, so
   it stays crisp at any size instead of being a raster.

   Pins, leader lines and labels are DOM, positioned as percentages of the
   plot, so the labels are selectable, translatable text rather than baked
   pixels, and each office can highlight on hover.

   Every position below was measured off the comp. Both `top` and `height`
   on an absolutely positioned child resolve against the container height,
   which is what lets the leader lines be expressed purely in percentages. */

.office-map { margin: 0; }

.office-map__plot {
  position: relative;
  width: 100%;
  max-width: 980px;
  margin-inline: auto;
  aspect-ratio: 1160 / 730;          /* the comp artwork's ratio */
  background: url('../img/us-map-dots.svg') center / contain no-repeat;
}

.office-map__pins { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; }

/* Each .office spans the whole plot so its dot, line and label can all be
   positioned in percentages of the container. That means they overlap, and
   without this the topmost one in DOM order (New York) would swallow every
   hover anywhere on the map. Only the dot and label are hit targets. */
.office { position: absolute; inset: 0; pointer-events: none; }
.office__dot, .office__label { pointer-events: auto; }

.office__dot {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: clamp(9px, 1.5%, 16px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--c-white);
  transition: box-shadow var(--dur-fast) var(--ease-out);
}
/* the NY/NJ marker is ringed in the comp — one pin, two labels */
.office--ringed .office__dot {
  box-shadow: 0 0 0 3px var(--c-navy), 0 0 0 5px var(--c-white);
}

.office__line {
  position: absolute;
  left: var(--x);
  top: var(--line-top);
  height: var(--line-h);
  width: 1px;
  background: rgba(255, 255, 255, 0.75);
}

.office__label {
  position: absolute;
  left: var(--label-x, var(--x));
  top: var(--label-y);
  transform: translateX(var(--label-shift, -50%));
  white-space: nowrap;
  /* "San Francisco / Bay Area" is the only two-line label, and the comp
     centres its lines on each other (centres differ by 0.34%, left edges by
     2.68%). Single-line labels are unaffected. */
  text-align: center;
  font-size: clamp(0.75rem, 1.4vw, 1.125rem);
  font-weight: 600;
  line-height: 1.25;
  color: var(--c-lime);
  transition: color var(--dur-fast) var(--ease-out);
}
.office--right .office__label { transform: translateY(-50%); }

/* Hover highlight only — these are informational, not controls, so they are
   not focusable. The list is fully readable without any of it. */
.office:hover .office__dot { box-shadow: 0 0 0 6px rgba(255, 255, 255, 0.22); }
.office--ringed:hover .office__dot {
  box-shadow: 0 0 0 3px var(--c-navy), 0 0 0 5px var(--c-white),
              0 0 0 10px rgba(255, 255, 255, 0.22);
}
.office:hover .office__label { color: var(--c-white); }

@media (prefers-reduced-motion: reduce) {
  .office__dot, .office__label { transition: none; }
}

/* Below the breakpoint the plot gets small and the labels collide, so the
   map becomes a plain graphic with the offices listed beneath it. */
@media (max-width: 700px) {
  .office__line, .office__label { display: none; }
  .office-map__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-6);
    margin-top: var(--sp-6);
    padding: 0;
    list-style: none;
    color: var(--c-lime);
    font-weight: 600;
  }
}
@media (min-width: 701px) { .office-map__list { display: none; } }

/* --- Bio grid ------------------------------------ ACF: `leadership` (Who we are) */
/* Two columns, TWO rows. The lockup belongs to the body row, not the heading
   row: the comp starts it 63px BELOW the body's first line, level with the
   copy. Sharing a single row with a .bio wrapper aligned it to the heading
   instead, putting it 115px too high. */
.bio-grid { display: grid; row-gap: clamp(1.5rem, 2.5vw, 2.5rem); }

@media (min-width: 900px) {
  .bio-grid {
    /* Wider than the homepage intro grid: the comp runs the bio copy out to
       0.631 W, which 62.1% cannot reach. The lockup column is unchanged -
       space-between keeps it anchored right. */
    grid-template-columns: 64.5% 22.9%;
    justify-content: space-between;
    column-gap: var(--sp-8);
  }
  .bio__name   { grid-column: 1; grid-row: 1; }
  .bio__body   { grid-column: 1; grid-row: 2; }
  /* Row 2 = the body row, so the lockup aligns with the body’s FIRST LINE.
     Measured in the comp: lockup top is 7px ABOVE the body top (the slab caps
     of "CELEBRATING" sit a little higher than the body x-height) - i.e. level.
     It is 63px below the HEADING top, which is a different measurement and
     must not be applied as an offset here. */
  /* No vertical nudge here: the lockup must sit exactly as it does on the
     homepage, and grid-row 2 already puts it on the body row. An 18px offset
     was added at one point from a Range-based measurement, which reads LINE
     BOXES rather than glyph ink - with line-height 0.8 on the label those
     differ by about that much, so it "corrected" a problem that did not
     exist and pushed this page 20px out of step with the homepage. */
  .bio-grid .anniversary { grid-column: 2; grid-row: 2; }
}

/* --- Intrinsic minimums ------------------------------------------------- */
/* A grid or flex item's automatic minimum size is its MIN-CONTENT width, so a
   track can be pushed wider than its own container by a single long word -
   and `overflow-wrap: break-word` does not help, because it deliberately does
   not reduce min-content size. `min-width: 0` lets the track take its
   fr share and leaves the wrapping to overflow-wrap, which then has a real
   line box to break against. No effect at the sizes the comp was measured at;
   it only stops the blowout once type is enlarged or the column is tiny. */
.page-head__grid > *,
.intro__grid > *,
.case-body__grid > * { min-width: 0; }

/* ==========================================================================
   v3.0 — home page, testimonials, logo carousel
   The three audience panels are gone; the home page is one navy ground (hero
   + client questions), then logos on grey, a rotating quote on white, and the
   conversation band. Measured off the v3.0 artboard as fractions of page
   width W: navy ends 1.0839 W, grey band to 1.4464 W, white to 1.7714 W.
   ========================================================================== */

/* One navy ground for hero + questions, so no seam can appear between them. */
.home-navy {
  background: var(--c-navy);
  color: var(--c-white);
}
.home-navy .hero { background: none; }

/* --- Hero teaser (markup comment 4) ------------------------------------- */
/* An outlined pill that fades in after the headline and scrolls the page to
   the questions. Lenis eases the jump — it is a plain in-page anchor, so it
   still works with no JS. */
.hero__teaser {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.5rem, 0.9vw, 0.9rem);
  margin-top: clamp(1.25rem, 2.4vw, 2.25rem);
  color: var(--c-white);
  text-decoration: none;
}
.hero__teaser > span:first-child {
  display: inline-flex;
  align-items: center;
  min-height: clamp(38px, 3.44vw, 70px);
  padding: var(--sp-2) clamp(1.5rem, 2.9vw, 3.75rem);
  border: 1px solid rgba(255, 255, 255, 0.85);
  border-radius: 999px;
  font-family: var(--ff-display);
  font-size: clamp(0.8125rem, 1.302vw, 1.5625rem);   /* .xd teaser: 25px */
  font-weight: 700;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.hero__teaser:hover > span:first-child { background: var(--c-white); color: var(--c-navy); }
/* The chevron below it: two borders rotated, so there is no icon file. */
.hero__teaser-chev {
  width: clamp(14px, 1.35vw, 26px);
  height: clamp(14px, 1.35vw, 26px);
  border-right: 3px solid var(--c-cyan);
  border-bottom: 3px solid var(--c-cyan);
  transform: rotate(45deg) translate(-2px, -2px);
}

/* --- Client questions ---------------------------------------------------- */
/* The navy ground ends 130px below the closing line on the 1920 artboard
   (footnote ink ends 1950, navy ends 2080). 14rem left 227px there. */
.questions { padding-bottom: clamp(2.5rem, 8.2vw, 8rem); }
.questions__title {
  max-width: 62ch;
  color: var(--c-white);
  font-size: clamp(1.25rem, 2.604vw, 3.125rem);   /* .xd: 50px */
  font-weight: 700;
  line-height: 1.3;
}
.questions__grid {
  display: grid;
  /* .xd: two 800px cards with 123px between them on 1723px of content — 7.1% */
  gap: clamp(1rem, 5.35vw, 6.4rem);
  margin: clamp(1.75rem, 3.4vw, 3.25rem) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 800px) {
  .questions__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.qcard {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(0.75rem, 1.2vw, 1.25rem);
  padding: clamp(1.25rem, 2.4vw, 2.25rem);
  border: clamp(1px, 0.21vw, 4px) solid var(--c-cyan);
}
.qcard__question {
  margin: 0;
  color: var(--c-white);
  font-size: clamp(1rem, 2.083vw, 2.5rem);   /* .xd card copy: 40px */
  line-height: 1.35;
}
.qcard__more {
  /* Pushed to the floor of the card, so this label and the pill under it line
     up across both cards however many lines the question runs to. The right
     question is a line shorter than the left, which left its CTA floating
     mid-card against the other one. */
  margin: auto 0 0;
  color: var(--c-white);
  font-family: var(--ff-display);
  font-size: clamp(0.875rem, 1.5625vw, 1.875rem);   /* .xd: 30px */
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}
.questions__foot {
  /* artboard sets this on two lines, in white — not the pale cyan the 2.0
     panels used for body copy on navy. */
  max-width: 92ch;
  margin: clamp(1.75rem, 3.4vw, 3rem) 0 0;
  color: var(--c-white);
  line-height: var(--lh-copy);
}

/* --- Client logo carousel (home) ---------------------------------------- */
/* The Clients page keeps the full grid; the home page runs the same logos as
   one row that scrolls. Horizontal scrolling is native, so it works without
   JS and stays reachable by keyboard and touch. */
.client-logos--carousel {
  background: #EFEFEF;
  /* artboard band 0.3625 W */
  padding-top: clamp(2rem, 8.85vw, 11rem);
  padding-bottom: clamp(2.5rem, 12vw, 15rem);
  /* the artboard also sets a white rule above this band */
  border-top: clamp(4px, 0.52vw, 12px) solid var(--c-white);
  overflow: hidden;
}
/* 114px between the label ink and the logo ink on the 1920 artboard
   (2296 -> 2410); 3rem left barely half of that. */
.client-logos--carousel .section-label { margin-bottom: clamp(1.5rem, 5.7vw, 6.75rem); }
.logo-grid--track {
  display: flex;
  gap: clamp(2rem, 5vw, 6rem);
  grid-template-columns: none;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  padding-bottom: var(--sp-2);
}
.logo-grid--track::-webkit-scrollbar { display: none; }
/* Four across at desktop, so the row rests on whole logos rather than
   slicing one at the edge; fewer as the screen narrows. */
.logo-grid--track .logo {
  --track-gap: clamp(2rem, 5vw, 6rem);
  flex: 0 0 auto;
  width: calc((100% - 1 * var(--track-gap)) / 2);
  scroll-snap-align: start;
}
@media (min-width: 700px) {
  .logo-grid--track .logo { width: calc((100% - 2 * var(--track-gap)) / 3); }
}
@media (min-width: 1000px) {
  .logo-grid--track .logo { width: calc((100% - 3 * var(--track-gap)) / 4); }
}

/* --- Rotating testimonials ---------------------------------------------- */
.testimonials {
  background: var(--c-white);
  /* artboard band 0.325 W */
  padding-top: clamp(2.5rem, 8.9vw, 11rem);
  padding-bottom: clamp(2rem, 5.7vw, 7rem);
}
.testimonials--alt { background: #EFEFEF; }
/* The artboard's mark is not a typographic quote: it is two thick slanted
   bars. Drawn in CSS so it keeps its weight at any size and needs no glyph. */
.testimonials__mark {
  display: flex;
  gap: clamp(4px, 0.42vw, 8px);
  margin: 0;
  /* the glyph itself is replaced; keep the text out of the box */
  font-size: 0;
  line-height: 0;
}
.testimonials__mark::before,
.testimonials__mark::after {
  content: "";
  width: clamp(6px, 0.55vw, 11px);
  height: clamp(18px, 1.72vw, 34px);   /* artboard ink 33px at 1920 */
  background: var(--c-blue);
  transform: skewX(-12deg);
}
.testimonials__track { position: relative; }
/* Every quote is in the markup. With JS the others are hidden; without it they
   simply stack, so nothing is lost to a reader or a crawler. */
.testimonials__item { margin: 0; }
.testimonials__item + .testimonials__item { margin-top: clamp(1.5rem, 3vw, 2.5rem); }
/*
 * With JS the quotes crossfade in place. They are stacked in one grid cell
 * rather than positioned absolutely, so the track keeps the height of the
 * TALLEST quote and the white band never resizes as they rotate — which is
 * what made the old display:none swap jump. visibility (not opacity alone)
 * keeps the inactive quotes out of the accessibility tree and out of the tab
 * order; it is delayed by the fade so it does not cut the transition short.
 */
[data-testimonials].is-live .testimonials__track { display: grid; }
[data-testimonials].is-live .testimonials__item {
  grid-area: 1 / 1;
  margin-top: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(0.75rem);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out),
              visibility 0s linear var(--dur-base);
}
[data-testimonials].is-live .testimonials__item.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
  transition-delay: 0s;
}
@media (prefers-reduced-motion: reduce) {
  [data-testimonials].is-live .testimonials__item {
    transform: none;
    transition: opacity 1ms linear, visibility 0s linear 1ms;
  }
}
.testimonials__quote {
  margin: clamp(0.75rem, 1.5vw, 1.5rem) 0 0;
  max-width: 92ch;   /* two lines at 1920, as the artboard sets it */
  color: var(--c-ink);   /* .xd: #414042, the same as body copy */
  font-size: clamp(1rem, 2.083vw, 2.5rem);   /* .xd quote: 40px */
  line-height: 1.325;                        /* .xd: 53 on 40 */
}
.testimonials__cite {
  margin-top: clamp(0.75rem, 1.4vw, 1.25rem);
  color: var(--c-ink);
  font-weight: 700;
  font-size: clamp(0.95rem, 1.5625vw, 1.875rem);   /* .xd: 30px */
}
.testimonials__dots {
  display: flex;
  justify-content: flex-end;
  /* .xd: four 50x12 bars at a 64px pitch, so 14px between them */
  gap: clamp(8px, 0.73vw, 14px);
  margin-top: clamp(1.5rem, 3vw, 3rem);
}
.testimonials__dot {
  /* The target, not the mark: the bar below is sized on its own, so a narrow
     dot still gives a 44px-wide place to press. */
  width: max(44px, clamp(24px, 2.604vw, 50px));
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  position: relative;
}
.testimonials__dot::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(24px, 2.604vw, 50px);   /* .xd: 50px wide at 1920 */
  height: clamp(4px, 0.625vw, 12px);   /* .xd: 12px */
  transform: translate(-50%, -50%);
  background: var(--c-cyan);
  opacity: 0.45;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.testimonials__dot.is-active::after,
.testimonials__dot:hover::after { opacity: 1; }

/* --- Approach pages (v3.0) -------------------------- page-templates/approach.php --
   Navy head with an eyebrow, then white: an intro, question/answer rows each
   under a short rule, and the "Getting started" close. Measured off the 1x
   artboard: answer column starts at 0.35 W, rules are 3px and 68px wide. */

.page-head__eyebrow {
  color: var(--c-white);
  margin-bottom: clamp(0.75rem, 1.6vw, 1.5rem);
}

.approach__heading {
  color: var(--c-blue);
  font-size: clamp(1.125rem, 2.1vw, 2.6rem);
  font-weight: 700;
  line-height: 1.25;
}
.approach__intro {
  max-width: 78ch;
  margin-top: clamp(0.75rem, 1.4vw, 1.25rem);
  font-size: clamp(1rem, 1.35vw, 1.65rem);
}
.approach__intro p { margin: 0; }
.approach__intro p + p { margin-top: var(--sp-4); }

.approach__list { margin-top: clamp(2.5rem, 5vw, 5rem); }
.approach__row {
  display: grid;
  gap: clamp(0.75rem, 2vw, 2.5rem);
}
.approach__row + .approach__row { margin-top: clamp(2rem, 7.3vw, 7.5rem); }   /* artboard gap 141px at 1920 */
@media (min-width: 900px) {
  /* question left, answer from 0.35 W */
  .approach__row { grid-template-columns: 33% 1fr; }
}
/* The artboard marks each question with a short, thick bar — 137x15px at
   1920 — not a hairline rule. */
.approach__q::before {
  content: "";
  display: block;
  width: clamp(60px, 7.1vw, 150px);
  height: clamp(8px, 0.78vw, 16px);
  margin-bottom: clamp(1.25rem, 3vw, 3.5rem);
  background: var(--c-ink);
}
.approach__q {
  margin: 0;
  max-width: 22ch;
  color: var(--c-ink);
  font-family: var(--ff-body);
  font-size: clamp(1.0625rem, 2.1vw, 2.6rem);
  font-weight: 400;
  line-height: 1.3;
}
.approach__a {
  color: var(--c-ink);
  max-width: 58ch;
  line-height: var(--lh-copy);
}
.approach__a p { margin: 0; }
.approach__a p + p { margin-top: var(--sp-4); }

.approach__close { margin-top: clamp(2.5rem, 5vw, 5rem); }

/* Approach head: taller than the short variant and shorter than the full one —
   the artboard band is 0.4724 W with the title on three lines. */
.page-head--approach {
  min-height: clamp(min(18rem, 78vh), min(47.24vw, 80vh), 57rem);   /* .xd 907; same fold guard as --hero-h */
  padding-bottom: clamp(2rem, 4.5vw, 5.5rem);
}
.page-head--approach .page-head__title { max-width: none; }   /* three lines, as drawn */
/* "Exclusive industry experience" over three lines rather than two. At the
   shared 20ch it set as "Exclusive industry" / "experience", which crowds
   the second line against the stats band on a narrow window. Narrower than
   the longest word, so each word takes its own line at every width. */
.page-template-who-we-are .page-head__title { max-width: 12ch; }

/* With no intro column the head is one column: otherwise the title keeps the
   46% track meant for the two-column layout and wraps far too early. */
@media (min-width: 900px) {
  .page-head--no-intro .page-head__grid,
  .page-head--approach .page-head__grid { grid-template-columns: 1fr; }
}

/* Home hero, measured ink-to-ink off the 1920 artboard:
     logo -> eyebrow 252 | eyebrow -> title 49 | title -> teaser pill 50
     teaser pill -> questions heading 319
   The hero is bottom-anchored, so opening up the eyebrow/title gap lifts the
   eyebrow by the same amount and closes the gap above it at the same time. */
.home-navy .hero { min-height: clamp(20rem, 42vw, 52rem); }
.home-navy .hero__title { margin-top: clamp(1rem, 2.05vw, 2.25rem); }
.questions { padding-top: clamp(2rem, 11.6vw, 11.7rem); }

/* The client replaced the last white-background logos, so nothing needs this
   today. It stays as insurance: the logo list is edited in the admin, and the
   next file someone drops in may well have a white ground again. Multiply
   drops white and leaves the ink, and does nothing at all to a transparent
   PNG or an SVG — so it costs nothing while it is not needed.
   Charles Schwab and Wells Fargo are unaffected: their grounds are coloured,
   not white, and multiply leaves a colour alone. */
.client-logos--carousel .logo img { mix-blend-mode: multiply; }

/* The artboard starts the carousel flush with its section label — the first
   mark sits on the content edge rather than centred in its cell — so the row
   reads off the same left rule as "Select clients" above it. The Clients page
   grid keeps its centring; this is scoped to the carousel. */
.client-logos--carousel .logo { justify-content: flex-start; }

/* --- 30 years (v3.0) ------------------------ page-templates/30-years.php --
   The navy lockup, then Jeanie's letter beside her portrait, signed off. The
   film and the timeline are gone — the artboard drops both. Measured off the
   1x artboard at 1920:
     navy band 0-736 | CELEBRATING ink 275 | "30" ink 334-526 | YEARS ink -616
     white band 750-2037 | heading ink 875-974 | body ink 995 | portrait 992
     signature ink 1708-1888
   Column fractions of the 1726 content: body 1209 (70.05%), gutter 89
   (5.16%), portrait column 428 (24.79%). Percentages, not vw, so they track
   the container rather than the viewport. */

.celebration__hero {
  background: var(--c-navy);
  color: var(--c-white);
  text-align: center;
  /* artboard: the header ink ends 135 and CELEBRATING starts 275 */
  padding-top: clamp(6rem, 14.38vw, 17.25rem);   /* the floor keeps the lockup clear of the header on a phone */
  /* YEARS ink ends 616, the navy ends 736 */
  padding-bottom: clamp(2.5rem, 6.25vw, 7.5rem);
}

.letter {
  background: var(--c-white);
  color: var(--c-ink);
  padding-top: clamp(2.5rem, 6.51vw, 7.8125rem);    /* artboard 125 */
  padding-bottom: clamp(3rem, 7.76vw, 9.3125rem);   /* artboard 149 */
}
.letter__title {
  margin: 0;
  max-width: 42ch;   /* the artboard sets it on two lines, 1011px wide */
  color: var(--c-navy);
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: 1.1;
}
.letter__grid {
  display: grid;
  column-gap: 5.16%;
  row-gap: clamp(2rem, 4vw, 3.5rem);
  margin-top: clamp(1rem, 1.1vw, 1.3125rem);   /* heading ink -> body ink: 21 */
}
@media (min-width: 900px) {
  .letter__grid { grid-template-columns: 70.05% auto; align-items: start; }
}
@media (max-width: 899px) {
  /* the portrait leads on a phone; beside the text there is no room for it */
  .letter__portrait { order: -1; }
}

.letter__body > :first-child { margin-top: 0; }
.letter__body p { margin: 0; line-height: var(--lh-copy); }
.letter__body p + p { margin-top: 1em; }

/* Supplied as black ink on a white JPEG. Multiply drops the white, so the
   mark sits on the page instead of in a box. */
.letter__signature {
  display: block;
  width: 26.1%;   /* artboard 316 of the 1209 body column */
  min-width: 180px;
  height: auto;
  margin-top: clamp(1rem, 2.08vw, 2.5rem);   /* artboard 40 */
  mix-blend-mode: multiply;
}

.letter__portrait { margin: 0; }
.letter__portrait img {
  display: block;
  width: 68%;   /* artboard 291 of the 428 portrait column */
  height: auto;
  border-radius: 50%;
}
@media (max-width: 899px) {
  .letter__portrait img { width: clamp(140px, 42vw, 240px); }
}

/* --- Services (v3.0) ------------------------ page-templates/how-we-work.php --
   The "How we work" artboard (1920x4748): navy page head, three columns of
   capability terms on white, the navy "How we collaborate on projects" row of
   four circles, then the shared rotating quote. Measured ink-to-ink off the 1x
   render at 1920:
     white band 1080-1822 | column heading cap 1246, baseline 1283
     terms cap 1348, item pitch 61, line pitch 37, last baseline 1613
     navy band 1823-3053 | heading cap 2046, baseline 2083
     circles 2212-2476 (264 across, centres on 229/656/1082/1509 and 2344)
     step copy cap 2590, line pitch 37, last baseline 2796
   Body copy here is 28px — between --fs-base (36) and the nav's 26 — so it
   gets its own scoped size rather than a token. The .xd's auto leading works
   out at 1.32 (37/28, 53/40 on the quote), not the 1.2 its stored line boxes
   claim. */

.svc-terms,
.collaborate { --svc-body: clamp(0.9375rem, 1.458vw, 1.75rem); }   /* .xd: 28px */

/* --- Capability terms ------------------------------ ACF: `capabilities` -- */
/* Three plain lists of terms. v3.0 dropped the term + description pairs the
   old comp had, so there is no two-column entry left to style. */

.svc-terms {
  background: var(--c-white);
  padding-top: clamp(2.5rem, 8.3vw, 10rem);        /* band top 1080 -> heading cap 1246 */
  padding-bottom: clamp(2.5rem, 10.5vw, 12.6rem);  /* last term baseline 1613 -> band bottom 1822 */
}
.svc-terms__grid {
  display: grid;
  gap: clamp(2rem, 1.98vw, 2.375rem);   /* .xd: three 550 columns 38 apart on 1726 of content */
}
@media (min-width: 900px) {
  .svc-terms__grid { grid-template-columns: repeat(3, 1fr); }
}
.svc-terms__grid > * { min-width: 0; }

.svc-col__title {
  margin: 0;
  color: var(--c-blue);
  font-size: var(--fs-xl);   /* .xd: 50px */
  font-weight: 700;
  line-height: 1;            /* .xd sets lineHeight 50 on the 50px face */
}
.svc-col__list {
  /* heading baseline 1283 -> first term baseline 1369 */
  margin: clamp(1rem, 2.6vw, 3.125rem) 0 0;
  padding: 0;
  list-style: none;
}
.svc-col__list li {
  /* .xd fills these pure black, not the --c-ink charcoal body copy uses. */
  color: #000;
  font-size: var(--svc-body);
  font-weight: 700;
  line-height: 1.32;   /* 37px on 28px */
}
/* Item pitch 61 less the 37px line — and the .xd agrees: paragraphAfterSpacing 24. */
.svc-col__list li + li { margin-top: clamp(0.5rem, 1.25vw, 1.5rem); }

/* --- How we collaborate ------------------------ ACF: `collaborate_steps` -- */

.collaborate {
  background: var(--c-navy);
  color: var(--c-white);
  padding-top: clamp(2.75rem, 11.25vw, 13.5rem);  /* band top 1823 -> heading cap 2046 */
  padding-bottom: clamp(3rem, 13.1vw, 15.75rem);  /* last step baseline 2796 -> band bottom 3053 */
}
.collaborate__title {
  margin: 0;
  font-size: var(--fs-xl);   /* .xd: 50px */
  font-weight: 700;
  line-height: 1;
}
.collaborate__grid {
  display: grid;
  /* .xd: 382 of text on a 427 pitch, so a 45px gutter. The container is 1440
     against the artboard's 1726, so the columns come out narrower and the
     step copy takes a line or two more — as it does everywhere on the site. */
  gap: clamp(2rem, 2.34vw, 2.8125rem);
  margin: clamp(1.5rem, 6.4vw, 7.7rem) 0 0;   /* heading cap 2046 -> circle top 2212 */
  padding: 0;
  list-style: none;
}
@media (min-width: 640px) {
  .collaborate__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
  .collaborate__grid { grid-template-columns: repeat(4, 1fr); }
}
.collaborate__step { min-width: 0; }

/* The label is the circle: a heading, not a decorated box with a caption. */
.collaborate__circle {
  display: grid;
  place-items: center;
  width: clamp(150px, 13.75vw, 16.5rem);   /* .xd: 264 across */
  aspect-ratio: 1;
  margin: 0;
  padding-inline: 0.75rem;
  border-radius: 50%;
  background: var(--c-cyan);
  color: var(--c-charcoal);   /* .xd: #414042 on the cyan, not the navy */
  font-size: var(--svc-body);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
}
.collaborate__body {
  margin: clamp(1.25rem, 5.47vw, 6.5rem) 0 0;   /* circle bottom 2476 -> copy cap 2590 */
  font-size: var(--svc-body);
  line-height: 1.32;
}

/* ==========================================================================
   Team — page-templates/who-we-are.php
   Rebuilt from the .xd, artboard 319d482c "Who we are" (1920x5558).

   The artboard draws on 1726 of content (x 97..1823) against this build's
   1440 at a 1920 viewport, so every HORIZONTAL number below is a fraction of
   CONTENT, not of the artboard: 1726 and 1440 are the same column. Vertical
   numbers are artboard pixels, which are 1:1 with CSS pixels at 1920.

   Append after the existing Team rules — these deliberately override the
   .stat / .office / .bio blocks written against the v2.0 PDF comp.
   ========================================================================== */

/* --- Stat row ----------------------------------- ACF: `stats` repeater (3) */
/* .xd: navy Rectangle 10 runs 1080..3061 and carries BOTH the stats and the
   map, so the head, this band and .offices are one unbroken navy field. */

.stats-band {
  /* .xd: the rectangle starts at 1080, the numerals' cap line at 1332 — 252px.
     At 125px/1 Manrope's cap sits ~17px under its line box, so 235px of
     padding. 235/1920 = 12.24%. */
  padding-top: clamp(3rem, 12.24vw, 14.6875rem);
  /* .xd: the tallest description ends at 1631, the dot field begins at 1980 —
     349px. The label row overflows 76px ABOVE the plot, so .offices adds
     nothing of its own and the whole gap is spent here. */
  padding-bottom: clamp(3rem, 18.18vw, 21.8125rem);
}

@media (min-width: 700px) {
  /* .xd: the columns are 452 / 452 / 260 wide and begin at x 100 / 748 / 1376
     — of the 1726 of content, 26.19% / 26.19% / 15.06% starting at 0.17% /
     37.72% / 74.10%. They are NOT equal: the third is short so that
     "Engagements over 30 years" sets in two lines, which equal thirds break
     after "30". An 11.15% gap puts the starts at 0 / 37.34 / 74.68, within
     8px of the file at a 1920 viewport, and leaves the 10% of trailing space
     the .xd also leaves.
     Percentages, not vw: these are fractions of the CONTENT, and content
     stops growing at --maxw while vw does not. */
  .stats {
    grid-template-columns: 26.19% 26.19% 15.06%;
    column-gap: 11.15%;
  }
}

.stat__num {
  /* .xd: 125px Light. The old 5vw came off the PDF comp and was half of it. */
  font-size: clamp(2.75rem, 6.51vw, 7.8125rem);
}

.stat__desc {
  /* .xd: 30px BOLD (not semibold), 40px line pitch = 1.333. */
  font-size: clamp(0.9375rem, 1.5625vw, 1.875rem);
  font-weight: 700;
  line-height: 1.333;
  /* .xd: 58px from the numeral's baseline to the description's cap line.
     1em leaves 53 — Manrope's 125px line box ends closer to its baseline than
     Proxima's does. */
  margin-top: 1.17em;
  /* The grid track is the measure now — at 30px a 24ch cap never binds, and
     capping it only stops "Engagements over 30 years" setting in the two
     lines the .xd gives it. */
  max-width: none;
}

/* --- Office map ---------------------------------- ACF: `offices` repeater */
/* Seven offices in v3.0: Chicago is new and New Jersey is gone, so no pin is
   shared by two labels any more and the ringed marker went with it.

   assets/img/us-map-dots-v3.svg is traced from the .xd's own artwork — all
   2236 dots at their exact centres, r 7.06 on a 1328.34 x 809.19 viewBox.
   It REPLACES us-map-dots.svg, which came off the PDF comp at a 1.76 aspect
   against the file's 1.64 (the old silhouette was 7% too wide). */

.offices {
  /* .xd: the dot field ends at 2790, the navy rectangle at 3061 — 271px. */
  padding-bottom: clamp(3rem, 14.11vw, 16.9375rem);
}

.office-map__plot {
  /* .xd: the dot field spans x 295.5..1623.9 — 1328.3 of the 1726 of content,
     76.96%, centred. Labels overflow it: the whole graphic is 1535 wide
     (88.9% of content) and 76px of it sits ABOVE the field. */
  width: 76.96%;
  max-width: none;
  aspect-ratio: 1328.34 / 809.19;
  background-image: url('../img/us-map-dots-v3.svg');
}

.office__dot {
  /* .xd: r 17 on the 1328px field — 2.56% of the plot, so it scales with it. */
  width: clamp(9px, 2.56%, 34px);
}

.office__line {
  /* The line simply joins the dot to its label, so its span IS the two y
     values and there is nothing to author: one number moved, both ends
     follow. (Both `top` and `height` on an absolutely positioned child
     resolve against the container height, which is what makes this work.) */
  top: min(var(--y), var(--label-y));
  height: calc(max(var(--y), var(--label-y)) - min(var(--y), var(--label-y)));
  width: 2px;                          /* .xd: a 2px stroke on a 1328px field */
  margin-left: -1px;
  background: var(--c-white);          /* .xd: solid white, not a tint */
}

.office__label {
  /* .xd sets these at 45px, but the map is ONE graphic and its labels are
     part of it, so they scale with the plot rather than holding an absolute
     size: 1108px of plot here against the .xd's 1328 is 0.834, and
     45 x 0.834 = 37.5px = 1.954vw. Held at a true 45px, "San Francisco"
     runs into "Park City". */
  font-size: clamp(0.75rem, 1.954vw, 2.344rem);
  line-height: 1.25;
}

/* --label-y is the FAR END of the leader line — the point the label meets.
   A label above the field hangs from it, one below sits under it; the .xd
   leaves ~14px of air at 45px either way, which is 0.31em / 0.42em once the
   line box is accounted for. */
.office--above .office__label {
  transform: translate(-50%, -100%);
  margin-top: -0.31em;
}
.office--below .office__label {
  transform: translateX(-50%);
  margin-top: 0.42em;
}
/* New York is the one label beside its pin rather than on a leader line:
   .xd puts it 43px right of the dot centre and centres it on the dot. */
.office--right .office__label {
  transform: translateY(-50%);
  text-align: left;
}

/* --- Leadership bio ------------------------------ ACF: `bio_*` (Team) --- */

.bio-band {
  /* .xd: the navy ends at 3061 and the heading's cap line is at 3159 — 98px,
     ~7 of which is the heading's own line box at 50px/1. Below, the body's
     last line ends 87px above the conversation band. --section-y would give
     128 both ways, which opens the page out where the .xd keeps it close. */
  padding-top: clamp(2rem, 4.74vw, 5.6875rem);
  padding-bottom: clamp(2rem, 4.375vw, 5.25rem);
}

.bio__name {
  font-size: var(--fs-xl);               /* .xd: 50px Semibold */
  font-weight: 600;
  line-height: 1;                        /* .xd: 50px pitch on 50px */
}

.bio__body {
  /* .xd: 36px LIGHT — the only Light body copy on the site, and the reason
     this page reads calmer than the marketing blocks. 48px pitch = 1.333. */
  font-size: var(--fs-base);
  font-weight: 300;
  line-height: 1.333;
  max-width: none;                       /* the 64.5% grid track is the measure */
}

.bio__body p + p {
  /* .xd: 97px between one paragraph's last baseline and the next's first,
     against a 48px line pitch — so the gap itself is 49px. */
  margin-top: 1.36em;
}

/* ==========================================================================
   Clients — page-templates/our-clients.php
   Rebuilt from the .xd, artboard a622b063 "Select clients" (1920x3756).

   The page is the navy head, the logo grid on white, then the shared rotating
   testimonial on the grey ground. The testimonial needs NO css here: it is
   template-parts/testimonials.php with 'ground' => 'alt', and .testimonials /
   .testimonials--alt above already land on the artboard (band 686 tall, mark
   at 2200, dots ending 109 above the close).

   So all that follows is the grid, and every rule is scoped away from
   .logo-grid--track / .client-logos--carousel so the home page's single
   scrolling row is untouched.

   Vertical numbers are artboard pixels, 1:1 with CSS pixels at 1920.
   Append after the existing "Client logo grid" rules — these deliberately
   override the grid the v2.0 comp set.
   ========================================================================== */

/* .xd: white band 1080 -> 2036; logo row centres 1324 / 1563.5 / 1806.5, so an
   even pitch of 241 with 244 from the top of the band to the first centre and
   229.5 from the last centre to the end — half a row plus 123.5 and 109. */
.client-logos:not(.client-logos--carousel) {
  padding-top: clamp(2rem, 6.43vw, 7.71875rem);
  padding-bottom: clamp(2.75rem, 5.68vw, 6.8125rem);
}

/*
 * Five across, wrapping, and each row CENTRED rather than stretched — which is
 * what puts the short last row (four logos in the artboard) in the middle.
 * A full row of five is exactly 5 x 20% = 100%, so centring cannot move it;
 * only a row that fails to fill the line shifts. That holds for any number of
 * logos and at every column count below, so the client can add or drop logos
 * in the repeater and the last row stays centred with nothing counting rows
 * in PHP or in an :nth-child formula.
 */
.logo-grid:not(.logo-grid--track) {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

/* Fixed row height, NOT auto rows: the rows hold very different shapes (a
   square Schwab, a 7:1 BlackRock wordmark) and an auto height would put the
   241 pitch at the mercy of whichever logo is tallest. */
.logo-grid:not(.logo-grid--track) > .logo {
  flex: 0 0 20%;
  height: clamp(4.5rem, 12.55vw, 15.0625rem);
}

/* Five across needs roughly 900px to stay legible; below that the logos would
   be smaller than the type around them. Only the basis changes — the wrap and
   the centring carry over, so a short last row is still centred. */
@media (max-width: 899px) {
  .logo-grid:not(.logo-grid--track) > .logo { flex-basis: 33.3333%; }
}
@media (max-width: 559px) {
  .logo-grid:not(.logo-grid--track) > .logo { flex-basis: 50%; }
}

/* ==========================================================================
   Perspectives + the two "What we're seeing" pages — v3.0
   .xd artboards: "Our thinking" (e064f2e6, 1920x6483),
                  "APPROACH AEO-GEO" (161f6cf8) and "APPROACH G1-G2"
                  (49f3f0bb), both 1920x4346.

   The artboards draw on 1726 of content (x 97..1823), which --maxw 1920 /
   --gutter 97px now reproduces exactly at a 1920 viewport. Every HORIZONTAL
   number below is still written as a fraction of CONTENT, not of the artboard,
   so the columns hold if --maxw moves again. Vertical numbers are artboard
   pixels, which are 1:1 with CSS pixels at 1920.

   Append after the existing rules: the .approach block here deliberately
   overrides the one written against the v2.0 comp (sizes, the answer column
   and the position of the rule).
   ========================================================================== */

/* --- "What we're seeing" ------------ parts/what-were-seeing.php / ACF: `seeing_cards`
   .xd: navy head ends 605, the 50px heading sets at 763, then two cards on
   #EFEFEF — 1723x472 at 885 and 1723x531 at 1387 — and "FEATURED ARTICLES" at
   2059. The whole run is one white field, so the gap between two bands is
   spent once, on the band below. */

.wws {
  padding-top: clamp(2.5rem, 8.23vw, 9.875rem);   /* band bottom 605 -> heading box 763 */
  padding-bottom: 0;                              /* the band below owns the gap */
}
/* .xd: card 2 ends 1918, "FEATURED ARTICLES" sets at 2059. */
.wws + .u-band { padding-top: clamp(2.5rem, 7.34vw, 8.8125rem); }
/* ...unless the band below is the grey one, which happens when no article is
   featured. That is a different field, so the gap cannot be borrowed from it:
   the white band closes itself, and the grey one keeps its usual padding.
   Without this the last card sat flush on the grey band's edge. */
.wws:has(+ .u-band--alt) { padding-bottom: var(--section-y); }
.wws + .u-band--alt { padding-top: var(--section-y); }

/* .xd: heading baseline 803, card 1 top 885. --lh-tight puts the box bottom at
   815, so 70 of it is margin — more than the 56 .section-label carries for the
   grid below, where the rule is a list and not a slab. */
.wws .section-label { margin-bottom: clamp(1.5rem, 3.62vw, 4.35rem); }

.wws__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(0.75rem, 1.56vw, 1.875rem);   /* .xd: 30px between the two cards */
}

.wws__card {
  position: relative;                      /* the stretched .wws__link */
  /* .xd fills these #EFEFEF — a warmer, flatter grey than --c-surface-alt
     (#F6F8FA), which the article grid further down the same artboard uses. */
  background: #EFEFEF;
  /* .xd: text inset 71 of the 1723 card, ink 60 under the top edge and the
     pill 52-61 above the bottom one. max() holds the side inset at 20px on a
     phone, where 4.12% of the column is 14 and the copy crowds the edge. */
  padding: clamp(1.25rem, 3.125vw, 3.75rem) max(1.25rem, 4.12%) clamp(1.25rem, 2.92vw, 3.5rem);
}

.wws__title {
  margin: 0;
  /* .xd: the title frame is 1308 of the 1584 inside the card — 82.6% — and
     that is what breaks both titles after "visibility" and after "prepare".
     Left to fill the card they run a word longer and the cards grow. */
  max-width: 82.6%;
  color: var(--c-blue);
  font-size: clamp(min(1.5rem, 9vw), 3.646vw, 4.375rem);   /* .xd: 70px Semibold */
  font-weight: 600;
  line-height: 1.2;              /* .xd: 84px pitch on 70px */
  text-wrap: wrap;               /* not balance: the .xd fills each line */
}
.wws__link { text-decoration: none; }
/* The card IS the link. One <a>, stretched — so the pill stays decorative and
   the same destination is not announced twice. */
.wws__link::after {
  content: "";
  position: absolute;
  inset: 0;
}
.wws__card:hover .wws__title,
.wws__link:focus-visible { color: var(--c-navy-deep); }
.wws__link:focus-visible { outline-offset: 6px; }

.wws__standfirst {
  /* .xd: 40px Semibold on the #414042 body charcoal, 50px pitch. */
  margin: clamp(0.5rem, 0.99vw, 1.1875rem) 0 0;   /* title box 1113 -> standfirst box 1132 */
  color: var(--c-ink);
  font-size: var(--fs-md);       /* .xd: 40px */
  font-weight: 600;
  line-height: 1.25;             /* .xd: 50px pitch on 40px */
}

/* .xd: standfirst box ends 1182, pill top 1229. */
.wws__card .u-pill { margin-top: clamp(1rem, 2.45vw, 2.9375rem); }

/* --- Approach pages, remeasured off v3.0 ----------- page-templates/approach.php --
   The v2.0 numbers under-set this page badly: the .xd runs the questions at
   50px and every body block at 36px, against the 40/26 the old rules gave, and
   it starts the answer at 52.5% of the content, not 35%. */

/* .xd: eyebrow 40px Semibold, no tracking, box 449; title box 554 — 58 apart.
   .u-eyebrow's 45px and 0.06em tracking set it a fifth wider than drawn. */
.page-head--approach .page-head__eyebrow {
  margin-bottom: clamp(0.75rem, 3.01vw, 3.625rem);
  font-size: var(--fs-md);       /* .xd: 40px */
  font-weight: 600;
  letter-spacing: 0;
}
.page-head--approach .page-head__title {
  /* .xd: 80px on a 90px pitch, three lines. The title frames are 1103 (AEO-GEO)
     and 1286 (G1-G2) — dragged, not designed. Manrope needs 1148 for G1-G2's
     third line and must stay under 1331 to keep its second from swallowing
     "NEXT", so 15.5em (1240 at 80px) sits in the middle and lands BOTH titles
     on the three lines drawn, breaking at the same words. Unconstrained,
     Manrope sets them in two long lines and the band reads half empty. */
  max-width: 15.5em;
  line-height: 1.125;
}

/* .xd: navy band ends 907, the 50px intro heading sets at 1015. */
.approach { padding-top: clamp(2.5rem, 5.625vw, 6.75rem); }

.approach__heading {
  font-size: var(--fs-xl);       /* .xd: 50px Semibold */
  font-weight: 600;
  line-height: 1;                /* .xd: 50px pitch on 50px */
}
.approach__intro {
  /* .xd: 36px LIGHT, 43px pitch, on a 1685 frame — the full content column. */
  max-width: none;
  margin-top: clamp(0.75rem, 1.77vw, 2.125rem);   /* heading box 1065 -> body box 1096 */
  color: #000;                   /* .xd fills it black, like the rows below */
  font-size: var(--fs-base);     /* .xd: 36px */
  font-weight: 300;
  line-height: 1.194;            /* .xd: 43px pitch on 36px */
}

/* .xd: intro body ends 1268, the first rule tops at 1396. */
.approach__list { margin-top: clamp(2.5rem, 6.69vw, 8.03rem); }

/* The rule belongs to the ROW, not to the question: the .xd starts both columns
   on the same line, 40px under it. Left on ::before of the question, the answer
   climbed to the rule's own top edge and the two columns never lined up. */
.approach__q::before { content: none; }
.approach__row::before {
  content: "";
  grid-column: 1 / -1;
  width: clamp(60px, 7.21vw, 138.5px);    /* .xd: Line 14, 138.5 long */
  height: clamp(8px, 1.042vw, 20px);      /* .xd: a 20px stroke, not a hairline */
  background: var(--c-ink);
}
/* Two columns from 1200, not 900. The .xd sets a 50px question in a 696px
   column — 13.9 characters a line — and --fs-xl stops shrinking at its 2rem
   floor below a 1200 viewport, so a split there would keep narrowing the
   column under type that no longer follows it. At 1200 and up the ratio holds
   at 13.6-13.9, the artboard's own. Below, both columns run full width. */
@media (min-width: 900px) {
  .approach__row { grid-template-columns: 1fr; }   /* cancels the v2.0 33% / 1fr */
}
@media (min-width: 1200px) {
  /* .xd: two 695 columns 210 apart on 1726 of content — 40.34% each with a
     12.19% gutter, and 7% of trailing space the artboard also leaves. */
  .approach__row {
    grid-template-columns: 40.34% 40.34%;
    column-gap: 12.19%;
  }
}
.approach__q {
  max-width: none;               /* the 40.34% track is the measure */
  /* h3 inherits text-wrap: balance, which was evening the three lines out and
     breaking them a word early. The .xd fills each line to the column. */
  text-wrap: wrap;
  /* .xd fills the questions and answers pure black, not the --c-ink charcoal
     the rest of the site's body copy uses. */
  color: #000;
  font-size: var(--fs-xl);       /* .xd: 50px Light */
  font-weight: 300;
  line-height: 1.22;             /* .xd: 61px pitch on 50px */
}
.approach__a {
  max-width: none;
  color: #000;
  font-weight: 300;              /* .xd: 36px Light */
  line-height: 1.194;            /* .xd: 43px pitch on 36px */
}

/* .xd: the last answer ends 2788, "Getting started" sets at 2931. */
.approach__close { margin-top: clamp(2.5rem, 7.45vw, 8.9375rem); }
/* .xd: that heading's box ends 2981 and its body sets at 3039 — nearly twice
   the gap the intro above takes, because it closes the page rather than
   introducing a list. */
.approach__close .approach__intro {
  margin-top: clamp(0.75rem, 3.02vw, 3.625rem);
  color: var(--c-ink);           /* .xd: #414042 here, black above */
}

/* ==========================================================================
   Responsive — Services (how-we-work.php) and Team (who-we-are.php)
   Append to assets/css/site.css AFTER the v3.0 Services / Team blocks.

   Every rule below is inside a `@media (max-width: …)` query, so the approved
   v3.0 rendering at 1600 and above is untouched — verified byte-for-byte with
   full-page captures at 1600x900, 1920x1080 and 1920x950 on both pages.

   Blocks touched here (.svc-*, .collaborate*, .stats*, .office*, .bio__body)
   appear on these two templates only; nothing shared is redefined.
   ========================================================================== */


/* --- Services: capability terms ------------------------------------------ */
/* `.svc-terms__grid` went straight from three columns at 900 to ONE below it,
   so 600–899 set three short lists in a single ~45%-wide column with half the
   band empty (768: one 691px column, 15px terms, a 777px-tall section).
   The longest term — "Thought leadership and executive content" — needs about
   310px at the 15px floor, which two columns clear from ~740 up and only
   wrap at the same place the three-column layout already wraps at 900.
   The row gap is opened because the second row now starts with a 32px
   heading rather than continuing a list. */
@media (min-width: 600px) and (max-width: 899px) {
  .svc-terms__grid {
    grid-template-columns: repeat(2, 1fr);
    row-gap: clamp(2.5rem, 6vw, 3.5rem);
  }
}

/* A term is author-entered, so one unbreakable string (a product or campaign
   name) would push the track past its container — automatic minimum size is
   min-content. Only below the desktop breakpoint, where a 15px term on a
   280px column has no slack. */
@media (max-width: 899px) {
  .svc-col__list li,
  .collaborate__body { overflow-wrap: break-word; }
}


/* --- Team: stat row ------------------------------------------------------ */
/* The .xd's 26.19 / 26.19 / 15.06 tracks are measured against 1726 of content
   at 1920: the third is deliberately short so "Engagements over 30 years"
   breaks in two. Below 900 the description has already hit its 15px floor
   while the tracks keep shrinking with the viewport, so that third column
   falls to 104px at 768 and 100px at 740x360 — about 14 characters a line,
   set in four. Even thirds and a tighter gutter hold every column near the
   28–30 characters the artboard gives them. */
@media (min-width: 700px) and (max-width: 899px) {
  .stats {
    grid-template-columns: repeat(3, 1fr);
    column-gap: clamp(1.5rem, 4vw, 2.5rem);
  }
}


/* --- Team: office map ---------------------------------------------------- */
/* Below 701 the leader lines and labels are hidden and the cities are listed
   under the map instead — so the 76.96% the artboard reserves so the label
   row can overflow the plot is, at those widths, dead navy either side. The
   plot fell to 215x131 at 320: a 2236-dot silhouette rendered smaller than a
   thumbnail, with the 9px pin floor (4.2% of the plot against the artboard's
   2.56%) blotting out whole states — Los Angeles and San Francisco nearly
   touch. Full width and a pin that keeps the drawn ratio give the map back:
   280x171 at 320, 540x329 at 600. */
@media (max-width: 700px) {
  .office-map__plot { width: 100%; }
  /* the artboard's own 2.56% of the plot, with the floor lowered to match */
  .office__dot { width: clamp(7px, 2.56%, 34px); }
}

/* The label clamp's floor is 12px, so between 701 — where the labels come
   back — and 716 the city names set at 13.7px, under the 14px body floor.
   Raising the floor moves nothing above 716, where the 1.954vw term already
   wins; at 701 the widest pair (San Francisco / Park City) still clears by
   21px and no label leaves the content box. */
@media (max-width: 899px) {
  .office__label { font-size: clamp(0.875rem, 1.954vw, 2.344rem); }
}


/* --- Team: leadership bio ------------------------------------------------ */
/* `.bio__body` drops its max-width because at 900 and up the 64.5% grid track
   is the measure. Below 900 the grid is a single column and nothing is left
   to constrain it: 86 characters a line at 768, 94 at 834, 95 at 844x390.
   36em is ~72 characters at any size in this face, so the cap tracks the type
   rather than the viewport. */
@media (max-width: 899px) {
  .bio__body { max-width: 36em; }
}

/* --- FAQ: touch targets ------------------------------- page-templates/faq.php */
/* Responsive pass, FAQ page. There is no v3.0 artboard for this page, so this is
   judged on its own terms: the only measured failure across 320-1920 (plus
   740x360 and 844x390 landscape) is target size. No overflow, no clipping, no
   reflow failure, no body copy under 14px anywhere.

   `.faq__contents a` and `.faq__more` are already inline-block with
   `padding-block: var(--sp-2)` — the existing comment explains why: an inline
   link's box is its font content area (21px), under WCAG 2.5.8's 24px AA
   minimum. That lands the box at 42px at every width below 1440, which clears
   AA but is 2px under the 44px target this pass is held to. The natural box
   reaches 44px on its own from ~1440 up (the type is vw-fluid), so `min-height`
   rather than more padding: it is a no-op wherever the box already passes, and
   nothing moves at the widths where it does.

   Capped at 1599.98px so the >=1600 rendering is provably untouched; between
   1440 and 1600 the rule matches but changes nothing. Prose links inside an
   answer are deliberately NOT included — WCAG 2.5.8 exempts a target in a
   sentence, and padding them would break the leading. */
@media (max-width: 1599.98px) {
  .faq__contents a,
  .faq__more { min-height: 44px; }
}

/* ==========================================================================
   Responsive — Home + 30 years
   front-page.php, page-templates/30-years.php and the parts they own:
   top-banner.php, anniversary.php, testimonials.php, client-logos.php.

   Append to assets/css/site.css. Nothing here changes the rendering at
   1600px and above:
     - every rule is either inside a max-width query below 1600, or
     - inside a max-height: 520px query, which cannot fire at any of the
       audited desktop heights (900 / 950 / 1080), or
     - written with max()/min() so the existing value still wins at >=1600
       (the two unqueried rules at the end are intrinsic-minimum insurance
       that only binds when a track is already overflowing, which it is not
       at 1600+).
   Verified by a 1920x4360 and 1920x3222 full-page pixel diff, before/after:
   0 differing pixels on both pages.
   ========================================================================== */

/* --- 1. Top banner: body copy under the 14px floor ------- parts/top-banner.php
   .top-banner__message floors at 0.8125rem, and 1.5625vw does not reach 14px
   until a 896px viewport — so the banner ran at 13px on every phone and
   tablet. max() lifts only the floor; at 896px and up the vw term already
   wins, so the curve is continuous and >=1600 is untouched. */
@media (max-width: 899px) {
  .top-banner__message { font-size: max(0.875rem, 1.5625vw); }
}

/* --- 2. Testimonial dots: 24x44 touch targets ---------- parts/testimonials.php
   The bar is 24px wide until a 1690px viewport (clamp(24px, 2.604vw, 50px)),
   so every phone and tablet had a 24px-wide control — under the 44px touch
   floor the rest of the theme keeps.

   The BUTTON grows to 44px; the visible bar keeps its exact clamp width,
   centred in it, so the mark itself is unchanged. gap:0 keeps the row from
   growing (pitch 44 rather than 24+8), and the negative right margin puts
   the last bar's right edge back on the content edge, where the artboard
   sets it — without it the centred bar would sit ~9px short of the rule. */
@media (max-width: 1279px) {
  .testimonials__dots {
    gap: 0;
    margin-right: calc((clamp(24px, 2.604vw, 50px) - 44px) / 2);
  }
  .testimonials__dot { width: 44px; }
  .testimonials__dot::after {
    inset: 50% auto auto 50%;
    width: clamp(24px, 2.604vw, 50px);
    transform: translate(-50%, -50%);
  }
}

/* --- 3. Landscape phone: the hero teaser fell below the fold -----------------
   740x360 and 844x390 are the real cases. The banner (88-99), the fixed
   header resting on it (88-95) and the hero's own 6rem top padding spend
   ~280 of a 360px screen, so the teaser pill — the thing that moves you down
   the page — finished 24px (740x360) and 22px (844x390) below the fold.

   The hero's top padding is NOT touched: it is the fixed header's clearance
   and the header is a shared file. The room comes from the banner, from the
   hero's own min-height (which, with align-items:flex-end, pushes the block
   DOWN when it binds) and from the gaps around the teaser.

   max-height: 520px cannot match at 900 / 950 / 1080, the heights this page
   is signed off at. */
@media (max-height: 520px) {
  .top-banner { padding-block: 0.375rem; }
  .top-banner .anniversary--compact { margin-inline: 0 clamp(0.75rem, 3vw, 2.5rem); }
  .anniversary--compact .anniversary__number { font-size: clamp(1.75rem, 4vw, 2.5rem); }

  /* min-height binds once the band is short, and flex-end then pushes the
     copy down into the fold rather than leaving air under it. */
  .home-navy .hero {
    min-height: 0;
    padding-bottom: clamp(1rem, 3vh, 2rem);
  }
  .hero__title { margin-top: var(--sp-2); }
  .hero__teaser { margin-top: 0.5rem; gap: 0.25rem; }
  .hero__teaser-chev { width: 12px; height: 12px; }
}

/* --- 4. 30 years: the portrait shrank to a thumbnail ---- page-templates/30-years.php
   The artboard's portrait is 68% of a 24.79% column — 291px at 1920. The
   column keeps tracking the viewport all the way down, but the body type
   beside it floors at 16px around 900px, so at 900 a 137px circle sat next
   to full-size copy with ~65px of dead white to its right. Worse, the
   stacked layout below 900 draws it at 240px, so the 899 -> 900 step halved
   it.

   A px floor fills the column instead. max() means the artboard's 68% still
   wins wherever it is the larger of the two: 68% passes 192px at about a
   1264px viewport, so this is inert from there up — and the query stops at
   1599 regardless. */
@media (min-width: 900px) and (max-width: 1599px) {
  .letter__portrait img { width: max(68%, min(100%, 12rem)); }
}

/* --- 5. 30 years: intrinsic minimums on the letter grid --- page-templates/30-years.php
   .letter__grid is the one two-column grid in these two pages that the
   "Intrinsic minimums" block above does not cover. A grid item's automatic
   minimum size is its min-content width, so one long unbroken word in the
   ACF letter body (a URL, a long product name) would push the 70.05% track
   past its own track size and take the page with it.

   Nothing in the current copy triggers it, which is why this changes no
   pixel at any width — it is insurance against content, not a layout fix. */
.letter__grid > * { min-width: 0; }
.letter__body { overflow-wrap: break-word; }

/* ==========================================================================
   Responsive — Perspectives cards, and every page head on a short landscape
   viewport. From the Clients/Perspectives audit; the dot fix it also carried
   is dropped, because the unscoped one merged with the home page already
   covers Clients.
   ========================================================================== */

/* The "What we're seeing" title is capped at 82.6% of its card, which is the
   artboard's own proportion at 1920 but leaves a sixth of the card empty once
   the card is a phone wide — the first title ran to five lines at 320px. The
   cap is released below the breakpoint where the card stops being wide. */
@media (max-width: 899px) {
  .wws__title { max-width: none; }
}

/* --- Page heads on a short landscape viewport ----------- parts/page-head.php
   The vh terms in --hero-h guard the fold, but clamp()'s rem floor sits under
   them and wins on a landscape phone. Releasing the floor (done in tokens.css)
   stops the band overflowing the screen; this sizes it by its own content so
   the head is not still mostly empty navy. Measured at 740x360 before this:
   the Clients head was 416px against a 360px viewport, with 150px of empty
   navy above the title and the intro starting below the fold.

   Not scoped to a template: the floor is on every page head, so every page
   head has the fault. It cannot match a desktop review viewport, so the
   approved rendering is untouched by construction. */
@media (max-height: 460px) and (orientation: landscape) {
  .page-head {
    min-height: 0;
    padding-top: clamp(3.5rem, 18vh, 6rem);
    padding-bottom: clamp(1.25rem, 6vh, 2.5rem);
  }
  /* The columns are stacked at these widths anyway; keep them tight so the
     whole band still clears a 360px screen. */
  .page-head__grid { gap: var(--sp-4); }
}

/* --- Perspectives: results while a request is in flight -------- blog.js --- */
/* The tiles dim rather than disappearing, and nothing moves — the grid keeps
   its height, so the page does not jump when the new set lands and the reader
   does not lose their place mid-scroll. */
[data-blog-results] { transition: opacity var(--dur-fast) var(--ease-out); }
[data-blog][data-blog-loading] [data-blog-results] { opacity: 0.45; }
@media (prefers-reduced-motion: reduce) {
  [data-blog-results] { transition: none; }
}

/* --- The client logo carousel --------------------------------- motion.js --
   One logo at a time, looping. Without JS, and under prefers-reduced-motion,
   the row stays the native horizontal scroller below and none of this
   applies — motion.js adds the class only when it takes over. */
[data-logo-carousel].is-stepping .logo-grid--track {
  /* The track stops being a scroller: the script moves it with a transform,
     and a scrollLeft of its own would fight that. The band already clips. */
  overflow: visible;
  scroll-snap-type: none;
  will-change: transform;
}

/* --- Subscribe: the answer line ---------------------------- subscribe.js --
   Empty until there is something to say, so it takes no space in the footer
   until it does. */
.subscribe__status:empty { display: none; }
.subscribe__status {
  margin: clamp(0.5rem, 1vw, 0.75rem) 0 0;
  font-size: clamp(0.8125rem, 1.1vw, 1rem);
  line-height: 1.4;
}
.subscribe__status[data-state="ok"]    { color: var(--c-lime); }
.subscribe__status[data-state="error"] { color: #FFB4A8; }
.subscribe__status[data-state="busy"]  { color: var(--c-white); opacity: 0.75; }

/* --- U-Tools ------------------------------- page-templates/utool.php ----- */
/* The 4D Method and (later) the Market Volatility Toolkit. Built from the
   theme's own bands rather than the live page's Elementor layout: the brief is
   the same content in the new design, not a copy of the old one. */

/* Lead: the sentence left, the tool's mark right. They stack under 900, where
   two columns would leave the mark too small to read. */
.utool-lead__grid {
  display: grid;
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
}
@media (min-width: 900px) {
  .utool-lead__grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}
.utool-lead__copy { margin: 0; }
.utool-lead__mark { justify-self: start; }
@media (min-width: 900px) { .utool-lead__mark { justify-self: end; } }
.utool-lead__logo { display: block; width: 100%; max-width: 20rem; height: auto; }

/* The diagram is the page's one piece of artwork; it gets the full content
   width and nothing else in the band. */
.utool-diagram__figure { margin: 0; }
.utool-diagram__img {
  display: block;
  width: 100%;
  max-width: 60rem;
  height: auto;
  margin-inline: auto;
}

/* The cards: same grey card as "What we're seeing", two up where there is room.
   auto-fit with a floor so three awkward columns never happen at 1100. */
.utool-cards__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(0.75rem, 1.56vw, 1.875rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}
.utool-card {
  position: relative;                      /* for the stretched link */
  display: flex;
  flex-direction: column;
  background: #EFEFEF;
  padding: clamp(1.25rem, 2.6vw, 2.5rem) max(1.25rem, 4.12%) clamp(1.25rem, 2.2vw, 2rem);
}
.utool-card__title {
  margin: 0 0 var(--sp-4);
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(1.25rem, 1.8vw, 1.75rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--ls-heading);
}
/* The whole card is the link: one target, one tab stop, and the title still
   carries the accessible name. */
.utool-card__link { color: inherit; text-decoration: none; }
.utool-card__link::after { content: ""; position: absolute; inset: 0; }
.utool-card:hover .utool-card__title,
.utool-card:focus-within .utool-card__title { text-decoration: underline; }
.utool-card__body {
  margin: 0 0 var(--sp-6);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--c-ink);
}
.utool-card__cta {
  margin-top: auto;                        /* the label sits on the floor */
  color: var(--c-blue);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}
.utool-card__cta::after { content: " \203A"; }

.utool-signup {
  margin: clamp(1.5rem, 3vw, 3rem) 0 0;
  font-size: var(--fs-lg);
  color: var(--c-blue);
}
.utool-signup p { margin: 0; }

/* Close: the claim beside the image it illustrates. */
.utool-close__grid {
  display: grid;
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
}
@media (min-width: 900px) {
  .utool-close__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
.utool-close__heading {
  margin: 0;
  color: var(--c-blue);
  font-family: var(--ff-display);
  font-size: clamp(1.375rem, 2.2vw, 2.25rem);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: var(--ls-heading);
}
.utool-close__img { display: block; width: 100%; height: auto; }

/* --- In-article button and the toolkit offer ------------------------------

   Six migrated articles carry <a class="btn">, which nothing styled, so the
   live site's button was printing here as a plain underlined link. The site
   has exactly one button shape — see .u-pill in base.css, and the note above
   .ff-btn-submit, which repeats it for markup that cannot carry the class.
   This is the third such place, and for the same reason: the markup is the
   migration's. Same pill, same lime, same ink. */
.post-body .btn,
.toolkit__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: clamp(120px, 8.958vw, 172px);
  min-height: clamp(44px, 3.958vw, 60px);
  padding: var(--sp-2) clamp(1.5rem, 3vw, 2.75rem);
  border: 0;
  border-radius: 999px;
  background: var(--c-lime);
  color: var(--c-ink);
  font-family: var(--ff-display);
  font-size: clamp(0.875rem, 1.25vw, 1.25rem);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.post-body .btn:hover,
.post-body .btn:focus-visible,
.toolkit__btn:hover,
.toolkit__btn:focus-visible { background: var(--c-lime-hover); color: var(--c-ink); }

/* The offer sits on its own band at the foot of the article, the way the live
   page sets it: a quiet strip, the button centred in it. */
.toolkit {
  margin-block: var(--sp-10);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  background: var(--c-surface-alt);
  text-align: center;
}

/* Three across, as the live page sets them, and never larger than they were
   drawn. Migrated, each of these was a full-width figure under a heading of
   its own, which is the stack the client asked to be rid of.

   Scoped through .toolkit, because the band is written inside the article and
   the article's list rules outrank a bare class: `.post-body ul` indented the
   row off the centre line of the button under it, and `.post-body li + li`
   dropped the second and third icons 12px below the first. */
.toolkit .toolkit__icons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(1.25rem, 4vw, 3rem);
  margin: 0 0 clamp(1.5rem, 3vw, 2.25rem);
  padding: 0;
  list-style: none;
}
.toolkit .toolkit__icons li { margin: 0; }
.toolkit__icons img {
  display: block;
  width: clamp(3.5rem, 7vw, 5rem);
  height: auto;
}

/* The dialog.

   Shaped like the one on the live site, which is as plain as this gets: a
   coloured bar with the title in it, white underneath, the button at the
   bottom. Navy and lime here rather than the sage green there, because those
   are the colours this site uses.

   Sized to the form, never taller than the screen — the form scrolls inside
   it rather than the page behind. */
.toolkit-modal {
  width: min(29rem, calc(100vw - 2rem));
  max-height: min(42rem, calc(100vh - 2rem));
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: var(--c-surface);
  color: var(--c-ink);
  box-shadow: 0 1.5rem 3.5rem rgb(14 62 100 / 30%);
  overflow: hidden;

  /* The closed state. .is-open is added a frame after showModal() so the
     browser has something to transition FROM; without that frame it opens
     already-open and nothing animates. */
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
/* A bar of its own height over a body that takes the rest. Only [open]: the
   browser hides a closed <dialog> with display:none, and a display declared on
   the element itself would beat that and leave the popup on the page. */
.toolkit-modal[open] {
  display: flex;
  flex-direction: column;
}

.toolkit-modal.is-open { opacity: 1; transform: none; }
.toolkit-modal.is-closing { opacity: 0; transform: translateY(0.25rem); }

/* ::backdrop exists only while the dialog is open, so it takes the same two
   classes and fades with it. */
.toolkit-modal::backdrop {
  background: rgb(14 62 100 / 0%);
  transition: background var(--dur-fast) var(--ease-out);
}
.toolkit-modal.is-open::backdrop     { background: rgb(14 62 100 / 55%); }
.toolkit-modal.is-closing::backdrop  { background: rgb(14 62 100 / 0%); }

/* Nothing moves, and the script closes it outright rather than waiting on a
   transition that will not fire. */
@media (prefers-reduced-motion: reduce) {
  .toolkit-modal,
  .toolkit-modal::backdrop { transition: none; }
  .toolkit-modal { transform: none; }
}

/* The close button is pinned to the popup's corner, out of the flow, so the
   bar holds only the title. Its right padding keeps a long title clear of the
   button: the button's inset, its size, and a gap. */
.toolkit-modal__head {
  --close: 1.75rem;
  flex: none;
  padding: var(--sp-5) calc(var(--sp-2) + var(--close) + var(--sp-3)) var(--sp-5) var(--sp-6);
  background: var(--c-navy);
}
/* Scoped, because the dialog is written inside the article and `.post-body h2`
   carries a type selector: it outranks a bare class and was setting this title
   in the article heading blue, which is the navy of the bar it sits on, under
   a 4rem top margin. That was the tall empty bar with no title in it. */
.toolkit-modal .toolkit-modal__title {
  margin: 0;
  color: var(--c-white);
  font-family: var(--ff-display);
  font-size: clamp(1.25rem, 4vw, 1.5rem);
  font-weight: 600;
  line-height: 1.25;
}
.toolkit-modal__close {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  width: var(--close);
  height: var(--close);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--c-white);
  font-size: 1.375rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.8;
  transition: opacity var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.toolkit-modal__close:hover,
.toolkit-modal__close:focus-visible { opacity: 1; background: rgb(255 255 255 / 18%); }
/* Whatever the bar leaves over. That height was subtracted by hand here, and
   the bar then outgrew the guess; the flex column measures it. */
.toolkit-modal__body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--sp-8) var(--sp-6);
  overflow-y: auto;
}

/* The form inside reuses .post-form, so every Fluent Forms rule above applies
   to it; only the box those rules draw is undone, the dialog being the box. */
.toolkit-modal .post-form {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}

/* The article pill, smaller. In a popup this narrow, under fields this size,
   the full-size one outweighed the form it sends. Same shape and colours, and
   still 44px tall, which a thumb can find. */
.toolkit-modal .post-form .fluentform .ff-btn-submit:not(.ff_btn_no_style) {
  min-width: 0;
  min-height: 2.75rem;
  padding: 0 var(--sp-6);
  font-size: 0.875rem;
}
