/* =========================================================================
   claude.css — THE PER-SITE BUILD LAYER. Buena Vista Financial Path.

   Claude builds every section's CSS here while Adam works in style.css —
   two files, no overlap, no merge conflicts. This file loads AFTER
   style.css (inc/enqueue.php) and the build concatenates in the same
   order, so anything declared here wins ties at equal specificity. It
   stays a separate SOURCE file permanently; nobody merges it by hand.

   Everything written here follows docs/css.md: shared, well-named
   classes; tokens for every color; vendor prefixes PREFIXED-FIRST,
   standard LAST; a reduced-motion fallback for every new animation
   (extend style.css's ADDED block pattern at the end of THIS file — a
   new .hsr variant that skips it is invisible content for those users).

   Measurements in the comments are quoted as MOCK px. original-designs/
   is a 1440px composition exported at 1920 (1.3333x) — divide before use.
   ========================================================================= */

/* =========================================================================
   THE SERVICE MARK, AND WHY THIS ONE PROPERTY IS HERE

   site-content/content.md carries "WMS℠" (U+2120, the service mark), and
   the Typekit build of inter-18pt renders it WRONG: the character is wired
   into the font's `ccmp` table as an ENCLOSING mark, so `X℠` shapes as a
   boxed X and the mark itself disappears. "WMS℠" comes out as "W" and a
   boxed M. Reproduced against kit vcc0cie in isolation; Segoe UI, Arial and
   the system stacks all render it correctly, and so does the design.

   A unicode-range @font-face fallback does NOT fix it — the font claims the
   codepoint, so the browser never falls through. Turning `ccmp` off does,
   and it is the smallest thing that does. `ccmp` composes and decomposes
   glyphs (a base plus a combining accent, mostly); this site's copy is
   English with precomposed accents, so nothing here needs it.

   IT IS A WORKAROUND, NOT THE FIX. The fix is the KIT: adding the glyph, or
   a character set that carries it correctly, in Adobe Fonts. Delete this
   rule the day the kit renders ℠ properly, and re-check that one card.

   It inherits, so it covers the headings too — which matters, because the
   mark appears in an <h3> ("WMS℠ — Wealth Management Specialist.").
--------------------------------------------------------------------------- */
body {
    font-feature-settings: "ccmp" 0;
}

/* ------------------------------------------------------------ surfaces
   The "Green" value on the shared Section Settings select (helpers.php
   maps green -> greenback). A named surface PAIR: background plus the
   text color that reads on it, so the client picks "Green" and contrast
   is not something they can get wrong. Inner elements that need a
   different treatment override it at their own render site.
--------------------------------------------------------------------------- */
.greenback {
    background: var(--green);
    color: #fff;
}

.greenback a:not(.mybutton) {
    color: #fff;
}

/* Hover is handled once, site-wide, in BUTTONS below — a brown fill with a
   white label reads on white, paper and green alike, so no per-surface
   override is needed here. The one exception is a button already sitting on
   brown; that lives with the other button rules, not out here. */

/* =========================================================================
   ANIMATION SYSTEM — THIS SITE'S ADDITIONS

   style.css owns the engine and says DO NOT REDESIGN, so nothing below
   changes it: .scale is a new .hsr VARIANT in the shape the engine already
   defines (hidden start state + animation-name naming the keyframe), and the
   banner drift is a scroll-driven animation of the same family as the
   starter's .parallax-* classes.
   ========================================================================= */

/* A reveal that grows from nothing instead of sliding. Used by the partner
   ring on Our Story (sections/partner_team.php, `hsr scale delay`), which is
   a circle — a circle that fades up reads as a smudge; one that scales reads
   as a ring being drawn.

   Opacity is deliberately NOT set to 0 here, unlike .zoom and .reveal: at
   scale(0) the element already occupies no space, so fading it as well only
   softens the arrival. The shared `reveal` keyframe ends at `transform:
   none`, which is what makes this three lines instead of a new keyframe.

   Reduced motion is already covered: .hsr sits on the same element and
   style.css's reduced-motion block resets `transform` on .hsr itself, so
   scale(0) is lifted with it. .scale is listed there too, per the rule in
   that file that every variant appears in the block. */
.scale {
    transform: scale(0);
    animation-name: reveal;
}

/* =========================================================================
   BUTTONS — the green pill.

   Every button in the design set is the same object: a fully rounded green
   pill with a white label, some carrying an arrow glyph. .mybutton is THE
   hook (templates emit it via starter_button(), Gravity Forms submits are
   filtered onto it), so the design lives on that class and nowhere else.

   White on --green is 3.44:1. That is below WCAG AA's 4.5:1 for body text
   and is a LOGGED, ACCEPTED exception (BRIEF.md, decision 11) — it is not an
   oversight, and it is not to be "fixed" without a new ruling. The hover
   state is not part of that exception: black on white is 21:1.
   ========================================================================= */
.mybutton {
    /* <button> vs <a> parity (docs/css.md): style.css already resets padding
       and font on the shared class, but not the UA appearance a real <button>
       carries — which is what the team section's "Read more" is, and what
       Gravity Forms' submit is filtered onto. Prefixed FIRST, standard LAST;
       Lightning CSS drops the standard property the other way round. */
    -webkit-appearance: none;
    appearance: none;
    background: var(--green);
    color: #fff;
    border: 2px solid var(--green);
    border-radius: 999px;
    font-size: 1.0625rem;
    font-weight: 500;
    line-height: 1.2;
    /* The word roll below flies the label out of the pill and back in from
       under it; the pill's own rounded edge is what the words leave through,
       so it clips. A visible edge, not an invisible wall (docs/animation.md),
       and the words are transparent before they reach it either way. */
    overflow: hidden;
    transition: background-color .3s ease, color .3s ease;
}

.mybutton .btn_face {
    padding: 1.25rem 2.25rem;
}

/* THE HOVER: the fill drops out to white, the green border stays exactly
   where it was, and the label (and the arrow with it, via currentColor)
   turns brown. One background-color transition, no pseudo-element, so a
   pointer that leaves mid-transition simply interpolates back from wherever
   it is — nothing to snap. The border is on the resting state already, so
   the pill never changes size or position between the two states.

   The arrow is a white <img>, not currentColor, so it would vanish against
   the white fill. filter: invert(1) flips it to black with the label. */
.mybutton:hover,
.mybutton:focus-visible {
    background: #fff;
    /* restated: style.css turns the border white on .darkback/.accentback,
       and the border staying put is the whole point of this treatment. */
    border-color: var(--green);
    color: #000;
}

/* Same selector shape as style.css's dark-surface override so it actually
   wins on specificity — the button behaves identically on every surface. */
.darkback .mybutton:hover,
.darkback .mybutton:focus-visible,
.accentback .mybutton:hover,
.accentback .mybutton:focus-visible,
#nav .mybutton:hover,
#nav .mybutton:focus-visible,
#site_menu .mybutton:hover,
#site_menu .mybutton:focus-visible {
    background: #fff;
    border-color: var(--green);
    color: #000;
}

/* The arrow's diagonal drift is GONE — it rides the word roll instead, one
   stagger step behind the last word (see THE WORD ROLL below). All that is
   left here is the colour flip, which is a state, not motion. */
.mybutton .buttonicon {
    width: 1.25em;
    /* 28 mock px arrow against a 17px label */
    -webkit-transition: -webkit-filter .3s ease;
    transition: filter .3s ease;
}

.mybutton:hover .buttonicon,
.mybutton:focus-visible .buttonicon {
    /* white glyph -> black, matching the label's .3s colour fade. */
    -webkit-filter: invert(1);
    filter: invert(1);
}

/* The arrow glyph sits well clear of the label in the mock. Target the
   icon-bearing button with :has(), never a modifier class (BRIEF.md,
   "Global behavior"). */
.mybutton:has(.buttonicon) .btn_face {
    gap: 1.75rem;
}

/* ----------------------------------------------------------- THE WORD ROLL
   On hover the label leaves through the top of the pill a word at a time and
   resettles up from the bottom, the icon following one step behind the last
   word. The current site does this per CHARACTER; per WORD is the same move
   at this label length, and it keeps each word a single readable, selectable
   token rather than a row of letter boxes.

   main.js does the splitting: each word becomes .btn_word inside one
   .btn_words wrapper, the icon picks up .btn_roll_icon, and every one of them
   carries --roll-i, its position in the stagger. The hover itself is the
   run-once pattern (docs/animation.md) — .btn_rolling goes on at
   mouseenter/focus and comes off at animationend, never on :hover, so
   leaving the button mid-flight cannot cut a word off half-way out.
   -------------------------------------------------------------------------- */
.btn_words {
    /* ONE flex item. Loose word spans would each become an item of .btn_face
       and inherit the label-to-icon gap; inside this wrapper they are normal
       inline flow, so the label spaces and wraps exactly as the plain text
       did. */
    display: inline-block;
}

.btn_word {
    display: inline-block;
}

.btn_rolling .btn_word,
.btn_rolling .btn_roll_icon {
    animation-name: btn_roll;
    animation-duration: .55s;
    animation-delay: calc(var(--roll-i, 0) * 65ms);
    /* linear here on purpose: the two halves set their own curves inside the
       keyframes, so the exit accelerates and the arrival decelerates. */
    animation-timing-function: linear;
    /* backwards, so a word still waiting its turn holds frame 0 — which IS
       the resting state, so the wait is invisible. */
    animation-fill-mode: backwards;
}

/* ...EXCEPT ON A FORM SUBMIT (Adam, 2026-09-03). The submit pill keeps the
   design — the same green, the same arrow, the same white-on-hover flip —
   and simply does not roll its label.

   Done HERE rather than by skipping the button in main.js, which is the
   starter's file and identical to the starter's copy: the roll's motion is
   already entirely per-site (everything above), so its exception belongs in
   the same per-site file. The cost is that main.js still splits the label
   into .btn_word spans and still toggles .btn_rolling on this one button —
   dead machinery, but harmless, and main.js was written expecting it: the
   label keeps its real spaces so the accessible name is unchanged, and the
   2000ms safety timer takes .btn_rolling back off even though animationend
   can never fire with nothing to animate.

   animation-name, not `animation`, so the shorthand cannot quietly reset
   the delay and fill-mode that a later starter change might rely on. */
.gform_button .btn_word,
.gform_button .btn_roll_icon {
    animation-name: none;
}

/* Starts AND ends at rest, and the swap from top to bottom happens while the
   word is fully transparent — there is no frame where anything crosses the
   middle of the button, and no endpoint to snap to. */
@keyframes btn_roll {
    0% {
        transform: translateY(0);
        opacity: 1;
        /* ease-IN: the word hangs, then leaves. The fade rides the same
           curve, so it stays solid until it is nearly out. */
        animation-timing-function: cubic-bezier(.5, 0, .75, 0);
    }

    45% {
        transform: translateY(-135%);
        opacity: 0;
    }

    55% {
        transform: translateY(135%);
        opacity: 0;
        /* ease-OUT: it arrives quickly and settles, the same curve the
           arrow used to travel on. */
        animation-timing-function: cubic-bezier(.22, .61, .36, 1);
    }

    100% {
        transform: translateY(0);
        opacity: 1;
    }
}

/* The skip link inherits --primary (the green) from style.css, which would
   put it at the same 3.44:1 as the buttons. It is the first control a
   keyboard or screen-reader user meets, so it takes the brown: 8.26:1. */
.skip_link {
    background: var(--brown);
}

/* =========================================================================
   NAV

   Transparent over a banner, solid brown everywhere else, and semi-opaque +
   blurred once scrolled. `has_banner` comes from inc/helpers.php — CSS alone
   cannot know whether a photo is about to sit behind the bar, and white
   links on a white page are invisible.

   FIXED, not sticky: a sticky bar reserves its own height in flow, which
   would push a full-bleed banner down by 124px instead of letting it run
   under the nav the way the mock has it. Non-banner pages get that height
   back as padding on <main>.
   ========================================================================= */
#nav {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 99;
    background: transparent;
    box-shadow: none;
    transition: background-color .3s ease;
}

body:not(.has_banner) #page_content {
    padding-top: var(--navHeight);
}

body:not(.has_banner) #nav {
    background: var(--brown);
}

/* Scrolled state — main.js adds the class. Prefixed FIRST, standard LAST:
   Lightning CSS reads the reverse order as an override and drops the
   standard property (docs/css.md). */
#nav.nav_scrolled {
    /* THE BRAND BROWN, at .94. This REVERSES the 2026-08-27 ruling that the
       scrolled bar is black — Adam's later call (BRIEF.md, decision 43), so
       the bar now matches the flyin panel it turns into on a phone instead
       of being the one black surface on the site. Written out as rgba rather
       than color-mix(in srgb, var(--brown) ...): color-mix needs Safari 16.2
       and this theme's browserslist floor is safari 14, where the whole
       declaration would be dropped and the scrolled bar would come out
       TRANSPARENT. Lightning CSS cannot fold it away either, because the
       colour it would fold is a custom property. These are --brown's own
       channels (#5D4B3F); if that token moves, move these with it.

       The drop shadow is what separates the bar from a light section once
       the surface is no longer near-black; the blur stays, and is why the
       alpha is .94 rather than solid.

       Prefixed FIRST, standard LAST (docs/css.md). */
    background: rgba(93, 75, 63, .94);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    box-shadow: 0 5px 5px rgba(0, 0, 0, .3);
}

/* --------------------------------------- the bar's SURFACE while it is open
   The z-index that used to be here is gone with the two-render nav: the
   panel is a CHILD of #nav now (decision 55), so it cannot cover the bar,
   and style.css lifts .homelink and .nav_right inside the query instead.
   Decision 38's bug — an overlay eating the only control that closes it —
   cannot recur in this structure.

   The BROWN, not transparent: the bar has to be opaque from the first frame
   of the slide, or the white logo and the white lines spend 350ms over
   whatever page content is scrolling past underneath. Brown is the panel's
   own colour, so the two read as one surface, and #nav's existing
   `background-color .3s ease` carries it there and back with no jump. On a
   non-banner page the bar is already brown and nothing moves.

   Killing the blur is now LOAD-BEARING rather than tidiness: a
   backdrop-filter makes its element the containing block for `position:
   fixed` DESCENDANTS, and the panel is one — left on, `#nav.nav_scrolled`
   would trap a full-screen menu inside a 76px bar. This rule is the same
   (1,1,0) specificity authored AFTER that one, so it wins. Prefixed FIRST,
   standard LAST (docs/css.md). */
body.menu_open #nav {
    background: var(--brown);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}

.nav_bar {
    min-height: var(--navHeight);
    gap: 2.5rem;
}

.nav_logo {
    width: 290px;
    height: auto;
    max-height: none;
    /* 386 mock px */
}

/* ----------------------------------------------- the bar shrinks on scroll
   DESKTOP ONLY, and the media query is load-bearing rather than cautious.
   `.nav_scrolled .nav_logo` is (0,2,0) and the mobile `.nav_logo` override
   further down is (0,1,0) — specificity beats source order, so unscoped
   these two rules would GROW the mobile bar from 76px to 100px and the
   mobile logo from 210px to 225px the moment anyone scrolled, which is the
   exact opposite of the effect. Scoping them above the same 1000px line the
   bar already breaks at is what prevents that.

   The transitions name their properties instead of using `all`: `all` on a
   flex container animates every property it is ever given, including ones
   whose interpolation is a layout thrash, and it silently picks up whatever
   the next person adds to the rule. Two things change here; two things
   transition.
--------------------------------------------------------------------------- */
@media (min-width: 1001px) {

    .nav_logo {
        transition: width .2s ease;
    }

    .nav_bar {
        transition: min-height .2s ease;
    }

    .nav_scrolled .nav_logo {
        width: 225px;
    }

    .nav_scrolled .nav_bar {
        min-height: 100px;
    }
}

.nav_links {
    gap: 2.125rem;
    /* 46 mock px between text runs */
}

.navitem,
.dropdown_link {
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 1.0625rem;
}

.navitem {
    color: #fff;
    /* The colour EASES rather than snapping (Adam, 2026-08-27). Named
       property, not `all` — the underline below rides `transform` and wants
       its own timing, and `all` would quietly take that over too. */
    transition: color .25s ease;
}

.navitem:hover,
.navitem:focus-visible {
    color: var(--orange);
}

/* ------------------------------------------------------ the hover underline
   Sweeps in from the left and retracts back to the left.

   scaleX on a pseudo-element rather than background-size: the rule is a
   composited transform either way, but a pseudo-element can sit OUTSIDE the
   text box (6px under the baseline, where the mock's spacing leaves room)
   without padding the anchor and reflowing the bar. Nothing in the nav
   clips: #nav, .nav_bar and .nav_links all have overflow visible —
   checked, because a decorative part hanging outside its box is exactly what
   an ancestor's overflow:hidden severs (docs/css.md).

   transform-origin stays LEFT in both directions. Exiting to the right reads
   better in isolation but needs the origin swapped mid-interaction, and an
   origin that changes while a transform is part-way through snaps — the
   no-jump rule (docs/animation.md). One origin, interpolated both ways,
   never jumps.

   currentColor means the bar inherits the same white -> orange ease the
   label is already doing, so the two arrive together instead of the
   underline landing in a colour the text has not reached yet.
--------------------------------------------------------------------------- */
.navitem {
    position: relative;
}

.navitem::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -.375rem;
    height: 2px;
    border-radius: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform .28s cubic-bezier(.4, 0, .2, 1);
}

/* :focus-visible alongside :hover — a keyboard user gets the same affordance
   a pointer does, which is the whole reason the underline is not a :hover-only
   flourish (docs/accessibility.md). */
.navitem:hover::after,
.navitem:focus-visible::after {
    transform: scaleX(1);
}

.dropdown_toggle {
    color: #fff;
}

/* The dropdown is now the PANEL doing nothing and the links carrying the
   whole design: the menu itself is a blurred hole in the page and each link
   is its own outlined pill, rather than the brown card the bar used to drop.
   Over a banner photo that reads as part of the nav without painting a slab
   across it.

   Prefixed FIRST, standard LAST — Lightning CSS reads the reverse order as
   an override and drops the standard property (docs/css.md). */
.dropdown_menu {
    background: transparent;
    -webkit-backdrop-filter: blur(5px);
    backdrop-filter: blur(5px);
    padding: 0;
    border-radius: 12px;
    box-shadow: none;
    overflow: hidden;
}

/* The pill fills from the left on hover. ONE background does it: a hard-stop
   two-colour gradient at twice the box width, parked on its white half and
   slid to its green half — so the fill is a background-position transition,
   which interpolates from wherever it is and cannot snap if the pointer
   crosses it twice quickly (hard rule 1). A width or a pseudo-element would
   both need a second box to animate. */
.dropdown_link {
    color: var(--green);
    border: 1px solid #b0b0b0;
    border-radius: 50px;
    margin: 2px;
    background: linear-gradient(90deg, var(--green) 50%, #fff 50%);
    background-size: 200% 100%;
    background-position: 100% 0;
    transition: background-position .2s ease, color .2s ease;
}

.dropdown_link:hover {
    background-position: 0 0;
    color: #fff;
}

.nav_right {
    gap: 1.5rem;
}

.menu_toggle {
    color: #fff;
    /* Label LEFT of the icon, so the lines themselves are the last thing in
       the bar and land on the container gutter the logo starts from. The
       DOM order is unchanged — the button is one control with its own
       aria-label, so nothing reads the two children in sequence. */
    flex-direction: row-reverse;
}

/* ================================================== the hamburger, and its X
   TWO STEPS, in this order, and the close runs them in reverse:

     1. the three lines slide together into the middle
     2. the middle one is gone by then, and the outer two cross

   Why the markup nests (header.php): step 1 is a translate and step 2 is a
   rotate, and on ONE element those are two halves of a single `transform` —
   which transitions as one value, on one clock, giving the single diagonal
   slide this is deliberately not. The individual `translate`/`rotate`
   properties would split them, but they are Safari 14.1 and this theme's
   browserslist floor is safari 14. A wrapper plus a bar is `transform` on
   two elements, which every target has had for a decade.

   Both directions interpolate from a resting state to a resting state and
   the delays mirror, so neither the open nor the close ever snaps (hard
   rule 1). The delay pairs are the whole mechanism: swap which of the two
   elements waits and you have swapped the order of the steps.

   Geometry: three 2px bars on a 7px pitch inside a 16px box, so the outer
   two travel exactly 7px to reach the middle one's centre line.
--------------------------------------------------------------------------- */
.hamb_lines {
    position: relative;
    display: block;
    width: 24px;
    height: 16px;
}

/* The wrapper POSITIONS and it does not paint. `background: none` is the
   load-bearing declaration: style.css's `.hamb_lines span` matches the
   wrapper as well as the bar inside it, so without this every wrapper draws
   its own currentColor bar underneath the one that is meant to be there.
   Coincident at rest, which is why it reads as a normal hamburger — but at
   the end of step 2 the three wrappers are stacked on the centre line still
   horizontal while the bars have rotated, and the X comes out an ASTERISK.
   The width and height were already being overridden here — this selector
   is (0,2,0) against that rule's (0,1,1), heavier on class count — and the
   background was simply the declaration nobody had overridden. */
.hamb_lines .hamb_line {
    position: absolute;
    left: 0;
    width: 100%;
    height: 2px;
    background: none;
    /* CLOSING clock: the wrapper un-translates SECOND. */
    transition: transform .22s ease .22s;
}

.hamb_lines .hamb_line:nth-child(1) {
    top: 0;
}

.hamb_lines .hamb_line:nth-child(2) {
    top: 7px;
}

.hamb_lines .hamb_line:nth-child(3) {
    top: 14px;
}

.hamb_lines .hamb_line span {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 1px;
    background: currentColor;
    /* CLOSING clock: the bar un-rotates FIRST, and the middle one fades back
       in during the un-translate, once there is a gap for it to appear in. */
    transition: transform .22s ease 0s, opacity .16s ease .28s;
}

/* The state is read off aria-expanded, which main.js already maintains — no
   second class to keep in sync with the ARIA. */

/* STEP 1 — the wrapper moves, with no delay. */
.menu_toggle[aria-expanded="true"] .hamb_line {
    transition-delay: 0s;
}

.menu_toggle[aria-expanded="true"] .hamb_line:nth-child(1) {
    transform: translateY(7px);
}

.menu_toggle[aria-expanded="true"] .hamb_line:nth-child(3) {
    transform: translateY(-7px);
}

/* STEP 2 — the bar rotates one step later. The two delays map positionally
   onto the `transform, opacity` list above: the rotate waits, the fade does
   not, so the middle line is already gone by the time the other two cross. */
.menu_toggle[aria-expanded="true"] .hamb_line span {
    transition-delay: .22s, 0s;
}

.menu_toggle[aria-expanded="true"] .hamb_line:nth-child(1) span {
    transform: rotate(45deg);
}

.menu_toggle[aria-expanded="true"] .hamb_line:nth-child(2) span {
    opacity: 0;
}

.menu_toggle[aria-expanded="true"] .hamb_line:nth-child(3) span {
    transform: rotate(-45deg);
}

/* The design's search icon and the rule beside it are REMOVED (BRIEF.md).
   What is left on the right is the client-portal pill, which is a little
   tighter than a content button. */
.nav_login .btn_face {
    padding: 1rem 1.9rem;
    /* 245 x 89 mock px */
}

/* ------------------------------------------------- the client-login pill
   The pill is BOTH the button and the disclosure toggle (header.php): there
   is no Buena Vista login, only four third-party platforms, so it opens a
   menu instead of going anywhere.

   .dropdown_toggle is authored AFTER .mybutton in style.css, so on equal
   specificity it wins and strips the pill back to a bare icon button. The
   fill, border and colour come back because claude.css loads last, but its
   `padding: .25rem` outlives them both — restated here, since .btn_face
   already carries the pill's padding and a second one would inflate it. */
.nav_login.dropdown_toggle {
    padding: 0;
}

/* The caret is a currentColor <svg>, deliberately NOT .buttonicon: that
   class is the design's diagonal arrow and hover-travels 5px up and right,
   which is wrong for a menu affordance. It inherits the label's colour
   fade, and style.css flips it on .open. */
.nav_login svg {
    flex: 0 0 auto;
}

/* The pill is the last thing in the bar, so its menu hangs from the RIGHT
   edge — the starter's left-aligned default would run it off-screen. Keyed
   on the menu's OWN id from header.php rather than a new class (CLAUDE.md 8);
   below 1000px the list is in flow and neither offset applies. */
#client_login_menu {
    left: auto;
    right: 0;
}

@media (max-width: 1000px) {
    .nav_bar {
        min-height: var(--mobileNavHeight);
    }

    .nav_logo {
        width: 210px;
    }

    /* The bar gets shorter here, so everything measured off --navHeight has
       to switch to its mobile companion in the same breakpoint — the token
       trap in docs/css.md. style.css already re-points scroll-margin-top;
       this is the other reader. */
    body:not(.has_banner) #page_content {
        padding-top: var(--mobileNavHeight);
    }

    /* The toggle is alone on the right, and its own padding is the only
       thing between the lines and the gutter. Zero on that side lands them
       on the same inset the logo starts from; the vertical padding stays,
       because it is half of what makes the tap target.

       Nothing here has to MOVE the client-login pill any more. Decisions 40
       and 41 both retire with the restructure: the pill is inside the one
       list, so it crosses the breakpoint with the links instead of being
       hidden out of .nav_right and rendered a second time in the panel, and
       .nav_right no longer needs its auto margin argued back because the
       panel leaves the flow. */
    .menu_toggle {
        padding-right: 0;
    }

    /* ---------------------------------------------------- the panel's LOOK
       The structure is style.css's; this is the brown surface, the narrower
       column and the type the mock draws. The design is the flyin's,
       unchanged — the selectors moved, not the look (decision 55). */
    #site_menu {
        background: var(--brown);
        padding-top: calc(var(--mobileNavHeight) + 3rem);
        grid-template-columns: min(480px, 100%);
        gap: 1.25rem;
    }

    .navitem {
        font-size: 1.25rem;

    }

    /* No hover underline in the panel. It is an affordance for a pointer
       that can hover, and on touch :hover sticks after the tap — the link
       would keep a 2px rule under it until something else was tapped. */
    .navitem::after {
        content: none;
    }

    /* Sub-links are plain translucent white here, not the bar's outlined
       green pills: the pill treatment belongs to the menu that drops out of
       the bar over a photo, and inside a brown panel it would read as four
       buttons. Same values .flyin_navitem carried. */
    .dropdown_link {
        color: rgba(255, 255, 255, .82);
        font-size: 1.0625rem;
        margin: 0;
        padding: 0;
        border: 0;
        border-radius: 0;
        background: none;
    }

    .dropdown_link:hover {
        color: #fff;
        background: none;
    }

    /* The pill keeps its own width instead of stretching the column it
       lands in. */
    .nav_login {
        justify-self: start;
    }

    /* And it sits a little apart from the nav items, as .flyin_foot did.
       Positional off the panel — the login wrap is always the last thing in
       header.php's list — rather than a class that exists to be targeted. */
    #site_menu>.navitem_wrap:last-child {
        margin-top: 1.5rem;
    }
}

/* =========================================================================
   FOOTER — brown, four columns, hairline rule, centred copyright.

   Track widths come off the mock: the brand block runs to about 42% of the
   container and the three link columns share the rest, pushed right. Row
   rhythm is a 40 mock px pitch on 17px links.
   ========================================================================= */
#footer {
    background: var(--brown);
    color: #fff;
    padding-top: clamp(4rem, 9vw, 8.75rem);
    padding-bottom: clamp(3rem, 5vw, 4.5rem);
}

.footer_grid {
    display: grid;
    grid-template-columns: 1.75fr .7fr 1.25fr 1fr;
    column-gap: 2rem;
    row-gap: 3.5rem;
}

.footer_brand {
    text-align: center;
}

.footer_logo {
    width: 248px;
    height: auto;
    /* 331 mock px */
}

.footer_col_title {
    font-size: 2rem;
    font-weight: 500;
    margin: 0 0 1.75rem;
    color: #fff;
}

.footer_list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: .55rem;
}

.footer_link {
    color: #fff;
    font-size: 1.0625rem;
    text-decoration: none;
}

.footer_link:hover,
.footer_link:focus-visible {
    color: var(--orange);
}

.footer_address {
    font-style: normal;
    font-size: 1.0625rem;
    margin: 0;
}

.footer_phone {
    margin: 1.75rem 0 0;
    font-size: 1.0625rem;
}

.footer_social {
    display: inline-flex;
    margin-top: 1.5rem;
    color: #fff;
    border-radius: 50%;
    transition: color .2s ease;
}

.footer_social svg {
    width: 34px;
    height: 34px;
    display: block;
}

.footer_social .li_glyph {
    fill: var(--brown);
}

.footer_social:hover {
    color: var(--orange);
}

/* The NewEdge disclosure (BRIEF.md, decision 76) — not in the mock. Fine print
   between the columns and the rule: it takes over the gap the columns kept
   from the rule, runs no wider than the rule, and is centred like the
   copyright under it. .85 white on the brown is 6.5:1. */
.footer_disclosure {
    margin-top: clamp(3rem, 8vw, 9rem);
    text-align: center;
}

.footer_disclosure p {
    max-width: 1080px;
    margin: 0 auto;
    font-size: .875rem;
    line-height: 1.6;
    color: rgba(255, 255, 255, .85);
    text-wrap: pretty;
}

.footer_disclosure p + p {
    margin-top: 1em;
}

/* The rule is inset from the container in the mock (1440 mock px against
   1648) and carries the copyright below it. A pseudo-element keeps it out of
   the markup — it is decoration, not content. It sits as far below the
   disclosure as the copyright sits below it. */
.footer_bottom {
    display: block;
    text-align: center;
    border-top: 0;
    padding-block: 0;
    margin-top: clamp(2rem, 4vw, 4.25rem);
}

.footer_bottom::before {
    content: "";
    display: block;
    height: 1px;
    background: rgba(255, 255, 255, .5);
    max-width: 1080px;
    margin: 0 auto clamp(2rem, 4vw, 4.25rem);
}

.footer_copyright {
    color: rgba(255, 255, 255, .85);
    font-size: 1.0625rem;
}

@media (max-width: 1000px) {
    .footer_grid {
        grid-template-columns: repeat(3, 1fr);
    }

    .footer_brand {
        grid-column: 1 / -1;
    }
}

@media (max-width: 700px) {
    .footer_grid {
        grid-template-columns: 1fr;
        justify-items: center;
        text-align: center;
    }

    .footer_col_title {
        margin-bottom: 1rem;
    }

    .footer_list {
        justify-items: center;
    }
}

/* =========================================================================
   HOME PAGE SECTIONS

   Every measurement below was pixel-sampled from original-designs/ and then
   DIVIDED BY 1.3333 — the exports are a 1440px composition at 1920px
   (BRIEF.md, decision 12). Mock pixels are quoted in comments so the next
   person can re-check a number against the JPEG without re-deriving the
   scale. Where the brief states a value (the three brand hexes, 96px h1,
   72px h2, 24px lead, 18px body) that value wins over the sample.
   ========================================================================= */

:root {
    /* Corner radii, sampled off the mock: 34-36 mock px on the large media
       (cards, the video, the lead image), 28 on the small collage tiles. Two
       values, not one per component — a third would be a fourth token
       nobody can tell apart. */
    --radius-lg: 26px;
    --radius-md: 20px;

    /* How far the home video hangs below the green surface it overlaps.
       Read by BOTH the surface gradient and nothing else — see the
       tenet_cards video block for why it is not a negative margin.
       375 mock px at 1440, scaled down on smaller screens where a fixed
       281px overhang would swallow the section under it. */
    --video-overhang: clamp(80px, 16vw, 281px);

    /* THE DISTANCE FROM THE VIEWPORT'S LEFT EDGE TO .container's CONTENT BOX.
       .container is (--container + 2 * --container-pad) wide, capped at 100%
       and centred, so its content edge sits one pad in plus half of whatever
       slack is left. A child that has to run off the side of the page pulls
       itself back out by exactly this (see .story_panel_photo).

       It reads 100vw, which INCLUDES the scrollbar gutter (html carries
       scrollbar-gutter: stable), so it over-measures by half a scrollbar —
       about 7px. That is deliberate: the error always points outwards, so a
       full-bleed child reaches PAST the edge rather than stopping short of
       it with a hairline of background showing. Anything using it therefore
       has to clip (docs/css.md — never let the body scroll sideways). */
    --container-inset: calc(var(--container-pad) + max(0px, (100vw - var(--container) - 2 * var(--container-pad)) / 2));

    /* Not a brand colour: LinkedIn's own, which the team icons have to be.
       Sampled, not guessed. The mock's second blue went with the NewEdge
       cards' arrow buttons (2026-09-09) and nothing else ever used it. */
    --linkedin: #0063C1;
}

/* The mock's centred CTA copy measures ~780 CSS px, not the starter's
   neutral 600 — .smallcontainer is a design-system width, so it is retuned
   here once rather than overridden per section. */
.smallcontainer {
    max-width: 780px;
}

/* --------------------------------------------------------------- the lift
   One drop shadow, one value, on every photo and card that is meant to sit
   off the page (Adam, 2026-08-27). `.shadow` is the utility for anything
   that wants it without being on this list.

   The services equivalents are included here rather than left off:
   .service_card is the same object as .tenet_card one page over, and
   .connection_photo the same object as .collage_photo — a shadow on one and
   not the other reads as a mistake, not a distinction. Called out because it
   is an extension of the list as given, not a copy of it.
--------------------------------------------------------------------------- */
.text_image_photo,
.collage_photo,
.video_facade,
.tenet_card,
.service_card,
.connection_photo,
.circle_photo,
.text_photo_image,
.story_panel_photo,
.team_photo,
.icon_card,
.partner_ring,
.post_card,
.video_tile_media,
.featured_post_media,
.shadow {
    box-shadow: 5px 5px 15px rgba(0, 0, 0, .2);
}

/* The Our Story additions above are NOT read off that mock — About-Page.jpg
   draws no shadow on any photo or card, and neither does the home mock the
   original list came from. The lift is Adam's direction applied site-wide,
   so the objects that repeat here (.text_photo_image is .text_image_photo
   one page over, .team_photo and .partner_ring are the page's portraits,
   .icon_card is a card) take it too. A shadow on the home page's photo cards
   and not on Our Story's would read as a miss, not a decision.

   Insights adds .post_card and .video_tile_media on the same reading, and
   Insight-page.jpg draws no shadow on anything either (sampled: the green
   resumes on the row under a video tile, white under a post card). Its FAQ
   rows are deliberately NOT on the list — those are rows in a stack, not
   cards standing off a page. */

/* ------------------------------------------------------- reveal delay utils
   For staggering a reveal that is NOT a cascade — two or three siblings that
   want to arrive in order without their parent becoming a .cascadeN.

   They lose inside a cascade, and that is correct: `.cascade > .hsr:nth-
   child(2)` is (0,3,0) against these (0,1,0), so the cascade keeps owning
   its own stagger and there is no way to half-override it from a child.
--------------------------------------------------------------------------- */
.delay {
    animation-delay: 100ms;
}

.delay-md {
    animation-delay: 200ms;
}

/* ------------------------------------------------- the mark + section head
   The little Buena Vista mark that opens most sections (helpers.php picks
   the colourway from the surface). Centred by default, because that is what
   every section but "The right fit" does; that one resets margin-inline at
   its own render site.
--------------------------------------------------------------------------- */
.deco_mark {
    display: block;
    width: 56px;
    /* 74 mock px, native size — never upscaled */
    height: auto;
    margin: 0 auto .9rem;
}

.section_head {
    /* Positioned so it paints ABOVE the orange disc, which overhangs up into
       this heading in the mock. Without it the disc — a positioned
       descendant of a later sibling — wins the paint order and clips the
       first word. */
    position: relative;
    z-index: 1;
    text-align: center;
    margin-bottom: clamp(2rem, 4.5vw, 3.75rem);
}

.section_head h2 {
    margin: 0;
}

/* THE lead paragraph — size comes from .largefont, this class adds only the
   weight, so a lead that wants the size without the weight can still have
   it (docs/css.md: never bundle size + decoration into one class). */
.lead_text {
    font-weight: 700;
}

.lead_text {
    margin-bottom: 1.75rem;
}

.lead_text p {
    margin: 0 0 1.5rem;
}

.lead_text p:last-child {
    margin-bottom: 0;
}

/* =========================================================================
   HOME BANNER — the photo, the scrim, and the one appearance of the script
   face on the whole site.
   ========================================================================= */
.home_banner {
    position: relative;
    display: flex;
    align-items: flex-start;
    /* 964px is the mock's banner at 1440 (1286 mock px) and the 82vw ramp
       exists to protect the composition below that, NOT for looks. The
       content stack barely shrinks with the viewport (the type clamps floor
       it), so a banner height that fell with vw put the button through the
       woman's forehead everywhere below ~1300px: measured at -79px clearance
       at 1024, -115px at 768, -96px at 600. The 820px floor and the 82vw
       ramp are what hold the button's bottom edge at ~52% of the banner
       across the range; .banner_photo's anchor is what keeps her head below
       that line. Re-measure against the photo before touching either number.

       The box this produces is 0.85:1 at 700px and ~2:1 at 1920 — the photo
       is cropped against a MOVING target, which is why the anchor is derived
       rather than eyeballed. That derivation lives on .banner_photo.

       Published as a custom property because the banner parallax needs the
       SAME number for its scroll range — one value, one place. */
    --banner_height: clamp(820px, 82vw, 964px);
    min-height: var(--banner_height);
    padding-block: 0;
    /* wins over .pad_y: same specificity, later file */
    color: #fff;
    overflow: hidden;
}

.banner_media {
    position: absolute;
    inset: 0;
}

/* <picture> is an INLINE box by default, and .banner_photo's height:100%
   would then resolve against nothing and collapse. The element carries the
   phone <source> and nothing else (sections/home_banner.php), so it takes
   the media box's shape and gets out of the way. */
.banner_media picture {
    display: block;
    width: 100%;
    height: 100%;
}

.banner_photo {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* 45% IS DERIVED, and the one number it is derived against is the
       button. The desktop photo (1600x1518, 1.054:1) is TALLER per unit
       width than the banner box at every width above ~830px, so cover crops
       it VERTICALLY there and this anchor decides which band survives — the
       opposite of the 1.49:1 frame this replaced, which was cropped
       horizontally over most of the range and needed 80% to hold the group
       down.

       The crown of the woman's hair sits at 52.4% of the frame (sampled);
       the button's bottom edge sits at 51.8% of the banner, and her head has
       to stay below it. At 45% the crown lands at 59.0% of the banner at
       1920, 55.5% at 1440, 54.3% at 1280, 53.6% from 1176 down to 830, and
       52.4% below that, where the box is finally taller than the photo, the
       full height shows and the anchor goes inert. Clear at every width, and
       tightest — ~5px — at the bottom of the range, which is the same margin
       the old frame carried at 1440.

       Carrying 80% over would have put her crown at 27.9% of the banner at
       1920, i.e. the headline across her chest. Push this much past 50% and
       the wide end fails first: 60% already reads 45.6% at 1920.

       Horizontally 50% is untouched. Below ~830px the crop turns horizontal
       and shows 81% of the frame's width at 700px; the three of them span
       2-93% and their faces sit at 12-20%, 40-50% and 74-84%, so what it
       trims is the near man's back and the far man's shoulder, never a
       face. All three are complete from ~830px up. */
    object-position: 50% 45%;
    display: block;
}

/* Sampled straight off the mock by dividing the design against the untouched
   source photo: a black wash at .63 behind the nav, holding near .55 through
   the headline, gone by 82% down. It exists so the white nav links are
   legible BEFORE .nav_scrolled takes over (BRIEF.md, "Global behavior"). */
.banner_scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg,
            rgba(0, 0, 0, .63) 0%,
            rgba(0, 0, 0, .56) 22%,
            rgba(0, 0, 0, .52) 42%,
            rgba(0, 0, 0, .30) 58%,
            rgba(0, 0, 0, .12) 70%,
            rgba(0, 0, 0, 0) 82%);
}

/* THE COMPOSITION CONSTRAINT, and it is a real one: the button must clear
   the woman's head. In the mock its bottom edge sits at 51.8% of the banner
   (666 of 1286 mock px), four pixels above the crown of her hair — close
   enough to read as deliberate, never over her face. Everything above it is
   budgeted to land it there: the mark, the two heading lines, a two-line
   intro at body size, and this padding. Change any of those and re-measure
   against the photo rather than trusting that it still fits. */
.banner_inner {
    position: relative;
    z-index: 1;
    width: 100%;
    text-align: center;
    padding-top: clamp(5.75rem, 9.1vw, 8.2rem);
    /* script ink starts ~124 CSS px down, just clear of the 124px bar */
    padding-bottom: clamp(3rem, 8vw, 7rem);
}

/* Both zeroed (Adam, 2026-08-27). .banner_display needs its own reset
   because it is a block span carrying .display, which brings `margin: 0 0
   1rem` with it — without this the two margins stack and the gap under the
   headline is 1rem larger than the one rule suggests.

   This TIGHTENS the home banner stack by ~38px, which moves the button UP
   and so ADDS to the head clearance documented on .banner_inner rather than
   eating into it. */
.banner_title,
.banner_display {
    margin: 0;
}

/* Alisha, orange, once on the whole site. It is a FRAGMENT of the <h1>, not
   a heading of its own, so it resets the caps the .display sibling carries
   rather than living outside the heading (docs/accessibility.md). */
.banner_script {
    display: block;
    font-family: var(--font-script);
    font-weight: 400;
    color: var(--orange);
    text-transform: none;
    letter-spacing: 0;
    line-height: .95;
    font-size: clamp(2.75rem, 1.2rem + 6.6vw, 6rem);
    margin-bottom: -.06em;
    /* the script's tail laps over the caps line in the mock */
}

.banner_display {
    display: block;
}

/* Standard body size, not the lead — Adam's call, 2026-08-27. At 18px the
   intro settles onto two lines, which is both what the mock shows and what
   keeps the button clear of the photo. `balance` evens those two lines the
   way the mock sets them; browsers without it just wrap normally. */
.banner_body {
    max-width: 600px;
    margin-inline: auto;
    text-wrap: balance;
}

.banner_body p {
    margin: 0;
}

.home_banner .bottom_links {
    margin-top: 2.25rem;
}

/* =========================================================================
   TEXT + IMAGE — "Money touches all parts of our lives"
   ========================================================================= */
.text_image {
    /* The brand disc deliberately overhangs the container to the left, the
       way the mock has it sitting 11px off the screen edge at 1440. Clipping
       at the SCREEN edge is a visible edge, not an invisible wall — but the
       page body must never scroll sideways for it (docs/css.md). `clip` does
       that without turning the section into a scroll container, and without
       clipping VERTICALLY, which matters because .fadeInUp starts its
       reveals 100px below their resting place. Old Safari's fallback is in
       the @supports block below, NOT a second `overflow` declaration on this
       rule — the minifier dedupes same-property fallbacks and silently keeps
       only the last one (verified against the built bundle). */
    overflow: clip;
}

.text_image_grid {
    display: grid;
    grid-template-columns: minmax(0, 823fr) minmax(0, 698fr);
    /* 823 / 698 mock px either side of a 126 mock px gutter */
    column-gap: clamp(2rem, 6.5vw, 94px);
    align-items: start;
}

.text_image_media {
    position: relative;
}

.text_image_photo {
    position: relative;
    z-index: 1;
    display: block;
    width: 100%;
    height: auto;
    /* natural ratio — an image beside text never takes a forced one */
    border-radius: var(--radius-lg);
}

/* The orange disc. Sized and offset as a PERCENTAGE of the media column so
   it tracks the photo at every width instead of needing its own breakpoints,
   and translated by its own size so a taller or shorter photo cannot move
   it. 594 mock px across, overhanging the photo by 122 left and 146 up. */
.brand_disc {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 0;
    width: 72.3%;
    aspect-ratio: 1;
    border-radius: 50%;
    background: var(--orange);
    transform: translate(-20.5%, -24.6%);
    /* "Slight animation" (BRIEF.md): a slow breath that rides the standalone
       `scale` property while `transform` holds the offset — two motions on
       one element must never share `transform` (docs/animation.md). Both
       endpoints are identical, so it loops seamlessly and can never snap. */
    animation: disc_breathe 9s ease-in-out infinite;
}

@keyframes disc_breathe {

    0%,
    100% {
        scale: 1;
    }

    50% {
        scale: 1.045;
    }
}

.text_image_copy p {
    margin: 0 0 1.75rem;
}

.text_image_copy> :last-child {
    margin-bottom: 0;
}

.text_image .bottom_links {
    margin-top: clamp(2.5rem, 5vw, 4rem);
}

/* =========================================================================
   TENET CARDS — "A different kind of financial relationship"

   Photo, wash, copy anchored to the bottom edge, and a fixed arrow in the
   corner. Opening the card grows the paragraph's grid row from 0fr to 1fr,
   which pushes the icon and title UP off the bottom anchor — the motion the
   mock draws, done as one interpolated transition rather than a swap between
   two layouts, so there is nothing to snap.
   ========================================================================= */
.tenet_grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 19px;
    /* 25 mock px, both axes */
}

.tenet_card {
    position: relative;
    /* These two lines are load-bearing, not boilerplate. With aspect-ratio
       and an auto width, min-height feeds BACK through the ratio and becomes
       a width: 360px at 812/574 sized the card to 509px, pushing both tracks
       past the container and putting 7px of horizontal scroll on the page
       between roughly 900 and 1100px wide. width:100% makes the width
       definite so the ratio can only ever compute the height from it, and
       min-width:0 keeps the grid track free to shrink. Both measured against
       a reduced test case, not assumed — dropping either one brings the
       overflow back. */
    width: 100%;
    min-width: 0;
    aspect-ratio: 812 / 574;
    /* A fixed ratio fails silently when the copy outgrows it, so the height
       is floored at the worst case: icon + two title lines + four text lines
       + the vertical padding (docs/css.md). */
    min-height: 360px;
    border-radius: var(--radius-lg);
    overflow: hidden;
    isolation: isolate;
}

.tenet_card_photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Resting wash: sampled off the mock as a black gradient running .20 at the
   top to .73 at the bottom — across the WHOLE card, not just under the
   title, which is what keeps a white title legible over any photo. */
.tenet_card_wash {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0, 0, 0, .20), rgba(0, 0, 0, .73));
}

/* Open wash: the flat brown, faded in over the gradient. Two layers, because
   a gradient cannot transition into a flat colour. */
.tenet_card_wash::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--brown);
    opacity: 0;
    transition: opacity .35s ease;
}

.tenet_card:hover .tenet_card_wash::after {
    opacity: 1;
}

.tenet_card_body {
    position: relative;
    z-index: 2;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    /* 85 / 73 / 64 mock px */
    padding: 3.4rem 4rem 3rem;
    color: #fff;
}

/* The four icons arrive at four different natural sizes (78x78, 68x68,
   82x62, 80x70 mock px) and the mock draws each at its own native size — a
   ~20% spread that reads as drift rather than composition. Fitting them all
   inside one 58px box keeps each icon's own proportions, lands three of the
   four within a few percent of the mock, and normalises the outlier. Sizing
   by the box rather than by width is what stops the wide lotus from
   out-measuring the square ones. */
.tenet_card_icon {
    width: auto;
    height: auto;
    max-width: 58px;
    max-height: 58px;
    margin-bottom: 2rem;
}

.tenet_card_title {
    font-size: 2rem;
    line-height: 1.38;
    font-weight: 700;
    margin: 0;
    color: #fff;
}

/* The 0fr -> 1fr row is the whole reveal, and it is a TRANSITION, which is
   what lets the whole thing be a CSS :hover with no JS behind it: a
   transition interpolates from wherever it is, so a pointer that changes
   its mind mid-run cannot snap it (docs/animation.md). overflow lives on
   BOTH boxes — the wrapper clips the child's padding at zero height, the
   child clips its own text.

   NO `visibility: hidden`, AND THAT IS THE LOAD-BEARING PART. It was here
   so a screen reader was never offered copy a button said was collapsed;
   with the button gone (2026-09-09) there is nothing to be inconsistent
   with, and visibility would instead take the paragraph out of the
   accessibility tree with no way left to put it back. Clipped to zero
   height is a purely VISUAL hide — the same mechanism .sr_only uses — so
   the copy is read in full, on every card, at every width. Put visibility
   back and this section starts hiding content from the people who can
   least afford it. See sections/tenet_cards.php. */
.tenet_card_reveal {
    display: grid;
    grid-template-rows: 0fr;
    overflow: hidden;
    transition: grid-template-rows .35s ease;
}

.tenet_card:hover .tenet_card_reveal {
    grid-template-rows: 1fr;
}

.tenet_card_text {
    min-height: 0;
    overflow: hidden;
    margin: 0;
    padding-top: 1.5rem;
}

/* The card text is a WYSIWYG field, so it arrives as <p> runs rather than one
   bare string — the account manager can italicise a phrase inside it without
   a developer. overflow:hidden above makes this a BFC, so these margins can
   never collapse out through the 0fr reveal and give the collapsed card
   height. */
.tenet_card_text p {
    margin: 0 0 1.25rem;
}

.tenet_card_text> :last-child {
    margin-bottom: 0;
}

/* --------------------------------------------------------- the home video
   Click-to-load facade. It overhangs the bottom edge of the green surface,
   and it does that WITHOUT a negative margin: the section paints its own
   surface as a gradient that stops --video-overhang short of its bottom
   edge, so the boundary moves and the section still occupies exactly the
   space it takes up. Nothing outside this section has to know, and the
   client can reorder sections freely.
--------------------------------------------------------------------------- */
.tenet_cards.has_video {
    padding-bottom: 0;
}

.tenet_cards.has_video.greenback {
    background: linear-gradient(180deg,
            var(--green) 0 calc(100% - var(--video-overhang)),
            transparent calc(100% - var(--video-overhang)));
}

.video_facade {
    position: relative;
    margin-top: clamp(3rem, 10vw, 152px);
    /* 203 mock px below the cards */
    /* The facade overhangs the green surface's bottom edge (see the gradient
       on .tenet_cards.has_video.greenback), so the space under it is the
       section's own padding MINUS the overhang — which left the next section
       crowding the video. This puts that space back, on the facade rather
       than on the section, so it travels with the thing that overhangs. */
    margin-bottom: clamp(3.5rem, 8vw, 8rem);
    aspect-ratio: 16 / 9;
    /* the player's ratio, so the loaded iframe fills the box exactly */
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--black);
}

.video_cover,
.video_frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

.video_cover {
    object-fit: cover;
}

.video_wordmark {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(387px, 42%);
    /* native width caps it — the lockup is never upscaled */
    height: auto;
}

.video_play {
    position: absolute;
    top: clamp(1rem, 3.2vw, 47px);
    right: clamp(1rem, 3.1vw, 45px);
    width: clamp(64px, 12.6vw, 182px);
    height: clamp(64px, 12.6vw, 182px);
    /* 242 mock px */
    border: 0;
    border-radius: 50%;
    background: #fff;
    color: var(--black);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    font: inherit;
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease;
}

.video_play svg {
    width: 38%;
    height: 38%;
}

.video_play:hover,
.video_play:focus-visible {
    background: var(--orange);
}

.video_facade.is_playing .video_cover,
.video_facade.is_playing .video_wordmark,
.video_facade.is_playing .video_play {
    display: none;
}

/* =========================================================================
   TEXT + COLLAGE — "The right fit"
   ========================================================================= */
.text_collage_grid {
    display: grid;
    grid-template-columns: minmax(0, 690fr) minmax(0, 822fr);
    column-gap: clamp(2rem, 7vw, 105px);
    align-items: start;
}

/* The one section whose mark and heading are set left, not centred. */
.text_collage_copy .deco_mark {
    margin-left: 0;
}

.text_collage_copy h2 {
    margin: 0 0 1.6rem;
}

.text_collage_copy p {
    margin: 0 0 1.5rem;
}

.text_collage_copy> :last-child {
    margin-bottom: 0;
}

/* Two independent column stacks, NOT a 2x2 grid: each column pushes its
   pair to its own top and bottom edges, and the per-slot ratios below give
   the two columns different photo heights — which is the mock's staggered
   inner edge. A grid would share row heights and lose the stagger. */
.collage {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 26px;
    /* 35 mock px */
}

.collage_col {
    min-width: 0;
    /* same automatic-minimum trap as .tenet_card above */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 26px;
}

/* THE SLOT OWNS THE SHAPE, NOT THE PHOTO (BRIEF.md, decision 75). A collage
   is geometry — its edges line up with each other — so every photo is
   cropped into its slot. On natural ratios, each photo swap was a crop to
   exact pixel measurements before the collage would close up again. */
.collage_photo {
    display: block;
    width: 100%;
    height: auto;
    object-fit: cover;
    border-radius: var(--radius-md);
}

/* "The right fit": the four slots, sampled off the 1920 mock — two 394px
   columns of 507 + 357 and 426 + 444. Both columns carry one gap and share a
   width, so their totals track each other at every width; the 6px the mock's
   own heights differ by lands in the left column's gap, as it does in the
   mock. A missing photo leaves one <img> that is both first and last child —
   it takes the bottom slot's ratio, and the collage is broken either way. */
.text_collage .collage_col:first-child .collage_photo:first-child {
    aspect-ratio: 394 / 507;
}

.text_collage .collage_col:first-child .collage_photo:last-child {
    aspect-ratio: 394 / 357;
}

.text_collage .collage_col:last-child .collage_photo:first-child {
    aspect-ratio: 394 / 426;
}

.text_collage .collage_col:last-child .collage_photo:last-child {
    aspect-ratio: 394 / 444;
}

/* =========================================================================
   CTA + CIRCLES — "Let's get to know each other"
   ========================================================================= */
/* The orange disc overhangs the collage up and to the LEFT, so past the
   point where the media column reaches the container edge it overhangs
   the page. Same `clip` reasoning and the same @supports fallback as
   .text_image above. */
.collage_text {
    overflow: clip;
}

.cta_circles {
    /* The row is wider than the screen by design and is clipped at the
       SCREEN edge — a visible edge, so no invisible wall. It still must not
       give the body a sideways scrollbar. Same `clip` reasoning, and the
       same @supports fallback, as .text_image above. */
    overflow: clip;
}

/* Safari and iOS Safari below 16 have no `overflow: clip`, and both are in
   this project's browserslist. They fall back to clipping on one axis, which
   does make the section a scroll container — acceptable here because neither
   section contains anything sticky. */
@supports not (overflow: clip) {

    .text_image,
    .collage_text,
    .cta_circles,
    .text_photo,
    .story_panel {
        overflow-x: hidden;
    }
}

/* The hairline that closes the section above it. Green, 1px, inset to the
   container — sampled, not assumed: it is the brand green, not a grey. */
.cta_divider {
    border-top: 1px solid var(--green);
    padding-top: clamp(3rem, 7vw, 146px);
}

.cta_circles_copy h2 {
    margin: 0 0 1.5rem;
}

.cta_circles_copy p {
    margin: 0;
}

.cta_circles_copy .bottom_links {
    margin-top: clamp(2rem, 4vw, 3.4rem);
}

/* Large, small, large, small, large — a 519 mock px pitch that runs off both
   edges. nth-child carries the rhythm so any number of photos still reads as
   the same row; the client adding a sixth does not break it. */
.circle_row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(14px, 2.45vw, 35px);
    margin-top: clamp(2.5rem, 5vw, 64px);
}

.circle_photo {
    flex: 0 0 auto;
    width: clamp(150px, 27.4vw, 395px);
    /* 527 mock px */
    aspect-ratio: 1;
    border-radius: 50%;
    overflow: hidden;
}

.circle_photo:nth-child(even) {
    width: clamp(119px, 21.7vw, 313px);
    /* 417 mock px */
}

.circle_photo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* =========================================================================
   SERVICES PAGE SECTIONS

   Same discipline as the home block above: sampled from
   original-designs/Services-Page.jpg and divided by 1.3333, with mock
   pixels quoted in the comments so any number can be re-checked against
   the JPEG. Two things here were derived rather than read off — the banner
   scrim and the card wash, both obtained by dividing the design against
   the untouched source photo in image-source/services-page/, which is the
   only way to separate an overlay from the picture under it.

   The internal banner is shared chrome, not services furniture: five more
   pages open with it. Its crop convention is documented in
   sections/internal_banner.php and enforced by exactly one rule here.
   ========================================================================= */

/* =========================================================================
   INTERNAL BANNER — the short photo banner every page but Home opens with.

   It is NOT a shorter home banner. The home banner is a full-height stage
   built around one composition constraint (the button must clear a
   particular head); this one is a fixed 563 CSS px strip with a single
   left-set caps line, and its photo is chosen to a shared crop rule instead
   of being protected individually. Nothing here is tuned to one photograph,
   which is the whole point — five more pages hang off these rules.
   ========================================================================= */
.internal_banner {
    position: relative;
    display: flex;
    align-items: center;
    /* 750 mock px / 1.3333 = 563 at 1440. This one falls with the viewport
       rather than holding a floor the way .home_banner does: there is no
       subject to keep clear of, so the only job is staying tall enough to
       carry the headline under the nav. Published as a custom property for
       the parallax range, exactly as .home_banner does. */
    --banner_height: clamp(320px, 39vw, 563px);
    min-height: var(--banner_height);
    padding-block: 0;
    /* wins over .pad_y: same specificity, later file */
    color: #fff;
    overflow: hidden;
}

.internal_banner_photo {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    /* THE PROTECTED BAND, and this is the only rule that knows about it.
       Every internal banner photo is cropped so its subject sits between 42%
       and 85% across the frame (see sections/internal_banner.php); 63% is
       the centre of that band, so holding the crop there keeps the subject
       in shot at every width — and keeps it out from under the headline,
       which sits hard left. Crop the next page's banner to the band rather
       than adding a breakpoint here. */
    object-position: 63% 50%;
}

/* THE BANNER PARALLAX
   COVERS BOTH BANNERS: .banner_photo (home) and .internal_banner_photo.
   Same effect on the same kind of object, so one rule.

   THIS REPLACES A VERSION THAT NEVER RAN. The first attempt rode the
   starter's `view()` engine, which is the right primitive on paper — the
   range is the element's own passage through the viewport, so it cannot
   depend on how long the page happens to be. It was inert here, and the
   reason is worth keeping: `view()` measures the subject against its NEAREST
   ANCESTOR SCROLL CONTAINER, and `overflow: hidden` makes an element one.
   Both banner sections clip their photo that way, so the timeline resolved
   against a box the photo never moves inside, progress was constant, and the
   computed transform sat at 0 at every scroll position. Measured, on the
   live install, before this was rewritten. Adam had been carrying a working
   version in the Customizer the whole time.

   So the timeline is the ROOT scroller, and the RANGE is stated explicitly.
   That last part is the whole design:

   1. `animation-range: 0 var(--banner_height)` pins the drift to the scroll
      distance over which the banner is actually on screen. Without it the
      range is the whole document, which makes the effect depend on page
      LENGTH — the same rule reads as a gentle drift on a long page and a
      lurch on a short one.

   2. AND ON A SHORT PAGE IT BREAKS. The photo drifts DOWN while the page
      scrolls UP, so it leaves a bare strip at the top of the banner; that
      strip stays above the fold only while the photo moves SLOWER than the
      page. Full-document range made that ratio a function of content: the
      live Customizer version measured 0.2 on Our Story and 0.42 on Articles
      — fine — but 1.85 on Terms & Conditions, which dragged 476px of bare
      banner into frame. Pinning the range makes the ratio the constant below
      instead, on every page and at every viewport.

   3. .45 IS THE RATIO, and it is what Adam's version happened to produce on
      the pages where it worked (0.42 on Articles). Under .5 the strip is
      always off-screen with room to spare; the drift still reads as depth.

   4. IT MOVES THE PHOTO, NOT .banner_media. The media box also holds the
      scrim, and the scrim is what keeps the white nav links legible before
      .nav_scrolled takes over. Drifting the container drags the gradient off
      the top of the banner and takes the nav's legibility with it.

   5. THE GUARD IS NOT OPTIONAL. `animation: <name> linear both` with a
      scroll timeline leans on animation-duration resolving to the timeline's
      length. Where animation-timeline is unsupported that declaration is
      dropped, the duration falls back to 0s, and the fill snaps the element
      to the final keyframe on load — a banner photo flung its full travel
      and held there, on the browsers least able to show it.

   6. NO SPARE MATERIAL NEEDED, and that is why the photo keeps `height:100%`
      here rather than being grown and pulled up the way the view() version
      grew it. The drift is one-directional and slower than the scroll, so
      the only edge it can expose is the top one, and that edge is always
      above the viewport. The crop stays exactly the one the composition was
      measured against — see .home_banner's note on the 52% line.
   ------------------------------------------------------------------------- */
@keyframes banner_drift {
    from {
        transform: translateY(0);
    }

    to {
        transform: translateY(var(--banner_drift, 0px));
    }
}

@supports (animation-timeline: scroll(root)) {
    @media (prefers-reduced-motion: no-preference) {

        .banner_photo,
        .internal_banner_photo {
            --banner_drift: calc(var(--banner_height) * .45);
            animation: banner_drift linear both;
            animation-timeline: scroll(root);
            animation-range: 0 var(--banner_height);
        }
    }
}

.internal_banner_scrim {
    position: absolute;
    inset: 0;
    /* Both gradients derived by dividing the mock against the untouched
       source in image-source/, not eyeballed, and both are load-bearing:

         down  — the nav is transparent over this banner until .nav_scrolled
                 takes over, so without it the white nav links sit on bare
                 photo.
         right — the one BRIEF.md names, and what carries the white headline.

       Two translucent blacks composite to the same result either way round,
       so the order here is readability, not maths.

       THE NUMBERS ARE OUR STORY'S, NOT SERVICES'. Both banner mocks were
       differenced against their source photos and they do NOT agree: the
       Services page runs .62 -> 0 by 60% across and .57 -> 0 by half the
       height, Our Story runs .78 -> 0 by 50% and .74 -> 0 by 68%. The
       stronger pair is what ships, for a reason that is not taste — Our
       Story's photo is bright foliage under the headline, and the weaker
       scrim leaves white-on-photo at 1.5:1 there, against the 3:1 WCAG needs
       for large text. Measured off a render, not assumed. The stronger pair
       clears 4.5:1 on the same pixels and only deepens the Services banner's
       left edge, where its photo is already dark.

       This is ONE rule for every internal banner on purpose (see
       sections/internal_banner.php). A per-page scrim would tune each
       gradient to the photo that happens to be there today and quietly fail
       the first time a client swaps one. */
    background:
        linear-gradient(180deg, rgba(0, 0, 0, .74) 0%, rgba(0, 0, 0, 0) 68%),
        linear-gradient(90deg, rgba(0, 0, 0, .78) 0%, rgba(0, 0, 0, .72) 15%, rgba(0, 0, 0, .58) 30%, rgba(0, 0, 0, .44) 38%, rgba(0, 0, 0, .12) 46%, rgba(0, 0, 0, 0) 54%);
}

.internal_banner_inner {
    position: relative;
    z-index: 1;
    /* NO width: 100% HERE. The banner is a flex row, and a stretched inner
       would override .container's own width and run the headline to the
       viewport edge. Letting .container win keeps the h1 on the same left
       gutter as every other section on the page — still hard left, just
       left of the container rather than left of the screen. */
    /* Clears the transparent 124px bar floating over the banner. Anything
       measured off --navHeight has to switch to the mobile companion in the
       1000px query where the bar shortens — the token trap in docs/css.md.
       That swap is in the responsive block below; this is its other half. */
    padding-top: var(--navHeight);
}

.internal_banner_title {
    margin: 0;
    /* 88px at 1440. .display clamps to 96 for the home banner's h1 and the
       internal banners run one step down (BRIEF.md, "Design scale"), so the
       size is reset here rather than a second display class being invented
       for a one-line difference. */
    font-size: clamp(2.25rem, 1.16rem + 4.82vw, 5.5rem);
}


/* =========================================================================
   COLLAGE + TEXT — "We are here to be of service."

   The mirror of .text_collage_grid, and deliberately its own set of classes
   rather than a `reverse` modifier: the media is on the other side, the
   heading lives inside the copy column instead of centred over the whole
   grid, and the collage is one tall photo beside a stack of two rather than
   two stacks of two. Three differences is a layout, not a flag.
   ========================================================================= */
.collage_text_grid {
    display: grid;
    /* 638 / 500 mock px either side of a 100 mock px gutter */
    grid-template-columns: minmax(0, 638fr) minmax(0, 500fr);
    column-gap: clamp(2rem, 6.9vw, 100px);
    /* The copy block and the collage are within 13 CSS px of sharing a
       centre line in the mock, so they are centred rather than top-aligned —
       which also survives the copy growing by a line. */
    align-items: center;
}

/* Overrides .collage's even split: the tall photo takes 365 of the 638 mock
   px and the stacked pair 255. Same class, later file, so this wins without
   a modifier or an !important (docs/css.md — load order is the cascade). */
.collage_text_media {
    position: relative;
    grid-template-columns: minmax(0, 365fr) minmax(0, 255fr);
}

/* The tall photo, at the 1920 mock's 485x806. :first-of-type, not
   :first-child — the disc's <span> is the media column's first child. */
.collage_text_media .collage_col:first-of-type .collage_photo {
    aspect-ratio: 485 / 806;
}

/* The pair SPLIT the tall photo's height instead of carrying ratios of
   their own. The gaps are fixed px while the widths scale, so two more fixed
   ratios would leave the bottoms a few px apart at nearly every width. A
   zero basis with no automatic minimum means the pair add no height of their
   own: the tall photo sets the row, the column stretches to it, and they
   share what is left after the gap. The mock draws them identical (341x394
   each, 394 + 18 + 394 = 806), so an even split IS the mock. */
.collage_text_media .collage_col:last-child .collage_photo {
    flex: 1 1 0;
    min-height: 0;
}

/* The photos have to paint over the disc. They are static boxes and the disc
   is a positioned one, so without this the disc wins the paint order and
   covers the collage — the columns are lifted rather than the disc pushed to
   z-index:-1, which would drop it behind the section's own background. */
.collage_text_media>.collage_col {
    position: relative;
    z-index: 1;
}

/* Same disc, different overhang: 427 mock px across against the media
   column's 638, hanging 71 left and 106 up over the tall photo's corner.
   Sized and offset as percentages so it tracks the collage at every width
   instead of needing breakpoints, exactly as .text_image_media does it. */
.collage_text_media .brand_disc {
    width: 66.9%;
    transform: translate(-16.7%, -24.9%);
}

/* .deco_mark is centred by default because most sections centre their head.
   This one is left-set with the copy column, so the auto margins are reset
   at the render site rather than the default being weakened for everyone. */
.collage_text_copy .deco_mark {
    margin-inline: 0;
}

.collage_text_copy h2 {
    margin: 0 0 1.5rem;
}

.collage_text_copy p:last-child {
    margin-bottom: 0;
}


/* =========================================================================
   SERVICE CARDS — "Our core services".

   Six photo cards, two up, that open to reveal the service description.
   BRIEF.md decision 2: cards, not the content document's wheel graphic.

   Related to .tenet_card, NOT a variant of it. Both are rollover photo
   cards; everything measurable about them differs. The wash below was
   sampled off THIS mock (by dividing the design against the untouched source
   photo) and runs 0 -> .97, where the home card's runs .20 -> .73. The home
   card carries a white arrow button; this one carries no control at all in
   the mock. The home card holds four short tenets; this one holds service
   copy that runs to ten lines. Sharing one component would have meant
   branching every one of those.
   ========================================================================= */
.service_grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 20px;
    /* 26 mock px, both axes */
}

/* A short paragraph under a section heading. Named for the slot rather than
   the page, so the next section that wants one does not invent a second. */
.section_head_intro {
    margin-top: 1.25rem;
}

.service_card {
    position: relative;
    /* width + min-width are the same pair .tenet_card documents: with
       aspect-ratio and an auto width, min-height feeds BACK through the
       ratio and becomes a width, which puts horizontal scroll on the page.
       Making the width definite means the ratio can only ever compute height
       from it. There is no aspect-ratio on this rule (see below) but the
       1000px rule adds one, and the trap is the same there. */
    width: 100%;
    min-width: 0;

    /* THE HEIGHT BUDGET, and it is the one number on this page that came
       from the copy rather than the mock.
       ---------------------------------------------------------------------
       The mock draws a 608x430 card and shows it open on Financial Planning,
       whose description is seven lines. Tax Planning's is TEN (524 chars
       against 347), and a fixed ratio fails SILENTLY when copy outgrows it
       (docs/css.md) — the paragraph would simply be cut off, with the card's
       own edge doing the cutting.

       So the mock's `aspect-ratio: 608/430` is deliberately NOT here. The
       container caps at 1236px, which caps the card at 608, which means the
       ratio would resolve to 430 and lose to this min-height at every width
       the two-column grid ever sees — it would be a comment pretending to be
       a declaration. The proportion it describes is recorded in this note
       instead, where it is honest.

       So the height is floored at the worst case across the whole two-column
       range: 55px of padding either side, a 68px icon, its 32px gap, one
       title line at 32/1.38, the reveal's 24px lead-in, and thirteen lines
       of 18/1.55 body — thirteen because the tightest the two-column grid
       ever gets is a ~470px card just above the 1000px breakpoint. That
       lands at 575; 580 is the round number above it.

       The cost is a card ~35% taller than the mock's. The alternative is
       trimming Tax Planning's description by three lines, which would bring
       the whole grid back to 430 — a copy decision, not a CSS one, and one
       flagged for the build lead rather than taken here. Re-measure this
       number if that copy changes. */
    min-height: 580px;

    border-radius: var(--radius-md);
    /* 20 mock px — the collage tile radius, not the large media one */
    overflow: hidden;
    isolation: isolate;
}

.service_card_photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Resting wash. Sampled, not carried over from the home card: this one
   starts at nothing and runs almost to solid, which is what lets a white
   title sit over the bottom of any photo the client swaps in. */
.service_card_wash {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg,
            rgba(0, 0, 0, 0) 0%,
            rgba(0, 0, 0, .15) 20%,
            rgba(0, 0, 0, .46) 50%,
            rgba(0, 0, 0, .81) 80%,
            rgba(0, 0, 0, .97) 100%);
}

/* Open wash: the flat brown faded in over the gradient. Two layers, because
   a gradient cannot transition into a flat colour. */
.service_card_wash::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--brown);
    opacity: 0;
    transition: opacity .35s ease;
}

.service_card.is_open .service_card_wash::after {
    opacity: 1;
}

/* THE ROLLOVER, and why it is four grid rows.
   -------------------------------------------------------------------------
   Resting, the icon and title sit on the floor of the card and the
   description is not there. Open, the pair is at the ceiling with the
   description under it. Done as two boxes swapping places that would snap;
   done as a margin it would fight the padding. Here row 1 is empty slack and
   row 4 is the description, and the two trade their `fr` share in ONE
   interpolation — so the pair rises at exactly the rate the copy appears,
   and there is no frame in which the card is in neither state.

   The card's height never changes, because row 1 gives up precisely what row
   4 takes. Nothing below the card moves, and a hover cannot jitter the page.
   ------------------------------------------------------------------------- */
.service_card_body {
    position: relative;
    z-index: 2;
    height: 100%;
    display: grid;
    grid-template-rows: minmax(0, 1fr) auto auto minmax(0, 0fr);
    /* 55 mock px */
    padding: clamp(1.8rem, 3.8vw, 3.44rem);
    color: #fff;
    transition: grid-template-rows .35s ease;
}

.service_card.is_open .service_card_body {
    grid-template-rows: minmax(0, 0fr) auto auto minmax(0, 1fr);
}

.service_card_slack {
    display: block;
    min-height: 0;
}

/* The six icons arrive between 58 and 78 mock px and the mock draws each at
   its own size. Fitting them inside one 68px box keeps every icon's own
   proportions and normalises the spread; sizing by the BOX rather than by
   width is what stops the wide ones out-measuring the square ones. */
.service_card_icon {
    justify-self: start;
    width: auto;
    height: auto;
    max-width: 68px;
    max-height: 68px;
    margin-bottom: 2rem;
}

.service_card_title {
    font-size: 2rem;
    /* 32px at 1440 — measured off a single cap in the mock, not a text block */
    line-height: 1.38;
    font-weight: 700;
    margin: 0;
    color: #fff;
}

/* The title IS the control — this mock draws no arrow to hang one on. A real
   <button> keeps the copy reachable on touch and on the keyboard, which a
   :hover rule strands on both (docs/accessibility.md). It is stripped back to
   its label so the visitor sees exactly what the mock draws.

   Prefixed FIRST, standard LAST: Lightning CSS reads the reverse order as an
   override and drops the standard property (docs/css.md). */
.service_card_toggle {
    -webkit-appearance: none;
    appearance: none;
    border: 0;
    margin: 0;
    padding: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

/* The hit area the card's design implies: the whole face. It stretches over
   .service_card_body, which is the positioned ancestor and fills the card. */
.service_card_toggle::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 3;
}

.service_card_toggle:focus-visible {
    outline: 3px solid var(--orange);
    outline-offset: 4px;
}

/* Collapsed copy is hidden with VISIBILITY, not opacity alone, so a screen
   reader is never offered text the button says is collapsed. The delay holds
   it visible until the row has finished closing; opening flips it first. */
.service_card_reveal {
    min-height: 0;
    overflow: hidden;
    visibility: hidden;
    transition: visibility 0s linear .35s;
}

.service_card.is_open .service_card_reveal {
    visibility: visible;
    transition: visibility 0s;
}

.service_card_text {
    margin: 0;
    padding-top: 1.5rem;
}

/* WYSIWYG field, so <p> runs rather than one bare string. .service_card_reveal
   above is overflow:hidden and therefore a BFC, so these margins cannot
   collapse out of it and add height to a card that is meant to be closed. */
.service_card_text p {
    margin: 0 0 1.25rem;
}

.service_card_text> :last-child {
    margin-bottom: 0;
}


/* =========================================================================
   CONNECTION PANEL — "One point of connection for your whole financial life"

   Green surface. The white body copy on it measures 3.44:1 and is the
   ACCEPTED exception on this build (BRIEF.md decision 11) — shipped as
   designed and carried on the launch checklist. Nothing here should be
   "fixed" by darkening the surface.
   ========================================================================= */
.connection_head {
    /* The heading breaks to three lines in the mock at 706 CSS px. Left to
       the full container it would set as two, which loses the stacked shape
       the section is composed around. Constraining the HEAD (not the h2)
       keeps the mark centred on the same axis for free. */
    max-width: 740px;
    margin-inline: auto;
}

.connection_grid {
    display: grid;
    /* 535 / 636 mock px either side of a 67 mock px gutter */
    grid-template-columns: minmax(0, 535fr) minmax(0, 636fr);
    column-gap: clamp(2rem, 4.6vw, 67px);
    align-items: center;
}

.connection_copy p:last-child {
    margin-bottom: 0;
}

.connection_media {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 20px;
    align-items: start;
}

.connection_photo {
    display: block;
    width: 100%;
    height: auto;
    /* natural ratio — an image beside text never takes a forced one */
    border-radius: var(--radius-md);
}

/* The stagger IS the composition: 64 CSS px in the mock, expressed against
   the media box's WIDTH (percentage margins resolve on width, never height)
   so the offset tracks the pair as it shrinks instead of stranding a fixed
   64px on a photo half the size. */
.connection_photo:nth-child(2) {
    margin-top: 10%;
}

/* NO HAIRLINE STRAIGHT AFTER THE GREEN PANEL. The mock draws the shared
   .cta_divider above the Services page's closing CTA, and the client took
   it off (2026-09-09): the panel is a filled surface, so its bottom edge is
   already the boundary and a 1px rule 100px under it is a second one drawn
   in open space. Positional and one rule, the same shape the FAQ block
   below used to carry — no page class, no second divider variant.

   `~` and not `+`: every section carrying its own JS emits a <script>
   straight after its </section>, so the CTA is not reliably the ADJACENT
   sibling. It also reads as the intent — "the surface above already closed
   this". */
.connection_panel~.cta_circles .cta_divider {
    border-top: 0;
    padding-top: 0;
}


/* =========================================================================
   HOME PAGE — responsive.

   The mock is a 1440px desktop composition and owes us nothing below that;
   everything here is ours. Breakpoints sit where the CONTENT breaks: 1000px
   is where the two-column sections lose their gutter, 768px is where they
   stack, 560px is where the cards' inner padding stops fitting.
   ========================================================================= */
@media (max-width: 1000px) {

    .text_image_grid,
    .text_collage_grid {
        column-gap: 2rem;
    }

    .tenet_card_body {
        padding: 2.4rem 2.6rem 2.2rem;
    }
}

@media (max-width: 768px) {

    .text_image_grid,
    .text_collage_grid {
        grid-template-columns: 1fr;
        row-gap: clamp(2rem, 6vw, 3rem);
    }

    /* The photo leads on mobile: the disc, the lead paragraph and the body
       all read better under it than beside a column that no longer exists. */
    .text_image_media {
        order: -1;
    }

    .tenet_grid {
        grid-template-columns: 1fr;
    }

    /* -------------------------------------- THE CARDS STOP BEING ROLLOVERS
       Below this width both card grids are ONE column, and a rollover in a
       single column is a worse deal than it looks: the reveal is the only
       way to the copy, it costs a tap, and on a phone the card is already
       the width of the screen — there is nothing to gain by hiding half of
       what it says. So the cards are simply OPEN here, and their height is
       set by their content (Adam).

       Every line below follows from that one decision:

       - aspect-ratio and min-height go. Both exist to hold a fixed box
         while the copy swaps in and out of it, and both now do the opposite
         job — a fixed height with the copy permanently in it either clips
         the longest card or pads out the shortest. The later `min-height`
         rules at 700 and 560 were the same reservation recomputed for
         narrower cards, and they are gone from those blocks rather than
         being overridden from here.
       - the reveal rows are pinned open, which is the SAME `1fr` :hover
         sets — the mechanism is untouched, it just has no second state at
         this width.
       - the wash sits at its open brown. The copy is now permanently over
         the photo, and the resting gradient tops out at .73 black, which is
         not a surface white body text should be read off. This is the state
         the card would be in if it were open, painted unconditionally.

       The tenet card's control is gone entirely (2026-09-09) — it is a CSS
       :hover now — so there is no longer a button to hide here. The
       services card still has one, because its toggle IS the title and
       cannot be taken away; its script pins aria-expanded at this width
       instead. See sections/service_cards.php.

       @media (hover: none) below repeats the two card-state rules for TOUCH
       at any width, which is the fallback docs/accessibility.md asks for by
       name. This block is the WIDTH half and that one is the CAPABILITY
       half; they overlap on a phone and neither is redundant.

       This is a state change, not an animation: nothing here interpolates,
       so there is nothing for the reduced-motion block to turn off. */
    .tenet_card,
    .service_card {
        aspect-ratio: auto;
        min-height: 0;
    }

    .tenet_card_reveal {
        grid-template-rows: 1fr;
    }

    .tenet_card_wash::after {
        opacity: 1;
    }

    .service_card_body {
        grid-template-rows: minmax(0, 0fr) auto auto minmax(0, 1fr);
    }

    .service_card_reveal {
        visibility: visible;
    }

    .service_card_wash::after {
        opacity: 1;
    }
}

/* THE TOUCH FALLBACK, and it is not optional. docs/accessibility.md: a
   component whose real content lives behind a hover state needs a
   `@media (hover: none)` state that renders it statically, height driven by
   content. The tenet card lost its button on 2026-09-09 and is a bare
   :hover now, so this IS that state — the cards are simply open on any
   device whose primary pointer cannot hover, at any width.

   Not left to the 768px block above, and not left to the browser
   synthesising a hover on tap: the first only covers phones, and the second
   is behaviour rather than contract — an iPad in portrait is 768-1024px
   wide with no hover at all.

   The geometry has to come with it. aspect-ratio and min-height hold a
   fixed box while copy swaps in and out of it; with the copy permanently
   in, they clip the longest card. Same three rules as the width block, same
   reasons, different question.

   The service cards are deliberately absent: their toggle is the card
   TITLE, so they still have a real control on touch and need no fallback. */
@media (hover: none) {

    .tenet_card {
        aspect-ratio: auto;
        min-height: 0;
    }

    .tenet_card_reveal {
        grid-template-rows: 1fr;
    }

    .tenet_card_wash::after {
        opacity: 1;
    }
}

/* THE PHONE BANNER PHOTO, and this width IS the <source media> in
   sections/home_banner.php — one number living in two files. Change it here
   and the swap and the crop disagree.

   Why the anchor moves. The phone photo is a portrait frame (1483x1902) of
   the same three people, and the banner box crosses its aspect ratio inside
   this range: below ~640px the box is TALLER per unit width than the photo,
   so cover shows the photo's full height and crops the sides only — the
   vertical anchor is inert there and only the horizontal one matters. From
   ~640 to 700 the box is wider than the photo and cover starts cutting
   vertically instead, and 0 keeps the TOP, which is the headroom the frame
   was chosen for; cutting from the top instead would walk the group up into
   the button.

   The vertical is what this frame buys. Their heads sit at 62.4% of it, so
   below 640px they land at 62.4% of the banner — 87px clear of the button's
   51.8%. The phone frame it replaces sat them at 44-52%, i.e. the button
   landed ON them.

   Harmless on the desktop photo when the client leaves the phone field
   empty: at 1.054:1 it is height-limited in any box narrower than that, so
   its vertical anchor never applies at these widths — but its 45% would be
   inert rather than wrong, and the horizontal 50% is close to this one. */
@media (max-width: 700px) {

    /* 44% IS DERIVED, not nudged, and it is a CHOICE BETWEEN FACES. This
       frame holds the group tighter than the one it replaces: they span the
       full width of it, and a 390px viewport can only show 61% of that
       (window = frame height x box ratio, so it is the photo's HEIGHT that
       sets how much width fits — cropping this file narrower would not buy
       a single extra pixel of group).

       So all three cannot be in shot at once, and the anchor picks which
       edge to spend. The near man's brow is at x=17% and the far man's face
       begins at x=78%: 44% puts the window's left edge exactly on the brow
       and its right edge exactly where the far man's face starts, which is
       the furthest right it can go while the near man keeps a head. It
       holds within a few points across 360-430px. Below ~44% the far man
       leaves the frame entirely; above ~52% the near man is reduced to his
       nose and the far man is still only half in. */
    .banner_photo {
        object-position: 44% 0;
    }
}

@media (max-width: 560px) {
    .tenet_card_body {
        padding: 2rem 1.6rem 1.8rem;
    }

    .tenet_card_title {
        font-size: 1.5rem;
    }

    .collage {
        gap: 14px;
    }

    .collage_col {
        gap: 14px;
    }
}

/* =========================================================================
   SERVICES PAGE — responsive.

   Same rule as the home block above: the mock is a 1440px desktop
   composition and owes us nothing below it. Breakpoints sit where THIS
   page's content breaks, which is why the service grid stacks at 1000px
   rather than joining the 768px stack — see the note on .service_grid below.
   ========================================================================= */
@media (max-width: 1000px) {

    /* The bar shortens here, so the banner's clearance has to switch to the
       mobile companion token in the same breakpoint (docs/css.md). */
    .internal_banner_inner {
        padding-top: var(--mobileNavHeight);
    }

    .collage_text_grid,
    .connection_grid {
        column-gap: 2rem;
    }

    /* THE SERVICE GRID STACKS EARLY, AND ON PURPOSE.
       ---------------------------------------------------------------------
       Two columns below this width means a ~420px card, which means a ~38
       character line, which means Tax Planning's description sets as
       seventeen lines and the card has to stand 690px tall on a tablet.
       Going to one column here gives the card the whole container back: the
       same copy sets as six lines and the card comes DOWN to 460. The
       breakpoint is where the content breaks, not where the device is. */
    .service_grid {
        grid-template-columns: 1fr;
    }

    .service_card {
        aspect-ratio: 16 / 9;
        /* Floored to the same worst case as the desktop rule, recomputed for
           a full-container card: see the long note on .service_card. */
        min-height: 460px;
    }
}

@media (max-width: 768px) {

    .collage_text_grid,
    .connection_grid {
        grid-template-columns: 1fr;
        row-gap: clamp(2rem, 6vw, 3rem);
    }

    /* The collage already leads in source order, so stacking puts the photos
       above the copy without an `order` — which is what the home page needed
       an override for. Nothing to do here but say so. */
}

@media (max-width: 560px) {

    .service_card_icon {
        max-width: 56px;
        max-height: 56px;
        margin-bottom: 1.5rem;
    }

    .service_card_title {
        font-size: 1.5rem;
    }

    .connection_media {
        gap: 14px;
    }
}

/* =========================================================================
   OUR STORY PAGE SECTIONS

   Same discipline as the two blocks above: every number was pixel-sampled
   from original-designs/About-Page.jpg and DIVIDED BY 1.3333 (the exports
   are a 1440px composition at 1920 — BRIEF.md decision 12), with mock pixels
   quoted so any of them can be re-checked against the JPEG. Where the brief
   states a value — the three brand hexes, 72px section headings, 24px lead,
   18px body — that value wins over the sample.

   Two sampled deltas worth recording rather than chasing:

     - This page's section headings measure 82.5 CSS px of line pitch against
       the home page's 80 and the green panel's 75. The brief states 72px, so
       .display-sm carries all of them and the designer's ±5% drift is not
       reproduced.
     - The hairline above "Still have questions?" samples #ABABAB here and
       the brand green on Home and Services. It is the shared .cta_divider on
       all three pages and stays green — a per-page grey variant of a 1px
       rule is the one-off class docs/css.md forbids, for a difference
       nobody can see.
   ========================================================================= */

/* =========================================================================
   COPY + PHOTO — "A small firm with a particular point of view."
   ========================================================================= */
.text_photo {
    /* The disc overhangs the photo to the RIGHT and can reach past the
       container on a viewport barely wider than it. Same `clip` reasoning
       and the same @supports fallback as .text_image above. */
    overflow: clip;
}

.text_photo_grid {
    display: grid;
    /* 754 / 823 mock px either side of a 75 mock px gutter. The copy column
       is wider than text_image's mirror-image 698 for one reason: the mock
       sets "with a particular" as one line, which is 7.5em and does not fit
       in 698. The gutter is what gives; the mock lets the heading come
       within 42 CSS px of the photo at its longest line. */
    grid-template-columns: minmax(0, 754fr) minmax(0, 823fr);
    column-gap: clamp(1.5rem, 3.9vw, 56px);
    /* The two columns are within 45 CSS px of sharing a centre line in the
       mock, so they are centred rather than top-aligned with a hand-tuned
       offset — which also survives the copy growing by a line. */
    align-items: center;
}

/* .deco_mark centres itself because most sections centre their head; this
   one is left-set with the copy, so the auto margins are reset at the render
   site rather than the default being weakened for everyone. */
.text_photo_copy .deco_mark {
    margin-inline: 0;
}

.text_photo_copy h2 {
    margin: 0 0 3rem;
    /* 66 mock px from the heading's last line box to the lead's */
}

.text_photo_copy p {
    margin: 0 0 1.75rem;
}

.text_photo_copy> :last-child {
    margin-bottom: 0;
}

.text_photo_media {
    position: relative;
}

.text_photo_image {
    position: relative;
    z-index: 1;
    display: block;
    width: 100%;
    height: auto;
    /* natural ratio — an image beside text never takes a forced one */
    border-radius: var(--radius-lg);
}

/* THE SAME DISC AS THE HOME PAGE'S, MIRRORED.
   594 mock px across on both pages (72.2% of this 823 mock px column against
   72.3% of the home page's), so only its anchor changes: it hangs off the
   photo's top RIGHT corner here, overhanging it 106 mock px right and 135
   up. Percentages are of the DISC's own size, so a taller or shorter photo
   cannot move it. */
.text_photo_media .brand_disc {
    left: auto;
    right: 0;
    transform: translate(17.9%, -22.8%);
}


/* =========================================================================
   STORY PANEL — "Why we see money differently"

   Heading alone on the left with the photo beneath it running off the edge
   of the screen, copy on the right, on the green surface.
   ========================================================================= */
.story_panel {
    /* The photo's bleed deliberately overshoots the viewport edge by half a
       scrollbar (see --container-inset), so this section MUST clip or the
       body gets a sideways scrollbar. Nothing inside it may rely on
       overflowing: the photo carries .reveal, not .fadeInUp, for exactly
       that reason (sections/story_panel.php). */
    overflow: clip;
    /* The photo IS the bottom edge of the section — the mock has no green
       under it. Same specificity as .pad_y, later file, so it wins without a
       modifier (docs/css.md: load order is the cascade). */
    padding-bottom: 0;
}

/* THE ONLY GRADIENT SURFACE IN THE DESIGN SET.
   Every other green in all six designs is flat #8E996F; this one darkens
   left to right, sampled from 142,153,111 at x<250 to 113,124,84 at 1760 —
   a black wash ramping 0 -> 20.5%. It is scoped to this layout rather than
   added to .greenback because it is this composition, not the surface: the
   acronym cards two sections down sit on the flat green. */
.story_panel.greenback {
    background-color: var(--green);
    background-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 12%, rgba(0, 0, 0, .205) 100%);
}

.story_panel_grid {
    display: grid;
    /* 784 / 700 mock px either side of a 164 mock px gutter. The left column
       is measured to the PHOTO's right edge (920 mock from the screen edge,
       784 from the container's), not to the heading. */
    grid-template-columns: minmax(0, 784fr) minmax(0, 700fr);
    column-gap: clamp(2rem, 8.5vw, 123px);
    /* Areas rather than nesting: the photo has to sit under the heading at
       desktop and read AFTER the copy when the section stacks. One swap in
       the responsive block does both. */
    grid-template-areas:
        "head copy"
        "media copy";
}

.story_panel_head {
    grid-area: head;
}

.story_panel_copy {
    grid-area: copy;
    /* Centred against the heading + photo stack. The mock puts it 67 CSS px
       lower than this; every alternative is a hand-tuned offset that the
       first extra line of copy invalidates. */
    align-self: center;
}

.story_panel_media {
    grid-area: media;
    align-self: end;
}

.story_panel_head .deco_mark {
    margin-inline: 0;
}

.story_panel_head h2 {
    margin: 0 0 clamp(2rem, 3.5vw, 51px);
}

.story_panel_copy p:last-child {
    margin-bottom: 0;
}

/* THE PHOTO RUNS OFF THE LEFT OF THE PAGE.
   One negative margin against --container-inset, which is the distance from
   the viewport to the container's content box. `max-width: none` is not
   optional — style.css caps every img at 100%, which would silently discard
   the width calc and leave the photo stopping at the container.

   The rounded corner is CSS on a rectangular photo, never a pre-cut alpha
   PNG (BRIEF.md). 370 mock px of radius on the top right only; the clamp
   tracks the viewport because the photo's own width does. */
.story_panel_photo {
    display: block;
    width: calc(100% + var(--container-inset));
    max-width: none;
    height: auto;
    margin-left: calc(-1 * var(--container-inset));
    border-top-right-radius: clamp(90px, 19.2vw, 277px);
}


/* =========================================================================
   TEAM MEMBERS — "Meet the team"

   One row per person: portrait left, name / job title / short intro / "Read
   more" right, closed by a hairline that spans the COPY column only.
   ========================================================================= */
.team_member {
    display: grid;
    /* The mock draws 680 / 820 either side of a 148 mock px gutter, which
       puts the portrait at 45% of the row — about 510 CSS px of headshot.
       The client asked for smaller (2026-09-09), so the portrait column
       drops to 32% (~360 CSS px) and the copy takes what it gives up. The
       gutter is unchanged, and the ratio is still written in the mock's own
       units so it reads against the measurement it came from. */
    grid-template-columns: minmax(0, 480fr) minmax(0, 1020fr);
    column-gap: clamp(2rem, 7.7vw, 111px);
    align-items: center;
}

.team_member+.team_member {
    margin-top: clamp(2.5rem, 5.4vw, 78px);
    /* 104 mock px, rule to next photo */
}

.team_photo {
    display: block;
    width: 100%;
    height: auto;
    /* natural ratio — an image beside text never takes a forced one. The
       four portraits arrive within 0.7% of the same ratio, so the rows line
       up without one being forced into a box it does not fit. */
    border-radius: var(--radius-lg);
}

/* The name and the LinkedIn icon share a row, the icon pinned to the far
   right of the copy column exactly as the mock has it. */
.team_member_title {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.team_member_name {
    margin: 0;
    /* 44px at 1440. Measured off the mock's longest name rather than a cap
       height: at 46 the widest of the four ("Brianna Shealey, WMS, AAMS®,
       CRPC®, AWMA®") sets as three lines where the mock sets two, and the
       row's rhythm goes with it. */
    font-size: clamp(1.75rem, 1.15rem + 1.87vw, 2.75rem);
    line-height: 1.17;
}

/* THE CREDENTIALS, on their own line under the name and smaller than it —
   the client's call (2026-09-09) against a mock that set the whole string
   at 44px. They come out of the SAME field as the name, split on the first
   comma (inc/helpers.php), so they are still inside the <h3> and still part
   of the accessible name the bio dialog is labelled by.

   Its own clamp rather than an `em` of the name: the name's own clamp
   bottoms out at 28px, and .45em of that is 13px — smaller than the body
   copy underneath it. Brown rather than the role's green, so the three
   lines under the portrait read as three different things. */
.team_member_creds {
    display: block;
    margin-top: .625rem;
    font-size: clamp(.9375rem, .82rem + .5vw, 1.1875rem);
    /* 19px at 1440 */
    line-height: 1.35;
    letter-spacing: .01em;
    color: var(--brown);
}

/* THE LINKEDIN ICON. LinkedIn's own blue with a white glyph, against the
   footer's white circle with a brown glyph — same renderer (helpers.php),
   different host, so the colours live with each host and not on .li_link. */
.team_linkedin {
    flex: 0 0 auto;
    margin-top: .35rem;
    color: var(--linkedin);
    transition: color .2s ease;
}

.team_linkedin svg {
    display: block;
    width: 55px;
    /* 73 mock px */
    height: 55px;
}

.team_linkedin .li_glyph {
    fill: #fff;
}

.team_linkedin:hover,
.team_linkedin:focus-visible {
    color: var(--brown);
}

/* Green on white is the same 3.44:1 pair as the white-on-green BRIEF.md
   decision 11 accepted, inverted — see the note in the launch checklist.
   Shipped as the mock draws it. */
.team_member_role {
    margin: 1.25rem 0 0;
    font-size: 1.375rem;
    /* 22px at 1440 */
    color: var(--green);
}

.team_member_excerpt {
    margin-top: 2.2rem;
}

.team_member_excerpt p {
    margin: 0 0 1.5rem;
}

.team_member_excerpt p:last-child {
    margin-bottom: 0;
}

.team_more {
    margin-top: clamp(2rem, 5.5vw, 79px);
}

/* The plus, sized and spaced like the PNG arrow the ACF button icons use, so
   the two buttons read as the same object. :has() rather than a modifier
   class, the same way the arrow is targeted (BRIEF.md, "Global behavior"). */
.btn_plus {
    display: block;
    flex: 0 0 auto;
    width: 1.375em;
    height: auto;
}

.mybutton:has(.btn_plus) .btn_face {
    gap: 1.75rem;
}

/* THE HAIRLINE UNDER EACH ROW spans the copy column only, which is what the
   mock draws. It is a grid item in the second row of the SAME grid rather
   than a border with a hand-computed left inset, so it tracks the column at
   every width and lands full-width for free when the row stacks. */
.team_member_rule {
    grid-column: 2;
    height: 1px;
    background: var(--text);
    margin-top: clamp(2rem, 4.9vw, 70px);
}

/* ...except after the last person. The rule's job is to separate one member
   from the next, and under the last one there is no next: the green
   acronyms panel that opens the following section is the boundary, and a
   hairline 70px above it is a second one drawn in open space (the client,
   2026-09-09). */
.team_member:last-child .team_member_rule {
    display: none;
}

/* --------------------------------------------------------- the bio dialog
   Lifecycle and ARIA are the dialog contract's (docs/accessibility.md);
   this is only the look. The backdrop IS the dialog element, so a click on it closes —
   the panel is a child and stops the click by being the click target.
--------------------------------------------------------------------------- */
.team_dialog {
    position: fixed;
    inset: 0;
    /* Above everything: the nav is 99 (and the mobile menu rides inside it),
       the cookie bar 200. A modal that traps focus must not leave a visible
       control stranded underneath it, which is what a cookie bar painting on
       top would be. */
    z-index: 300;
    display: flex;
    padding: clamp(1rem, 4vw, 3rem);
    background: rgba(23, 18, 14, .72);
    /* The BACKDROP scrolls, not the panel. Two nested scroll containers is
       one too many, and `align-items: center` on a flex parent whose child
       outgrows it pins the child's top above the scroll origin where it can
       never be reached — the classic centred-modal trap. `margin: auto` on
       the panel centres it while it fits and lets it sit against the padding
       when it does not. */
    overflow-y: auto;
    opacity: 0;
    transition: opacity .3s ease;
}

.team_dialog.is_open {
    opacity: 1;
}

.team_dialog_panel {
    position: relative;
    width: 100%;
    max-width: 780px;
    margin: auto;
    padding: clamp(2rem, 4vw, 3.5rem);
    border-radius: var(--radius-lg);
    background: #fff;
    color: var(--text);
    box-shadow: 5px 5px 40px rgba(0, 0, 0, .35);
}

.team_dialog_close {
    -webkit-appearance: none;
    appearance: none;
    position: absolute;
    top: clamp(.75rem, 1.6vw, 1.25rem);
    right: clamp(.75rem, 1.6vw, 1.25rem);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--light);
    color: var(--text);
    font: inherit;
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease;
}

.team_dialog_close:hover,
.team_dialog_close:focus-visible {
    background: var(--brown);
    color: #fff;
}

.team_dialog_head {
    display: flex;
    align-items: center;
    gap: clamp(1rem, 2.5vw, 2rem);
    margin-bottom: 2rem;
    padding-right: 3rem;
    /* clears the close button */
}

.team_dialog_photo {
    flex: 0 0 auto;
    width: clamp(90px, 14vw, 140px);
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 50%;
}

.team_dialog_name {
    margin: 0;
    font-size: clamp(1.375rem, 1rem + 1.2vw, 2rem);
    line-height: 1.2;
}

.team_dialog_head .team_member_role {
    margin-top: .5rem;
    font-size: 1.125rem;
}

.team_dialog_head .team_linkedin {
    margin-top: .9rem;
    display: inline-flex;
}

.team_dialog_head .team_linkedin svg {
    width: 34px;
    height: 34px;
}

.team_dialog_bio p {
    margin: 0 0 1.5rem;
}

.team_dialog_bio> :last-child {
    margin-bottom: 0;
}

/* Keeps the page from scrolling behind the open dialog — the same mechanism
   body.menu_open uses for the mobile nav panel. */
body.modal_open {
    overflow: hidden;
}


/* =========================================================================
   ICON CARDS — "A quick word on those acronyms"

   Four up on the green surface. Everything the card has to say is visible at
   rest and the rollover only recolours it, which is why this one is a plain
   CSS :hover with no control and no JS — nothing is HELD behind the hover
   (docs/accessibility.md).
   ========================================================================= */
.icon_grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 20px;
    /* 26 mock px, both axes */
}

.icon_card {
    /* 75 / 62 mock px of padding */
    padding: clamp(2rem, 4.5vw, 56px) clamp(1.5rem, 3.75vw, 46px);
    border-radius: var(--radius-lg);
    background: var(--light);
    color: var(--text);
    transition: background-color .3s ease, color .3s ease;
}

.icon_card:hover {
    background: var(--brown);
    color: #fff;
}

/* The icons arrive between 74 and 93 px tall and the mock draws each at its
   own size. Fitting them inside one 56px box keeps every icon's proportions
   and normalises the spread; sizing by the BOX rather than by width is what
   stops the wide ones out-measuring the square ones. Same treatment as
   .service_card_icon. */
.icon_card_icon {
    display: block;
    width: auto;
    height: auto;
    max-width: 56px;
    max-height: 56px;
    margin-bottom: 2.75rem;
    /* The PNGs are brown line art; on the brown card the mock shows them
       white. brightness(0) flattens them to black first, so invert() lands
       on pure white rather than the pale blue an invert of brown gives.
       Prefixed FIRST, standard LAST. */
    -webkit-transition: -webkit-filter .3s ease;
    transition: filter .3s ease;
}

.icon_card:hover .icon_card_icon {
    -webkit-filter: brightness(0) invert(1);
    filter: brightness(0) invert(1);
}

.icon_card_title {
    margin: 0 0 1.125rem;
    font-size: 1.1875rem;
    /* 19px at 1440 */
    line-height: 1.2;
}

.icon_card_text {
    margin: 0;
    font-size: 1rem;
    line-height: 1.4;
}

/* WYSIWYG field — the paragraph spacing matches the run-on line-height
   rather than the 1.25rem the two rollover cards use, because this card is
   short by design and a full blank line in it reads as a gap. */
.icon_card_text p {
    margin: 0 0 .875rem;
}

.icon_card_text> :last-child {
    margin-bottom: 0;
}


/* =========================================================================
   PARTNER TEAM — "The team behind our team"

   A split head over three ringed portraits that link out to NewEdge
   (BRIEF.md decision 1: linked cards, no popup).
   ========================================================================= */
.partner_head {
    display: grid;
    /* 857 / 691 mock px either side of a 100 mock px gutter */
    grid-template-columns: minmax(0, 857fr) minmax(0, 691fr);
    column-gap: clamp(2rem, 6.9vw, 100px);
    align-items: start;
}

.partner_head_title .deco_mark {
    margin-inline: 0;
}

.partner_head_title h2 {
    margin: 0;
}

/* Its own full-width row between the head and the portraits
   (sections/partner_team.php) — so it reads after the copy at every width
   instead of splitting the heading from it on a phone. The margin below is
   smaller than the one above because .partner_grid brings its own. */
.partner_link_row {
    margin: clamp(1.75rem, 4vw, 56px) 0 0;
}

/* The label carries the underline and the arrow does not — which is the
   whole reason the label has a box of its own (sections/partner_team.php). */
.partner_link {
    display: inline-flex;
    align-items: center;
    gap: 1.2em;
    font-size: 1.4375rem;
    /* 23px at 1440 */
    font-weight: 700;
    color: var(--text);
    text-decoration: none;
}

.partner_link_label {
    text-decoration: underline;
    text-underline-offset: .28em;
}

.partner_link .arrow_icon {
    flex: 0 0 auto;
    width: 1.15em;
    height: auto;
    transition: transform .5s cubic-bezier(.22, .61, .36, 1);
}

/* The arrow travels along the diagonal it already points down, the same
   motion and the same easing as the button arrow. */
.partner_link:hover .arrow_icon,
.partner_link:focus-visible .arrow_icon {
    transform: translate(4px, -4px);
}

.partner_head_copy p:last-child {
    margin-bottom: 0;
}

.partner_grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: clamp(1.5rem, 3.7vw, 53px);
    row-gap: clamp(3rem, 6.7vw, 96px);
    margin-top: clamp(3rem, 6.8vw, 97px);
}

.partner_card {
    display: flex;
    text-align: center;
}

/* A column that fills the card. It stretched to bottom-align the arrows
   across a row when there were arrows; with those gone (2026-09-09) it
   still earns its place, because `flex: 1` is what gives every card in a
   row the same height and keeps the ring at the top of each one. */
.partner_card_link {
    display: flex;
    flex: 1;
    flex-direction: column;
    align-items: center;
    color: inherit;
    text-decoration: none;
}

.partner_card_link:focus-visible {
    outline: 3px solid var(--orange);
    outline-offset: 6px;
}

/* THE RING IS THE CONTAINER, NOT THE PHOTO (BRIEF.md): a brown border, a
   white gap that is this box's padding, and any rectangular headshot cropped
   to the circle inside it. Locked to 1:1, so the client cannot post a
   portrait that turns into an oval.

   The circle measures 429 of the mock's 502 px column — 86%, or about 324
   CSS px across. The client asked for smaller (2026-09-09); 70% is ~264px
   and leaves the ring's own border untouched, so the object reads the same,
   just less of the row. */
.partner_ring {
    display: block;
    width: 70%;
    margin-inline: auto;
    aspect-ratio: 1;
    /* WITHOUT THIS THE CIRCLE IS NOT A CIRCLE. As a column flex item the ring
       gets min-height: auto, which resolves to the min-content height of the
       photo inside it — and a portrait headshot's intrinsic height beats the
       square the aspect-ratio asked for, so the ring grows by the difference
       and drops that card's name below its neighbours'. Silent, and only on
       the non-square photos: Will Brand's 354x378 export is 18px taller than
       the rest. */
    min-height: 0;
    padding: clamp(5px, .9vw, 13px);
    border: clamp(6px, .98vw, 14px) solid var(--brown);
    border-radius: 50%;
    background: #fff;
    transition: border-color .3s ease;
}

.partner_photo {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
}

.partner_name {
    display: block;
    margin-top: clamp(1.25rem, 2.2vw, 31px);
    font-size: clamp(1.25rem, .9rem + 1.1vw, 1.625rem);
    /* 26px at 1440 */
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
}

.partner_role {
    display: block;
    margin-top: clamp(.75rem, 1.3vw, 19px);
    /* Was the minimum gap above the arrow; now the card simply ends here. */
    margin-bottom: 0;
    font-size: 1.0625rem;
    /* 17px at 1440 */
    font-weight: 700;
    color: var(--brown);
}

/* THE HOVER THE ARROW USED TO CARRY. With the arrow removed the card had no
   state at all, and a photograph does not read as a link on its own — so
   the ring takes the brand green and the name underlines, which is the cue
   every other outbound link on the site already uses. :focus-visible is on
   the same rule, because a keyboard has exactly the same question. */
.partner_card_link:hover .partner_ring,
.partner_card_link:focus-visible .partner_ring {
    border-color: var(--green);
}

.partner_card_link:hover .partner_name,
.partner_card_link:focus-visible .partner_name {
    text-decoration: underline;
    text-underline-offset: .22em;
}


/* =========================================================================
   OUR STORY PAGE — responsive.

   Same rule as the two blocks above: the mock is a 1440px desktop
   composition and owes us nothing below it. Breakpoints sit where THIS
   page's content breaks.
   ========================================================================= */
@media (max-width: 1000px) {

    .text_photo_grid,
    .story_panel_grid,
    .partner_head,
    .team_member {
        column-gap: 2rem;
    }

    /* Four 200px cards is a 22-character line; the CFP card's copy sets as
       eighteen lines at that measure. Two columns gives it back the room. */
    .icon_grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 768px) {

    .text_photo_grid,
    .partner_head {
        grid-template-columns: 1fr;
        row-gap: clamp(2rem, 6vw, 3rem);
    }

    /* Heading, then copy, then the photo — which is why the photo is a
       sibling of the copy rather than nested in the head. It keeps its bleed
       and its corner; only its place in the order changes. */
    .story_panel_grid {
        grid-template-columns: 1fr;
        grid-template-areas:
            "head"
            "copy"
            "media";
        row-gap: clamp(2rem, 6vw, 3rem);
    }

    .story_panel_head h2 {
        margin-bottom: 0;
    }

    .team_member {
        grid-template-columns: 1fr;
        row-gap: clamp(1.75rem, 5vw, 2.5rem);
    }

    /* One column means the portrait would take the whole container — 728px
       of headshot at this breakpoint, which is the opposite of the change
       above. Capped, and left where the row starts. */
    .team_member_media {
        max-width: 340px;
    }

    /* Full width once there is only one column — the rule closes the whole
       row, not a column that no longer exists. */
    .team_member_rule {
        grid-column: 1;
    }

    .team_member_name {
        font-size: clamp(1.625rem, 1.1rem + 2.4vw, 2.25rem);
    }
}

@media (max-width: 640px) {

    .icon_grid {
        grid-template-columns: 1fr;
    }

    /* Three rings on a phone are 100px across and the names below them wrap
       to four lines. Two is the floor; one would waste the row entirely. */
    .partner_grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .team_dialog_head {
        display: block;
    }

    .team_dialog_photo {
        margin-bottom: 1.25rem;
    }

    .partner_link {
        font-size: 1.25rem;
    }
}

/* THE INTERNAL BANNER BELOW THE POINT WHERE THE COPY CAN NO LONGER KEEP
   CLEAR OF THE SUBJECT — the treatment BRIEF.md asks for, applying to every
   internal banner and not just this page's.
   -------------------------------------------------------------------------
   Above this width the banner keeps the mock's 2.56:1 box (min-height rides
   39vw) so object-fit crops nothing and the directional scrim does its job.
   Below it the 320px floor takes over, the box goes square-ish, and cover
   starts throwing away up to half the frame — while the headline, which the
   left-to-right scrim exists to carry, now runs the full width of the
   banner. A directional gradient on that box just darkens one corner and
   rescues nothing, so it becomes one flat wash.

   78% is where object-position has to sit to CENTRE the protected band once
   the crop is that severe: the window is [P(1-f), P(1-f)+f], and at 375px
   (f = .458) that puts the band's centre back in the middle of the frame.
   It is derived from the band, not from this page's photo, so it holds for
   every banner cropped to the convention in sections/internal_banner.php. */
@media (max-width: 700px) {

    .internal_banner_photo {
        object-position: 78% 50%;
    }

    .internal_banner_scrim {
        background: rgba(0, 0, 0, .48);
    }
}


/* =========================================================================
   ADDED: reduced-motion fallbacks for everything declared in THIS file.
   Same convention as style.css's block — chrome transitions off, content
   still fully visible. New .hsr variants get added here the moment they are
   created; one that skips this block is content those users never see.
   ========================================================================= */
/* =========================================================================
   INSIGHTS PAGE

   Sampled off original-designs/Insight-page.jpg and divided by 1.3333
   (BRIEF.md, decision 12); mock pixels are quoted so the next person can
   re-check a number against the JPEG.

   Two shapes on this page are NOT new sections — the banner is
   internal_banner and "Money shouldn't feel mysterious" is text_photo, the
   same copy-left / photo-right / disc-on-the-top-right object Our Story
   opens with. Reusing it is the point of that layout existing.

   ONE MEASUREMENT WORTH NAMING: the video grid and the FAQ list both run
   1080 mock px, narrower than the 1236 container the post cards use. It is
   the same number in both places and it belongs to those two components, so
   it lives on their own classes rather than becoming a third container
   width the whole site then has to know about.
   ========================================================================= */

/* =========================================================================
   VIDEO GALLERY — "Less confusion. More confidence."
   ========================================================================= */
.video_grid {
    /* 1440 mock px measured 243->1682 in the export = 1080 at 1440 */
    max-width: 1080px;
    margin-inline: auto;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* 37 export px between the columns and 38 between the rows — one gap */
    gap: 28px;
    /* A two-line title next to a one-line title must not stretch the shorter
       tile: both start at the top of the row and end where their own copy
       ends. */
    align-items: start;
}

/* The tile is a real <button> (sections/video_gallery.php explains why the
   whole picture is the target rather than the circle drawn on it), so it
   takes the same appearance reset .mybutton does — prefixed FIRST, standard
   LAST, or Lightning CSS drops the standard property. */
.video_tile {
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    /* The button wraps the picture AND the title, so its own text has to sit
       under the picture the way running copy does, not centred the way a
       button's label would. */
    text-align: left;
    cursor: pointer;
}

/* The picture is the box the mock draws: it keeps the ratio, the radius and
   the clip that the title, sitting outside it, must not inherit. */
.video_tile_media {
    position: relative;
    display: block;
    /* 702 x 466 export px */
    aspect-ratio: 3 / 2;
    border-radius: var(--radius-md);
    /* The cover fills the box and the play circle sits on top of it; both
       are clipped to the rounded corner rather than the cover carrying its
       own radius. The pill's edge is visible, so this clip is an edge and
       not an invisible wall (docs/animation.md). */
    overflow: hidden;
}

/* Under the picture, in the relationship .post_card_title has to its own
   photo one section down — a step smaller, because six of these read as a
   grid of labels and one of those reads as a headline. White on --green is
   the accepted exception (BRIEF.md decision 11); 20px bold clears the 3:1
   large-text bar that 16px body copy does not. */
.video_tile_title {
    display: block;
    margin-top: 1rem;
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.35;
}

/* A row whose video URL has not arrived yet renders as a picture with no
   play control — same box, nothing to press. */
.video_tile_empty {
    cursor: auto;
}

.video_tile_cover {
    display: block;
    width: 100%;
    height: 100%;
    /* A forced ratio is legitimate here: the cells of a grid have to match,
       and this one is not beside text (docs/css.md). */
    object-fit: cover;
    transition: transform .6s cubic-bezier(.22, .61, .36, 1);
}

.video_tile_play {
    position: absolute;
    top: 50%;
    left: 50%;
    /* 151 export px across, centred on the tile */
    width: clamp(64px, 9vw, 113px);
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: #fff;
    /* the triangle samples #1A1914 — the page's warm near-black, not a grey */
    color: var(--text);
    transform: translate(-50%, -50%);
    transition: transform .4s cubic-bezier(.22, .61, .36, 1);
}

.video_tile_play svg {
    /* The path already sits 6% right of the box's centre, which is the
       optical nudge a triangle in a circle wants — no margin needed. */
    width: 38%;
    height: auto;
}

/* The mock draws no hover state. This is the restrained one the rest of the
   site would expect: the picture pushes in a little and the circle grows.
   Both are transitions, so a pointer that leaves mid-move interpolates back
   from wherever it is — there is nothing to snap (docs/animation.md). */
.video_tile:hover .video_tile_cover,
.video_tile:focus-visible .video_tile_cover {
    transform: scale(1.04);
}

.video_tile:hover .video_tile_play,
.video_tile:focus-visible .video_tile_play {
    transform: translate(-50%, -50%) scale(1.08);
}

/* The ring now wraps the picture AND its title, since both are the button.
   The radius is here only so the outline curves with the picture it starts
   on — nothing is clipped to it. */
.video_tile:focus-visible {
    border-radius: var(--radius-md);
    outline: 3px solid #fff;
    outline-offset: 3px;
}

/* ------------------------------------------------------------- the popup
   ONE dialog per section, populated by whichever tile was pressed. Same
   lifecycle and the same z-index reasoning as .team_dialog on Our Story:
   above the nav (99, the mobile menu included) and the cookie bar (200),
   because a modal that traps focus must not leave a visible control stranded
   under it.
--------------------------------------------------------------------------- */
.video_dialog {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex;
    padding: clamp(1rem, 4vw, 3rem);
    background: rgba(23, 18, 14, .82);
    overflow-y: auto;
    opacity: 0;
    transition: opacity .3s ease;
}

.video_dialog.is_open {
    opacity: 1;
}

.video_dialog_panel {
    position: relative;
    width: 100%;
    max-width: 1080px;
    margin: auto;
}

.video_dialog_frame {
    /* Empty until a tile is pressed. The box is declared here so the panel
       does not resize under the visitor as the iframe lands. */
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-md);
    background: #000;
    overflow: hidden;
}

.video_dialog_frame iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

.video_dialog_close {
    -webkit-appearance: none;
    appearance: none;
    position: absolute;
    /* Outside the frame, not over it — the player's own controls live in the
       top-right corner of a Vimeo embed. */
    top: -60px;
    right: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: #fff;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease;
}

.video_dialog_close:hover,
.video_dialog_close:focus-visible {
    background: var(--green);
    color: #fff;
}

/* =========================================================================
   LATEST POSTS — "A little clarity goes a long way."

   The section renders nothing at all until a post exists
   (sections/post_cards.php), which is how it stays hidden for launch. The
   card itself is the SHARED starter_post_card(), so index/archive/search and
   the Articles page get this design for free and cannot drift from it.
   ========================================================================= */
/* .post_grid is what index.php / archive.php / search.php render. It joins
   the selector rather than restating it: those pages show the same card and
   have no mock of their own, so "the same grid the designed pages use" is
   the only answer that cannot drift. */
.post_card_grid,
.post_grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* 31 export px */
    gap: 23px;
}

.post_card {
    /* 530 x 758 export px, #F4F4F4 — the same pale card surface as the FAQ
       rows below, which is why both read --paper rather than a local hex. */
    background: var(--paper);
    border-radius: var(--radius-md);
    /* 15 export px of card around the photo; the copy is inset further, by
       its own padding below. */
    padding: 12px;
}

.post_card_link {
    display: flex;
    flex-direction: column;
    height: 100%;
    color: inherit;
    text-decoration: none;
}

.post_card_media {
    /* Concentric with the card: inner radius = outer radius - the padding
       between them, which is what keeps the two curves parallel. */
    border-radius: calc(var(--radius-md) - 12px);
    /* 500 x 315 export px */
    aspect-ratio: 16 / 10;
    overflow: hidden;
}

.post_card_media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.post_card_body {
    /* 62 export px from the card edge to the copy, less the 12px above */
    padding: 2.25rem 2.125rem 2.125rem;
    /* The button sits on the bottom edge of every card whatever the excerpt
       does, which is what makes a row of three read as one object. */
    display: flex;
    flex-direction: column;
    flex: 1;
}

/* The glyph and the date are one line, so the row is a flex box rather than
   a bare <p>. GREEN, and this is the first mock that draws a card date at
   all: the Insights cards carry none (starter_post_card()'s show_date), so
   the brown here was never checked against a design. Articles draws it in
   the brand green and the glyph inherits it through currentColor. */
.post_card_date {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0 0 .5rem;
    font-size: .9375rem;
    color: var(--green);
}

.post_card_date .calendar_icon {
    flex: 0 0 auto;
    width: 1.15em;
    height: auto;
}

.post_card_title {
    /* 33 export px of line pitch on a 24px bold face */
    margin: 0 0 .75rem;
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.35;
}

.post_card_excerpt {
    margin: 0 0 1.75rem;
}

/* The pill inside the card is a <span> (helpers.php explains why), so it
   needs no reset — only the smaller geometry the mock draws for it:
   159 x 52 export px against the page button's 280 x 67. */
.post_card_more {
    margin-top: auto;
    align-self: flex-start;
    font-size: .9375rem;
}

.post_card_more .btn_face {
    padding: .875rem 1.5rem;
    gap: 1.25rem;
}

.post_card_more .arrow_icon {
    flex: 0 0 auto;
    width: 1.25em;
    height: auto;
}

/* The card is one link, so the pill answers to the CARD's hover and focus,
   not its own — the visitor is never asked to find the button inside the
   thing they are already pointing at. */
.post_card_link:hover .post_card_more,
.post_card_link:focus-visible .post_card_more {
    background: #fff;
    border-color: var(--green);
    color: #000;
}

/* ------------------------------------------------------- the brown button
   "View all articles" is the one button in the design set that is brown
   rather than green. A variant class on the shared button, passed as
   starter_button()'s $variant — not a second button component.

   The contact form's "Submit Now" is the same brown pill, and joins the
   selector rather than restating it: the submit's class list comes from the
   gform_submit_button filter, which is starter code and has no business
   knowing this site paints one particular form's button brown. Only its
   WIDTH and its label/arrow spacing are the contact panel's, and those live
   with the rest of the contact page. */
.mybutton.brown,
.contact_form_panel .gform_wrapper .gform_button {
    background: var(--brown);
    border-color: var(--brown);
}

.mybutton.brown:hover,
.mybutton.brown:focus-visible,
.contact_form_panel .gform_wrapper .gform_button:hover,
.contact_form_panel .gform_wrapper .gform_button:focus-visible {
    background: #fff;
    border-color: var(--brown);
    color: #000;
}

/* =========================================================================
   FAQ ACCORDION — "Frequently asked questions"
   ========================================================================= */
.faq_list {
    /* the same 1080 mock px as the video grid above */
    max-width: 1080px;
    margin-inline: auto;
    display: grid;
    /* 8 export px between rows */
    gap: 6px;
}

.faq_item {
    background: var(--paper);
    /* 18 export px = 13 mock, a shade tighter than --radius-md's 20. It
       rounds UP to the pair rather than adding a third token — :root's note
       is explicit that two values is the policy, and nobody can see 7px of
       curve on a 104px bar. */
    border-radius: var(--radius-md);
    overflow: hidden;
    transition: background-color .3s ease, color .3s ease;
}

/* Open takes the brand green and white copy — the same accepted 3.44:1 pair
   as every other green surface on this site (BRIEF.md, decision 11). */
.faq_item.is_open {
    background: var(--green);
    color: #fff;
}

.faq_heading {
    /* The <h3> is structure, not size: it exists so a screen-reader user can
       skim fifteen questions. Everything visual is on the button. */
    margin: 0;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
}

.faq_q {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    display: flex;
    align-items: center;
    /* 24 export px. It sat between the ringed "?" and the question until the
       client removed the mark (2026-09-09); it is now the minimum between a
       long question and the chevron. */
    gap: 1.125rem;
    /* 139 export px tall, first ink 60 in from the left edge — which is the
       QUESTION now that the mark is gone. */
    padding: 2.25rem 2.375rem 2.25rem 2.8125rem;
    border: 0;
    background: none;
    color: inherit;
    text-align: left;
    font-family: inherit;
    font-size: 1.375rem;
    font-weight: 700;
    line-height: 1.3;
    cursor: pointer;
}

.faq_q_text {
    flex: 1;
}

/* --------------------------------------------------------- the chevron
   Two bars meeting at centre-bottom that rotate into a minus when the panel
   opens (BRIEF.md). Two boxes rather than a glyph or an SVG swap, because
   only boxes can TRANSITION between the two shapes — and a transition
   interpolates from wherever it is, so a fast double-click cannot snap it.
--------------------------------------------------------------------------- */
.faq_chevron {
    position: relative;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
}

/* Both bars pivot on their own BOTTOM edge rather than on the end they
   share (Adam). The shared-centre pivot swept each bar through a quarter
   turn and no further; pivoting at the bottom is what lets the open state
   below take a whole turn without the two of them coming apart, and the
   66% width keeps the mark reading at the size the extra sweep needs.

   transform-origin is set HERE, on the shared rule, and no longer in the
   two rules under it — those are (0,2,1) to this one's (0,1,1), so an
   origin left in either would still win and the bars would pivot on two
   different points. */
.faq_chevron span {
    position: absolute;
    top: 50%;
    width: 66%;
    height: 2px;
    border-radius: 1px;
    background: currentColor;
    -webkit-transform-origin: 50% 100%;
    transform-origin: 50% 100%;
    transition: transform 1s ease;
}

.faq_chevron span:first-child {
    left: 0;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
}

.faq_chevron span:last-child {
    left: 50%;
    -webkit-transform: rotate(-45deg);
    transform: rotate(-45deg);
}

/* A FULL TURN into the minus, not a quarter turn to it. 360deg and 0deg are
   the same resting position, so the bars finish exactly where the old rule
   put them — the mark still opens to a minus and closes to a chevron, it
   just spins on the way. It cannot snap in either direction: a transition
   interpolates from wherever it currently is, so a fast double-click
   reverses mid-spin rather than jumping (hard rule 1). Reduced-motion users
   get the state with no spin — .faq_chevron span is already listed in the
   block at the end of this file. */
.faq_item.is_open .faq_chevron span {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
}

/* The panel's height is set in px by the section's JS and animated to it;
   `hidden` arrives only after the close finishes. No `display` here — a
   bare display value resurrects a [hidden] element (docs/css.md). */
.faq_panel {
    overflow: hidden;
    transition: height .4s cubic-bezier(.22, .61, .36, 1);
}

.faq_answer {
    /* The hairline under the question samples white at 30% over the green */
    border-top: 1px solid rgba(255, 255, 255, .3);
    /* .faq_q's own left padding. The mock inset the answer 38px against a
       question that started at 45 because the 45 was the "?" ring, not the
       words; with the ring gone (2026-09-09) the question's first letter
       and the answer's line up on one edge — which is what the phone
       breakpoint below has always done. */
    margin-inline: 2.8125rem;
    padding: 2.25rem 0 2.75rem;
}

.faq_answer p {
    margin: 0 0 1.25rem;
}

.faq_answer p:last-child {
    margin-bottom: 0;
}

/* INSIGHTS DRAWS NO HAIRLINE AT ALL, and this one rule is why.

   The page used to draw two: one inside "A little clarity" (its own
   .cta_divider, landing 60px under the green video surface) and one above
   the FAQ, which the mock put there instead of above the closing CTA
   (decision 20). The client took the hairlines off on 2026-09-09 and Adam
   ruled that nothing replaces them — a removal, not a move. The FAQ's came
   out of the markup (sections/faq.php); BOTH closing CTAs are covered here.

   Keyed on .video_gallery, which is the section the green surface belongs
   to and appears on this page only, so `~` reaches every .cta_circles after
   it — which on Insights is both of them. It replaces the narrower
   `.faq ~ .cta_circles` rule this block used to carry; that one could not
   see the first CTA, because the first CTA comes BEFORE the FAQ.

   `~` and not `+`: every section that carries its own JS emits a <script>
   straight after its </section>, so the CTA is never the ADJACENT sibling.
   Delete this rule and both .cta_dividers come back. */
.video_gallery~.cta_circles .cta_divider {
    border-top: 0;
    padding-top: 0;
}

@media (max-width: 1000px) {

    .post_card_grid,
    .post_grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 768px) {
    .video_grid {
        grid-template-columns: 1fr;
    }

    .faq_q {
        gap: .875rem;
        padding: 1.5rem 1.5rem 1.5rem 1.75rem;
        font-size: 1.125rem;
    }

    .faq_answer {
        margin-inline: 1.75rem;
        padding: 1.5rem 0 1.75rem;
    }

    .video_dialog_close {
        top: -52px;
    }
}

@media (max-width: 700px) {

    .post_card_grid,
    .post_grid {
        grid-template-columns: 1fr;
    }

    .post_card_body {
        padding: 1.75rem 1.5rem 1.5rem;
    }
}


/* =========================================================================
   CONTACT PAGE

   Sampled off original-designs/Contact-Page-2.jpg and divided by 1.3333
   (BRIEF.md, decision 12); mock pixels are quoted so the next person can
   re-check a number against the JPEG.

   Only ONE shape on this page is new — the copy + form pair below. The
   banner is internal_banner, and the closing "Ready to chat?" is
   cta_circles with an empty photo repeater, which is exactly what that
   layout already renders when a row carries no circles.
   ========================================================================= */

/* The two columns are 400 and 683 CSS px inside a 1236 container, with the
   153 that is left over between them — the panel's right edge lands exactly
   on the container's, and the copy's left edge on the container's other
   side. Written as fr so the pair keeps those proportions at every width
   rather than holding a desktop measurement; minmax(0, ...) because a long
   unbreakable string in either column would otherwise push the grid wider
   than its container (fr floors at min-content without it). */
.contact_form_grid {
    display: grid;
    grid-template-columns: minmax(0, 400fr) minmax(0, 683fr);
    gap: clamp(2.5rem, 12.4vw, 153px);
    align-items: start;
}

/* Left-set with the copy, the same way text_photo's is — the auto margins
   are reset at the render site rather than the default being weakened for
   everyone. */
.contact_form_copy .deco_mark {
    margin-inline: 0;
}

.contact_form_copy h2 {
    margin: 0 0 3rem;
    /* 60 mock px from the heading's last line box to the lead's */
}

.contact_form_copy p {
    margin: 0 0 1.75rem;
}

.contact_form_copy> :last-child {
    margin-bottom: 0;
}

/* THE PANEL. .greenback (the named surface pair) carries the green and the
   white that reads on it; this rule carries only what is this box's own —
   42 mock px of radius, and padding that runs a little deeper at the bottom
   than the top the way the mock draws it (86 top / 80 sides / 105 bottom). */
.contact_form_panel {
    border-radius: clamp(1.25rem, 2.2vw, 32px);
    padding: clamp(1.75rem, 4.4vw, 62px);
    padding-bottom: clamp(2rem, 5.1vw, 72px);
}

/* -------------------------------------------------------- THE FORM ITSELF
   Gravity Forms in its `gravity-theme` skin (chosen in the section file, and
   the reason every selector below carries .gravity-theme). All of it is
   scoped to .contact_form_panel: a form dropped anywhere else on the site
   keeps Gravity Forms' own appearance rather than inheriting a green
   panel's worth of styling it is not sitting on.

   The skin's own rules are specific — .gform_wrapper.gravity-theme <thing> —
   so these carry the same shape plus the panel, which is what wins them the
   cascade without reaching for !important.
   ------------------------------------------------------------------------- */

/* gravity-theme lays the fields out on a 12-column grid, and a field set to
   "Half" width in the form editor spans 6 of them. That is how Name and
   Email share a row in the mock — form STRUCTURE is the client's, in the
   form editor, and not something this page hardcodes. Only the gutters are
   ours: 37 mock px between rows, 14 between the pair. */
.contact_form_panel .gform_wrapper.gravity-theme .gform_fields {
    grid-row-gap: clamp(1.25rem, 2.6vw, 28px);
    grid-column-gap: clamp(8px, 1vw, 12px);
}

/* The labels are the small white caps above each field. 15 mock px, and the
   letter-spacing is what keeps caps at that size readable. */
.contact_form_panel .gform_wrapper.gravity-theme .gfield_label {
    color: #fff;
    font-size: .6875rem;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: .07em;
    text-transform: uppercase;
    margin-bottom: .75rem;
}

/* The required asterisk is part of the label, so it takes the label's
   colour — gravity-theme paints it its own red, which on this green is both
   off-brand and under 3:1. The asterisk is never the only signal anyway:
   the control carries `required`, and Gravity Forms names the field in the
   error summary. */
.contact_form_panel .gform_wrapper.gravity-theme .gfield_required {
    color: inherit;
}

/* THE CONTROLS. 102 mock px tall boxes with a 14px radius, white on the
   green. The border is white rather than absent at rest, so the error state
   can colour it without the box changing size under the visitor. */
.contact_form_panel .gform_wrapper.gravity-theme input[type=text],
.contact_form_panel .gform_wrapper.gravity-theme input[type=email],
.contact_form_panel .gform_wrapper.gravity-theme input[type=tel],
.contact_form_panel .gform_wrapper.gravity-theme input[type=url],
.contact_form_panel .gform_wrapper.gravity-theme input[type=number],
.contact_form_panel .gform_wrapper.gravity-theme select,
.contact_form_panel .gform_wrapper.gravity-theme textarea {
    width: 100%;
    background: #fff;
    color: var(--text);
    border: 1px solid #fff;
    border-radius: 10px;
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.4;
    padding: 1.55rem 1.75rem;
    transition: border-color .2s ease;
}

/* 284 mock px, and it is the one control a visitor may want bigger. */
.contact_form_panel .gform_wrapper.gravity-theme textarea {
    display: block;
    min-height: 213px;
    resize: vertical;
}

/* The placeholder has to read as a hint and not as an answer, while still
   clearing 4.5:1 on white — #6B6560 measures 5.3:1. */
.contact_form_panel .gform_wrapper.gravity-theme ::-webkit-input-placeholder {
    color: #6B6560;
}

.contact_form_panel .gform_wrapper.gravity-theme ::placeholder {
    color: #6B6560;
    opacity: 1;
}

/* The site's focus ring is --primary, which IS this panel's background — an
   outline the exact colour of the surface behind it is no outline at all.
   The warm near-black measures 5.4:1 on the green, so it is the one that
   shows here, and the offset keeps it clear of the white box. */
.contact_form_panel .gform_wrapper.gravity-theme :focus-visible {
    outline: 2px solid var(--text);
    outline-offset: 3px;
}

/* ERRORS. gravity-theme's #c02b0a is a fine red on its own white summary box
   — which keeps it — but as body text on this green it is 2.3:1. So the
   per-field message goes to --text (5.4:1) and leans on weight and position
   to read as an error, while the RED stays where it works: on the white
   control's border, where 5.9:1 against white clears the 3:1 a non-text
   indicator needs. */
.contact_form_panel .gform_wrapper.gravity-theme .gfield_error .gfield_label,
.contact_form_panel .gform_wrapper.gravity-theme .validation_message {
    color: var(--text);
}

.contact_form_panel .gform_wrapper.gravity-theme .validation_message {
    background: none;
    border: 0;
    padding: .5rem 0 0;
    font-size: .9375rem;
    font-weight: 600;
}

.contact_form_panel .gform_wrapper.gravity-theme [aria-invalid=true] {
    border-color: #C02B0A;
}

.contact_form_panel .gform_wrapper.gravity-theme .gfield_error {
    background: none;
    border: 0;
    padding: 0;
}

/* The summary box is Gravity Forms' own white panel; it only needs the
   panel's corner radius so it does not read as a different object dropped
   inside this one. */
.contact_form_panel .gform_wrapper.gravity-theme .gform_validation_errors {
    border-radius: 10px;
}

/* The confirmation REPLACES the whole form inside the panel after an ajax
   submit — so it inherits .greenback's white and needs only to not be a bare
   line of body copy sitting in a box built for a form. */
.contact_form_panel .gform_wrapper .gform_confirmation_message {
    color: #fff;
    background: none;
    border: 0;
    padding: 0;
    font-size: clamp(1.25rem, 1.16rem + .38vw, 1.5rem);
    font-weight: 700;
    line-height: 1.4;
}

/* Gravity Forms' ajax spinner sits next to the submit; all it needs is to
   stop shoving the pill sideways while it runs. */
.contact_form_panel .gform_wrapper .gform_ajax_spinner {
    margin-left: .75rem;
    vertical-align: middle;
}

/* THE SUBMIT. Full width of the field column, in the brown pill (the variant
   itself is declared with the other brown button up in BUTTONS), label hard
   left and arrow hard right — 64 mock px in on one side and 55 on the other,
   against the centred label every other button on the site draws. 110 mock
   px tall.

   Three selectors because Gravity Forms has moved the submit twice across
   versions: its own .gform_footer, a #field_submit row, and a
   .gfield--type-submit cell in the field grid. The button inside is the same
   filtered .mybutton in all three (inc/gravity-forms.php), so this is one
   button with three possible parents, not three buttons. */
.contact_form_panel .gform_wrapper.gravity-theme .gform_button,
.contact_form_panel .gform_wrapper.gravity-theme #field_submit button,
.contact_form_panel .gform_wrapper.gravity-theme .gfield--type-submit button {
    display: block;
    width: 100%;
}

.contact_form_panel .gform_wrapper .gform_button .btn_face {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.9rem 2.5rem 1.9rem 3rem;
}

/* 30 mock px between the last field and the pill, against the 37 between
   fields — the submit sits a little closer to what it submits. */
.contact_form_panel .gform_wrapper.gravity-theme .gform_footer,
.contact_form_panel .gform_wrapper.gravity-theme .gfield--type-submit {
    margin: clamp(1rem, 2vw, 22px) 0 0;
    padding: 0;
}

/* The mock closes the green panel and opens the CTA 136 CSS px apart, which
   is roughly ONE section padding, not the two that stacking a large-padded
   section on a normal-padded one gives. The panel is a panel and not a
   surface: its rounded bottom edge plus its own 72px of inner padding
   already read as the end of the section, so the CTA's top padding carries
   the whole seam. Positional, the same shape as the .cta_divider rule above
   — Section Settings still own everything else about this section,
   including its top. */
.contact_form {
    padding-bottom: 0;
}

/* ------------------------------------------------------------- responsive
   ONE breakpoint, and it is the width where 400px of copy beside 683px of
   form stops fitting: the columns stack, the copy keeps a readable measure
   instead of running the full container, and the panel takes the width.
   Nothing above this needed a second value. */
@media (max-width: 900px) {

    .contact_form_grid {
        grid-template-columns: minmax(0, 1fr);
        gap: clamp(2.5rem, 6vw, 56px);
    }

    .contact_form_copy {
        max-width: 34rem;
    }
}


/* =========================================================================
   ARTICLES PAGE

   Sampled off original-designs/Articles-Page.jpg and divided by 1.3333
   (BRIEF.md, decision 12); mock pixels are quoted so the next person can
   re-check a number against the JPEG.

   Two shapes are new. The banner is internal_banner, the cards are the
   SHARED starter_post_card() the Insights teaser already uses, and the
   closing "Still curious?" is cta_circles with an empty photo repeater —
   the same three reuses the Contact page makes.
   ========================================================================= */

/* -------------------------------------------------------- FEATURED POST
   "Featured Updates" — one post, photo left, byline right, on the green.

   825 and 698 mock px either side of a 125 mock px gutter, which is the
   1236 container exactly: the photo's left edge lands on the container's and
   the copy's right edge on the container's other side. Written as fr so the
   pair keeps those proportions at every width rather than holding a desktop
   measurement; minmax(0, ...) because a long unbreakable title would
   otherwise push the grid wider than its container.

   The anchor IS the grid — sections/featured_post.php explains why the whole
   block is one link. */
.featured_post_link {
    display: grid;
    grid-template-columns: minmax(0, 619fr) minmax(0, 524fr);
    gap: clamp(2rem, 7.6vw, 94px);
    align-items: center;
    color: inherit;
    text-decoration: none;
}

/* The site's focus ring is green, which is this section's own background —
   so the ring is white here, the same call .video_tile makes on the green
   gallery one page over. */
.featured_post_link:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 6px;
    border-radius: 4px;
}

.featured_post_media {
    /* 825 x 726 mock px, and all four corners are rounded — measured, not
       assumed: the left edge sits ON the container, it does not bleed off
       the page the way .story_panel_photo does. */
    aspect-ratio: 825 / 726;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.featured_post_media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A LABEL, NOT A HEADING — 29 mock px bold. The section's <h2> is the post
   title below it (sections/featured_post.php), so this is a <p> that looks
   like an eyebrow rather than a heading shrunk with CSS. */
.featured_post_eyebrow {
    margin: 0 0 1rem;
    font-size: 1.375rem;
    font-weight: 700;
}

/* 72 mock px on 93.5 of line pitch. Not .display-sm: that is the 96 mock px
   section heading, and this heading belongs to a post inside a section, one
   step down from the "Latest Articles" below it. */
.featured_post_title {
    margin: 0 0 1.75rem;
    font-size: clamp(2rem, 1.28rem + 2.36vw, 3.375rem);
    line-height: 1.3;
    font-weight: 700;
    letter-spacing: -.02em;
}

/* 33.25 mock px of line pitch on the 18px body face — tighter than the
   site's 1.55, which is the mock's own setting for this five-line block and
   not a guess. */
.featured_post_excerpt {
    margin: 0;
    line-height: 1.4;
}

/* Name left, date hard right, 87 mock px under the excerpt. Wraps rather
   than crushing: a long author name and a date on one 523px line is the
   first thing to give at a narrow width. */
.featured_post_meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .5rem 1.5rem;
    margin: clamp(2rem, 4vw, 3.5rem) 0 0;
    font-size: 1.25rem;
}

/* The mock underlines the date. It is inside the link that wraps this whole
   block, so the underline reads as what it is rather than as a second
   target — which is the reason it survives rather than being dropped as a
   non-link that looks like one. */
.featured_post_date {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    text-decoration: underline;
    text-underline-offset: .35em;
}

.featured_post_date .calendar_icon {
    flex: 0 0 auto;
    width: 1.15em;
    height: auto;
}

/* The one hover the mock does not draw. A block this size with no response
   to the pointer reads as a picture, not a link; it is the same inversion
   the card's pill already makes, spent on the title instead. */
.featured_post_link:hover .featured_post_title,
.featured_post_link:focus-visible .featured_post_title {
    text-decoration: underline;
    text-underline-offset: .12em;
}

/* --------------------------------------------------------- ARTICLE GRID
   "Latest Articles" — every post but the featured one, two up.

   702 mock px cards either side of a 36 mock px gutter: 1440 mock px in
   total, which is NARROWER than the 1236 container the heading centres in.
   The grid holds its own width rather than the section holding two, so the
   deco mark and the <h2> above it still centre on the container. */
.article_grid_cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 26px;
    max-width: 1080px;
    margin-inline: auto;
}

/* THE ONE THING THE ARTICLES CARD DOES DIFFERENTLY.
   The shared card is 16/10 because that is what the Insights mock draws in a
   375 CSS px column. Here the column is 501 and the mock draws 668 x 351 —
   and the client's exports for this page are cut to exactly that, so holding
   16/10 would crop the top and bottom off pictures that are already the
   right crop. The picture answers to the column it is in; everything else
   about the card stays shared. */
.article_grid_cards .post_card_media {
    aspect-ratio: 668 / 351;
}

/* The mock closes the grid and opens the CTA 100 CSS px apart — ONE section
   padding, not the two that stacking gives. Positional, the same shape as
   the .contact_form rule above: Section Settings still own everything else,
   including this section's top. */
.article_grid {
    padding-bottom: 0;
}

/* Articles draws no hairline above its closing CTA — Home, Services and Our
   Story draw one there and Insights draws it a section earlier (BRIEF.md
   decisions 20 and 25). Same positional shape as the .faq rule above, and
   `~` for the same reason. */
.article_grid~.cta_circles .cta_divider {
    border-top: 0;
    padding-top: 0;
}

/* ------------------------------------------------------------- responsive
   900 is the width where 619px of photo beside 524px of copy stops fitting.

   700 RESTATES A DROP THE SHARED GRID ALREADY MAKES, and it has to. The
   shared .post_card_grid breakpoints live earlier in this file, and
   .article_grid_cards above is the same specificity from further down — so
   it wins the cascade at EVERY width and the two-column rule would survive
   onto a phone. Same value, same breakpoint as the shared rule, deliberately
   duplicated rather than fixed by making the selector heavier. */
@media (max-width: 900px) {

    .featured_post_link {
        grid-template-columns: minmax(0, 1fr);
        gap: clamp(1.75rem, 5vw, 48px);
    }

    /* The photo keeps the mock's near-square crop at desktop; stacked, it
       becomes a banner over the copy rather than a half-screen-tall block
       the reader has to scroll past to reach the headline. */
    .featured_post_media {
        aspect-ratio: 16 / 9;
    }
}

@media (max-width: 700px) {

    .article_grid_cards {
        grid-template-columns: minmax(0, 1fr);
    }
}


@media (prefers-reduced-motion: reduce) {

    #nav,
    .nav_bar,
    .nav_logo,
    .footer_social,
    .footer_link,
    .navitem,
    /* The underline still APPEARS on hover and focus, and the bar still
       SHRINKS on scroll — both are states, and dropping them would cost
       these users the affordance rather than the motion. They just arrive
       instead of easing. */
    .navitem::after {
        transition: none !important;
    }

    /* The disc keeps its offset (that is layout, not motion) and stops
       breathing. */
    .brand_disc {
        animation: none !important;
        scale: 1 !important;
    }

    /* Belt to style.css's braces. .hsr is already in that file's reset list
       and sits on the same element, so `transform: none` lifts the scale(0)
       either way — but the engine's own rule is that every variant appears
       in a reduced-motion block, and a variant that only works by accident
       of a sibling selector is the kind of thing that stops working when
       somebody uses .scale without .hsr. */
    .scale {
        animation: none !important;
        transition: none !important;
        opacity: 1 !important;
        transform: none !important;
    }

    /* The hamburger still BECOMES an X — that is the state, and the state is
       the only thing telling these users the panel is open. It arrives in
       one frame instead of over two steps. */
    .hamb_line,
    .hamb_line span {
        transition: none !important;
    }

    /* The card rollover, the button hover and the video facade still WORK —
       they just change state instantly instead of interpolating. The button
       still goes white with a brown label; it simply arrives. */
    .tenet_card_wash::after,
    .tenet_card_reveal,
    .service_card_body,
    .service_card_wash::after,
    .service_card_reveal,
    .video_play,
    .mybutton,
    .mybutton .buttonicon {
        transition: none !important;
    }

    /* The word roll is decoration on top of a state change that still
       happens — the label is already there and the pill still goes white
       with a brown label. main.js also declines to add .btn_rolling under
       reduced motion; this is the belt to that braces. */
    .btn_word,
    .btn_roll_icon {
        animation: none !important;
    }

    /* INSIGHTS. Every one of these is a STATE, and dropping the state would
       cost these users the affordance rather than the motion — so they all
       still change, they just arrive instead of easing. The accordion still
       opens and closes (its JS sets the height either way and re-hides on a
       timer, never transitionend, which is exactly why it re-hides at all
       under this query); the chevron still becomes a minus; the popup still
       appears; the video tile's hover is the one piece of pure decoration
       here, and it simply stops. */
    .video_tile_cover,
    .video_tile_play,
    .video_dialog,
    .video_dialog_close,
    .faq_item,
    .faq_chevron span,
    .faq_panel,
    .post_card_more {
        transition: none !important;
    }

    /* The picture and the circle stay exactly the size they are. */
    .video_tile:hover .video_tile_cover,
    .video_tile:focus-visible .video_tile_cover {
        transform: none !important;
    }

    .video_tile:hover .video_tile_play,
    .video_tile:focus-visible .video_tile_play {
        transform: translate(-50%, -50%) !important;
    }

    /* Our Story. The acronym card still INVERTS on hover, the LinkedIn icon
       still changes colour, the dialog still opens and the outbound arrow
       still moves to where it was going — all of them are states, and
       dropping them would cost these users the affordance rather than the
       motion. They arrive instead of easing.

       The dialog's fade is the one that matters: every dialog on this site
       re-hides on a TIMER rather than transitionend precisely because
       transitionend never fires when the transition is removed here. */
    .icon_card,
    .icon_card_icon,
    .team_linkedin,
    .team_dialog,
    .team_dialog_close,
    .partner_ring,
    .partner_link .arrow_icon {
        transition: none !important;
    }

    /* The arrow keeps pointing where it points and stops travelling. */
    .partner_link:hover .arrow_icon,
    .partner_link:focus-visible .arrow_icon {
        transform: none !important;
    }

    /* Contact. The form control's border still turns red on an invalid
       field — that is the error STATE, and dropping it would cost these
       users the signal rather than the motion. It arrives instead of
       easing. */
    .contact_form_panel .gform_wrapper.gravity-theme input,
    .contact_form_panel .gform_wrapper.gravity-theme select,
    .contact_form_panel .gform_wrapper.gravity-theme textarea {
        transition: none !important;
    }
}
