
/* ===========================================================================
   The Taft Group
   Mockup: design/mockup/home-864.png (864px native, built at 1440px, x1.6667).

   Every value below traces to a measurement in design/SPEC.md. If a number
   here is not in SPEC.md, it is a bug - not a preference.

   TWO KINDS OF NUMBER LIVE HERE.
   - Sizes and colours are measured off the mockup directly.
   - Vertical gaps are measured cap-to-cap, then corrected for CSS line-box
     leading, because CSS lays out line boxes and Phase 1 measures ink. Each
     one carries its arithmetic: `measured G  -B(prev)  -A(next)`, where A is
     the leading above a cap and B the space below a baseline, both derived
     from the real font metrics. See SPEC.md "Leading correction".
   =========================================================================== */

:root {
  /* --- Colour ------------------------------------------------------------
     Navy and gold are the client's own brand values from the supplied brand
     bundle; the mockup samples agree inside its own noise, and the logo art
     carries these exact values, so the mark sits correctly on the page.

     THE GROUND IS THE CLIENT'S NAMED TOKEN, NOT A MEASUREMENT, and that is
     deliberate. Three client documents call the ground warm ivory - the brand
     README (#F8F5EF), the Capabilities implementation reference (#F7F3EC),
     and the capabilities mockup itself (measures #FAF8F5). Only the homepage
     raster disagrees, at #FCFCFC. The implementation reference is the most
     recent and the most explicit, and it describes the ivory system as
     "established on the approved homepage" - so the homepage raster is the
     outlier, not the rule. One token to revert if that reading is wrong.
     SPEC.md "Colour". */
  --navy:          #04234D;   /* brand; mockup panels read #04203A          */
  --gold:          #C6922F;   /* brand; mockup golds #BF8F47..#C9A168       */
  /* --- THE TONAL SCALE (2026-09-01, Jacob) ----------------------------
     The 1200 container was controlling width, but the canvas and every band
     were near-identical ivory, so the page read as one uninterrupted surface.
     Three light tones now carry the hierarchy, with navy and gold for
     emphasis. Section grounds run full width (see "FULL-BLEED" below) and the
     1200 container sits inside them - that is what makes the steps legible
     without alternating stripes or boxing everything.

       canvas     #F2EEE6  the recessed ground the page sits on
       primary    #F8F5EF  the default section ground  (--paper)
       elevated   #FFFFFF  content lifted off the page (--white-band)
       soft       #FCFBF8  the quieter elevated tone; the header
       deep warm  #F5EDE4  a step below primary       (--paper-warm)

     --paper moves from #F7F3EC to #F8F5EF, which is the value the brand
     README names - see the note below, which called that out already. */
  --tone-canvas:   #F2EEE6;
  --tone-soft:     #FCFBF8;
  --tone-elevated: #FFFFFF;
  --tone-edge:     rgba(4, 35, 77, .07);   /* the faint band separator */
  --paper:         #F8F5EF;   /* SEE NOTE BELOW - the client's named token   */
  --paper-warm:    #F5EDE4;   /* framework band: --paper less the measured
                                 homepage delta (-2,-6,-8)                  */
  --ink:           var(--navy);
  --ink-body:      #1E2936;   /* client's named "Body ink"; measured #3E4654 */
  --ink-muted:     #6B7280;
  /* FORM FEEDBACK, AND THE ONLY TWO VALUES ON THIS SITE THAT ARE NOT MEASURED
     OFF A MOCKUP. They cannot be: the contact mockup draws an unwired form, so
     it has no failed field and no sent state to sample. Named here rather than
     written as literals on the two rules that need them, because the field
     border and the status line MUST NOT drift apart into two near-identical
     reds - which is the failure CONTACT-FORMS.md wrote this rule from.

     Both are picked to sit with the navy and the ivory rather than shout over
     them, and both clear 4.5:1 on --paper (#F8F5EF): the red at 6.4:1, the
     green at 5.6:1. Colour is never the only signal - .field__error carries
     the words, and this only draws the eye to them. */
  --bad:           #9A3B2F;   /* a failed field, and the failed status line */
  --good:          #2E6A4F;   /* the sent line, and nothing else            */
  --rule:          rgba(198, 146, 47, .30);   /* the gold, as a hairline    */
  /* HERO BRAND ART (2026-09-05 mockup refresh). Two values, both measured off
     about-838: the decorative hairline samples rgb(230,198,151) over the ivory
     ground, which is --gold at 45% (30% renders 208,214 - visibly weaker than
     the drawing); the watermark samples #F2EBDE flat. They are a DIFFERENT
     ROLE from --rule, which divides content - these draw the brand mark and
     never separate anything, so rule 5's ban on gold-as-decoration is met by
     them being one measured pair rather than a per-page flourish. */
  --rule-brand:    rgba(198, 146, 47, .45);
  --brand-wash:    #F2EBDE;
  --rule-soft:     rgba(4, 35, 77, .12);      /* card borders               */
  --on-navy:       #F8F9FA;
  --on-navy-muted: #A1AAB8;
  --rule-on-navy:  rgba(198, 146, 47, .42);

  /* --- Layout ------------------------------------------------------------
     TWO containers, and the mockup is consistent about the difference:
     the page container holds the header, hero, navy band and footer;
     the narrower grid holds the capability cards and the framework row. */
  /* --- THE SHELL SCALE ---------------------------------------------------
     THE ONE NUMBER THAT MAKES A 1440 DESIGN LOOK RIGHT IN A 1200 SHELL.

     Every measurement in SPEC.md was read off a mockup that draws 1306px of
     content inside a full-bleed 1440 canvas. Capping the shell at 1200 leaves
     1088 of content - so the type kept its 1440 sizes inside a column 17%
     narrower, and every line the mockup breaks in two broke in three. That is
     the "bent out of shape": card titles on four lines, the navy H2 on three,
     the experience labels shredded into columns too narrow to hold them.

     Scaling the whole composition by 1200/1440 restores every proportion at
     once, because the leading correction is linear: a gap of
     (measured - B(prev) - A(next)) scaled by s equals the same expression
     computed from scaled sizes. So sizes, gaps and components all take one
     factor and every line break returns to the one the mockup draws.

     EVERY MEASURED NUMBER IS STILL ON THE PAGE, as `calc(<measured> *
     var(--u))`. The comment beside each one still reads true, and SPEC.md
     still says where it came from. Changing the shell width is this one line
     plus --wrap - not a re-measure.

     IT IS A LENGTH, NOT A MULTIPLIER, AND THAT IS WHAT LETS IT RAMP.
     This was `--s: .8333`, unitless, with every call site written
     `calc(170px * var(--s))`. Unitless is the one thing CSS cannot interpolate
     against the viewport: calc() will not divide a length by a length, so
     there is no way to write a number that slides from 1 to .8333 across a
     band. The consequence was a 20% STEP at exactly 900/901 in all 566 sizes
     on the site at once - QA.md, "Responsive design is fluid" and "no
     breakpoint changes a size where the layout is unchanged", and MOBILE.md
     rule 1, which was written from this exact failure on another site.

     As a LENGTH the ramp is ordinary clamp() arithmetic, exact in every
     engine, and a call site reads `calc(170 * var(--u))` - 170 design units.
     The measured number is still the number on the page.

     (The unitless form can be recovered with tan(atan2(<length>, 1px)), and
     that was tried first because it touches no call sites. Firefox resolves it
     1.4% off - 84.38 where Chromium and WebKit both give 83.33 - so the
     anchors do not land and a residual step survives in one engine. Verified
     across all three before it was abandoned; do not reach for it again.)  */
  --u: .8333px;    /* 1200 / 1440, as a unit */

  --wrap:          1180px;   /* THE SITE IS 1180 WIDE. Jacob's call, 2026-09-11,
                                standardising one content width across the page;
                                1200 before, and still inside the client's own
                                build references, which all say "recommended
                                maximum content width 1200-1280".
                                The mockups measure 1306 of content in a full-bleed
                                shell; that is now scaled into the shell, so section
                                heights no longer match the mockups 1:1.

                                --u DELIBERATELY DID NOT FOLLOW IT DOWN. The note
                                on --u says changing the shell is "this one line
                                plus --wrap", and 1180/1440 would be .8194. It is
                                still .8333, because --u scales the BODY ladder
                                (--body, --body-sm, --stat) as well as spacing, and
                                re-deriving it would have shrunk every body size on
                                the site by 1.7% - a change nobody asked for, to
                                chase 20px of shell. The 1.7% now lands as 20px of
                                extra side air instead, which is what it is for. */
  /* PHONE GUTTER 24 (2026-09-05, Jacob: "a consistent 20-24px page gutter
     throughout"). The floor was 32, which is a tablet gutter applied to a
     phone: at 390 it took 64 of 390 - 16.4% of the screen - off every line of
     copy. Only the floor moves, so nothing above ~680 changes: 4.7vw passes 24
     at 510 and 32 at 681, and both sides of that were already on the vw term. */
  --gut: clamp(24px, 4.7vw, 46px);
  /* Responsive side padding. THE LOAD-BEARING NUMBER IS THE 1088 COLUMN, NOT
     THE CEILING. Every width-solved line break on this site was solved against
     the content column that 1200 - 2x56 produced; the note here used to say
     the ceiling is 56 rather than the 64 the tier allows, because 64 would
     narrow the column to 1072 and re-break them.

     56 -> 46 (2026-09-11), so the shell moving 1200 -> 1180 does not. 1180 -
     2x46 is 1088, the same column, and the twenty pixels come off the side
     padding instead of off every line of copy on the site.

     THIS WAS MEASURED, AFTER GETTING IT WRONG. At 56 the new shell gave a 1068
     column and ELEVEN blocks across six pages gained a line - .gap__body, the
     /about promise headline, the /experience H1, three experience-detail
     notes, two capability card titles - several of them leaving one word or
     part of one on a line of its own. The worst was .gap__body: that sentence
     wants 1075 on one line, 1088 gave it one line, and 1068 left "technology."
     alone under 984px of text. Jacob's 2026-09-03 instruction for that
     paragraph was "there are too many wraps there on desktop", so the 20px
     had taken back the exact thing it bought.

     4.7vw passes 46 at 979, so nothing below about 980 changes at all. */

  /* Shell-relative geometry, for art positioned against a full-bleed section.
     --shell-w is the 1200 box (or the viewport when narrower); --edge is the
     gap from the viewport edge to it. A percentage inside a full-bleed section
     now means "of the viewport", which is never what these want. */
  /* --- WIDTH TIERS (Jacob, 2026-09-01) ---------------------------------
     One width for everything was the other half of the flat-page problem:
     the constraint was never wrong, the sameness was.

       --wrap        1200   ALL content - text, photography, grids
       --measure      680   body-copy columns
       --gut       32..56   responsive side padding
       (full width)         background bands, photography, navy sections

     --shell is the ACTIVE tier for a section. `.wrap` reads it, and so do
     --shell-w/--edge, which is what keeps section-anchored art aligned to
     whichever tier its section is on. Overriding all three together is
     deliberate: a custom property that references another resolves where it
     is DECLARED, so redeclaring --shell alone would not move --edge. */
  --shell:   var(--wrap);
  --shell-w: min(var(--shell), 100%);
  --edge:    calc((100% - var(--shell-w)) / 2);

  /* --- THE SYSTEM LAYER (design/STYLE-GUIDE.md, 2026-09-01) -------------
     These outrank the per-mockup measurements below them. Before this layer
     the site carried 8 H1 sizes, 42 distinct H2 sizes and 13 H3 sizes - a
     bespoke scale per section, which is what made pages read as enlarged
     rather than spacious. Six steps now, and nothing else sets a heading. */
  /* LITERAL, NOT --s-SCALED. These are the sizes the site renders, which is
     what a type scale is: a system decision, not a mockup proportion. The
     first version of this scale was written in build px and multiplied by --s,
     so it rendered 50/33/20 against the 64-76/42-52/24-30 it was supposed to
     hit - a third under, from an instruction to reduce hero headlines
     "slightly". --measure and --gut are literal for the same reason. */
  /* DISPLAY 70 -> 64 and H2 46 -> 48 (2026-09-11, Jacob): "reduced the headline
     slightly so the first screen feels less oversized", and section headlines
     "roughly 48px rather than allowing some to become disproportionately
     large". Both stay inside the ranges the guide already states - 64 is the
     floor of display's 64-76, 48 is mid-range for h2's 42-52 - so this narrows
     the gap between the hero and the sections it sits above rather than adding
     a step. Nothing else in the scale moves. */
  --t-display:   64px;  --t-display-lh: 74px;   /* hero, 64-76        */
  --t-h1:        53px;  --t-h1-lh:      63px;   /* secondary hero; was 56/66 */
  /* THE SECTION HEADLINE HAS TWO VALUES, AND ONLY THIS ROLE DOES (2026-09-12,
     Jacob). 43 is the interior value and is declared here because eleven of the
     twelve pages are interior; the home page overrides it to 48 on its own body
     class, a few lines below. Asked for in two instructions a day apart -
     "roughly 48px" for home on the 11th, "42-44px" for the interior pages on
     the 12th - and chosen deliberately over one value for both: the front door
     carries a larger section headline than the pages behind it.

     IT IS STILL ONE ROLE. Nothing sets a heading size outside this scale, and
     no page has two H2 sizes in it. What differs is the value of the token, per
     page, in one place. `npm run verify` reads the scale through a probe
     appended to <body>, so it measures whichever value the page actually has -
     which is the reason the probe rewrite of 2026-09-11 had to land first. */
  --t-h2:        43px;  --t-h2-lh:      52px;   /* interior section headline; home 48/58 */
  --t-h3:        34px;  --t-h3-lh:      44px;   /* sub-section        */
  --t-h4:        27px;  --t-h4-lh:      35px;   /* card, 24-30        */
  --t-h5:        21px;  --t-h5-lh:      29px;   /* small titles       */

  /* Measure. A paragraph that fills 1088 is unreadable however well it is
     set; 620 build is about 72 characters at --body. */
  /* 680 -> 640 (2026-09-11, Jacob): "tightened paragraph widths so copy is
     easier to scan". At --body 17.5 the old cap ran about 78 characters, over
     the 60-75 a reader scans without losing the line start; 640 lands ~73 and
     still sits clear of --measure-lede below it, so the two remain two steps
     rather than collapsing into one. */
  --measure:      640px;   /* body-copy column; literal, not scaled - */
  --measure-lede: 620px;   /* readability is absolute, not proportional.
                              DECLARED, NOT YET READ - hero ledes still carry
                              their own measured max-widths. See --cols. */

  /* The grid: 12 columns inside the content column, 24 gutter.
     DECLARED, NOT YET READ. STYLE-GUIDE rule 6 names these; every grid on the
     site is still expressed as `fr` ratios that happen to land on whole
     columns rather than deriving from them. Left in place as the stated
     intent, and flagged here so nobody assumes they are load-bearing. */
  --cols:   12;
  --gutter: calc(24 * var(--u));

  /* Rhythm. Sections alternate between these two; two narrative sections
     should never sit next to each other - the alternation is the pace. */
  --sp-narrative-t: clamp(52px, 6.7vw, calc(96 * var(--u)));   /* 80 at >=1194 */
  --sp-narrative-b: clamp(48px, 6.1vw, calc(88 * var(--u)));   /* 73                */
  --sp-evidence-t:  clamp(34px, 3.9vw, calc(56 * var(--u)));   /* 46.7              */
  --sp-evidence-b:  clamp(32px, 3.6vw, calc(52 * var(--u)));   /* 43.3              */
  /* THE FLAT TIER, AND SINCE 2026-09-12 IT IS THE WHOLE SITE.
     Home took it first, at 39 a side for a 78 gap: "a consistent 78px section
     spacing instead of the larger, varying gaps". The interior pages followed a
     day later at 35 a side for a 70 gap: "standardize ordinary section padding
     to 64-76px". Both numbers are the GAP BETWEEN two sections, so each side of
     the join is half of it - which is what this token is.

     35 HERE, 39 ON HOME, for the same reason the section headline is 43 here and
     48 there: the front door gets a little more air than the pages behind it.
     Interior is the default because eleven of the twelve pages are interior.

     THE READING IS ARITHMETIC, NOT A GUESS. "64-76px section padding" could mean
     64-76 a side, and it cannot: at 70 a side the five ordinary sections on
     /capabilities alone would go from 450 of padding to 700, and every page in
     that instruction would get TALLER against a target asking it to lose 550 to
     750. Half of 70 loses 100 there and the targets come within reach. The same
     phrasing was confirmed as the gap for home on the 11th.

     The gaps it replaces were 90, 120 and 123, depending on which pair of
     tiers happened to meet: evidence-b 43 + evidence-t 47, narrative-b 73 +
     evidence-t 47, evidence-b 43 + narrative-t 80. Every one of them is larger
     than 78, so the page gets shorter at all ten joins.

     IT IS FLAT, WHICH RULE 8'S ALTERNATION IS NOT, and that was the trade when
     only home took it. Now that every page does, the two tiers above have no
     readers left: they stay declared because they are the record of what the
     rhythm was, and because removing them would take the reasoning with them.
     Rule 8 is rewritten around this - see STYLE-GUIDE.md.

     Literal, and it does not ramp: 35 and 39 both sit inside the old clamps'
     mobile floors (34-52), so there is nothing for a breakpoint to narrow, and
     the phone keeps the same number the shell has. His mobile instruction asks
     for 48-56 of section padding on a phone, which is the GAP again - 24 to 28
     a side - and 35 is close enough to it that a second ramp would be noise. */
  --sp-flat:        35px;   /* interior; home overrides to 39 */

  /* THE .ed PROSE SECTION'S INTERNAL RHYTHM, and it has to be :root because
     .ed is a site-wide component now.

     These five were declared on .page-about, which was correct while .ed
     existed only on /about. When home's four prose sections were folded onto
     .ed on 2026-09-12 they took every .ed rule with them - and three of those
     rules resolve one of these tokens, which home does not inherit. An
     undefined custom property makes a declaration INVALID AT COMPUTED-VALUE
     TIME, so it does not fall back to the old value or to the inherited one:
     margin-top became its initial value, 0. Home shipped with a headline
     sitting flat on its body and, in How we scale, paragraphs with no space
     between them at all.

     Nothing caught it. check:widths, check:layout, check:buttons, verify and
     the element snapshot were all green - a gap of 0 overflows nothing,
     overlaps nothing and is not a size that steps. It was found by measuring
     the rendered gap between a headline's last line box and the first line of
     its body, which read 0 where 28 was due.

     The values are unchanged from the ones measured for /about, so /about does
     not move; home gains the rhythm it was supposed to have had.

     They are also why the "consistent gaps" pass tightened one page and not
     four: nothing else read them. Now every .ed does.

     The client sent the About copy with no drawing (2026-09-08), so these are
     INHERITED from values already measured on that page rather than measured
     off a mockup of their own - recorded under Assumptions in SPEC-about.md.
     Tightened 2026-09-12 ("use consistent gaps between headings, copy,
     images, cards and buttons") from 42/49/20/30/24; the ratios are kept,
     head < block, para tightest. */
  --ed-gap-head:  calc(28 * var(--u));   /* eyebrow -> block   (was 42) */
  --ed-gap-block: calc(34 * var(--u));   /* headline -> body   (was 49) */
  --ed-gap-para:  calc(16 * var(--u));   /* paragraph -> paragraph (was 20) */
  --ed-gap-pull:  calc(24 * var(--u));   /* prose -> statement line (was 30) */
  --ed-gap-row:   calc(20 * var(--u));   /* hairline row pad   (was 24) */
  /* Responsive by clamp, not by media query. Every earlier attempt at mobile
     spacing restated a section's padding inside a breakpoint, which is how a
     system drifts: the tier is right at 1440 and hand-tuned at 720. One
     narrowing curve, applied to both tiers, and no section restates it. */

  /* Button paint. The default ground is light; a navy ground overrides these
     four (see ".sec--navy" below) so the primary never lands navy-on-navy. */
  --btn-p-bg:   var(--navy);  --btn-p-fg:   #FFFFFF;  --btn-p-bd:   var(--navy);
  --btn-p-bg-h: #06305F;      --btn-p-fg-h: #FFFFFF;  --btn-p-bd-h: #06305F;

  /* Leading, where a block sets its own size. Three ratios, so a paragraph
     cannot arrive at 2.32 the way .cpanel__lede had (15.8px type on 36.7px
     leading, which reads as double-spaced). */
  --lh-lede:   1.6;    /* ledes and long-form paragraphs   */
  --lh-tight:  1.35;   /* labels, chips, uppercase runs    */

  /* Interaction */
  --ease:      cubic-bezier(.4, 0, .2, 1);
  --dur-image: .4s;
  --dur-rule:  .3s;
  --dur-link:  .2s;
  /* THE GRID IS THE CONTENT COLUMN. It used to be 993 - the mockup's 1192
     scaled - sitting centred inside the 1088 that .wrap gives, so card edges
     and framework steps missed the section headlines above them by 47px on
     each side. Rule 6 asks for 12 columns INSIDE the content column; this is
     that column, and now the verticals line up. */
  --grid:          100%;

  /* --- Type families -----------------------------------------------------
     INFERRED from the raster - see SPEC.md "Assumptions". Every size below
     is measured off cap heights and holds whatever the family turns out
     to be. Rendered cap heights were checked against the measured ones and
     agree within 1px across all 17 roles. */
  --serif: "Playfair Display", Georgia, "Times New Roman", serif;
  --sans:  "Lato", -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;

  /* --- Type scale --------------------------------------------------------
     size = cap-height(build) / cap-ratio  (Playfair .7125, Lato .7165) */
  /* THE PRE-GUIDE HEADING SCALE IS GONE (2026-09-03). --h1, --h2-gap,
     --h2-caps, --h2-frame, --h2-navy, --h2-cta and --card-t lived here and
     were read by the HOMEPAGE ONLY: every other page moved to --t-* when
     rule 2 landed, but home's rules sit above that migration in the file and
     were missed. It shipped four different H2 sizes (39.2/36.7/35.0/30.8/25.0
     rendered) and card titles at 19.2 where the site sets 27.

     There is one heading scale in this file now. --stat stays because a
     metric NUMERAL is not a heading - rule 5 governs it, not rule 2. */
  /* 50.8 -> 45 (2026-09-11, Jacob: "reduced the height of the statistics
     band"). The numeral is what sets that band's height, so this is where the
     reduction has to come from - the padding above and below it is the rhythm
     token and is already flat at 39. It stays ONE size on all three pages that
     carry the component (home, /about, /experience): rule 1 says the same
     component gets the same treatment, so shrinking it only on home would have
     been a restyle rather than a shorter band. */
  --stat:        calc(54 * var(--u));  --stat-lh:    calc(60 * var(--u));  /* was 61/67 = 50.8/55.8; cap 26 -> 43.3 */
  --wordmark:    calc(32 * var(--u));  /* WIDTH-SET, not cap-set: "THE TAFT GROUP" spans
                                            x55-214 = 160 mockup px, which is 222 at the 1200
                                            shell. 32 renders 223; the old 27 renders 188. */

  --body:        calc(21 * var(--u));  --body-lh:    calc(30 * var(--u));  /* cap 9 -> 15; base gap 18.3 -> 30  */
  /* THE SMALL-TEXT LADDER. Three steps below --body, and every supporting
     line on the site lands on one of them. A card's second line was 17.5 on
     home, 14.2 on capabilities and 13.3 on experience before this - the same
     role at three sizes, which is rule 2's problem one level down. */
  --body-sm:     calc(19 * var(--u));  --body-sm-lh: calc(27 * var(--u));  /* 15.8 - card and supporting copy,
                                                                 arrow links, framework labels     */
  --body-xs:     calc(16 * var(--u));  --body-xs-lh: calc(24 * var(--u));  /* 13.3 - metric labels, fine print */
  --label:       calc(19 * var(--u));                        /* stats label, cap 8 -> 13.3       */
  --note:        calc(17 * var(--u));                        /* footnote; 15px cap reads as ascender,
                                                 width x160-700 = 900 build settles it */
  --nav:         calc(18 * var(--u));   /* "Capabilities" w58 -> 97 build */
  --btn:         calc(16 * var(--u));   /* label + 2x26 padding = 242 = measured */
  --btn-h:       calc(50 * var(--u));   /* the box; a token so the phone step
                                             is one line in the mobile :root  */
  --eyebrow:     calc(14 * var(--u));                        /* cap 6 -> 10                      */
  --eyebrow-track: .18em;
  --btn-track:   .02em;

  /* --- Components --------------------------------------------------------
     Every corner in the mockup is square except the icon badges and the
     framework circles, which are circles. No shadows anywhere.
     ONE EXCEPTION SINCE 2026-09-12: the icon square on a card has a 6px
     radius - --icon-radius below, and the only radius on the site. */
  /* 80.8 -> 72 (2026-09-11, Jacob). LITERAL now, not the measured 97: a
     persistent chrome height is a system decision, the way the type scale and
     the gold rules already are, and 72 is not a number off any mockup. The
     mark inside it is 37.5 and the header CTA 36.7, so 72 still leaves 17 of
     air above and below the tallest thing in the row. */
  --head-h:      72px;   /* was calc(97 * var(--u)) = 80.8, mockup y0-58 */
  --btn-gap:     calc(35 * var(--u));   /* 184 -> 203 = 21 -> 35 */
  /* GOLD RULES: two lengths, one thickness. There were ELEVEN widths on the
     site - 14, 18, 26, 30, 42, 59, 66, 68, 70, 72, 80 - which is what "gold as
     general decoration" looks like in numbers. Literal, like the type scale:
     an accent's size is a system decision, not a mockup proportion. */
  --rule-short-w:  40px;   /* the dash under an eyebrow or beside a heading */
  --rule-long-w:   70px;   /* the centred rule above a section or a CTA     */
  --rule-h:         3px;
  --dash-w:      var(--rule-short-w);   /* retained names, single source */
  --dash-h:      var(--rule-h);
  --cta-rule-w:  var(--rule-long-w);
  --cta-rule-h:  var(--rule-h);
  /* THE CLOSING BLOCK'S THREE GAPS (2026-09-12, Jacob: "reduce repeated
     closing CTAs ... to about 260px", and "use consistent gaps").
     Every interior page ends on the same shape - a gold rule, a headline, a
     line or two of copy, a button - and it was defined FIVE times, once per
     page, with five sets of gaps: the rule-to-headline gap was 0, 10, 16.7 and
     20 depending on the page, the button gap 10, 18, 20 and 24, and the button
     itself 34.2, 36.7, 41.7 or 51.7 tall. Three tokens and one grouped rule
     replace all of it. */
  --cta-gap-head: calc(20 * var(--u));   /* rule -> headline          */
  --cta-gap-body: calc(14 * var(--u));   /* headline -> supporting line */
  --cta-gap-btn:  calc(44 * var(--u));   /* last line -> button       */
        /* 24 -> 44 (2026-09-12, Jacob: "needs more space between it and the
           buttons below it ... every page has a section like that at the
           bottom and they are all too close to the button"). Measured across
           all six closing blocks first: 14 to 22px of INK between the last
           line and the button top, and 9 on /ai-transformation. A 41.7px
           button under a 43px serif headline needs about the button's own
           height of air; 44u is 36.7, which lands the ink gap at 31-39. */
  --hairline:     2px;
  --card-w:     calc((100% - 2 * var(--card-gap)) / 3);   /* was 382 at 1192 */
  --card-gap:    calc(23 * var(--u));   /* 14 -> 23 */
  --card-img-h: calc(170 * var(--u));   /* photo y826-927 = 102 -> 170 */
  --card-pad:    calc(26 * var(--u));   /* was 33 (measured text inset 20 -> 33);
                                           27.5 -> 21.7 with the 2026-09-11 card
                                           tightening. ONE token, because
                                           .card__badge reads it too: the badge's
                                           left edge and the title's left edge are
                                           the same line, and hardcoding the body
                                           padding would have moved one without
                                           the other. */
  /* ICON CIRCLES: three diameters. There were twelve - 64 through 97 - so no
     two pages drew the same mark at the same size. --circle-sm is the third
     step and it arrived with the 2026-09-11 home tightening, as a STEP rather
     than as two components each picking their own smaller number: the compact
     framework band and the capability-card badge both take it, and both had
     the 72 as their height driver. Nothing else moves to it. */
  --circle-sm:    60px;
  --frame-ring:   72px;   /* the framework step's ring; was --circle-sm 60
                             (2026-09-12, "can the circles be a little bit
                             bigger"). Its own token because --circle-sm also
                             sizes the card badge, which did not change. */   /* a circle in a compact band, and the card badge */
  --circle:       72px;   /* a numbered or icon circle in a row of them */
  --circle-lg:    84px;   /* the icon that heads a block                */
  --ring-stroke:  2.2px;  /* the gold ring, converted from the superseded
                             artwork's stroke-width 2.5 in a 96 viewBox      */
  /* THE ICON SQUARE (2026-09-12, Jacob): "place each icon inside a consistent
     navy square with slightly rounded corners ... keep every square the same
     size, with identical icon scale and internal padding".

     ONE BOX, ONE ART SIZE, ONE INSET, and they are literals for the same
     reason the type scale is: a container shape is a system decision, not a
     mockup proportion. 72 is the existing --circle step, so the square is the
     diameter the ringed marks already had; 48 is the artwork inside it, which
     leaves 12 of navy on every side.

     --icon-sq DOES NOT RAMP. "Scales cleanly on mobile without enlarging the
     icon" - so the square is the same 72 at 320 as at 1440, and the two mobile
     rules that used to shrink a ring to 56 are gone: they existed because an
     84 ring sat BESIDE the copy and overflowed a 320 phone, and the icon is
     above the copy now. */
  /* 72 -> 60 when the square moved onto the heading's row (2026-09-12). At 72
     it took 85 of a 300 content column on home's three-up cards and pushed a
     heading that already set on three lines onto four. 60 is --circle-sm, an
     existing step, and it is also the proportion the reference draws: the
     square there is about 0.85 of a two-line heading, which is 60 against this
     scale rather than 72. */
  /* 60 -> 48, art 40 -> 32 (2026-09-12, Jacob, from a third reference: "48px
     icon, 22px card heading, 14px gap - with the description and button
     aligned beneath the entire heading group"). The inset keeps its ratio, so
     the artwork still sits in a fifth of the box on every side.
     THIS IS THE THIRD SIZE THIS TOKEN HAS HAD TODAY - 72 centred above the
     heading, 60 when it moved onto the heading's row, 48 now that the heading
     came down a step. Each one was measured against what the heading did in a
     300px column; none of them is a preference. */
  --icon-sq:        48px;
  --icon-art:       32px;
  --icon-pad-file:   4px;   /* a file trimmed to its ink, not a ringed drawing */
  --icon-pad:       calc((var(--icon-sq) - var(--icon-art)) / 2);   /* 12 */
  /* THE SITE'S FIRST CORNER RADIUS, and it is deliberate - see the note above
     this block, which said every corner is square. "Slightly rounded" is his
     word; 6 on a 72 box is about 8%, enough to read as intent at a glance and
     not enough to read as a pill. */
  --icon-radius:     6px;
  /* The gaps around it, shared by every icon card: image -> square -> heading
     -> description -> link. Five components set five different sets of these
     before. */
  --icon-gap-above: calc(20 * var(--u));   /* image -> square       16.7 */
  --icon-gap-side:  14px;   /* square -> heading, across. His number, literal */
  --icon-gap-below: calc(16 * var(--u));   /* square -> heading     13.3 */
  --card-gap-desc:  calc(10 * var(--u));   /* heading -> description 8.3 */
  --card-gap-link:  calc(14 * var(--u));   /* description -> link   11.7 */
  --badge:       var(--circle-sm);   /* the card badge is the compact mark */
  --dims-art-w: 42.6%;   /* AI dimensions photo, x439-771 of the content       */
  /* --hero-img-w and --hero-fade are gone (2026-09-12): the photograph no
     longer occupies a share of the shell and no longer feathers into paper.
     It is inset: 0 behind the copy, with a navy scrim. */
}
/* ===========================================================================
   THE HOME PAGE'S TWO TOKEN VALUES (2026-09-12)

   Two tokens hold a different value on the home page than on the eleven
   interior pages, and this is the only place either difference is expressed.
   It is not a second scale and not a page stylesheet: two declarations, on one
   body class, both of them values for roles that already exist.

     --t-h2    48 here, 43 interior   "roughly 48px" (11th) / "42-44px" (12th)
     --sp-flat 39 here, 35 interior   a 78 gap here, a 70 gap interior

   The home page is the front door and reads a step larger and a step airier
   than the pages behind it. Every OTHER token is one value for the whole site;
   if a third one ever wants to join these two, that is the moment to ask
   whether the home page is becoming its own design rather than the first page
   of this one.

   The mobile block near the bottom of this file re-ends the interior ramps at
   43 and leaves these alone, so nothing steps at 901 on either side.
   =========================================================================== */
body.page-home {
  --t-h2: 48px;  --t-h2-lh: 58px;
  --sp-flat: 39px;
}
/* Below the shell, the design re-composes rather than shrinking: no mobile
   mockup was supplied (SPEC.md "Assumptions"), and the breakpoint blocks below
   set their own sizes, authored against the unscaled scale. So the shell scale
   is a desktop-composition number and stands down here.

   IT STANDS DOWN ALONG A RAMP, NOT AT A LINE. This block used to set --s: 1
   flat, which meant every size on the site changed by 20% between 900 and 901
   with nothing structural moving - the whole of the width sweep's error list, and three QA.md checks at once. The two scales are both deliberate; only the
   junction between them was wrong.

   --pw IS THE RAMP, AND IT IS A LENGTH BECAUSE EVERYTHING HERE HAS TO BE.
   It runs 0px at 390 to 510px at 900 - the clamp is on the INPUT, so no token
   below needs one and none of them can get the direction wrong. Every value is
   then the same shape:

       calc(<what the phone renders>px - <delta> * var(--pw) / 510)

   where delta is (phone value - what 901 renders), positive when the token
   shrinks into the desktop scale and negative when it grows. Both anchors are
   exact: at 390 every token renders EXACTLY what it rendered before this
   change, and at 900 exactly what 901 renders, so crossing the breakpoint
   moves nothing. Below 390 --pw is pinned at 0 and the phone values hold flat, which is what a floor is for.

   390 is the anchor because that is where the phone build was tuned - the
   2026-09-05 type pass, Jacob, "body copy, card titles and buttons all up a
   step, and the eyebrow up two". Those numbers are preserved exactly; what
   changes is that they now travel to the desktop scale instead of jumping. */
@media (max-width: 900px) {
  :root {
    --pw: clamp(0px, calc(100vw - 390px), 510px);
    --u: calc(1px - .1667 * var(--pw) / 510);   /* 1 -> .8333, the shell scale */
    /* THE CARD PHOTO KEEPS ITS DRAWN PROPORTION WHEN THE ROW STACKS.
       --card-img-h is a HEIGHT, and it was measured against a card 382 wide -
       one of three across the 1088 column. At 900 the row is one column, so
       the card is 815 wide and the photo was still 170 tall: a 4.80:1
       letterbox for a 2.26:1 photograph, which cut the top off every head in
       the frame. It got worse the closer the viewport came to the breakpoint -
       2.25:1 at 430, 4.09 at 768, 4.80 at 900 - and nothing overflowed, so
       check:widths saw a clean page the whole way.

       The card is the content column here, which is the viewport less two
       gutters. Dividing that by the drawn 382:170 gives the SAME 170 at the
       382 the mockup drew and holds the proportion at every other width. Both
       measured numbers stay on the page, and the badge below still reads this
       token for its offset, so it follows the photo down. */
    --card-img-h: calc((100vw - 2 * var(--gut)) * 170 / 382);
  }
}
/* --- Reset -------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
img, picture, svg, video { display: block; max-width: 100%; }
h1, h2, h3, p, figure, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--tone-canvas);
  color: var(--ink-body);
  font-family: var(--sans);
  font-size: var(--body);
  line-height: var(--body-lh);
  -webkit-font-smoothing: antialiased;
}
/* MEASURE - STYLE-GUIDE rule 2. A paragraph that fills the 1088 column is
   unreadable however well it is set. This only ever caps: any block that
   already carries a tighter max-width keeps it, being more specific. */
main p { max-width: var(--measure); }
/* ...EXCEPT THE ONES THAT ARE NOT PROSE. Five layout rows on this site are
   marked up as <p> - a flow chain, a tag strip, the AI hero's step row, the
   platform strip, the detail hero's kind badge - and a measure meant for
   reading was silently laying them out. It cost /experience its platform
   strip, which needed 746px, got 680, and dropped "SharePoint" onto a line of
   its own; and it cut the AI hero's step row to 680 of the 1088 its rule is
   drawn across. A row is not a paragraph, whatever tag it uses. */
main p:is(.chain, .tags, .ahero__labels, .platforms__row, .dhero__kind) { max-width: none; }
/* INTERACTION - STYLE-GUIDE rule 9. Subtle, and a response to the user:
   nothing fades, bounces or moves on load. */
.card__art, .case__art, .band-img, .dwork__strip, .xhero__group img, .dhero__art, .hero__art, .ahero__art, .cexp__art, .dims__art {
  overflow: hidden;
}
.card__art img, .case__art img, .band-img img, .dwork__strip img, .xhero__group img, .cexp__art img {
  transition: transform var(--dur-image) var(--ease);
}
.card:hover .card__art img, .case:hover .case__art img, .xhero__group:hover .xhero__utility, .band-img:hover img {
  transform: scale(1.03);
}
/* ======================= HERO BRAND ART ==================================
   New in the 2026-09-05 mockup refresh: every hero that is not carrying a
   photograph now carries the brand mark as a watermark, a large thin gold
   ellipse and two vertical gold hairlines. Measured off about-838 and
   normalised to the CONTENT COLUMN, not to the canvas - the five new rasters
   disagree with each other on container width (72.4% to 90.1% of canvas), so canvas proportion is not a measurement here. The shell stays 1200
   (SPEC.md, "THE SITE IS 1200 WIDE", Jacob) and every x below is build px
   from the content column's left edge, at 1088 wide.

     mockup content column   x41-796 = 756  ->  1088     x1.4392
     vertical rules          x582, x639     ->  779, 861
     ellipse centre          x847.7, y275.5 ->  1161, 56.4% of hero height
     ellipse semi-axes       320.1 x 177.5  ->  461 x 255, rotated 19.1
     watermark left          x540           ->  718, full hero height

   The ellipse is a MEASURED ELLIPSE, not an approximated circle: a conic fit
   to the 818 gold arc pixels returns a discriminant of -2.7e-11 and traces
   the drawn curve along its whole visible length, where the best circle fit
   diverges below the leftmost point. Rotation and both axes are that fit.

   It is decoration: aria-hidden, pointer-events none, and it sits under the
   copy at z-index 0 with .wrap/__inner lifted above it. */
.brandart { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
/* THE ART IS COMPOSED AGAINST THE BAND, NOT AGAINST AN ABSOLUTE SIZE. Both
   the mark and the arc are drawn to the hero's full height and are cropped by
   it - the mark shows 57% of its width before the viewport edge takes it, the
   arc shows a sweep rather than a closed ellipse. Sized in fixed build px
   they stopped cropping the moment the hero grew (and it does grow: our H1
   holds --t-h1 56 where the drawing solves 49.9). So height drives both, and
   the crop survives whatever the copy does to the band. */
.brandart__mark {
  position: absolute; top: 0; bottom: 0;
  left: calc(var(--edge) + var(--gut) + 718 * var(--u));
  right: 0;
  background: var(--brand-wash);
  -webkit-mask: url("/assets/taft-monogram-watermark.png") left center / auto 100% no-repeat;
          mask: url("/assets/taft-monogram-watermark.png") left center / auto 100% no-repeat;
}
.brandart__rule {
  position: absolute; top: 0; bottom: 0; width: 1px; background: var(--rule-brand);
}
.brandart__rule--a { left: calc(var(--edge) + var(--gut) + 779 * var(--u)); }
.brandart__rule--b { left: calc(var(--edge) + var(--gut) + 861 * var(--u)); }
/* ANCHORED BY ITS LEFT EDGE, NOT ITS CENTRE. Height-relative art scales with
   the band, and centring it meant a taller band pushed the arc's leftmost
   point back into the headline. The drawing starts it at x539.7 - the same
   x the watermark starts at, which is not a coincidence in the drawing - so
   that edge is the anchor and the arc grows right and down from it.

   539.7 is the rotated ellipse's true leftmost: cx - sqrt((a cos19.1)^2 +
   (b sin19.1)^2) = 847.7 - 308.0. The bounding box is 616 x 395.4 raster, and
   the rotation lives inside the SVG so this element's box IS that box. */
.brandart__arc {
  position: absolute; top: 56.3%;                  /* centre y, 275.5 of the 386 band */
  left: calc(var(--edge) + var(--gut) + 718 * var(--u));
  height: 102.4%; aspect-ratio: 616 / 395;         /* 395.4 of the 386 band */
  transform: translateY(-50%);
}
.brandart__arc ellipse { fill: none; stroke: var(--rule-brand); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
/* On a navy ground the wash flips, the way every other reversed mark does. */
.sec--navy .brandart__mark { background: rgba(255, 255, 255, .05); }
.sec--navy .brandart__arc ellipse, .sec--navy .brandart__rule { stroke: var(--rule-on-navy); }
.sec--navy .brandart__rule { background: var(--rule-on-navy); }
/* Below 900 the art is dropped, not shrunk. It is drawn against a copy column
   that is 48-55% of the hero; on a phone the copy is 100% of it, so the mark
   would sit under the headline rather than beside it. */
@media (max-width: 900px) { .brandart { display: none; } }
/* Gold rules grow from their measured width. The width is set per block, so
   the transform is the only thing that moves. */
.rule-short { transition: transform var(--dur-rule) var(--ease); transform-origin: left center; }
.dcta .rule-short, .cta .rule-short, .about-cta .rule-short { transform-origin: center; }
section:hover > .wrap > .rule-short,
section:hover > .wrap > *:first-child > .rule-short { transform: scaleX(1.4); }
/* Text links: a gold underline wiping in from the left. */
.site-nav a, .site-foot__links a { position: relative; }
.site-nav a::before, .site-foot__links a::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: -6px; height: 2px;
  background: var(--gold); transform: scaleX(0); transform-origin: left center;
  transition: transform var(--dur-link) var(--ease);
}
.site-nav a:hover::before, .site-foot__links a:hover::before { transform: scaleX(1); }

/* --- Primitives --------------------------------------------------------- */
.wrap { max-width: var(--shell); margin-inline: auto; padding-inline: var(--gut); }
/* THE WIDE TIER IS GONE (2026-09-03, Jacob): "the photos and text have to
   stay on the 1200". Heroes used to redeclare --shell-w/--edge at 1360 so
   their artwork reached 80px past the content shell on each side; Experience
   went further and pulled its whole .wrap onto the wide tier with a
   compensating padding-left. Both are removed.

   ONE SHELL NOW. Every photograph, every line of type and every grid sits
   inside --wrap. Only section GROUNDS bleed - the navy bands, the ivory
   steps, the full-width photo band - which is the distinction Jacob drew:
   colour may run to the viewport edge, content may not.

   Nothing redeclares --shell-w/--edge any more, so art positioned against a
   section (.hero__art, .ahero__art, .dhero__art) lands on the 1200 box that
   :root sets, level with the logo and the headline. */

/* Compositions that sit INSIDE the content grid reach out to the wide edge
   rather than being re-parented: the Experience photo group and the About
   portrait panel. */
/* About keeps the standard grid: it is the editorial silhouette (rule 1) and
   its hero is a portrait, not a sweeping composition. The wide tier is for
   Home, AI, Experience and the detail page. */

/* FULL-BLEED BACKGROUNDS, 1200 CONTENT. Reversed 2026-09-01 (Jacob): section
   grounds now run to the viewport edge and `.wrap` alone holds the content at
   1200. Capping the blocks themselves made the whole page read as one
   uninterrupted surface, because the canvas and every band were near-identical
   ivory and the bands could not separate themselves from it.

   The tonal scale is what does the separating; this rule is what lets it be
   seen. SUPERSEDES SPEC.md "The site is 1200 wide, not full-bleed".

   Anything absolutely positioned against a SECTION now has the viewport as its
   containing block, not the shell, so those rules use --edge/--shell-w rather
   than raw percentages. Anything inside `.wrap` was already safe. */
body > header, body > main, body > footer { margin-inline: auto; }
/* Every section paints the primary tone unless it says otherwise, so the
   canvas shows only where it is meant to - behind a lifted panel - rather
   than leaking through any section that happens not to set a ground. */
main > section { background: var(--paper); }
.grid-wrap { max-width: var(--grid); margin-inline: auto; }
/* ===========================================================================
   THE SECTION SYSTEM. Three orthogonal things a section declares: its RHYTHM, its GROUND, and nothing else. Both were per-section arithmetic before
   2026-09-03, which is how the site ended up with section padding from 6 to
   98 build and an invisible button on the AI hero.
   =========================================================================== */

/* --- Rhythm (STYLE-GUIDE rule 8) ----------------------------------------
   Two tiers, and sections alternate between them. Written into the guide on
   2026-09-01 and never applied: `.sec--narrative` and `.sec--evidence`
   existed in prose only, while 41 sections each carried their own measured
   pair. Measured optical padding ran 10px on /capabilities .practices to
   85px on /ai-transformation .ahero - the same job, eight times the space.

   These are RAW padding, not leading-corrected. A section's first element
   absorbs its own half-leading, so the optical gap varies by ~12px between a
   section opening on an eyebrow and one opening on a display headline. That
   is the residual, and it is a twelfth of the spread it replaces. */
.sec--narrative { padding-top: var(--sp-narrative-t); padding-bottom: var(--sp-narrative-b); }
.sec--evidence  { padding-top: var(--sp-evidence-t);  padding-bottom: var(--sp-evidence-b);  }
/* The flat tier - see --sp-flat. Home only, and it replaces the other two on
   that page rather than sitting alongside them: a section takes exactly one
   rhythm class, so there is nothing to resolve when both are in the markup. */
.sec--flat { padding-top: var(--sp-flat);        padding-bottom: var(--sp-flat);        }
/* --- Navy ground (STYLE-GUIDE rule 7) ------------------------------------
   THE RULE ALREADY EXISTED: "on a navy ground the secondary is the primary, because solid navy on navy is invisible" - and it was applied by hand to
   the two sections the rule happens to name. The AI hero is navy, carries a
   .btn--primary, and was therefore shipping a navy button on navy: the label
   rendered as bare bold text with no box at all.

   So the ground now flips the button, rather than an author remembering to.
   Any navy surface takes this class - sections, the footer, the contact
   panel's aside - and every .btn--primary inside it becomes the gold outline
   the rule asks for. Nothing has to be restated at the call site. */
.sec--navy, .site-foot, .cpanel__aside {
  --btn-p-bg: transparent;   --btn-p-fg: var(--gold);
  --btn-p-bd: var(--gold);
  --btn-p-bg-h: var(--gold); --btn-p-fg-h: #FFFFFF;
  --btn-p-bd-h: var(--gold);
}
.sec--navy { background: var(--navy); color: var(--on-navy); }

.eyebrow {
  font-size: var(--eyebrow);
  font-weight: 700;
  letter-spacing: var(--eyebrow-track);
  text-transform: uppercase;
  color: var(--gold);
  line-height: 1.2;
}
/* HEADINGS BALANCE THEIR RAG. Only the AUTOMATIC break moves - a <br> the
   markup sets still wins - so this changes nothing where a line break is
   authored and everything where a 46px headline was dropping its last word
   onto a line by itself. Six of those on /experience and /capabilities came
   from the same cause: a heading width-solved against 1306 now setting in
   1088. Unsupported browsers get the ragged break they already had. */
main h1, main h2, main h3, main h4, .acta { text-wrap: balance; }
/* ===========================================================================
   THE SECTION HEADLINE, DECLARED ONCE (2026-09-12)

   It was declared THIRTY-ONE times. Thirteen of those were byte-for-byte
   identical - serif, 400, --t-h2, its leading, navy - and the rest varied only
   by a margin, a max-width, or a colour because the ground was navy. Every one
   of them was a chance for the next section to be 2px different from its
   neighbours, which is the same failure rule 2 was written from one level up:
   42 distinct H2 sizes before the scale existed.

   :where(), NOT :is(), and that is the whole trick. :where() contributes no
   specificity, so this rule sits at (0, 0, 1) and EVERY existing rule outranks it
   - including .stats__h at (0, 1, 0), which would otherwise have lost its
   on-navy colour and gone navy on navy. A section overrides by saying only what
   differs, and a new section that says nothing gets this for free.

   The four sub-section headlines that take --t-h3 (.cexp, .cpanel__aside, .cpanel__form, .cpartner) keep their own size and leading; they now inherit
   the family and weight from here rather than restating them. */
main :where(h2, .acta) {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h2); line-height: var(--t-h2-lh);
  color: var(--navy);
}
/* margin-inline: 0 because <hr> inherits `auto` from the UA stylesheet, which
   silently centred every left-aligned gold rule on the site. The centred
   ones set `auto` back explicitly. */
.rule-short {
  width: var(--dash-w); height: var(--dash-h);
  background: var(--gold); border: 0; margin-inline: 0;
}
.cta .rule-short { width: var(--cta-rule-w); height: var(--cta-rule-h); }
/* THREE BUTTONS, AND ONLY THREE - design/STYLE-GUIDE.md rule 7.
   Before this: "Discuss a Transformation" shipped as gold-outline-sm, as
   gold-outline AND as solid navy; "View the Experience" as two of those; and
   the AI hero carried a fourth, filled-gold style of its own. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--btn-h);
  padding-inline: calc(26 * var(--u));
  font-size: var(--btn);
  font-weight: 600;
  letter-spacing: var(--btn-track);
  line-height: 1;
  white-space: nowrap;
  border: var(--hairline) solid transparent;
  transition: background-color var(--dur-link) var(--ease),
              color var(--dur-link) var(--ease),
              border-color var(--dur-link) var(--ease);
}
/* primary: the main action in a section. Painted from tokens, so a navy
   ground can repaint it without either side knowing about the other -
   see "Navy ground" above. */
.btn--primary { background: var(--btn-p-bg); color: var(--btn-p-fg); border-color: var(--btn-p-bd); }
.btn--primary:hover { background: var(--btn-p-bg-h); color: var(--btn-p-fg-h); border-color: var(--btn-p-bd-h); }
/* secondary: the alternative beside a primary, and the header CTA */
.btn--secondary { color: var(--gold); border-color: var(--gold); }
.btn--secondary:hover { background: var(--gold); color: #fff; }
/* the header only */
.btn--sm { height: calc(44 * var(--u)); }
/* A CARD ACTION WRAPS; A HERO'S DOES NOT (2026-09-12).
   .btn is nowrap on a fixed height, which is right for two words in a hero and
   wrong for "Explore Enterprise Change Capability" in a 272px column: with the
   arrow links converted to buttons, check:widths caught five of them pushing
   /capabilities 74px sideways at 320. These grow a line instead.
   min-height rather than height, so a one-line label sits in exactly the same
   box as every other button on the site. */
:is(.card, .dtrio__col, .svc__main, .engage) .btn, .challenge__all {
  white-space: normal;
  /* max-width, because .btn is inline-flex and an inline-flex box is
     max-content wide by default - it will not wrap into a narrower parent
     unless it is told it cannot exceed it. check:widths caught the last one
     this way: 319px of nowrap button at the 320 gutter. */
  max-width: 100%;
  height: auto; min-height: var(--btn-h);
  padding-block: calc(8 * var(--u));
  text-align: center;
}
/* .arrow-link IS GONE (2026-09-12, Jacob): "the Explore with the arrow
   hyperlinks should get replaced by the treatment under AI & Workforce
   Transformation ... inside a button with that mouseover treatment."

   All forty of them - every card action, every hero's second action, the
   in-page "Explore the Practices" - are .btn--secondary now, which is the
   treatment practice 02 already had. Its gold-fill-on-hover is the mouseover
   he means, and it comes from rule 7's three buttons rather than from a rule
   of its own. The base class, its svg, its hover transform and the
   .arrow-link--down variant are all deleted: nothing references them.

   WHAT THIS UNDOES, AND IT IS THE CLIENT'S OWN DISTINCTION: section 4 of
   COPY-DECK.md marks four of the five practices "LINK:" and only AI & Workforce
   Transformation "BUTTON: Explore AI Transformation". Jacob has asked for the
   button everywhere, so the deck's link/button split is gone. Recorded in SPEC
   rather than argued about - but it is his call over the deck, not a tidy-up. */

/* ===========================================================================
   THE ICON SQUARE, ON EVERY ICON-AND-CONTENT CARD (2026-09-12, Jacob)

   Six components carried an icon above or beside a heading and a description, and every one of them did it differently: home's was a navy CIRCLE
   straddling the photograph, /capabilities' practices had a bare 72 ring flush
   left, its challenge tiles an 84 ring beside the title, /ai-transformation's
   human cards an 84 ring beside the copy, /experience's legend keys the same
   at 84, and the detail pages' "About this practice" another 84 beside its
   heading. Four sizes, two shapes, five sets of gaps.

   One square now: 72, navy, 6px radius, the artwork at 48 inside it, centred
   above a heading that stays left-aligned.

   THE ARTWORK IS THE ARTWORK. "Use the existing gold icon artwork - do not
   create or replace any icons." The supplied icons stroke their figure in
   #04234D, which is this square, so on navy they would have rendered as an
   empty gold ring. Thirteen of them now point at a -reversed variant: six were
   supplied with the bundles, seven were derived on 2026-09-12 by swapping that
   one stroke for the on-navy ivory and touching nothing else - the same rule, and the same comment, as the supplied six. No path, ring, viewBox or colour
   beyond that stroke was altered.

   Home's five cards are the exception in one respect: their glyph is an inline
   SVG stroked --gold with no ring, not one of the icon files, so their square
   holds a gold glyph where every other card's holds a ring around an ivory
   one. Swapping them for the practice files would be replacing an icon, which
   the instruction rules out. Recorded in SPEC.
   =========================================================================== */
.card__badge, .tile__icon, .hcard > img, .legend__icon, .dtrio__about > img {
  display: block; flex: none;
  width: var(--icon-sq); height: var(--icon-sq);
  padding: var(--icon-pad);
  background: var(--navy);
  border-radius: var(--icon-radius);
}
/* THE OPTICAL TOP, NOT THE BOX TOP, AND IT CANNOT BE ONE NUMBER.
   A heading's line box carries leading above its cap, so a square aligned to
   the box top reads as sitting high against the letters beside it. The offset
   is (line-height - font-size x cap-ratio) / 2, and these six components put
   the square beside six different heading steps - so one value would be right
   for one of them and wrong for the rest. Each sets its own, and the icon
   reads it:

     home card      --t-h5 21/29, Playfair .7125   -> 7
     practice card  --t-h4 27/35                    -> 8
     human card     --t-h5 21 on 1.3 (uppercase Lato .7165) -> 6
     legend key     --eyebrow 11.7 on --lh-tight    -> 3
     about-practice --t-h2 43/52                    -> 11

   The tile has no heading beside its square - the title is Lato at 14 - so it
   keeps the default. */
.card__badge, .tile__icon, .hcard > img, .legend__icon, .dtrio__about > img { margin-top: var(--icon-cap, 3px); }
.card__body   { --icon-cap:  7px; }
/* the practice card's 8px went with its 27px heading: one card, one heading step, one offset */
.hcard        { --icon-cap:  6px; }
.legend__item { --icon-cap:  3px; }
.dtrio__about { --icon-cap: 11px; }
/* Home's badge holds an inline glyph rather than a file, so it centres a child
   instead of insetting an image. Same box, same 12 of air around the art. */
.card__badge { padding: 0; display: flex; align-items: center; justify-content: center; }
.card__badge svg { width: var(--icon-art); height: var(--icon-art); stroke: var(--gold); }
/* ...but a badge that IS the file insets it, like every other icon square.
   The padding: 0 above was written for home's inline glyph, which sizes its
   own svg child - and an <img class="card__badge"> took it too, so the five
   practice cards drew their artwork at the full 48 while .tile__icon three
   sections below drew THE SAME FILE at 32. One component, one file, two sizes,
   against "same size everywhere, identical icon scale and padding". Measured
   on 2026-09-12 at 4x: the practice ring reached the square's corner radius
   and the challenge ring sat 8px inside it. */
img.card__badge, .tile__icon, .dtrio__about > img { padding: var(--icon-pad-file); }

/* AND A FILE TRIMMED TO ITS INK NEEDS A BIGGER BOX THAN A RINGED DRAWING DID.
   --icon-pad 8 was measured against artwork that carried its own margin: a
   gold ring at 92% of a 96 viewBox, with the figure only ~58% of it. The
   replacement files are trimmed to the drawing itself, so 8 of CSS inset on
   top of no drawn margin set a 2:1 icon - the three people and the arrow, the
   two figures and the chip, the bars and the tick - at 32 wide by 15 tall in a
   48 square, and they read as lost in it while the squarer icons beside them
   looked right. 4 gives the set a 40 box, which puts a square icon at the 43
   the old ring occupied and lifts the wide ones by a quarter.

   This is the cost of a bounding-box normalisation and it is the honest one: a
   2:1 drawing in a square frame cannot carry the same mass as a 1:1 drawing
   without being allowed out of the frame. Normalising on ink AREA instead was
   measured and is worse - it shrinks the squarer icons to 28 to match the wide
   ones rather than lifting the wide ones, because the box is the binding
   constraint either way. */
/* ===========================================================================
   THE SQUARE SITS ON THE HEADING'S ROW (2026-09-12, Jacob, from a three-up
   comparison: "those icons need to go on the same row as the header ... like
   the middle option"). SQUARE, the middle of CENTERED CIRCLE / SQUARE /
   RECTANGLE.

   One row: the square in column 1, the heading beside it in column 2, both
   vertically centred against each other. Everything under them - description, tags, link, rule - spans the pair and starts back at the card's own left
   edge, which is the square's left edge. That is what the reference draws: the
   description is NOT indented under the heading.

   THREE CARDS HAD TO GIVE THEIR HEADING UP FIRST. .hcard, .legend__item and
   .dtrio__about wrapped heading and description together in one div, so the
   heading could not share a row with anything - the whole block sat beside the
   square. The heading is a sibling now and the div holds the rest. No copy
   moved.

   :where() on the spanning rule, deliberately: it contributes no specificity, so the two placement rules under it win on being more specific rather than
   on source order, and a component can still place a child of its own. */
:where(.card__body, .hcard, .legend__item, .dtrio__about) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--icon-gap-side);
  /* START, NOT CENTER (2026-09-12, Jacob: "the icons should be top aligned").
     Centred, the square sat halfway down whatever its heading happened to be,
     so three cards in a row put their icons at three different heights - a
     four-line heading dropped one of them 35px below its neighbour. Top-
     aligned they agree with each other whatever the headings do. */
  align-items: start;
}
:where(.card__body, .hcard, .legend__item, .dtrio__about) > * { grid-column: 1 / -1; }
/* column 1: the square. No grid-row, so a card with a photograph above places
   it in row 2 by ordinary auto-placement rather than colliding with the art. */
.card__badge, .hcard > img, .legend__icon, .dtrio__about > img { grid-column: 1; }
/* column 2: the heading, whichever element each card uses for it.

   CENTRED ON THE SQUARE (2026-09-12, Jacob: "the titles next to the icons
   should be vertically centered"). The row is start-aligned so the squares
   agree with each other across a row of cards - that is the 2026-09-12
   instruction above and it stays - but a ONE-LINE heading is 27px against a
   48px square, so start-aligning it too left it sitting 16px above the
   square's centre. align-self on the heading alone centres it without moving
   the square: the square keeps its place at the top of the row, and a heading
   TALLER than the square still fills the row, so nothing changes where the
   heading is two or more lines. */
.card__body > h3, .hcard > h3, .legend__item > .eyebrow,
.dtrio__about > h2 { grid-column: 2; align-self: center; }
/* ONE ROW NEEDS A ROW'S WORTH OF WIDTH, and below 1080 these cards do not have
   it. The square takes 60 plus a 13 gutter out of the heading's column, and on
   the three-up grids that is the difference between a heading and a stack of
   single words: check:layout failed "AI-Enabled Performance" at 7.0 characters
   over three lines in a 134px box, the challenge tiles at 9.3 over four in
   102px, and the detail pages' "About this practice" with them.

   So the square goes back above the heading below 1080 and the heading takes
   the whole card. THAT IS A BREAKPOINT CHANGING STRUCTURE, which is what
   breakpoints are for - and the treatment either side of it is the same
   square, the same size, the same inset. Only its place in the card moves. */
/* 1080 -> 1179 (2026-09-12). The content column is 1088 at every viewport of
   1180 and up - the shell caps there and the gutter caps at 979 - and it
   SHRINKS below it. So 1080 left a band from 1081 to 1179 where the square was
   still beside the heading in a column that had started closing: at 1100 the
   heading had 216px and check:layout caught Workforce & Operating Model
   Transformation on three lines of 14 characters. Side by side only where the
   column is at full width. */
@media (max-width: 1179px) {
  :where(.card__body, .hcard, .legend__item, .dtrio__about) {
    grid-template-columns: minmax(0, 1fr);
  }
  .card__badge, .hcard > img, .legend__icon, .dtrio__about > img { grid-column: 1 / -1; }
/* The heading takes the full width and the gap the row used to give it
     sideways, now above it. */
  .card__body > h3, .hcard > h3, .legend__item > .eyebrow, .dtrio__about > h2 {
    grid-column: 1 / -1; margin-top: var(--icon-gap-below);
  }
  /* The tile keeps its title and arrow together and drops the square above. */
  .tile { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--icon-gap-below); }
  .tile__icon { grid-column: 1 / -1; }
}
/* A photograph the client has not supplied. Correctly sized, and it names
   the file that is missing rather than standing in for it. */
.missing {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: calc(4 * var(--u)); text-align: center; padding: calc(10 * var(--u));
  background: rgba(4, 35, 77, .05);
  border: var(--hairline) dashed rgba(4, 35, 77, .25);
  color: var(--ink-muted);
  font-size: calc(13 * var(--u)); line-height: 1.4;
}
.missing b { color: var(--ink); font-size: calc(13 * var(--u)); }
/* In a hero the panel is absolutely positioned and sized by the section, so
   the placeholder has to fill it the way the <img> does - otherwise it
   collapses to a line of grey text in the corner. And the ground is navy, so
   the navy-tinted box and border above are invisible on it. */
.dhero__art .missing {
  width: 100%; height: 100%;
  background: rgba(248, 249, 250, .06);
  border-color: rgba(248, 249, 250, .28);
  color: var(--on-navy-muted);
}
.dhero__art .missing b { color: var(--on-navy); }

/* =========================== 1. Header  y0-57 =========================== */
.site-head {
  background: var(--tone-soft);
  border-bottom: 1px solid var(--tone-edge);
}
.site-head .wrap { height: var(--head-h); display: flex; align-items: center; }
.brand span { white-space: nowrap; }
.brand { display: flex; align-items: center; gap: calc(14 * var(--u)); }
.brand img { height: calc(45 * var(--u)); width: auto; }   /* mark y18-45 -> 45 */
.brand span {
  font-family: var(--serif);
  font-size: var(--wordmark);
  letter-spacing: .015em;
  color: var(--navy);
  white-space: nowrap;
}
.site-nav { display: flex; gap: calc(28 * var(--u)); margin-left: calc(40 * var(--u)); font-size: var(--nav); }
.site-nav a { white-space: nowrap; }
/* never let a nav item break in two */

/* THE FIFTH NAV ITEM, AND THE 80px IT DOES NOT FIT IN (2026-09-10).
   Adding Services made five. Measured: brand 235 + nav 459 + button 203 + the
   wrap's 43 of padding = 940, so the header fits from 980 up and overflowed
   the button off the right edge at every width from 901 to 979 - on all 15
   pages, which is what check:widths caught.

   The nav is hidden at 900 and below, so that band is the whole problem: 79px
   wide, above the collapse point and below where five items fit.

   THE FIRST FIX WAS WRONG AND IS WORTH RECORDING. Tightening the gap and the
   margin across 901-980 made the header FIT - check:widths went green on all
   18 pages - and produced a header where "THE TAFT GROUP" printed on top of
   "Capabilities" and "About" printed on top of the button. Nothing overflowed, so check:widths passed; the boxes do not overlap either, because a flex item
   overflowing its own content box is not two boxes intersecting, so
   check:layout passed too. It was caught by looking at a render at 940.

   Five items, this wordmark and a 203px button do not fit under 980. Squeezing
   the space between them only moves the failure somewhere no check is looking.

   So the nav collapses at 980 instead of 900. That is a breakpoint changing
   STRUCTURE, which is what breakpoints are for, and it is honest about the
   real constraint.

   COLLAPSING AT 980 WAS ALSO WRONG, and measuring properly is what showed it.
   scrollWidth said the header "fitted" at 980; the actual gaps between the
   three items said otherwise. Measured brand-to-nav and nav-to-button across
   the range: with five items the header does not clear until 1080. Four items
   cleared at 1000 with 14px to spare, so the fifth costs about 78px and there
   were only 14 going spare. This is a regression I introduced, not one I
   uncovered.

   Collapsing at 1080 would have taken the nav off 1024 laptops and iPad
   landscape, which is not a trade worth making for a nav item.

   SO THE WORDMARK YIELDS INSTEAD, and only in the band where the nav is
   visible and space is short. Below 901 the nav is hidden, the header is
   monogram + button, and the wordmark has all the room it needs - so it stays
   there. Between 901 and 1100 the monogram carries the brand on its own, which
   is what it is for, and every nav item and the CTA keep their space. Above
   1100 nothing changes.

   Below 901 the nav is reached through the menu toggle (2026-09-14; see
   .navtoggle in the 900 block). This note used to say the site had none. */
@media (min-width: 901px) and (max-width: 1100px) {
  .site-head .brand span { display: none; }
}
.site-nav a { color: var(--navy); }
.site-nav a:hover { color: var(--gold); }
.site-head .btn { margin-left: auto; }
.navtoggle { display: none; }   /* the phone menu button - see the 900 block */
/* Above 900 the menu box is transparent to layout: nav and CTA stay flex items
   of the header row. It becomes the phone panel in the 900 block. */
.site-menu { display: contents; }

/* /contact carries no header CTA (2026-09-14): it would link to the page it
   is on. Every other page keeps the secondary, per rule 7. */

/* ============================ 2. Hero  y59-419 ========================== */
/* NO GROUND OF ITS OWN. It carried background: var(--paper) and, being 370
   lines below .sec--navy at identical specificity, beat it - so the section
   was navy in the markup and ivory in the render. Invisible at full width
   because the photograph covers it, and obvious at 390 where the photograph
   used to be hidden: an --on-navy headline on ivory paper. The class supplies
   the ground now, like every other navy section. */
.hero { position: relative; overflow: hidden; }
/* A HERO PAIRS TWO ACTIONS, SO THE PRIMARY STAYS FILLED (2026-09-12).
   The navy rule turns every .btn--primary into the gold outline, because solid
   navy on navy is invisible - written for a band carrying ONE button, and
   correct there. This hero carries a primary AND a secondary, and the flip made
   them the same gold outline side by side: no hierarchy at all between
   "Explore Our Capabilities" and "View the Experience". Filled gold with white
   text satisfies the rule the flip exists for - nothing navy on navy - and
   keeps the two apart. It is not a new treatment either: it is exactly what
   .btn--secondary:hover already paints.

   Inverts on hover, which mirrors the secondary filling on hover, so the pair
   swap appearance rather than both going gold.

   NOTE, not fixed here: /ai-transformation's navy hero has the same primary +
   secondary pair and therefore the same two identical buttons. Scoped to .hero
   deliberately rather than changed site-wide, which would also repaint
   /capabilities' single-button navy band. */
.hero.sec--navy {
  --btn-p-bg: var(--gold);   --btn-p-fg: #FFFFFF;       --btn-p-bd: var(--gold);
  --btn-p-bg-h: transparent; --btn-p-fg-h: var(--gold); --btn-p-bd-h: var(--gold);
}
/* COPY ABOVE ARTWORK, four heroes (2026-09-12). Each of these sections paints
   a photograph or a brand mark behind its own copy, and the copy has to sit
   above it - see the .dhero note about what happens when the lift goes on the
   wrong element. Four identical copies before this. */
.hero .wrap, .ahero .wrap, .cintro .wrap, .dhero__copy {
  position: relative; z-index: 1;
}
/* The hero height, kept as a FLOOR on the section rather than as padding
   on the wrap: the rhythm class sets the space, this only stops a short hero
   collapsing under its own photograph.
   501.6 -> 430 (2026-09-11, Jacob: "shortened the hero ... so the first screen
   feels less oversized"). The drawn 602 build px is what it was; it is a
   literal now, for the same reason --head-h is. 430 is just above what the
   copy stack actually needs at --t-display 64 - 39 of flat rhythm, a two-line
   headline at 148, the rule, the four-line body and the button row come to
   about 425 - so the floor still does its job for the photograph and no longer
   holds open space nothing is using. */
.hero { min-height: 430px; }
.hero__copy { max-width: calc(560 * var(--u)); }
.hero h1 {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-display); line-height: var(--t-display-lh);
  letter-spacing: -.005em; color: var(--on-navy);   /* was --navy, on paper */
}
.hero .rule-short { margin-top: calc(14 * var(--u)); }        /* 17  -B(h1) 3.5   */
.hero__body {
  margin-top: calc(40 * var(--u));                            /* 48  -A(body) 8.5 */
  max-width: calc(409 * var(--u)); color: var(--on-navy-muted);   /* was --ink-body, on paper */
  /* SET BY THE MOCKUP'S OWN LINE BREAKS. The old 288/480 read x330 as the
     right edge, but x330 is where the HERO PHOTO starts, not where the copy
     ends; the ink is x41-268 = 228 -> 314 on the shell. Set to 314 the copy
     broke to five lines, because this Lato runs ~8% wider than the raster's
     face (SPEC "typefaces are inferred"). Solved against the four lines the
     mockup draws instead: the longest, "business performance by aligning
     leadership," needs 340, and line 2 pulls up "into" at 343. 341 renders
     the mockup's four lines; 409 x --s = 341. */
}
.hero__btns { margin-top: calc(36 * var(--u)); display: flex; gap: var(--btn-gap); }
.btn { white-space: nowrap; }  /* 43 -B(body) 7 */
/* THE COPY SITS ON THE PHOTOGRAPH (2026-09-12, Jacob: "i want to do the hero
   text on top of the hero image"). The photograph used to occupy the right of
   the shell and feather into the paper, with the copy beside it on paper; it is
   full bleed behind the copy now, and .hero takes .sec--navy so the ground
   under and around it is navy.

   THE SCRIM IS NOT DECORATION. The supplied frame is 2.25:1 with the four
   executives centre-right and the left third filled with daylight through the
   windows - the brightest part of the image is exactly where the copy goes.
   Ivory text straight onto it is unreadable, so a navy gradient carries the
   left and releases the right, where the people are. Measured against the
   frame rather than guessed: the scrim is heaviest to x38% and clears by x70%.

   The gradient is on the ART, not on .hero, so it paints between the
   photograph and .wrap - which already carries the z-index lift that the
   COPY ABOVE ARTWORK rule gives it. */
/* BACK ON THE SHELL (2026-09-12, Jacob: "can we bring the hero back to 1200?").
   It was full bleed for one pass, with the rule waived, because a neutral scrim
   held to the shell met the navy bands at the image's edge with a visible step.
   That is solved instead of traded now - see the scrim below - so the rule is
   un-waived and the photograph sits in the same 1180 column as the copy, like
   every other photograph on the site. The GROUND still goes edge to edge. */
.hero__art {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: min(100%, var(--wrap));
}
.hero__art img { width: 100%; height: 100%; object-fit: cover; object-position: 62% center; }
.hero__art::after {
  content: ""; position: absolute; inset: 0;
  /* TIGHTENED 2026-09-12 ("that blue scrim is too much"). It held .28 at 74%
     and .18 all the way to the right edge, so the whole photograph carried a
     blue wash. It now clears to FULLY TRANSPARENT by 78%, which leaves the
     right two-thirds - the four figures, the table, the room - in their own
     colour, and keeps the weight only where the copy is.

     Measured, because "without it" was the actual question and the answer is
     that it cannot come off: on the bare photograph the headline reads 1.00:1
     and so does the body - ivory type on a sunlit window is not low contrast,
     it is invisible. Half strength fails too, 2.65:1 on the headline, and it
     still fails with the body turned full white. This gradient is the least
     blue that clears AA: headline 5.74:1, body 4.62:1. */
  /* IT STARTS AS THE BAND AND TURNS NEUTRAL, which is what lets the photograph
     come back to the shell without a seam. The first stop is exactly --navy at
     .95, so where the image meets the navy band there is nothing to see; by 30%
     it is the near-black that keeps the photograph's own colour, and it clears
     to transparent by 78%.

     Measured at the edge, worst colour difference over five heights: this 17,
     an all-navy scrim 19, a flat neutral one 62. So the blend is not a
     compromise between the two - it is better than either, and its contrast is
     the best of the three at 9.12 on the headline and 6.10 on the body. */
  background: linear-gradient(95deg,
    rgba(4, 35, 77, .95)   0%,
    rgba(7, 24, 47, .93)  12%,
    rgba(10, 13, 17, .89) 30%,
    rgba(10, 13, 17, .70) 46%,
    rgba(10, 13, 17, .14) 63%,
    rgba(10, 13, 17, 0)   78%);
}
/* AND THE PLACEHOLDER FILLS IT THE SAME WAY, so a missing photograph occupies
   exactly the frame the real one will. Six copies before this. .dhero__art
   .missing keeps its own rule: it is on navy and repaints. */
.hero__art .missing, .card__art .missing, .cexp__art .missing, .case__art .missing {
  width: 100%; height: 100%;
}
/* ========================== 3. Stats  y422-568 ========================== */
/* NAVY, as it is on Experience: the same component gets the same treatment
   site-wide. Silhouettes differ by shape, not by re-styling shared parts
   (STYLE-GUIDE rule 1). THREE metrics here since 2026-09-03, four still on
   Experience - a different count, not a different treatment. */
/* Ground and rhythm both arrive from the markup: .sec--navy .sec--evidence */
.stats__row { display: grid; grid-template-columns: repeat(3, 1fr); }
/* was 4: the Savings
                                            cell went 2026-09-03, leaving the three aggregates */
.stats__cell { text-align: center; padding-inline: calc(20 * var(--u)); }
.stats__cell + .stats__cell { border-left: var(--hairline) solid var(--rule-on-navy); }
.stats__n {
  font-family: var(--serif); font-size: var(--stat); line-height: var(--stat-lh);
  color: var(--gold);
}
.stats__u {                                    /* the gold underline */
  width: calc(97 * var(--u)); height: calc(3 * var(--u)); background: var(--gold); border: 0;  /* x112-169 = 58 -> 97 */
  margin: calc(11 * var(--u)) auto 0;                         /* was 14; 22 -B(stat) 8 */
}
/* 14 -> 11 above and 12 -> 10 below, with the numeral: the three gaps inside
   this cell were measured against a 50.8 numeral and read loose under a 45 one.
   The band is 23px shorter than it was, between this and --stat. */
.stats__l { margin-top: calc(10 * var(--u)); font-size: var(--label); line-height: 1.3; color: var(--on-navy); }
/* Optional. The home page carries no caption; /experience must, because the
   build reference says to retain the sentence explaining that the figures
   come from both Taft client engagements and transformation leadership
   experience, and not to imply all of them came from Taft contracts. */
/* margin-inline IS load-bearing here, and its absence was a live bug on
   /experience before /about grew a second caption line. "main p" caps every
   paragraph at --measure 680; a block-level p with that cap and no auto
   margin sits at the LEFT of the 1088 content column, so text-align:center
   centres it inside its own 680 box rather than under the row - the note sat
   204px left of the band centre at 1440. It only looks right below about 680
   of content width, which is why a phone render never showed it. */
.stats__note {
  margin-top: calc(24 * var(--u)); margin-inline: auto; text-align: center;   /* was 36 */
  font-size: var(--note); line-height: 1.5; color: var(--on-navy-muted);
}
/* /about adds a headline over the row and a fifth figure under it. Both are
   ADDITIONS to the component, not a restyle of it: the numerals, the gold
   underline, the label and the navy ground are the same on all three pages
   that carry it (STYLE-GUIDE rule 1). */
.stats__h {
  margin-top: 0;   /* was 15u, the About page's measured eyebrow -> h2 */
  margin-bottom: calc(28 * var(--u)); /* was 42, its measured eyebrow -> block; the
                                         statistics band is asked to come down and this
                                         is the largest gap in it (2026-09-12) */
  font-family: var(--serif); color: var(--on-navy)}
/* FOUR cells, /about only. The drawn 20 cell inset drops to 10 for one
   reason: "90% -> 99%" is the widest numeral on the site. At --stat it sets
   about 238, and a quarter of the 1088 content column is 272, which the drawn
   inset cuts to 239 - inside a pixel of overflowing. 10 leaves 252. */
.stats__row--4 { grid-template-columns: repeat(4, 1fr); }
.stats__row--4 .stats__cell { padding-inline: calc(10 * var(--u)); }
.stats__row--4 .stats__n { white-space: nowrap; }
/* Below 1240 the shell is the viewport, so the cell shrinks and that numeral
   stops fitting. Two up, then one. The base .stats__row is stacked at 900 by
   a rule EARLIER in this file, and at equal specificity the later rule wins -
   so this modifier has to restate it or it would hold four columns on a
   phone. */
@media (max-width: 1240px) {
  .stats__row--4 { grid-template-columns: repeat(2, 1fr); row-gap: calc(28 * var(--u)); }
  .stats__row--4 .stats__cell:nth-child(odd) { border-left: 0; }
}
@media (max-width: 900px) {
  .stats__row--4 { grid-template-columns: 1fr; row-gap: calc(24 * var(--u)); }
}
/* The fifth figure. It is a count of regions rather than a business metric -
   see the markup note on /about - so it takes the line under the row instead
   of a fifth gold numeral. Gold serif figure, navy ground, no new step. */
.stats__regions { margin-top: calc(24 * var(--u));   /* was 36 */ margin-inline: auto; text-align: center; }
.stats__regions b {
  display: block;
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h4); line-height: var(--t-h4-lh); color: var(--gold);
}
.stats__regions span {
  display: block; margin-top: calc(6 * var(--u));
  font-size: var(--label); line-height: 1.3; color: var(--on-navy);
}
.stats__regions + .stats__note { margin-top: calc(14 * var(--u)); }
/* ====================== 4. The transformation gap ======================= */
/* THE WHITE CENTRED BAND, five sections of it (2026-09-12). Rule 1's tonal
   sequence puts an elevated white band at these five points - two on home, two
   on capabilities, one on ai-transformation - and each declared the same pair
   for itself. Grouped, so the sequence is legible in one place instead of
   being reconstructed from five rules pages apart. A section that needs a
   different ground still says so on its own line below. */
.gap, .inter, .challenge {
  background: var(--tone-elevated); text-align: center;
}
/* The headline's drawn break holds down to 420. Below it, the forced break
   lands on top of the natural one and the two sentences rag into FOUR lines in
   a 326 column; letting them wrap on their own gives three and a better rag.
   No mobile mockup exists to contradict this (SPEC "Assumptions"). */
@media (max-width: 420px) { .gap h2 br { display: none; } }
.gap h2 {
  margin-top: 0;   /* was 17u, a gap measured from the eyebrow above it. THE EYEBROWS ARE
                      GONE FROM THIS PAGE (2026-09-12, Jacob), so this headline is the
                      section's first element and the rhythm token sets its distance. */
  font-family: var(--serif)}
/* The drawing sets a centred gold rule between the headline and the sentence.
   It was the one .rule-short on the page that never made it into the markup. */
.gap .rule-short { margin: calc(26 * var(--u)) auto 0; width: var(--rule-long-w); height: var(--rule-h); }
.gap__body {
  margin-top: calc(26 * var(--u));                            /* 47 -B(h2) 6.4 -A(body) 8.5 */
  margin-inline: auto; color: var(--ink-body);
  /* FULL PANEL, 2026-09-03 (Jacob): "lets give it the whole 1200px panel to
     stretch out... there are too many wraps there on desktop."
     Two caps had to go, not one. Its own was the measured ink, x288-575 = 288
     -> 480 build = 400 rendered, which put a 135-character sentence on THREE
     lines inside a 1088 panel. Dropping that alone only reached 680, because
     `main p` caps every paragraph at --measure site-wide. Hence `none`.
     THIS IS AN EXPLICIT EXCEPTION TO STYLE-GUIDE RULE 2, made on request, and
     the only one on the site. It is defensible here and nowhere else: the
     measure exists so the eye can find the start of the NEXT line, and at
     1200+ this paragraph has no next line - it is one centred line. Below
     1024 it wraps again and runs wider than 680 while doing so, which is the
     cost. Recorded in SPEC.md and in STYLE-GUIDE rule 2. */
  max-width: none;
}

/* ==================== 5. Connected capabilities ======================== */
.caps__head { text-align: center; }
.caps h2 {
  margin-top: 0;   /* was 1px, an eyebrow gap; the eyebrow is gone */
  font-family: var(--serif)}
.caps__rows { margin-top: calc(24 * var(--u)); display: grid; gap: calc(27 * var(--u)); }  /* row1 bottom 1059 -> row2 top 1075 = 16 -> 27 */
.caps__row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--card-gap); }
.caps__row--two { grid-template-columns: repeat(2, var(--card-w)); justify-content: center; }
/* RULE 3: none of these is clickable, so none is bordered. The photograph
   and the badge group each card; space separates them. */
/* THE BADGE WAS BEING CLIPPED IN HALF, at every width (found 2026-09-05).
   It is meant to straddle the photograph's bottom edge, and it did until
   `.card__art { overflow: hidden }
` arrived with the hover zoom - which clips
   every descendant whose containing block is inside it, the badge included.
   So the photo cut it off flat and the mark read as a semicircle.

   The escape is the containing block, not the clip: an absolutely positioned
   element is NOT clipped by an overflow ancestor that is not its containing
   block. Positioning moves to .card and the badge is offset from the card's
   top instead of the photo's bottom - the same place, measured from the other
   end. .card__art keeps its overflow, so the zoom still clips to the frame. */
/* A COLUMN, SO THE BODY CAN TAKE THE SLACK (2026-09-12, Jacob: "the explore
   buttons should all be aligned with each other at the bottom"). The cards are
   grid items and already stretch to the tallest in their row; what they did
   not do was pass that height down, so each button sat wherever its own copy
   ended - and with headings of two, three and four lines that was three
   different heights. */
.card { background: transparent; position: relative;
        display: flex; flex-direction: column; }
.card__body { flex: 1; }
.card__art { height: var(--card-img-h); }
/* A PHOTOGRAPH FILLS ITS FRAME - one rule, six frames (2026-09-12). This was
   written out six times, once per card or hero that holds a picture, and it is
   the same three declarations every time: fill the box the frame sets and crop
   to it rather than letter-boxing. The frames themselves still set their own
   heights, which is the part that genuinely differs. */
.card__art img, .card__art img, .cexp__art img, .case__art img, .dhero__art img {
  width: 100%; height: 100%; object-fit: cover;
}
/* .card__art .missing - see the grouped rule above */
/* IN FLOW, AND NO LONGER TOUCHING THE PHOTOGRAPH (2026-09-12). It was a navy
   CIRCLE absolutely positioned to straddle the photo's bottom edge, which is
   what the long note above is about - the clipping, the containing block, the
   1.3px of clearance the card's top padding had to preserve. All of that goes:
   "center the square horizontally within the card, above the heading" and "do
   not overlap the icon with the image or card edges". The badge moved out of
   .card__art in the markup, so .card__art keeps its overflow for the hover
   zoom and there is nothing left inside it to clip.
   The box, the radius and the inset come from the shared icon square. */
/* The image -> square gap is .card__body's padding-top now, because the badge
   sits inside that box rather than between it and the photograph. */
/* LESS PADDING INSIDE THE CARD (2026-09-11, Jacob). Three of the four sides
   moved and the top is the one that could not move freely: it is not air, it is
   the badge's clearance. The badge straddles the photo's bottom edge, so half
   of it hangs into the body - 36 of the old 72 - and the old 30.8 put the title
   cap 1.3px under the badge's lowest point. --circle-sm takes the badge to 60, so only 30 hangs in, and 25 of padding puts the cap at 176 against a badge
   ending at 171.7: 4px of clearance where there was 1.3.
   Sides 27.5 -> 21.7 and bottom 19.2 -> 13.3 are ordinary tightening. */
/* The top padding was the badge's clearance; the badge is above this box now, so it takes the shared square -> heading gap. */
.card__body { padding: var(--icon-gap-below) var(--card-pad) calc(16 * var(--u)); }
.card h3 {
  font-family: var(--serif); font-weight: 400;
  /* --t-h4 27 -> --t-h5 21 ("22px card heading"). 21 IS THE SCALE'S SMALL-TITLE
     STEP and 22 is not on the scale at all, so this takes the step rather than
     adding a seventh size - the same move the five-track block titles made.
     One pixel under what he asked for, and it buys the cleaner wrapping the
     reference is about: card 01's heading goes from four lines to two. */
  font-size: var(--t-h5); line-height: var(--t-h5-lh); color: var(--navy);
}
.card p {
  margin-top: var(--card-gap-desc);      /* was 7u; shared across icon cards */
  font-size: var(--body-sm); line-height: var(--body-sm-lh); color: var(--ink-body);
}
/* Row 3 takes the leftover height and the button sits at the bottom of it, so
   every card in a row puts its action on the same line. */
.card__body { grid-template-rows: auto auto 1fr; }
/* FULL WIDTH, AND FULL WIDTH OF THE COPY (2026-09-12, Jacob: "full width to
   the text and icon width, not the slightly large width of the image above").
   stretch, not start: the button is a grid item spanning both columns of
   .card__body, and that box IS the text column - the body is inset by
   --card-pad on each side while .card__art bleeds to the card edge. So filling
   its own grid area gives exactly the width he asked for and nothing has to
   know the image is wider. */
.card .btn { margin-top: var(--card-gap-link); justify-self: stretch; align-self: end; }
/* ===========================================================================
   ONE CARD, THREE PAGES (2026-09-12, Jacob)

   > those cards should be constructed the same way as the cards on the home
   > page ... these are all the same basic thing but all are different
   > components with different layouts and its hard to get them all aligned

   .card, .pcard and .xcard were three components for one thing: a photograph, an icon, a heading, a line of copy and an action. They had three heading
   steps, three sets of padding, three button treatments and three ways of
   stacking, which is why nothing lined up across pages. There is one now, and
   home's is the layout it took, because that is the one he named.

   THE SLOTS, all optional except the heading:
     .card__art     the photograph, full bleed to the card
     .card__badge   the navy icon square, column 1 of the heading's row
     h3             the heading
     .card__meta    a small gold fact line   (was .xcard__client)
     p              the description          (was .pcard__b, .xcard__metric)
     .card__tags    a row of terms           (was .tags)
     .btn           the action, full width, on the card's floor

   THE MODIFIERS carry the colour, which is the part he wanted kept per page:
     (none)         transparent, for a card on an ivory section     - home
     .card--panel   a white panel                                   - experience
     .card--boxed   a white panel with a hairline                   - capabilities

   WHAT CHANGED ON THE PAGE, deliberately: the capability and experience card
   headings come down from --t-h4 27 to --t-h5 21, which is home's step; their
   buttons go full width and sit on the card's floor; and the experience
   photograph now bleeds to the panel edge instead of sitting inset. Those are
   the three ways they differed from home, and aligning them was the ask. */
.card--panel { background: var(--tone-elevated); }
.card--boxed { background: var(--tone-elevated); border: var(--hairline) solid var(--rule); }
/* The wide card sets its photograph beside the body. Measured across card 01's
   interior: photo 316 of 738 (42.8%), a 16px gutter (2.2%), text 399. */

/* A small gold fact line, above the description. */
.card .card__meta {   /* .card p beats a bare .card__meta on specificity: the client
                       line came out ink instead of gold until this was scoped */
  margin-top: var(--card-gap-desc);
}
/* ===========================================================================
   THE CLIENT LINE, ONE ROLE AND ONE TREATMENT (2026-09-12, Jacob: "the sub
   text is in different colors... why?")

   The gold he asked about is correct and it stays: it is the attribution -
   whose engagement this is - and the description beside it is prose. Two
   different things, so two colours. What was wrong is that the SAME role had
   four treatments, which is why one column reading differently looked like a
   fault rather than a distinction:

     experience card       13.33  Lato      gold
     case study            20     PLAYFAIR  NAVY
     selected experience   17.50  Lato      gold
     detail rail           15.83  Lato      gold

   One treatment now: gold, Lato, --body-sm. --body-sm because it is the
   ladder's supporting-copy step and the attribution should sit WITH the copy
   it qualifies, not above or below it - at 13.33 it was smaller than the
   description under it, which inverts the hierarchy, and at 20 in Playfair it
   competed with its own 34px heading and read as a second headline.

   The case study's navy Playfair was the real inconsistency on the page he was
   looking at, one column over from the one he asked about. */
:is(.card .card__meta, .case__client, .cexp__sub, .dtrio__client) {
  font-family: var(--sans); font-weight: 400;
  font-size: var(--body-sm); line-height: var(--body-sm-lh);
  color: var(--gold);
}

/* A LIST OF TERMS, STACKED. It was a row divided by hairlines, with
   flex-wrap: wrap and a comment saying it "stacks on a phone" - and no rule
   ever did that, so it only ever wrapped. A wrapped divided row puts the
   border-left of the first term on a new line at the START of that line, so
   each wrapped line opened with a stray hairline: "Strategy & governance" then
   "| Leadership alignment" then "| Readiness & adoption".

   Latent while the cards were two columns wide and obvious the moment they
   became three (2026-09-12) - three terms average 340 of text and the card's
   body is about 350, so they wrap at every width this component is used at.
   A divided row that always wraps is not a divided row, so they stack, and the
   dividers go with the row that justified them. */
.card__tags {
  margin-top: calc(10 * var(--u));
  display: flex; flex-direction: column; align-items: flex-start;
  row-gap: calc(5 * var(--u));
  font-size: var(--body-xs); line-height: var(--lh-tight); color: var(--ink-body);
}

/* A card with no icon puts its heading across both columns rather than leaving
   column 1 empty - the experience cards have no icon. */
.card__body > h3:first-child { grid-column: 1 / -1; }

/* ====================== 6. Framework  y1299-1492 ======================== */
.frame { background: var(--paper-warm); text-align: center; }
.frame h2 {
  margin-top: 0;   /* was 2px, an eyebrow gap; the eyebrow is gone */
  font-family: var(--serif)}
/* ONE COMPACT HORIZONTAL BAND (2026-09-11, Jacob). It was already a
   horizontal row, but at a 72 circle with the label and its two-line
   description under it each step stood 144 tall and the six read as six
   stacks rather than as one band. --circle-sm takes 12 off the top, the three
   internal gaps take 6 more, and the arrow now derives its offset from the
   circle instead of carrying a measured 28.3 - so the connecting line runs
   dead through the centre of all six rings and the band reads as one
   horizontal move. The step width is unchanged: it is what leaves the arrows
   room to draw.
   THE DESCRIPTIONS STAY. They are the client's words (COPY-DECK section 3)
   and a label-only band would drop them; compacting is not the same as
   deleting his copy. */
.frame__row {
  margin-top: calc(16 * var(--u));                            /* was 20; 27 -B(h2) 7.4 */
  display: flex; align-items: flex-start; justify-content: center;
}
/* 160 -> 176 (2026-09-12). The step text went from 11.67 to 17.5, and at 160
   the description had 117px to set 38 characters in - about 11 a line. The
   arrows give the width up: six steps at 176 leave 5 x 42 between them, which
   still draws a connector. */
.frame__step { width: calc(192 * var(--u)); flex: none; }
.frame__circle {
  width: var(--frame-ring); height: var(--frame-ring); margin-inline: auto;
  border: var(--hairline) solid var(--gold); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
/* The glyph keeps the ring's proportion: it was 30 in a 60 ring, so 36 in 72.
   The width/height attributes in the markup stay 30 - they are the intrinsic
   size and the aspect ratio; this sizes the box. */
.frame__circle svg { width: 36px; height: 36px; stroke: var(--gold); }
/* The 01-06 numerals were removed at the client's request (Michael Blanchard, 2026-09-08). The label now takes the numeral's measured gap from the circle
   -- 6px, not its own 4px -- so the step keeps the distance the mockup drew
   between the ring and the first line of text under it. The Capabilities
   page's copy of this framework never carried numerals, so the two now agree. */
/* THE LABEL AND THE DESCRIPTION ARE ONE SIZE NOW (2026-09-12, Jacob: "that
   step text (clarify outcomes, etc...) is just way to small. nothing on the
   site should be that small. have that be the same size").

   The description was calc(14 * var(--u)) = 11.67, which is --eyebrow - the
   smallest step on the site, and it exists for uppercase labels rather than
   for sentence-case copy. The label was --body-sm 15.83. Both take --body 17.5,
   so the step reads as one block: a name and a line about it, at the size the
   rest of the site uses for a line of copy. */
.frame__l { margin-top: calc(5 * var(--u)); font-size: var(--body-sm); line-height: var(--lh-tight); color: var(--navy); }
.frame__d {
  margin-top: calc(6 * var(--u)); max-width: 100%; margin-inline: auto;   /* was 140u against a 160u step; the
                                                    step sets the measure now */
  font-size: var(--body-sm); line-height: var(--lh-lede); color: var(--ink-muted);   /* was 14u = 11.67, the eyebrow step */
}
/* DERIVED, not measured: the arrow sits on the circle's horizontal centre
   line, less half the hairline it is drawn with. It was a measured 28.3
   against a 72 circle, which was 7.7 above that centre and is the reason the
   connecting line used to read as slightly high. Now it follows --circle-sm
   wherever that goes. */
.frame__arrow { flex: 1; display: flex; align-items: center; color: var(--gold);
                margin-top: calc(var(--frame-ring) / 2 - var(--hairline) / 2); }
.frame__arrow i { flex: 1; height: var(--hairline); background: var(--gold); }
.frame__arrow svg { flex: none; }
/* SIX ACROSS ONLY WHERE SIX FIT (2026-09-12). The content column is 1088 at
   1180 and above and shrinks below it: at 901 it is 816, and six steps at
   176u are 880 before a single arrow is drawn - check:widths caught
   scrollWidth +9 and check:layout the description at 12.7 characters a line.
   So the band becomes two rows of three between 901 and 1179, where each step
   gets a third of the column instead of a sixth and the text sets properly.
   The arrows go: they connect a sequence along a line, and there are two lines
   here. Below 901 it is already one column.

   A breakpoint changing STRUCTURE rather than size, which is what breakpoints
   are for - the ring, the label and the description are the same at every
   width. "One compact horizontal band" (2026-09-11) is what 1180 and up
   still does. */
@media (min-width: 901px) and (max-width: 1179px) {
  .frame__row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
                row-gap: calc(34 * var(--u)); justify-items: center; }
  .frame__arrow { display: none; }
  .frame__step { width: 100%; max-width: calc(230 * var(--u)); }
}
/* =============== 7. Selected experience (navy)  y1495-1645 ============== */
/* THE BAND UNSTACKED SIDEWAYS (2026-09-11, Jacob: "consolidated the
   selected-experience metrics into a cleaner four-column row").

   It was two columns - 520fr of headline beside 716fr holding all four metrics
   - and that 716 track is what every awkward thing about this band came from.
   Four cells inside it were 149 wide, which is narrower than the labels they
   hold: the two notes below record the cell inset going 10 -> 6 -> 2 chasing
   "Direct Taft Engagement" and then "National Healthcare" onto the lines they
   are written for, and an authored <br> being overridden anyway.

   Head above, row below, and the row gets the whole content column: the cells
   are 267 now instead of 149, so the labels set on their own line with room to
   spare and the inset goes back to a normal 13.3. The headline also moves off
   the h3 step onto --t-h2 with every other section headline on the page, which
   is the second half of the same instruction - it could not sit at 48 in a 520
   track, and in 717 it sets on the two lines it is written for. */
.exp .wrap { text-align: center; }
/* 860 -> 1000 build (717 -> 833 rendered). At 717 this headline set on THREE
   lines at --t-h2 and the band came out 58px taller than the two-column one it
   replaced - which is the opposite of the point. 833 holds "Experience where
   transformation" on line one and breaks where the sentence does. */
.exp__head { max-width: calc(1000 * var(--u)); margin-inline: auto; }
.exp h2 {
  margin-top: 0;   /* was 2px, an eyebrow gap; the eyebrow is gone */
  font-family: var(--serif); color: var(--on-navy)}
.exp .btn { margin-top: calc(19 * var(--u)); }   /* 28 -B(h2-navy) 9.25 */
.exp__items { margin-top: calc(34 * var(--u)); display: grid; grid-template-columns: repeat(4, 1fr); }
.exp__item { text-align: center; padding-inline: calc(16 * var(--u)); }
/* THE INSET HISTORY, AND WHY IT IS OVER. 10 -> 6: at 10 the 162px column could
   not hold "Direct Taft Engagement" on the one line the mockup sets it on.
   6 -> 2, for the same reason one composition later, when the items column
   became 149 rather than 162 - at 6 the label box was 137 where "National
   Healthcare" needs 140, so the authored <br> was overridden and it set on
   THREE lines. 2 gave the box 145.7.
   2 -> 16 (2026-09-11): the four-column row above is full width now, so the
   cell is 267 and the inset stopped being load-bearing. It was never a
   measurement, only a normalization, and it is now the normalization it always
   claimed to be rather than a number defending a line break. */
.exp__item + .exp__item { border-left: var(--hairline) solid var(--rule-on-navy); }
/* THE FOUR MARKS ARE FILES NOW (2026-09-12, Jacob supplied replacements).
   They were inline SVG at 30x30 taking their stroke from this rule; the new
   art is gold already - 1254px PNG, zero non-gold pixels above alpha 200 -
   so nothing recolours it and nothing needs to.

   60, NOT 30, AND THAT IS THE RING'S DOING. The superseded glyphs were bare
   figures; each of these is a figure inside a gold ring, which is the same
   device .astep__c and the capability hero use. At 30 the ring would be the
   whole icon and the figure inside it about 17px. --circle-sm is 60 - the
   token whose own comment reads "a circle in a compact band", which is what
   this band is - and it puts the figure back at about 34, near the 30 the bare
   glyphs had. No new number.

   Trimmed to their ink and squared before scaling, so all four rings are the
   same diameter: the supplied canvases framed them from 1053 to 1127 of 1254. */
.exp__item img {
  display: block; width: var(--circle-sm); height: var(--circle-sm);
  margin-inline: auto;
}
/* TWO LINES OF SPACE WHETHER THE LABEL USES THEM OR NOT. Three of the four
   labels set on two lines and "Major U.S. Utility" on one, so in a four-column
   row the metric under it sat 20px higher than the other three and the row read
   as ragged rather than as a row. min-height, not a subgrid: it is two lines of
   this element's own leading, so it follows the label's size wherever that
   goes, and it needs no support story. */
.exp__t { margin-top: calc(18 * var(--u)); font-size: calc(19 * var(--u)); line-height: 1.3;
          min-height: 2.6em; color: var(--on-navy); }  /* icon bottom -> label 22 */
.exp__m { margin-top: calc(12 * var(--u)); font-size: var(--note); line-height: 1.3; color: var(--on-navy-muted); }
.exp__m b { font-family: var(--serif); font-weight: 400; font-size: calc(23 * var(--u)); color: var(--on-navy); }  /* cap 10 -> 16.7 */

/* =========================== 8. CTA  y1646-1764 ========================= */
.cta { text-align: center; }
.cta .rule-short { margin-inline: auto; }
.cta h2 {
  margin-top: calc(10 * var(--u));                            /* 20 -A(h2-cta) 9.7 */
  font-family: var(--serif)}
/* .cta .btn's own 20u is gone (2026-09-12). Home's closing block was never in
   the grouped rule above - it is .cta, and the group named the other five - so
   it kept its own gap and was one of the three that did not move when the
   token changed. It is in the group now. */
/* 25 -B(h2-cta) 5 */

/* ===========================================================================
   THE INTERIOR CLOSING BLOCK, DEFINED ONCE (2026-09-12)

   Five sections, one shape: `.ccta` (capabilities, services), `.xcta`
   (experience), `.about-cta`, `.cclose` (contact) and `.dcta` (all nine detail
   pages). Each had its own copy of this block and its own gaps - see the token
   note above for the five sets of numbers - which is why the same closing
   section measured 342, 366, 323, 476 and 292 on five pages that are meant to
   read as the same moment.

   WHAT IS SHARED IS THE SPACING, NOT THE CONTENT. They hold different things
   and still do: `.ccta` and `.xcta` open on their headline with no rule, `.cclose` carries no button because the page it closes IS the contact form, and `.about-cta` runs to two paragraphs. So this sets the gaps BETWEEN
   whatever is there and nothing else, and the heights still differ by what
   each one holds - which is the honest version of "about 260".

   `.cta` on home keeps its own rules: it is the one closing block on a page
   with a different rhythm and a different headline size, and it was signed off
   a day before these. It is the obvious next thing to fold in here.
   =========================================================================== */
.cta, .ccta, .xcta, .about-cta, .cclose, .dcta { text-align: center; }
:is(.cta, .ccta, .xcta, .about-cta, .cclose, .dcta) .rule-short {
  margin-inline: auto; width: var(--rule-long-w); height: var(--rule-h);
}
/* margin-top: 0 by default, because two of the five have no rule above the
   headline - a blanket gap there would push their headline off the section's
   own padding instead of away from a rule that is not present. */
:is(.cta, .ccta, .xcta, .about-cta, .cclose, .dcta) h2 {
  margin-top: 0}
:is(.cta, .ccta, .xcta, .about-cta, .cclose, .dcta) .rule-short + h2 {
  margin-top: var(--cta-gap-head);
}
/* The supporting line. --body-sm is the ladder's "supporting copy" step, and
   it replaces three sizes: .ccta's 14.2, which was off the ladder entirely, .about-cta's 15.8, which was already this, and .dcta's 17.5, which steps
   down one. */
.ccta__body, .about-cta p, .dcta p {
  margin-top: var(--cta-gap-body); margin-inline: auto; max-width: var(--measure-lede);
  font-size: var(--body-sm); line-height: var(--body-sm-lh); color: var(--ink-body);
}
.about-cta p + p { margin-top: var(--ed-gap-para); }
/* The system button height, not four measured ones. Two of the five were under
   the 44px tap floor at 34.2 and 36.7; --btn-h is 41.7 on the shell and ramps
   to 54 on a phone, which is the height every other button on the site has. */
:is(.cta, .ccta, .xcta, .about-cta, .cclose, .dcta) .btn {
  margin-top: var(--cta-gap-btn); height: var(--btn-h);
}
/* ========================== 9. Footer  y1765-1820 ======================= */
/* 18.3/21.7 -> 13.3/15 (2026-09-11, Jacob: "reduced the height of the closing
   CTA and footer"). The CTA's share of that is the flat rhythm token; the
   footer sets its own padding, so it is this line. The row inside is 37.5 tall
   - the reversed mark - which puts the bar at 66 against the 77 it was, and
   still leaves it taller than half the 72 header above it. */
.site-foot { background: var(--navy); color: var(--on-navy-muted); padding-top: calc(16 * var(--u)); padding-bottom: calc(18 * var(--u)); }
.site-foot .wrap { display: flex; align-items: center; gap: calc(40 * var(--u));
  /* WRAPS AT EVERY WIDTH, not from a breakpoint. The row is three nowrap
     items - brand, links, copyright - and wrapping only switched on at 900,
     so between about 901 and 945 there was nowhere for the copyright to go
     and it pushed ~10px past the viewport on EVERY page. Letting the row wrap
     always costs nothing above that (it fits, so nothing moves) and removes
     the magic number instead of nudging it. */
  flex-wrap: wrap; }
.site-foot .brand span { color: var(--on-navy); font-size: calc(23 * var(--u)); }
.site-foot .brand img {
  height: calc(45 * var(--u));                 /* footer mark y1778-1804 = 27 -> 45 */
  /* NO FILTER. The footer now carries the real reversed lockup
     (taft-monogram-reversed.png, ivory #F7F3EC + gold #C6922F, delivered
     2026-09-01), so the gold survives. The stand-in this replaces was
     `filter: brightness(0) invert(1)`, which made the mark legible by
     throwing the gold away - the one thing the AI Transformation build
     reference explicitly forbids. The HEADER keeps the navy+gold mark: it
     sits on paper, where that is the correct artwork. */
}
.site-foot__links { display: flex; align-items: center; gap: calc(16 * var(--u)); margin-left: calc(40 * var(--u)); font-size: var(--note); }
.site-foot__links a { white-space: nowrap; }
.site-foot__links a { color: var(--on-navy-muted); }
.site-foot__links a:hover { color: var(--gold); }
.site-foot__links i { width: var(--hairline); height: calc(16 * var(--u)); background: var(--rule-on-navy); }
.site-foot__copy { margin-left: auto; font-size: calc(14 * var(--u)); color: var(--on-navy-muted); white-space: nowrap; }

/* =========================================================================
   Responsive
   NO mobile or tablet mockup was supplied - see SPEC.md "Assumptions".
   Everything below 1440 stacks rather than reflowing into a layout the
   mockup does not show.
   ========================================================================= */
@media (max-width: 1240px) {
/* `--card-w: calc(300 * var(--u))` was here and is gone (2026-09-11). It is
     read by ONE rule - the two centred cards in the second capability row - and
     its job is to make those two sit on the same track as the three above them.
     The base value does that at any width, because it is a percentage of the
     content column; a fixed 250 did it only at the width it was written for, and between 1180 and 1240 it put two 250 cards under three 343 ones. The
     row is "three then two centred" at every width now, which is the whole
     point of the arrangement. */
  /* `.exp .wrap { grid-template-columns: 1fr }
` lived here to unstack the
     headline from the metrics. The band is head-above-row at every width now
     (see section 7), so there is no two-column state left to undo. */
  /* opacity .3 removed 2026-09-07 - it ghosted the whole photograph to solve
     what the fade was doing to one figure. The fade and the crop solve it now, so the image stays at full strength here. 52% of the viewport and 52% of
     --shell-w are the same number below 1200, so this only trims the 1200-1240
     sliver where the shell has stopped growing. */
/* .hero__art's 52% is gone with the side-by-side layout: it is inset: 0 now. */
/* .site-nav's margin-left and gap were restated here identically to the base
     rule, so this block did nothing for the nav - except win on source order
     and silently defeat the 901-980 ramp above, which is how it was found.
     Removed; the base rule already sets both. */
  .site-foot__links { margin-left: calc(40 * var(--u)); }
}

@media (max-width: 900px) {
  :root {
    /* EVERY TOKEN HERE IS A RAMP, not a mobile value - see --pw above. The
       left-hand number is what the phone renders and is unchanged; the comment
       on the right is what it reaches by 900, which is exactly what 901
       renders. Nothing moves across the breakpoint any more. */
    /* RE-ENDED for the 2026-09-11 shorter stats band: the shell now renders
       45/50, so these ramp to 45/50. The phone end is unchanged - 40 was
       already the floor and the band is one column down here, so there was
       nothing to reclaim. */
    --stat:        calc(40px + 5 * var(--pw) / 510);         /* 40   -> 45      */
    --stat-lh:     calc(46px + 4 * var(--pw) / 510);         /* 46   -> 50      */
    /* TYPE PASS 2026-09-05, Jacob, on the phone build: body copy, card titles
       and buttons all up a step, and the eyebrow up two.

       BODY 18 -> 19 and BODY-SM 17 -> 17.5. The mobile ladder had collapsed to
       a 5.9% step between them (18/17) where the shell runs 10.8% (17.5/15.8),
       so the two roles read as one size set slightly wrong. 19/17.5 is 8.6%
       and both are legible at arm's length. --body carries the hero ledes, so
       "enlarge the hero body copy" is this line and not a hero override.

       Those are still the numbers, and they are still the numbers AT 390. */
    --body:        calc(19px - 1.5007 * var(--pw) / 510);    /* 19   -> 17.4993 */
    --body-lh:     calc(29px - 4.001 * var(--pw) / 510);     /* 29   -> 24.999  */
    --body-sm:     calc(17.5px - 1.6673 * var(--pw) / 510);  /* 17.5 -> 15.8327 */
    --body-sm-lh:  calc(26px - 3.5009 * var(--pw) / 510);    /* 26   -> 22.4991 */
    /* THE EYEBROW: 14 -> 16, and .18em -> .10em. Uppercase carries no
       ascender or descender to read height from, so it undersells its own
       size; tracked at .18em on a 342px column it also stopped reading as a
       word. .10em is not a new number - it is the tracking .astep__l and the
       .dstats labels already use, so this is the file's second tracked step,
       not a third. The shell keeps 14/.18em: it is 11.7 rendered there, on a
       1088 column, and it works. */
    --eyebrow:     calc(16px - 4.3338 * var(--pw) / 510);    /* 16   -> 11.6662 */
    /* THE ONE THING ON THIS SITE THAT STILL STEPS AT 900, and it stays that
       way deliberately. Tracking is a RATIO, not a size: `.eyebrow` does not
       always render at --eyebrow - /capabilities/* overrides it to 21 - and
       .10em is what follows the label to whatever size it lands on. Ramping it
       needs a unitless factor times an em, which is the one arithmetic CSS
       will not do (see --u above), and pinning it to px instead broke exactly
       that case: the 21px eyebrow went 2.1px -> 1.6px and re-wrapped the page
       under it, 32px shorter at 320. Measured, reverted, left alone.

       What actually steps here is .10em -> .18em on one uppercase label - half
       a pixel of tracking. The sweep does not measure letter-spacing, and no
       size moves with it. */
    --eyebrow-track: .10em;
    /* Buttons: 16 -> 17 with the box 50 -> 54. A 50px control clears the 44
       tap-target floor but reads small beside 19px body copy. */
    --btn:         calc(17px - 3.6672 * var(--pw) / 510);    /* 17   -> 13.3328 */
    --btn-h:       calc(54px - 12.335 * var(--pw) / 510);    /* 54   -> 41.665  */
    --head-h:      calc(68px + 4 * var(--pw) / 510);         /* 68   -> 72      */
    /* The scale's mobile step. Redefining the tokens here is what makes
       "nothing else sets a heading size" true at every width - the per-section
       overrides this replaced had drifted into their own scale. Literal at
       both ends, so these ramp between two literals and never touch --u. */
    /* display re-ended for the 2026-09-11 scale change (70 -> 64). Its phone
       end is unchanged: 40 is what a 390 column holds and was not chosen from
       the shell value.

       H1 AND H2 RE-ENDED AND RE-FLOORED for 2026-09-12, to his mobile
       instruction - "40-44px H1s, 32-36px H2s". H1 34 -> 42 at the phone and
       56 -> 53 at the shell; H2 30 -> 32 and 48 -> 43. These are the INTERIOR
       values: the home page's --t-h2 is set on its body class and is not
       reached by this block, so it keeps ramping 30 -> 48 as it did. That
       leaves home at 30 against interior's 32 on a phone, which is the one
       place the two-value role reads slightly backwards; it is half a pixel of
       consequence on a heading nobody sees next to the other, and moving it
       would be editing the home page in an interior-page pass. */
    --t-display:    calc(40px + 24 * var(--pw) / 510);  --t-display-lh: calc(48px + 26 * var(--pw) / 510);
    --t-h1:         calc(42px + 11 * var(--pw) / 510);  --t-h1-lh:      calc(50px + 13 * var(--pw) / 510);
    --t-h2:         calc(32px + 11 * var(--pw) / 510);  --t-h2-lh:      calc(40px + 12 * var(--pw) / 510);
    /* The interior rhythm ramps; home's is flat at 39 on its body class. His
       "48-56px section padding" on a phone is the gap again - 24 to 28 a side -
       and 26 sits in it. Ends at 35 so nothing steps at 901. */
    --sp-flat:      calc(26px + 9 * var(--pw) / 510);   /* 26 -> 35 */
    --t-h3:         calc(26px + 8 * var(--pw) / 510);   --t-h3-lh:      calc(34px + 10 * var(--pw) / 510);
    --t-h4:         calc(24px + 3 * var(--pw) / 510);   --t-h4-lh:      calc(32px + 3 * var(--pw) / 510);
    --t-h5:         calc(19px + 2 * var(--pw) / 510);   --t-h5-lh:      calc(26px + 3 * var(--pw) / 510);
    --wordmark:    calc(24px + 2.6656 * var(--pw) / 510);    /* 24   -> 26.6656 */
  }
/* HOME'S SECTION HEADLINE NEEDS ITS OWN RAMP, and leaving it out was a live
     bug for the length of one check run (2026-09-12). `body.page-home` sets
     --t-h2: 48px outside any media query, and a body-class declaration beats
     the :root ramp above for everything inside <body> - so the home page kept
     a 48px headline all the way down to 320 instead of ramping to 30. At 320
     that overflowed: check:widths reported scrollWidth 369 on / and nothing
     else, and no element's box was over the viewport because it was the TEXT
     spilling out of a box that still measured 320.

     This ramp is the one home had before the token moved onto the body class, unchanged: 30 at the phone, 48 where the shell begins. The lesson for the
     next page-scoped token is that it needs a value in EVERY block the token
     it overrides appears in. --sp-flat is deliberately not here: 39 flat is
     what home has had since the 11th at every width. */
  body.page-home {
    --t-h2:       calc(30px + 18 * var(--pw) / 510);   /* 30 -> 48 */
    --t-h2-lh:    calc(38px + 20 * var(--pw) / 510);   /* 38 -> 58 */
  }
  .site-nav { display: none; }
/* THE MENU (2026-09-14, Jacob: "on mobile, there is no menu dropdown"). The
     nav was hidden here with nothing to reach it, so a phone could not get to
     any page but by the footer. No mobile mockup exists (SPEC "Assumptions"),
     so it is built from the system: navy on the header’s own ground, the
     --nav size, 44px rows (PAGE.md tap floor).

     The row is monogram + wordmark left, toggle right. The nav and the CTA
     share .site-menu, which opens as ONE panel under the header row.

     LAID OVER THE PAGE, NOT IN FLOW (same day, Jacob: "the menu should lay
     over the site and not push the content down"). Absolute against
     .site-head, which is lifted to z-index 50 - above the z-index 1 the hero
     copy blocks take - so the panel covers the hero instead of moving it. */
  .site-head { position: relative; z-index: 50; }
  .navtoggle {
    display: flex; flex-direction: column; justify-content: center; gap: 5px;
    width: 44px; height: 44px; margin-left: auto; margin-right: -10px;   /* bars flush with the gutter */
    padding: 0 10px; background: none; border: 0; cursor: pointer;
  }
  .navtoggle span {
    display: block; height: 2px; background: var(--navy);
    transition: transform var(--dur-image) var(--ease), opacity var(--dur-image) var(--ease);
  }
  /* THE WORDMARK AND THE TOGGLE SHARE ONE ROW, and at 24px they need 297 (brand
     263 + the toggle's 34 inside the gutter). The column is 312 at 360 and 272
     at 320, so below about 345 the toggle wrapped onto a row of its own. Ramped
     rather than stepped: 7.5vw - 3px is exactly 24 at 360, where --wordmark
     floors, and 21 at 320, where the brand comes to 238. */
  .site-head .brand span { font-size: min(var(--wordmark), calc(7.5vw - 3px)); }
  body.nav-open .navtoggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  body.nav-open .navtoggle span:nth-child(2) { opacity: 0; }
  body.nav-open .navtoggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
  .site-menu {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    padding: 0 var(--gut) calc(22 * var(--u));
    background: var(--tone-soft);
    border-top: var(--hairline) solid var(--tone-edge);
    border-bottom: var(--hairline) solid var(--tone-edge);
    box-shadow: 0 12px 24px rgba(4, 35, 77, .12);
  }
  body.nav-open .site-menu { display: block; }
  .site-menu .site-nav { display: flex; flex-direction: column; gap: 0; margin-left: 0; }
  .site-menu .site-nav a {
    display: flex; align-items: center; min-height: 44px;
    border-bottom: var(--hairline) solid var(--tone-edge);
  }
  .site-menu .site-nav a::before, .site-menu .site-nav a.is-active::after { display: none; }
  .site-menu .site-nav a.is-active { color: var(--gold); }
  .site-menu .btn { margin-top: calc(20 * var(--u)); }
/* PRE-EXISTING MOBILE OVERFLOW, fixed here. With the nav hidden the header is
     still brand + CTA on one nowrap row - about 443px of content in a 342px
     column at 390 - so the page scrolled sideways on every phone. Same for the
     two-button rows, which are 515 wide against the same 342. No mobile mockup
     was supplied (SPEC "Assumptions"), so these wrap rather than invent a
     layout the drawing does not show. */
  .site-head .wrap { flex-wrap: wrap; height: auto; row-gap: calc(14 * var(--u));
                     padding-top: calc(16 * var(--u)); padding-bottom: calc(16 * var(--u)); }
  .site-head .btn { margin-left: 0; }
  .hero__btns, .chero__btns, .cta__btns { flex-wrap: wrap; }
/* ON A PHONE THE PHOTOGRAPH IS A BAND ABOVE THE COPY, SHOWN WHOLE (2026-09-14,
     Jacob: "right now the hero is cropped dark and behind tight text.. we
     should just show the hero image to fit"). It sat behind the copy here, cut
     to about 390x600 from a 2.25:1 frame, which kept two of the four people,
     and the copy ran the full column width, so the scrim had to cover the
     whole picture at .82-.88 to hold 4.5:1. Stacked, the text is on solid navy
     and needs no scrim, and the band takes the file’s own 1800x800 ratio, so
     nothing is cropped at any phone width. The same move .ahero makes at 900.

     The section’s top padding comes off so the photo meets the header, and
     the copy takes that padding instead. */
  .hero { min-height: 0; padding-top: 0; }
  .hero__art {
    position: static; transform: none; width: 100%;
    aspect-ratio: 1800 / 800;
  }
  .hero__art img { object-position: center; }
  .hero__art::after { background: none; }
  .hero .wrap { padding-top: var(--sp-flat); }
/* the rhythm clamp already narrows the padding */
  /* One column, not two: 2-up was right for four cells and leaves the third
     of three orphaned in its own row. The hairline moves to the top edge, which is the swap .cexp__metric already makes when it unstacks. */
  .stats__row { grid-template-columns: 1fr; row-gap: calc(24 * var(--u)); }
  .stats__cell { border-left: 0 !important; }
  .stats__cell + .stats__cell {
    border-top: var(--hairline) solid var(--rule-on-navy);
    padding-top: calc(24 * var(--u));
  }
  .caps__row, .caps__row--two { grid-template-columns: 1fr; }
/* THE SIX-STEP FRAMEWORK GOES TO ONE COLUMN (2026-09-05, Jacob: "the
     two-column layout makes everything too tiny").

     2-up put a 72px circle, a 30px numeral, a label and a two-line
     description into a 155px column - the description set at 14px and broke
     after two or three words. One column per step, circle left and text
     right, gives the description the full 342 and costs nothing in height:
     the row layout is ~105 per step against ~200 for a full-width centred
     stack, so six of them land within 40px of the 2-up block they replace.

     The two text children are grid-placed rather than wrapped in a div, so
     no page markup changes: the label takes row 1, the description takes
     row 2, and the circle spans both. */
  .frame__row { display: grid; grid-template-columns: 1fr; gap: calc(30 * var(--u)); }
  .frame__arrow { display: none; }
  .frame__step {
    width: 100%; text-align: left;
    display: grid; grid-template-columns: var(--circle-sm) 1fr;
    column-gap: calc(20 * var(--u)); align-items: baseline;
  }
  .frame__circle { grid-area: 1 / 1 / 3 / 2; align-self: start; margin-inline: 0; }
  .frame__l { grid-area: 1 / 2 / 2 / 3; margin-top: 0;
              font-size: calc(19px - 3.1673 * var(--pw) / 510);         /* 19   -> 15.8327 */
              line-height: calc(26px - 7.001 * var(--pw) / 510); }      /* 26   -> 18.999  */
  .frame__d { grid-area: 2 / 2 / 3 / 3; max-width: none; margin-inline: 0;
              margin-top: calc(4 * var(--u));
              font-size: calc(17.5px - 5.8338 * var(--pw) / 510);       /* 17.5 -> 11.6662 */
              line-height: calc(26px - 9.334 * var(--pw) / 510); }      /* 26   -> 16.666  */
  .exp__items { grid-template-columns: repeat(2, 1fr); row-gap: calc(28 * var(--u)); }
/* The 13.3 inset belongs to the four-column row, where the cell is 267. Here
     the cell is 186 at 421 and that inset took the label box to 153 - which set
     "Major Academic / Healthcare System" on three lines and overrode the
     authored <br>. check:layout caught it at 421-430. The column count changes
     at this breakpoint, so the inset is allowed to change with it. */
  .exp__item { padding-inline: calc(2 * var(--u)); }
  .exp__item:nth-child(odd) { border-left: 0; }
  .site-foot .wrap { flex-wrap: wrap; gap: calc(20 * var(--u)); }
  .site-foot__links { margin-left: 0; flex-wrap: wrap; }
  .site-foot__copy { margin-left: 0; }
}

@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
/* ===========================================================================
   CAPABILITIES PAGE
   Mockup: design/mockup/capabilities-847.png (847px native, x1.7001).
   Measured in design/SPEC-capabilities.md. Shares the header, footer, buttons, eyebrow and arrow-link above; everything below is this page.
   =========================================================================== */

.page-caps {
  /* The ground override that used to live here is gone: warm ivory is now the
     site-wide --paper. See the note in :root. */
  --cgrid: min(calc(1261 * var(--u)), 100%);   /* card grid x51-792 = 742 -> 1261 */
  --ctile: #F4ECE2;  /* the tile is the ground darkened by the measured
                        (-3,-7,-10); that contrast is what was measured, the
                        absolute value follows --paper */
}

/* Active nav item */
.site-nav a.is-active { color: var(--navy); position: relative; }
.site-nav a.is-active::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -calc(8 * var(--u));
  height: var(--hairline); background: var(--gold);
}

/* ------------------------------- Intro --------------------------------- */
.chero { text-align: center; }
.chero h1 {
  margin-top: 0;   /* was 10u, a gap measured from the eyebrow above it; the eyebrows went 2026-09-12 */
  font-family: var(--serif); font-weight: 400;
  /* --t-display -> --t-h1 (2026-09-12): "reduce interior H1s from 56-64px to
     roughly 50-56px". Display is the home page's step; this is an interior
     hero, which is exactly what --t-h1 is for - "hero on a secondary page".
     64 -> 53. The width solve in the comment below was for the old size. */
  font-size: var(--t-h1); line-height: var(--t-h1-lh);   /* cap 32 -> 54 read 76; the line
                                           width x205-666 = 785 build settled it at display */
  color: var(--navy);
}
.chero__body {
  margin-top: calc(18 * var(--u));              /* 34 -B(h1) 7.2 -A(body) 8.75 */
  max-width: calc(700 * var(--u)); margin-inline: auto;   /* body x232-621 = 390 -> 663 */
  color: var(--ink-body);
}
.chero__btns {
  margin-top: calc(32 * var(--u));              /* 39 -B(body) 7.2 */
  display: flex; align-items: center; justify-content: center; gap: calc(34 * var(--u));
}
/* .arrow-link--down went with it - the /capabilities hero's "Explore the
   Practices" is a button now. */

/* --------------------- Full-bleed roadmap image ------------------------ */
/* THE ONLY PHOTOGRAPH ON THE SITE THAT RAN TO THE VIEWPORT EDGE, and the one
   thing Jacob's 2026-09-03 rule rules out: colour may bleed, pictures may not.
   The section still paints the ivory full width - so the band still reads as a
   band - and the picture sits on the same 1200 box as every other photograph, level with the hero above it. */
.band-img { height: calc(360 * var(--u)); background: var(--paper); }
/* 384 -> 300: "reduce the
                    opening image band from 384px to approximately 300px". Drawn y317-588 = 271 -> 461. */
.band-img img, .band-img .missing {
  display: block; width: min(var(--wrap), 100%); height: 100%;
  margin-inline: auto; object-fit: cover; object-position: 50% 47%;   /* per the bundle manifest */
}
.band-img .missing { border-left: 0; border-right: 0; }

/* ---------------------------- Intersections ---------------------------- */
/* .inter - see the grouped rule above */
.inter h2 {
  margin-top: 0;                 /* 12 -B(eyebrow) 2.9 -A(h2) 13 = -4, clamped */
  font-family: var(--serif);   /* cap 17 -> 28.9 */
  color: var(--navy)}
.inter__body {
  margin-top: calc(7 * var(--u));               /* 24 -B(h2) 7.85 -A(body) 8.9 */
  max-width: calc(720 * var(--u)); margin-inline: auto;
  font-size: calc(19 * var(--u)); line-height: calc(30 * var(--u)); color: var(--ink-body);
}
/* ===========================================================================
   THE LABELLED SEQUENCE, ONE TREATMENT (2026-09-12, Jacob: "how can we make
   those 4 elements pop more")

   This device appears twice: .chain here (Technology -> Process -> Roles ->
   Capabilities) and .ahero__labels on /ai-transformation (Strategy -> Work ->
   Workforce -> Value). Same job - four terms joined by arrows, read as a
   sequence - and two treatments:

     .chain           15.83  weight 400  no tracking  sentence case
     .ahero__labels   15.83  weight 600  .18em        UPPERCASE

   The AI one was already the stronger of the two, and at the client's own
   request: "change the highlighted words into a bigger font" (Michael
   Blanchard, 2026-09-08). So this is not a new idea to make the chain pop, it
   is the treatment he already approved for the identical row, applied to the
   row that never got it. Same SIZE either way - --body-sm is the step rule 2
   assigns to framework labels, which is the job both rows do - so what carries
   the weight is caps, tracking and 600, which is what turns running gold text
   into a sequence of labels.

   Each keeps its own LAYOUT: this one centres with a gap, the AI one spreads
   under a hairline. Only the type is shared. */
:is(.chain, .ahero__labels) {
  font-size: var(--body-sm); line-height: var(--lh-tight); font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--gold);
}
.chain {
  margin-top: calc(22 * var(--u));
  display: flex; align-items: center; justify-content: center; gap: calc(18 * var(--u));
}
.chain svg { stroke: var(--gold); flex: none; }

/* ------------------------------ Practices ------------------------------ */
.practices__head { text-align: center; }
.practices h2 {
  margin-top: 0;                 /* was 2px, an eyebrow gap */
  font-family: var(--serif);   /* cap 16 -> 27.2 */
  color: var(--navy)}
.pgrid {
  margin-top: calc(24 * var(--u));              /* 31 -B(h2) 7.1 */
  max-width: var(--cgrid); margin-inline: auto;
  /* minmax(0, 1fr), NOT 1fr. 1fr floors at min-content, so practice 02's
     button - nowrap, 265px at 320 - set the floor for every track and pushed
     all five cards to 305 in a 272 column. check:widths caught +9 at 320.
     MOBILE.md rule 4. */
  /* ONE COLUMN OF ROWS, 3 + 2 (2026-09-12). It was two columns, which was
     right while practice 01 was a wide card spanning both: five cards in two
     columns is 2 + 2 + 1, and lifting the photograph out would have left the
     fifth alone in a half-width row. Three then two, with the second row
     centred - the layout HOME already gives these same five practices
     (.caps__row / .caps__row--two), so the two pages now set them the same
     way rather than two ways. */
  display: grid;
  /* THE TIGHT STACK BECAME FIVE CARDS (2026-09-05 refresh). The superseded
     raster drew them as a block divided by hairlines, and the 10/7 gutters
     here were measured off exactly that. capabilities-850 draws five bordered
     cards with air between them, so the gutter takes the 16 the refresh uses
     for every other card grid (.cnext__grid) rather than a sixth
     value measured off a raster whose scale does not hold - see SPEC-
     capabilities.md, "This raster cannot set a size". */
  gap: calc(12 * var(--u));   /* 16 -> 12: "the gap between rows" (2026-09-12) */
}
/* THE PRACTICE PHOTOGRAPH HEADS THE SECTION (2026-09-12, Jacob: "make the
   image centered and bigger, and then have the cards underneath it").

   620 AND NOT WIDER, AND THAT IS THE FILE'S LIMIT, NOT A CHOICE. The supplied
   photograph is 620x260 and so is the client's original in
   assets/originals/caps-bundles/ - there is no larger source. It rendered at
   452x193 inside practice 01, so 620 is 1.37x bigger and every pixel is its
   own; the full 1051 content column would be a 1.7x upscale, which on a 2x
   screen is 3.4x and visibly soft. If it should run wider, the file has to
   come first.

   The 2.38:1 is the frame's, taken from the image rather than imposed on it,
   so nothing is cropped at any width. */
.practices__art {
  margin-top: calc(24 * var(--u));
  max-width: var(--cgrid); margin-inline: auto;   /* 620 -> the card row, 1051 */
}
.practices__art img { display: block; width: 100%; height: auto; aspect-ratio: 620 / 260; }
/* .pcard - folded into .card */
/* .card--wide is gone (2026-09-12). Practice 01 was the only card on the site
   that used it, and its photograph now heads the section instead of sitting
   beside its own body. The modifier, its art height, its icon-gap reset and
   its two 1240 overrides went with it. */
/* The 230 frame that was .pcard__art is gone. It was already dead: the only
   capability card with a photograph is the wide one, and .card--wide gives its
   art the full height of the row. Renaming it onto .card__art made a dead rule
   live and put home's cards in a 191.7 frame instead of their 141.7 - the
   snapshot caught it as home growing 215px. */
/* .pcard--wide .card__art - folded into .card */
/* fills the card, as drawn */
/* .card__art img - see the grouped rule above */
/* .card__art .missing - see the grouped rule above */
/* The icon alone, since the numeral went. The box comes from the shared icon
   square; the ring inside it is the supplied asset at --icon-art, unfiltered, as the bundle manifest requires. It is column 1 of the heading's row. */
/* .pcard__head is gone: the badge is a direct child of .card__body, and the
   space above it is that box's padding-top in every card. */
/* .pcard h3 - folded into .card */
/* .pcard__b - folded into .card */
/* The tag row and the Explore link run to the card edge. That was the
   mockup's own hanging-indent detail; since the numerals went and the title
   came flush, the whole card block simply shares one left edge. */
.tags {
  margin-top: calc(10 * var(--u));
  display: flex; flex-wrap: wrap; align-items: center;
  font-size: var(--eyebrow); line-height: var(--lh-tight); color: var(--navy);
}
.tags span { padding-inline: calc(14 * var(--u)); }
.tags span + span { border-left: var(--hairline) solid var(--rule); }
.tags span:first-child { padding-left: 0; }


/* ------------------------------ Challenge ------------------------------ */
/* .challenge - see the grouped rule above */
.challenge h2 {
  margin-top: calc(3 * var(--u));    /* cap 15 -> 25.5 */
  color: var(--navy)}
.tiles {
  margin-top: calc(18 * var(--u)); max-width: var(--cgrid); margin-inline: auto;
  display: grid;
  /* TWO UP, NOT THREE, because the title took the card-heading step
     (2026-09-12). Three tiles across the 1051 row leave the title 270px beside
     its square and 179px at 901 once the square moves above it, and at
     --t-h5 21 that is 12.7 characters a line over three lines: check:layout
     failed "Managing too much simultaneous change?" at every width from 901 to
     1440. Two up gives it about 440, and one line each at 1440.

     Four tracks with every tile spanning two, and the third starting at track
     2, puts it in tracks 2-3 - centred, the same move the practice cards and
     the services band make with a short last row. Two of four tracks plus the
     gap between them is (W - g)/2, exactly what two tracks would be, so the
     first two tiles are the width they would have been either way. */
  grid-template-columns: repeat(4, minmax(0, 1fr)); gap: calc(41 * var(--u));
}
/* WHITE CARDS, NOT FILLED TILES (2026-09-05 refresh). They were a solid beige
   panel on the warm band; the new drawing sets them as white cards with the
   same hairline every other card in the refresh carries, which is what stops
   this row reading as three buttons. The hover keeps its job - it is the only
   hover on the page and these ARE links - but it now warms the card rather
   than darkening an already-dark panel. */
.tiles > .tile { grid-column: span 2; }
.tiles > .tile:nth-child(3) { grid-column: 2 / span 2; }
/* One row: square, title, arrow. Three columns rather than the two it had
   while the icon was above the title. */
.tile {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--icon-gap-side);
  /* min-height: calc(129 * var(--u)) removed 2026-09-12 - "remove forced
     minimum heights when the content does not require them". These sit in a
     grid, so the row already equalises them; the 107 floor only held the row
     open past what the longest tile needed. */
  padding: calc(22 * var(--u)) calc(24 * var(--u));   /* was 26/28 */
  background: var(--tone-elevated); border: var(--hairline) solid var(--rule);
  text-align: left;
  transition: background-color .18s ease, border-color .18s ease;
}
.tile:hover { background: var(--ctile); border-color: var(--gold); }
.tile__icon { }   /* box from the shared icon square; column 1 by order */
/* AS BIG AS THE CARD HEADINGS ABOVE THEM (2026-09-12, Jacob: "can you make
   the words in each little card as big as the header text in the cards right
   above them?"). The practice card h3 is --t-h5 21/29; this was 14.17/21.67,
   the small Lato label the drawing gave it. Size and leading only - the face
   stays Lato, because these are questions rather than headings and rule 2's
   step is about SIZE. */
.tile__t { font-size: var(--t-h5); line-height: var(--t-h5-lh); color: var(--navy); }   /* was 17u = 14.17 */
.tile__arrow { stroke: var(--gold); flex: none; }
.challenge__all { margin-top: calc(16 * var(--u)); }
/* A LAST ROW OF TWO CENTRES (2026-09-12, Jacob: "there are 5 areas by flexible
   support. can you center two of them if there are just two on a line.. right
   now they left align.. you can set it up like the other cards we've been
   working with").

   .blocks is three columns and this band has five items, so 4 and 5 sat in
   columns 1 and 2 with a third of the row empty on the right. Six tracks with
   every item spanning two, and item 4 starting at track 2, puts the pair in
   tracks 2-3 and 4-5 - one track plus one gap of air each side, so they centre.

   THE ITEM WIDTH DOES NOT CHANGE, which is why this is done with tracks rather
   than by splitting the markup into rows: two of six tracks plus the gap
   between them is (W - 5g)/3 + g = W/3 - 2g/3, and one of three tracks is
   (W - 2g)/3 = W/3 - 2g/3. The same number. The cards above and below stay
   exactly as wide as they were.

   Scoped to .emodels: .blocks is the shared grid and every other band that
   uses it has a count that fills its rows. */
/* AND IT HAS TO BE PER COLUMN COUNT, because .blocks is auto-fit: its track
   count comes from the width, not from a class. Measured on this band - 3 up at
   1179 and above, 2 up from 820 to 1178, 1 up below that. Unscoped, the six
   tracks applied at 320 too and put three items across a 272 column:
   check:widths caught scrollWidth +47. */
@media (min-width: 1180px) {          /* 3 up: items 4 and 5 centre as a pair */
  .emodels .blocks { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .emodels .blocks > li { grid-column: span 2; }
  .emodels .blocks > li:nth-child(4) { grid-column: 2 / span 2; }
}
@media (min-width: 820px) and (max-width: 1179px) {   /* 2 up: item 5 centres alone */
  .emodels .blocks { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .emodels .blocks > li { grid-column: span 2; }
  .emodels .blocks > li:nth-child(5) { grid-column: 2 / span 2; }
}

/* ------------------- Selected experience (navy, full bleed) ------------ */
/* .cexp takes NO rhythm class: its photograph is a grid cell that runs the
   full band height, so section padding would inset the picture. A full-bleed
   image band sets its own height, like .band-img. */
.cexp__inner {
  max-width: var(--wrap); margin-inline: auto;   /* see .ahero-about__inner */
  display: grid;
  grid-template-columns: 578fr 280fr 448fr;   /* gold divider measured at x340 -> 578;
                                               photo starts x505 -> 858        */
  /* min-height: calc(292 * var(--u)) removed 2026-09-12. It was dead: the copy
     column already stands 294 against its 243 floor, so nothing was being held
     open and removing it changes no pixel. Dead machinery in a rule about
     height is worse than none - the next person reads it as the reason. */
}
.cexp__copy { padding: calc(30 * var(--u)) 3% calc(30 * var(--u)) 7.2%; }   /* copy starts x55 -> 7.2% */
.cexp h2 {
  margin-top: calc(12 * var(--u));
  font-size: var(--t-h3); line-height: var(--t-h3-lh);   /* was 28, set by line width: "From implementation
                                           to adoption" measures x55-308 = 432 build
                                           and renders 427 at 28px in a 444 column  */
  color: var(--on-navy)}
.cexp__sub { margin-top: calc(14 * var(--u)); }   /* size, face and colour: the client line above */
.cexp__body { margin-top: calc(10 * var(--u)); font-size: calc(17 * var(--u)); line-height: calc(26 * var(--u)); color: var(--on-navy-muted); }
.cexp__metric {
  padding: calc(32 * var(--u)) calc(30 * var(--u));
  border-left: var(--hairline) solid var(--rule-on-navy);
  align-self: center;
}
.cexp__n { font-size: calc(21 * var(--u)); color: var(--on-navy); }
.cexp__n b { font-family: var(--serif); font-weight: 400; font-size: calc(41 * var(--u)); color: var(--gold); }
/* ONE SIZE FOR THE SENTENCE (2026-09-12, Jacob: "this text adoption following
   a 30-day post-go-live period should be the same font size as the 99%
   preceeding it"). The block read at three sizes - the gold 99% at 34, the word
   "adoption" beside it at --body 17.5, and this note at 14.17 - so the one
   sentence "adoption following a 30-day post-go-live period" was set in two of
   them. It takes --body, matching the half of that line it continues. The gold
   number keeps 34: it is the figure, not the sentence. */
.cexp__note { margin-top: calc(10 * var(--u)); font-size: calc(21 * var(--u)); line-height: calc(30 * var(--u)); color: var(--on-navy); }   /* 17u -> 21u, .cexp__n's size */
.cexp__metric .btn { margin-top: calc(24 * var(--u)); }
/* .cexp__art img - see the grouped rule above */
/* .cexp__art .missing - see the grouped rule above */

/* ------------------------------- Approach ------------------------------ */
.approach { text-align: center; }
.approach h2 {
  margin-top: 2px}
.approach__row {
  margin-top: calc(8 * var(--u)); max-width: calc(996 * var(--u)); margin-inline: auto;   /* x128-713 -> 996 */
  display: flex; align-items: flex-start; justify-content: center;
}
.astep { width: calc(118 * var(--u)); flex: none; }
/* THE RING IS THE FRAME, SO IT BELONGS HERE AND NOT IN THE ARTWORK
   (2026-09-12). Every superseded framework file drew its own gold circle -
   <circle r="43" stroke="#C6922F" stroke-width="2.5"/> in a 96 viewBox - so
   six copies of one frame shipped inside six drawings, and a replacement
   drawing without it would have quietly dropped the rings off the row. It is
   the same decision the navy square already made: the component owns the
   frame, the file owns the figure.

   The numbers are that artwork's, converted from its viewBox to this box:
     ring outer   2 x (43 + 1.25) / 96 = 92.2%  ->  77px   (the 84 box carried
                  the drawing's own margin; the box is the ring now)
     stroke       2.5 / 96                      ->  2.2px
     figure       20..76 of 96 = 58.3%          ->  49px, so 12 of padding */
.astep__c, .icon-ring {
  padding: var(--ring-pad);
  border: var(--ring-stroke) solid var(--gold); border-radius: 50%;
}
/* THE PADDING IS THE STROKE ONLY, because the ARTWORK now carries the inset
   (2026-09-12, Jacob: "they are not showing great").

   It was 12, derived from the superseded drawing whose figure was 58.3% of its
   viewBox. That left a 48.6 content box, and each icon was contain-fitted into
   it - which fits a BOUNDING BOX, not a circle. In a round frame that
   under-fills anything wider than it is tall, because a circle has corner room
   a square does not. Measured: align, diagnose and enable reached 95% of the
   ring's inner radius, mobilize 83%, adopt 85% and REALIZE 75% - visibly
   floating in the middle of its ring, which is what he saw.

   The six files are scaled per icon now so each one's half-diagonal is 95% of
   the inscribed circle, so the padding goes to 0 and the canvas maps to the
   ring's inner box, 77 - 2 x 2.2 = 72.6. The square icons come out at 48.8
   against the 48.6 they had - unchanged - and realize goes 48.6 -> 61.6 wide,
   adopt 48.6 -> 54.1, mobilize 35.9 -> 40.8. All six at 95%.

   .dhero__kind .icon-ring keeps its 10: its practice icon also serves a navy
   SQUARE on the cards, where a bounding-box fit is the right one, so that file
   cannot be circle-fitted without breaking the other use. */
.astep__c { width: 77px; height: 77px; margin-inline: auto; --ring-pad: 0px; }
.astep__l {
  display: block; margin-top: calc(4 * var(--u));
  font-size: var(--eyebrow); line-height: var(--lh-tight); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--navy);
}
/* THE ARROWS ARE GONE (2026-09-05 refresh). The superseded drawing joined the
   six steps with gold connectors; capabilities-850 sets them as a plain row of
   ringed marks with their labels and nothing between. The span stays in the
   markup as a hook and simply does not draw, so the row still distributes on
   the same flex it always did. */
.astep__arrow { display: none; }
.astep__arrow--legacy { flex: 1; display: flex; align-items: center; color: var(--gold); margin-top: calc(42 * var(--u)); }
.astep__arrow i { flex: 1; height: var(--hairline); background: var(--gold); }
.astep__arrow svg { flex: none; }
.approach__note { margin-top: calc(10 * var(--u)); font-size: calc(17 * var(--u)); color: var(--ink-body); }
/* --------------------------------- CTA --------------------------------- */
/* Ground only. The shape is the interior closing block, defined once beside
   home's .cta - the gaps, the headline, the supporting line and the button all
   come from there. */
.ccta { background: var(--paper-warm); }

/* ------------------------------ Responsive ----------------------------- */
@media (max-width: 768px) { .xhero__overlay { display: none; } }
@media (max-width: 1240px) {

  .cexp__inner { grid-template-columns: 1fr 1fr; }
/* THE HEIGHT IS A SIDE-PANEL NUMBER AND THIS IS NO LONGER A SIDE PANEL.
     At 1241 the art is a 412-wide column beside the copy, at 1.40:1. Here it
     spans the section full-bleed - 1200 wide at the top of the band - and 260
     was carried across unchanged, so a 2.07:1 photograph sat in a 5.54:1 slot
     with 37% of it left. 260 is not a measurement: nothing below the desktop
     composition was drawn. So it becomes a floor, and the band grows with the
     viewport at 2.8:1, the widest this photograph takes before the crop rule
     bites. No ceiling - the band only exists below 1241, so it is bounded by
     the breakpoint itself. */
  .cexp__art {
    grid-column: 1 / -1;
    height: max(calc(260 * var(--u)), calc(100vw / 2.8));
  }
  .approach__row { flex-wrap: wrap; gap: calc(24 * var(--u)); }
  .astep__arrow { display: none; }
}
@media (max-width: 900px) {
  .pgrid { grid-template-columns: minmax(0, 1fr); }
  .tiles { grid-template-columns: 1fr; gap: calc(20 * var(--u)); }
  .tiles > .tile, .tiles > .tile:nth-child(3) { grid-column: 1; }
  .cexp__inner { grid-template-columns: 1fr; }
  .cexp__copy { padding: calc(28 * var(--u)) calc(24 * var(--u)); }
  .cexp__metric { border-left: 0; border-top: var(--hairline) solid var(--rule-on-navy); padding: calc(24 * var(--u)); }
  .astep { width: 45%; }
  /* .pcard h3, .pcard__b margin-left reset - folded into .card */
/* .card__badge took --circle here and --circle at the shell, which was the
     same number twice; the square is --icon-sq at every width now. */
  /* THIS ONE FAILS THE OTHER WAY. capabilities-hero-workshop is 3.15:1, the
     widest photograph on the site, and 260 is a fixed height - so the band is
     too TALL for it on a phone rather than too wide: 1.38:1 at 360, which
     throws away 56% of a panorama. It is the same fault as the bands above
     (a height that does not know its own width) with the sign reversed, so it
     takes a ceiling instead of a floor. 260 still applies from 600 up, where
     the band is wide enough to earn it. */
  .band-img { height: min(calc(260 * var(--u)), calc(100vw / 2.3)); }
}
/* ===========================================================================
   AI TRANSFORMATION PAGE
   Mockup: design/mockup/ai-transformation-862.png (862px native, x1.6705).
   Measured in design/SPEC-ai-transformation.md. Shares the header, footer, buttons, eyebrow and arrow-link above.

   Its icons and hero artwork are supplied assets that carry their own gold
   ring (r=42 of a 96 viewBox = 0.875 of the box), so nothing here draws a
   circle around them and nothing filters them - both are forbidden by the
   bundle manifests.
   =========================================================================== */

/* ------------------------------ Navy hero ------------------------------ */
.ahero { position: relative; overflow: hidden; }
/* ground: .sec--navy */
/* THE PHOTOGRAPH SPANS THE SECTION (2026-09-12). It was a 16:9 frame held to
   the SHELL with a mask feathering it in across x48-57%, so the navy ground
   showed through the left third and the picture was a right-hand panel in all
   but name. The replacement is an OUTPAINTED 2167x725 frame - 2.99:1, with a
   dark glass-walled interior extended across its left 35% - so the photograph
   itself now provides the dark ground the copy sits on, and it runs edge to
   edge with no mask at all. Original at assets/originals/ai-hero-v2/.

   ANCHORED RIGHT, and that is measured rather than chosen. A 1440x600 frame
   is 2.4:1 against the source's 2.99:1, so cover shows 1740 of 2167 px - 80%
   - and 20% has to go. Rendered all three windows: right-anchored keeps all
   FOUR colleagues and still leaves the left 40% of the frame dark; centred
   loses the fourth; left-anchored loses two of them. At 1794px and wider the
   frame is wider than the source, so the crop turns vertical and the whole
   width shows.

   HELD TO 1200 (2026-09-14, Jacob: "make the hero image on ai transformation
   fixed to 1200"). It ran to the viewport edge; it is now a centred 1200 box
   on the navy ground, positioned the way .hero__art is on Home. 1200 is his
   number, not --wrap (1180) - the photograph sits 10px past the content shell
   each side. At 1200x600 cover shows 1450 of 2167 px, still right-anchored,
   so all four colleagues stay in frame. */
.ahero__art {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: min(100%, 1200px);
  pointer-events: none;
}
.ahero__art picture { display: block; width: 100%; height: 100%; }
/* A SCRIM OVER THE COPY, and only because it is needed. The brief was "a
   subtle overlay only if needed for readability", so it was measured without
   one first: the body and the label row were fine at 5.70 and 5.48, and the H1
   read 1.62:1 at 1440 and 2.73:1 at 1920 - it is 70px and 700 wide, so its
   longest line runs past the frame's dark 40% and onto the table. Navy rather
   than neutral, because this section IS navy and that is the page's identity.
   It clears completely before the first colleague. */
.ahero__art::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(95deg,
    rgba(4, 35, 77, .82)  0%,
    rgba(4, 35, 77, .78) 30%,
    rgba(4, 35, 77, .55) 48%,
    rgba(4, 35, 77, .12) 64%,
    rgba(4, 35, 77, 0)   78%);
}
.ahero__art img { width: 100%; height: 100%; object-fit: cover; object-position: 100% center; }
/* THE FLOW ROW RUNS THE FULL CONTAINER AND THE PHOTOGRAPH IS UNDER HALF OF IT, so "Workforce" and "Value" were gold-on-desk and effectively unreadable.
   The row keeps its width - that is the drawing - and the picture darkens to
   navy beneath it instead. Below .wrap's z-index, so only the art is touched. */
.ahero::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: calc(300 * var(--u)); pointer-events: none;
  /* .92 -> .97 at 45% (2026-09-12). With the outpainted frame the label row
     sits over more of the table than it used to, and the gold measured 4.88:1
     at 1920 - over the 4.5 floor by 0.38, which is the thin-margin mistake
     this session has already made twice. It reads 5.6-6.1 now. The gradient
     still only covers the bottom 250, so the photograph above it is untouched. */
  background: linear-gradient(to top,
    var(--navy) 0%, rgba(4, 35, 77, .97) 45%, rgba(4, 35, 77, 0) 100%);
}
/* .ahero .wrap - see the grouped rule above */
/* 714 -> 600 (2026-09-12): "reduce the hero from 763px to approximately
   580-620px". A FLOOR IS STILL RIGHT HERE and this is why it was reduced
   rather than removed - the band is a photograph with a navy gradient over it, so the content does require a height rather than merely reaching one. At 600
   the copy still clears it and the section lands at 600 + 2x35 = 670... which
   is why the copy above it tightens too; see the ahero gaps below. */
.ahero { min-height: 600px; }
/* was calc(857 * var(--u)) = 714, y99-1018 = 919 drawn */
.ahero__copy { max-width: calc(840 * var(--u)); }
/* was 580, solved when the H1 was
                                        58 build; at 70 rendered it needs this to keep the
                                        drawing's three lines. Still holds the button row. */
.ahero h1 {
  margin-top: calc(36 * var(--u));   /* was 52 */
  font-family: var(--serif); font-weight: 400;
  /* --t-display -> --t-h1, 64 -> 53 (2026-09-12): an interior hero takes the
     secondary-hero step. Was 58 build and width-set to "Turn AI ambition" =
     370; the scale won then (rule 2) and it wins again here. */
  font-size: var(--t-h1); line-height: var(--t-h1-lh);
  color: var(--on-navy);
}
.ahero .rule-short {
  width: var(--rule-short-w); height: var(--rule-h);
  margin-top: calc(20 * var(--u));   /* was 30 */
}
.ahero__body {
  margin-top: calc(28 * var(--u));   /* was 38 */
  max-width: calc(414 * var(--u));   /* holds the drawing's three lines */
  font-size: calc(21 * var(--u)); line-height: calc(34 * var(--u));   /* width-set: line 1 = 337 */
  color: var(--on-navy);
}
.ahero__btns {
  margin-top: calc(38 * var(--u));   /* was 55 */
  display: flex; align-items: center; gap: calc(33 * var(--u));   /* x356 -> x391 = 35 */
}
/* IN FLOW, not absolutely positioned (2026-09-03). It used to hang off the
   wrap's bottom edge, which worked only while the wrap carried the hero's
   bottom padding; the moment the rhythm moved that padding to the section the
   row rose into the button row and sat on top of it. In flow it cannot
   collide with anything, it spans the content column without needing --edge, and the responsive override that already made it static goes away. */
.ahero__labels {
  margin-top: calc(36 * var(--u));   /* was 52 */
  padding-top: calc(26 * var(--u));   /* was 38 */
  border-top: var(--hairline) solid var(--rule-on-navy);
  display: flex; align-items: center; justify-content: space-between;
  /* Inset from the rule, which runs the full container: the labels pitch
     evenly at 364 from x141, so the row spans x141-1295 inside a rule of
     x66-1420. Normalized to a symmetric 100 -> 93 build. */
  padding-inline: calc(93 * var(--u));
  /* The inset drops 93 -> 62 to buy the wider run its room; the labels still
     pitch evenly and the rule above them is untouched. The type itself is the
     shared labelled-sequence rule above - it was declared here, and .chain had
     a weaker copy of it. */
  padding-inline: calc(62 * var(--u));
}
.ahero__labels svg { stroke: var(--gold); flex: none; }

/* ------------------------- Workforce conversation ---------------------- */
.conv { text-align: center; }
.conv h2 {
  margin-top: 0;   /* was 16u, an eyebrow gap */
  font-family: var(--serif);   /* matched to this page's other section
                                           headings (dimensions 47, agentic 47) */
  color: var(--navy)}
.conv .rule-short { margin: calc(18 * var(--u)) auto 0;   /* was 26 */ width: var(--rule-long-w); height: var(--rule-h); }
.conv__grid {
  margin-top: calc(20 * var(--u));   /* was 30 */ max-width: calc(1120 * var(--u)); margin-inline: auto;
  display: grid; grid-template-columns: 1fr 1fr;
}
/* The gold cross between the four questions is two borders, not four rules. */
.conv__grid p {
  padding: calc(20 * var(--u)) calc(36 * var(--u));   /* was 30/44 */
  font-family: var(--serif); font-weight: 400;
  /* Was 40px, which is section-heading scale - against a 47px H2 the questions
     competed with it instead of sitting under it, and the longest line ran
     into the gold divider. 28px restores the hierarchy this page uses
     everywhere else (47 heading / 28 supporting) and clears the rule. */
  font-size: calc(28 * var(--u)); line-height: calc(38 * var(--u));
  color: var(--navy);
}
/* FOUR CARDS, NOT A DIVIDED GRID (2026-09-05 refresh). Same move .tile
   makes: the hairlines that split the block become a box around each
   question. The drawing also puts a ringed icon at the left of each card and
   the bundle has no icon for any of the four - they are listed in
   assets/MANIFEST.md rather than substituted, so the cards ship without and
   the slot is a real gap, not a plausible stand-in. */
.conv__grid { gap: calc(16 * var(--u)); }
.conv__grid p {
  background: var(--tone-elevated);
  border: var(--hairline) solid var(--rule);
  padding: calc(24 * var(--u)) calc(26 * var(--u));
}

/* --------------------------- Five dimensions --------------------------- */
.dims { background: var(--tone-elevated); border-top: var(--hairline) solid var(--rule); }
                                     /* full-width hairline measured at y920-922 */
.dims__head { text-align: center; }
.dims h2 {
  margin-top: 0;                 /* was 19u, an eyebrow gap */
  font-family: var(--serif);   /* cap 20 -> 33.4 */
  color: var(--navy)}
/* The photograph sits over the right of rows 3-4 rather than in a column of
   its own - the row dividers run full width underneath it. */
.dims__body { position: relative; margin-top: calc(25 * var(--u)); }
.dims__list { display: grid; }
.dim {
  display: flex; align-items: center; gap: calc(16 * var(--u));
  /* Right padding, not a narrower list, so the row dividers still run full
     width under the photograph - border-top spans the padding box. The mockup
     ends its longest row at x410 against artwork starting x443; without this
     the copy ran under the photo at 1200 and "workforce implications." was
     cut off mid-word. */
  /* THE PHOTOGRAPH MOVED LEFT (2026-09-05 refresh), so the reserved column
     moves with it - and it is a MARGIN, not the padding it was on the right.
     border-top spans the padding box, so as padding the row dividers ran back
     under the photograph; the drawing rules only the copy. */
  padding: calc(8 * var(--u)) 0;
  margin-left: calc(var(--dims-art-w) + calc(16 * var(--u)));
  /* min-height: calc(115 * var(--u)) - the measured row pitch, 69 -> 115 -
     removed 2026-09-12 with the rest of the forced minimums. Five rows each
     held 96 tall whatever they held; the tallest needs about 70, so the list
     was carrying 130 of air it had no use for. The photograph over rows 3-4 is
     sized from the list rather than the reverse, so it follows. */
}
.dim + .dim { border-top: var(--hairline) solid var(--rule); }
/* --circle-lg -> --circle (84 -> 72), 2026-09-12: "compress the five
   dimensions into a more consistent card grid". The icon is what sets these
   rows - with the min-height gone, each row is the icon plus 13 of padding -
   so five rows lose 60 between them and the list comes down without the copy
   moving at all. It also removes a step at 901 that nobody had noticed: the
   mobile block below already sets this icon to --circle, so the ring grew 12px
   crossing that breakpoint in the other direction. --circle-lg stays the step
   for an icon that HEADS a block, which is what it is for. */
.dim__icon { width: var(--circle); height: var(--circle); flex: none; }
.dim h3 {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h4); line-height: var(--t-h4-lh);   /* cap 12 -> 20 -> 28 MEASURED, BUILT 23 */
  color: var(--navy);
}
/* 28/19 MEASURED, BUILT 23/16, and the icon, gap and number column trimmed to
   84/16/44. Every row is one line in the mockup; the 1200 shell leaves 417px
   of copy measure where 1440 left 906, and at 19px all five wrapped. These are
   the mockup's own proportions re-solved against the narrower measure. */
.dim p { margin-top: calc(4 * var(--u)); font-size: var(--body-xs); line-height: var(--body-xs-lh); color: var(--ink-body); }
/* IT FILLS THE LIST NOW. Centred against a five-row list it left a band of
   empty ivory above and below; the drawing runs the photograph the full height
   of the rows beside it, so the aspect ratio gives way to the list. */
.dims__art {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--dims-art-w);
  border: 1px solid rgba(4, 35, 77, .14);   /* the manifest asks for a 1px
                                               neutral border on warm ivory */
}
.dims__art img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 48%; }

/* ------------------------------ Agentic AI ----------------------------- */
/* THE COPY TAKES WHAT IT NEEDS AND THE ART TAKES THE REST (2026-09-14, Jacob:
   "its too small generally speaking.. should take up more of that extra space,
   and smoothly scale down as width decreases"). 770fr/476fr was solved for the
   network diagram, a 2:1 drawing that filled its height; the row of people
   that replaced it is 4.5:1, so in a 476 column it rendered 397x88 with half
   the band empty. The copy column is now auto - its hard-broken headline sets
   it, 522 at 43px - and the art is 1fr of what is left: 513 at the shell. */
.agentic .wrap { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: calc(64 * var(--u)); align-items: center; }
.agentic h2 {
  margin-top: 0;                 /* was 17u, an eyebrow gap */
  font-family: var(--serif);   /* cap 20 -> 33.4; base gap 29.5 -> 49 */
  color: var(--on-navy)}
.agentic__body {
  margin-top: calc(21 * var(--u));                 /* 30 -B(h2) 0 -A(body) 9 */
  max-width: calc(480 * var(--u));
  font-size: calc(21 * var(--u)); line-height: calc(30 * var(--u));
  color: var(--on-navy-muted);
}
.agentic__art { width: 100%; height: auto; }   /* x460-744 = 285 -> 476 */

/* -------------------------- The human-AI workforce --------------------- */
.human { text-align: center; }
.human > .wrap > h2 {
  margin-top: 0;   /* was 12u, an eyebrow gap */
  font-family: var(--serif);   /* cap 18 -> 30 */
  color: var(--navy)}
/* THREE EQUAL COLUMNS (2026-09-14, Jacob: "look at the greater value section
   its also weird"). As a flex row each card was as wide as its own content -
   320, 256 and 267 at 1440 - so the equation read lopsided, the hairlines
   above the cards were three different lengths and the operators sat off
   centre between them. A grid gives the three cards one width and the two
   operators their own auto tracks. */
.human__row {
  margin-top: calc(40 * var(--u));   /* was 20: the headline’s descenders sat 20px off the three hairlines */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: stretch; column-gap: calc(22 * var(--u));
}
.hcard {
  padding: calc(12 * var(--u)) calc(20 * var(--u));   /* min-height 115u removed 2026-09-12;
                                       the three cards are flex items on `stretch`, so the row
                                       equalises them without a floor. Vertical padding 10 -> 12
                                       because the floor was doing that job for the short one. */
  border-top: var(--hairline) solid var(--rule);   /* a divider above each, not a box (rule 3) */
  text-align: left;
  /* The row stretches the three cards to one height, and a grid hands that
     extra height to its rows - so the shorter cards pushed their headings down
     to different levels (259, 249 and 277 at 1000). start keeps every card’s
     square, heading and list at the top, level with its neighbours. */
  align-content: start;
}
/* Size, inset and radius come from the shared icon square - 84 here would
   have overridden it, which is what the measurement caught. */
.hcard h3 {
  font-size: var(--t-h5); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--navy); line-height: 1.3;
}
.hcard p { margin-top: calc(6 * var(--u)); font-size: calc(17 * var(--u)); line-height: calc(24 * var(--u)); color: var(--ink-body); }
/* .hcard--out's align-items: center is gone (2026-09-12, Jacob: "the icon for
   ai enabled should be top aligned like the rest"). It re-centred the whole
   card because it is the short one - no list under its heading - and on a row
   of stretched cards that pushed its square and title 16px below the other
   two. It takes the shared start like its neighbours; being shorter, it now
   simply sits at the top of its column. */
.human__op {
  display: flex; align-items: center;
  font-size: calc(34 * var(--u)); color: var(--gold); line-height: 1;
}

/* 40 -> 72 (2026-09-14). The closing headline sat 33px under the last card,
   close enough to read as the equation’s fourth term rather than a new
   block. */
.human__cta { margin-top: calc(72 * var(--u)); }/* .acta - every declaration it had is now the shared headline */

/* ITS OWN 14u IS GONE (2026-09-12). This is the sixth closing block on the
   site and the only one that was not in the grouped rule above, so it kept a
   gap nobody else had - 9px of ink between "Is the organization?" and the
   buttons, the tightest of the six and the one he pointed at. It takes
   --cta-gap-btn like the other five. */
.acta__btns {
  margin-top: var(--cta-gap-btn);
  display: flex; align-items: center; justify-content: center; gap: calc(30 * var(--u));
}
/* and the same 44u height, on the sixth closing block. */

/* ------------------------------ Responsive ----------------------------- */
@media (max-width: 1240px) {
  .ahero { min-height: 0; }
/* LESS PHOTO IS A MASK STOP, NOT A NARROWER BOX. The intent here was right -
     the copy column is fixed, so the photo should take the slack - but
     `width: 56%` took it off the WRONG EDGE. The base rule spans the shell and
     lays navy over the left of it with a mask, so the element's right edge is
     what bleeds off the hero; shrinking the element to 56% of the VIEWPORT
     while it stayed anchored at left: var(--edge) ended the photograph
     mid-section. Below 1241 it read as a vertical band of picture floating
     through the middle of the headline with navy resuming to its right - at
     901 the photo stopped at x505 of 901 - and it starved the frame: a 1.78:1
     photograph in a 0.68:1 box, 38% of it surviving.

     The box goes back to the shell, and the two mask stops move right instead, which is what "less photo, longer feather" actually asks for. The frame
     goes 0.68:1 -> 1.21:1 at 901 and 0.89 -> 1.56 at 1240, so it holds the
     crop rule across the band, and the photograph reaches the right edge at
     every width the way the drawing has it. */
/* The 54/63 feather is gone with the desktop one (2026-09-12): the outpainted
     frame carries its own dark left, so there is nothing to dissolve into and
     re-masking here would have put the old split back between 901 and 1240. */
/* Unpinned from the list it becomes a full-width band, and 260 was carried
     over from the side panel: 3.79:1 at 901 and 5.06:1 at 1200 against a
     2.07:1 photograph, 41% of it left at the top of the band. Ramped at 2.8:1, the widest this one takes. The ceiling is the breakpoint. Below 900 this
     is overridden back to the flat 260 - see the note there, where a DIFFERENT
     photograph is swapped in and the arithmetic changes with it. */
  .dims__art {
    position: static; transform: none; width: 100%;
    /* THE FILE’S OWN SHAPE (2026-09-14, Jacob: the stacked AI hero was
       "cropping too much off the top" - "same issue is happening for the
       picture down below?"). The 2.8:1 ramp put this 1112x538 (2.07:1) frame
       in a 2.25-2.8:1 band, centre-anchored, which cut the standing pair at
       the hairline from about 600 up. At its own ratio nothing is cropped. */
    height: auto; aspect-ratio: 1112 / 538;
    margin-top: calc(26 * var(--u));
  }
/* THE RESERVED COLUMN GOES WITH IT. .dim keeps a 42.6% margin-left holding
     space for a photograph that is position:absolute at 1241 and a static band
     from here down - so below 1240 that margin was 149 of a 312 row on a phone, and the description set 14 characters a line against dead ivory. The
     reservation moved from padding-right to margin-left in the 2026-09-05
     refresh and this override was never moved with it; padding-right: 0 has
     been zeroing something that is already zero ever since. */
  .dim { padding-right: 0; margin-left: 0; }
  /* STACKED, IT RAMPS. Under the copy the art starts at the 513 it has beside
     the copy at the shell, so nothing changes size as the layout stacks at
     1240, and falls with the viewport to the full 342 column at 390:
       w = 20.12vw + 263.5px    1240 -> 513,  900 -> 445,  700 -> 404,  390 -> 342
     min() hands over to the column below that, 272 at 320. Running it the full
     column instead made it 1088 at 1240 - twice its desktop size the moment the
     layout stacked - and a flat 513 cap held still from 1240 to 560. */
  .agentic .wrap { grid-template-columns: minmax(0, 1fr); gap: calc(36 * var(--u)); }
  .agentic__art { width: min(100%, calc(20.12vw + 263.5px)); }
}
/* THE ROW STACKS AT 940, NOT AT 900 WITH EVERYTHING ELSE. Its three cards plus
   two operators need 931px of content box; the shell gives 824 at 901, so the
   row overflowed its own centred flex line and spilled BOTH ways - the first
   card sat at x-11 and the last ran 11px past the viewport, measured per pixel
   from 901 to 929. The icons are the reason it does not simply compress: the
   circles are literal (--circle-lg: 84px, "an accent's size is a system
   decision"), so at --s .8333 every other part of the card shrank 17% around
   them and they did not.

   940 is the measured 929 rounded up to the next ten, for the copy to breathe.
   Wrapping the flex line was the other candidate and is worse: it leaves the
   gold "=" orphaned at the end of line one. This stacks into the composition
   the phone build already uses, 40px higher up. */
@media (max-width: 940px) {
  /* STACKED, THE EQUATION IS A CENTRED COLUMN. Stretched to the full width the
     cards were left-aligned text in a centred section, with the operators
     centred between them and 400px of empty ivory to the right of every card
     at 700. The section headline and the closing CTA are both centred, so the
     cards are too: 420 wide - "Pattern recognition" and its neighbours on the
     lines they are written to - with the square above each heading. */
  .human__row { grid-template-columns: minmax(0, 1fr); max-width: 420px; margin-inline: auto; }
  .human__op { justify-content: center; padding-block: calc(10 * var(--u)); }
  .hcard { justify-items: center; text-align: center; }
}

@media (max-width: 900px) {
/* Separately composed mobile artwork, anchored low - the hero-artwork
     manifest and the build reference both say not to crop the desktop SVG. */
  /* Stacked: the photograph becomes a band under the copy rather than a
     side panel, and the feather comes off since there is nothing to feather
     into. No mobile crop was supplied - see SPEC-ai-transformation.md. */
  /* THE BAND'S HEIGHT IS FIXED AND ITS WIDTH IS NOT, which is the same fault
     the card photo had one breakpoint up: 300px against a viewport running
     360 to 900 is 1.20:1 at the bottom of the range and 3.00:1 at the top, and a 1.78:1 photograph loses 41% of itself by 900. 300 is not a measured
     number - no mobile crop was supplied - so it becomes the FLOOR rather than
     the value, and above ~750 the band grows with the viewport at 2.5:1, which
     is the widest this photograph takes before the crop rule bites. The phone
     keeps exactly the 300 it had. */
  /* ONE SHAPE ACROSS THE STACKED RANGE, AND IT KEEPS THE HEADS (2026-09-14,
     Jacob, on a ~695px screenshot: "its cropping too much off the top"). The
     clamp above made a 300-400 band that ran 1.30:1 at 390 and 2.5:1 by 700
     against the 900x712 mobile crop, centre-anchored - so past about 500 the
     window slid down the frame and took the tops of four heads with it.

     The people occupy y140-620 of 712 (hair to the table edge). A 9:5 band
     shows 500 of the 712, and anchoring at 57% puts that window at y120-620:
     (712 - 500) x .57 = 121. Same ratio at every width, so the crop is the
     same at 390 as at 900. In a 700-tall window the headline starts at 524 at
     695 wide and is whole on the first screen; at 900 the band is 500 and only
     its first line is. */
  .ahero { padding-top: 0; }
  .ahero .wrap { padding-top: var(--sp-flat); }
  .ahero__art {
    position: static; transform: none; width: 100%;
    height: auto; aspect-ratio: 9 / 5;
    -webkit-mask-image: none; mask-image: none;
  }
  /* AND NO SCRIM ON THE STACKED BAND. The scrim exists so the H1 can sit on
     the photograph; here the photograph is a band above the copy and the copy
     is on solid navy, so the only thing a scrim would do is mute the picture -
     measured contrast on the phone is 14.73:1 with it gone. */
  .ahero__art::after { background: none; }
  /* The crop swap is in the MARKUP, as a <picture> media source - see the note
     there. A CSS content: url() would have worked visually and been invisible
     to check:layout, which reads naturalWidth and would have gone on reporting
     the wide frame's 2.99:1 in a 1.20:1 band forever. */
  .ahero__art img { object-position: 50% 57%; }   /* y121-621 of 712 - see the band note above */
  .ahero .rule-short { margin-top: calc(28 * var(--u)); }
  .ahero__body { margin-top: calc(26 * var(--u)); }
  .ahero__btns { margin-top: calc(30 * var(--u)); flex-direction: column; align-items: flex-start; gap: calc(18 * var(--u)); }
  .ahero__labels { flex-wrap: wrap; gap: calc(14 * var(--u)) calc(26 * var(--u)); justify-content: flex-start; }
  .conv__grid { grid-template-columns: 1fr; max-width: none; }
/* Ramped to what 901 renders (28/38 of the shell), not left on its phone
     pair - see --pw. Same shape as every other override in this block. */
  .conv__grid p { font-size: calc(26px - 2.6676 * var(--pw) / 510);      /* 26 -> 23.3324 */
                  line-height: calc(32px - .3346 * var(--pw) / 510);     /* 32 -> 31.6654 */
                  padding: calc(20 * var(--u)) 0; border-left: 0 !important; }
  .conv__grid p + p { border-top: var(--hairline) solid var(--rule); }
  .dim { flex-wrap: wrap; gap: calc(12 * var(--u)); min-height: 0; }
  .dim__icon { width: var(--circle); height: var(--circle); }
/* The biggest single step left after the token ramp: .acta is --t-h2 above
     900 and was a flat 26 below it, so the closing line jumped 26 -> 46.
     RE-ENDED 46 -> 48 with --t-h2 on 2026-09-11. This ramp hardcodes the step
     it is chasing rather than reading the token, so moving the token left a
     2px jump at exactly 901 - which `npm run verify` caught as a 46px H2 on a
     scale whose h2 is 48. Anything here ending at a scale value has to move
     when that value does; there are no others.
     RE-ENDED AGAIN, 48 -> 43 (2026-09-12): /ai-transformation is an interior
     page, so its section headline is 43 now. Twice in two days is the argument
     for reading the token instead of restating it - and the reason it cannot is
     that a ramp needs two literal ends to interpolate between. The phone end
     drops 26 -> 24 to keep the same proportion it was solved at. */
  .acta { font-size: calc(24px + 19 * var(--pw) / 510);                  /* 24 -> 43 */
          line-height: calc(30px + 22 * var(--pw) / 510); }              /* 30 -> 52 */
  .acta__btns { flex-direction: column; gap: calc(16 * var(--u)); }
}
/* ===========================================================================
   EXPERIENCE PAGE
   Mockup: design/mockup/experience-864.png (864px native, x1.6667).
   Measured in design/SPEC-experience.md. Shares the header, footer, buttons, eyebrow and arrow-link above.
   =========================================================================== */

/* --xband is retired: the industries band now takes --paper-warm from the
   tonal scale rather than its own one-off measurement. */

/* -------------------------------- Hero --------------------------------- */
/* ===========================================================================
   EXPERIENCE HERO - V2 (2026-09-01)
   Mockup: design/mockup/partial/experience-hero-1536.png
   Assets: Taft-Experience-Hero-Assets-V2. 1536 canvas: x0.9375 = build.

   Replaces the invented "architectural linework" SVG with three supplied
   photographs and the bundle's gold connector artwork. The industry row that
   sat between the body and the buttons is not in this drawing and its rules
   went with it. Measured in design/SPEC-experience.md, "The V2 hero".
   =========================================================================== */
/* THE EXPERIENCE PAGE'S TONAL SEQUENCE (Jacob, 2026-09-01):
   header near-white -> hero ivory -> metrics navy -> explanation white ->
   featured engagement pale warm -> cards white on ivory -> industries deeper
   beige -> technology navy -> CTA ivory -> footer navy. */
/* ON THE 1200 SHELL like every other block (2026-09-03). This hero used to
   push its .wrap to 1360 and claw the left edge back with padding, so the
   copy column and the photo group shared 80 extra px. The H1 was width-solved
   against that wider column; at 1088 "complex environments." wraps where the
   drawing breaks it, which is the same break, so nothing was lost. */
.xhero { background: var(--paper); position: relative; overflow: hidden; }
/* band y98-752 = 654 -> 511 rendered; the group (449) plus 30 above and 32
   below is the whole height, so the copy is centred against it. */
/* copy x52-1501 of the container, group x823-1500 = 53.2% / 46.8% */
/* 70/30, measured not guessed: "complex environments." sets at 737px on the
   display step, and 64fr of the 1088 content column gave 696 - so the drawing's
   two lines broke to three with "complex" orphaned. 70fr gives 762. */
/* 50/50 (2026-09-05 refresh). The superseded drawing gave the group 30% of the
   hero; experience-762 sets its leftmost card at x375 of a 66-694 content
   column, so the group is 319 of 629 = 50.7%. The H1's three drawn lines come
   back with it: at 70fr the copy column held "complex environments." on one
   line and the drawing breaks it. */
/* START, NOT CENTER (2026-09-14, Jacob: "the experience and about heros have
   too much top padding/spacing. have them be akin to the how we engage page").
   /services sets its H1 on the section padding, 35 at the shell. Here the copy
   was centred against the photo group and then pushed down by 33 above the
   rule and 38 above the H1, so the H1 started at 97. The copy now starts on the
   padding line like /services; the rule-to-H1 gap is /services’ own 20. */
.xhero__inner { display: grid; grid-template-columns: 50fr 50fr; align-items: start; }
/* 53.2/46.8 was the drawing's split, solved when the H1 was 59 build. At 70
   rendered "complex environments." needs 640 and the old column gave 523. */
/* no gap: the split IS the gap - the copy runs to 422 inside a 579 track. */
/* min-width:0 on both tracks: a grid item defaults to min-width:auto, so the
   photo column refused to shrink below the images' intrinsic 1600px and blew
   the whole grid past the shell. */
.xhero__copy, .xhero__group { min-width: 0; }
.xhero__copy { max-width: none; }
/* The rule moved ABOVE the headline: the drawing sets it under the eyebrow, x52-152 = 101x1 -> 95 build, where the old hero had a dash beneath the H1. */
.xhero .rule-short {
  margin-top: 0;   /* was 33u - see .xhero__inner */
  width: var(--rule-long-w); height: var(--hairline);
}
.xhero h1 {
  margin-top: calc(20 * var(--u));   /* was 38u; /services’ H1-to-rule gap */
  font-family: var(--serif); font-weight: 400;
  /* --t-display -> --t-h1, 64 -> 53 (2026-09-12): an interior hero takes the
     secondary-hero step. The old width solve was "complex environments." = 518
     at display; the authored break still lands, now on a shorter line. */
  font-size: var(--t-h1); line-height: var(--t-h1-lh);
  color: var(--navy);
}
.xhero__body {
  margin-top: calc(34 * var(--u));
  font-size: var(--body); line-height: var(--body-lh);   /* was 18 build = 15 rendered, the only
                                                            hero lede on the site not at --body */
  max-width: calc(560 * var(--u));      /* widest line x52-591 = 540 -> 422 rendered; +54 for
                                             the larger size, so the drawn 3 lines still hold */
  color: var(--ink-body);
}
.xhero__btns { margin-top: calc(48 * var(--u)); display: flex; align-items: center; gap: calc(34 * var(--u)); }
/* the button height is the system's 50, not this hero's measured 53 - rule 7 */

/* --- the photographic group ------------------------------------------- */
.xhero__group { position: relative; }
/* height:auto is load-bearing. The width/height HTML attributes map to the
   CSS width/height properties as presentational hints, so height was definite
   at the file's own 960/1050 and aspect-ratio was ignored - the panels came
   out full-resolution tall. No width here either: it would outrank the
   per-panel widths below on specificity. */
.xhero__utility, .xhero__tiles img { display: block; height: auto; object-fit: cover; }
/* The connector artwork sits BEHIND the panels - in the drawing every line
   stops at a photo edge rather than crossing it - and bleeds left of the
   group, which is where its left-hand nodes and stubs live. Its own
   preserveAspectRatio plus object-fit:contain keep it undistorted. */
.xhero__overlay {
  position: absolute; z-index: 0; pointer-events: none;
  top: 0; height: 100%;
  left: calc(-74 * var(--u)); width: calc(100% + 74 * var(--u));
  object-fit: contain;
}
/* utility: the dominant panel, inset inside the group - x923-1374 of
   x823-1500 = 14.75% in, 66.7% wide, 452x293. */
.xhero__utility {
  position: relative; z-index: 1;
  margin-left: 14.75%; width: 66.7%;
  aspect-ratio: 452 / 293;
}
/* two EQUAL tiles, per the bundle manifest - the drawing measures 298 and
   313, which is its own noise. Gap 67 -> 63, top gap 48 -> 45. */
.xhero__tiles {
  position: relative; z-index: 1;
  margin-top: calc(45 * var(--u));
  display: grid; grid-template-columns: 1fr 1fr; gap: calc(63 * var(--u));
}
.xhero__tiles img { width: 100%; aspect-ratio: 305 / 234; }
/* ---------------------------- Stats (navy) ----------------------------- */
/* The Experience ledger USED to be a second copy of the metric band - same
   navy, same gold numerals, same job, its own class names and its own
   padding. It is now the shared `.stats` component (STYLE-GUIDE rule 1:
   shared components keep their treatment across pages), which also settled a
   real difference between the two drawings: the home band underlines each
   figure in gold and the Experience ledger does not. One treatment, and it is
   the one that was measured. */

/* --------------------- How to read + featured case --------------------- */
.selected { background: var(--tone-elevated); }
.selected__head { text-align: center; }
.selected__head .rule-short { margin-inline: auto; width: var(--rule-long-w); height: var(--rule-h); }
.selected h2 {
  margin-top: calc(15 * var(--u));   /* cap 18 -> 30 */
  color: var(--navy)}
.selected__lede {
  margin-top: calc(12 * var(--u)); max-width: calc(900 * var(--u)); margin-inline: auto;
  font-size: calc(19 * var(--u)); line-height: calc(28 * var(--u)); color: var(--ink-body);
}
.legend {
  margin-top: calc(26 * var(--u));
  display: grid; grid-template-columns: 1fr 1fr; gap: calc(60 * var(--u));
  /* The pair does NOT span the container. Measured y700-790: icon x95-152,
     rule x170, copy x191-358; second item icon x431-488, rule x506, copy
     x527-760 - a block of x95-760 = 666 -> 1110 build inside a 1306 container.
     Left uncapped it filled 1088 and each copy column ran ~85% wide, which ran
     both classifications onto long single lines instead of the two the mockup
     sets them on. */
  max-width: calc(1110 * var(--u)); margin-inline: auto;
}
/* CARDS (2026-09-05 refresh), like every other pair or trio in this pass. */
.legend__item {
  background: var(--tone-elevated); border: var(--hairline) solid var(--rule);
  padding: calc(16 * var(--u)) calc(22 * var(--u));   /* was 22/26 */
}
/* The supplied icons carry their own gold ring (r=42 of 96 = 0.875 of the
   box), so no CSS circle and no filter - the bundles forbid both. */
/* Size, inset and radius come from the shared icon square. */
/* THE INNER LEFT RULE IS GONE (2026-09-12). It divided the icon from the
   copy when they sat side by side; the icon is above the copy now, so the rule
   was a hairline down the left of a text block with nothing on the other side
   of it. The two mobile blocks that zeroed it for the stacked phone layout no
   longer have anything to zero. */
/* The second classification is two lines by design and must not be shortened
   (see SPEC-experience.md). At 1200 the column is 384 wide and the label needed
   394 at 14px, so it broke to three colliding lines. 13px fits it on the two the
   markup asks for; the min-height holds both items' copy on one line, which is
   how the mockup draws them. */
.legend__item .eyebrow { line-height: var(--lh-tight); }
/* min-height 40u removed 2026-09-12:
                                 both labels set on one line at every width from 360 up, so it was
                                 reserving a second line neither of them uses */
.legend__item p:last-child { margin-top: calc(10 * var(--u)); font-size: calc(17 * var(--u)); line-height: calc(25 * var(--u)); color: var(--ink-body); }

.case {
  background: var(--tone-canvas);   /* the ground separates it; RULE 3 removes
                                      the border that used to as well */
  margin-top: calc(28 * var(--u));
  /* 640/666. The drawing's 548/758 was scaled from a 1306 container; inside
     1088 it left the body 456px, and the four metrics need 424 of that before
     padding - so every label broke to three lines. An even split gives 533. */
  display: grid; grid-template-columns: 640fr 666fr;
  /* no border: the --tone-canvas ground already separates it from the white */
  /* min-height: calc(500 * var(--u)) = 417 removed 2026-09-12 - "reduce the
     selected-experience feature from 892px by placing its information closer
     to the image". The floor WAS the distance: the copy needs about 340, so
     the photograph's cell was being held 77 taller than anything in it and the
     body's text sat centred in the slack. The image is a grid cell with
     object-fit, so it takes whatever height the copy sets and the two now meet
     at the copy's own height. */
}
.case__body { padding: calc(22 * var(--u)) calc(26 * var(--u)); }   /* was 32/36; "placing its
                                information closer to the image" is this padding and the floor above */
.case h3 {
  margin-top: calc(14 * var(--u));
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h3); line-height: var(--t-h3-lh);   /* cap 18 -> 30; base gap 28 -> 47.
                                           42/47 MEASURED, BUILT 34/38: the
                                           mockup's 42px sat in a 500px measure
                                           and the 1200 shell leaves 383, where
                                           it broke to four lines. 42 x 383/500
                                           is 32, so 34 keeps the mockup's own
                                           size-to-measure ratio and restores
                                           the two lines the markup sets. */
  color: var(--navy);
}
.case__client { margin-top: calc(10 * var(--u)); }   /* was serif 24 navy - see the client line above */
/* scaled with the heading above, and the
                                         extra 4px stops it reading as a fifth
                                         heading line */
.case__lede { margin-top: calc(12 * var(--u)); max-width: calc(470 * var(--u)); font-size: var(--body-sm); line-height: var(--body-sm-lh); color: var(--ink-body); }
.case__metrics { margin-top: calc(26 * var(--u)); display: flex; }
.case__metrics div { padding-inline: calc(8 * var(--u)); }   /* 13 MEASURED - see below */
.case__metrics div:first-child { padding-left: 0; }
.case__metrics div + div { border-left: var(--hairline) solid var(--rule); }
.case__metrics b {
  display: block;
  font-family: var(--serif); font-weight: 400; font-size: calc(27 * var(--u)); line-height: 1.1;
  color: var(--navy);
}
/* 33/14 and 13px of inset MEASURED, BUILT 27/13 and 8. The four metrics need
   424px at the mockup's own proportions and the 1200 shell leaves 383, so every
   label broke to three lines where the mockup draws two. */
.case__metrics span { display: block; margin-top: calc(8 * var(--u)); font-size: var(--body-xs); line-height: var(--lh-tight); color: var(--ink-muted); }
.case .btn { margin-top: calc(24 * var(--u)); height: calc(48 * var(--u)); }
/* .case__art img - see the grouped rule above */
/* .case__art .missing - see the grouped rule above */

/* -------------------- Leadership experience cards ---------------------- */
.xcards { background: var(--paper); }
.xcards__head { text-align: center; }
.xcards h2 {
  margin-top: 2px;   /* cap 15 -> 25 */
  color: var(--navy)}
.xcards__grid {
  margin-top: calc(18 * var(--u));
  display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(20 * var(--u));   /* row spacing 26 -> 20 */
}
/* FOUR leadership cases since 2026-09-08, not the three the mockup drew: the
   client's deck carries four and the fourth had no page at all. Three columns
   would orphan it on its own row, so four sit 2x2. The card, its photograph
   frame and its type are untouched; only the column count moves
   (STYLE-GUIDE rule 1). */
.xcards__grid--4 { grid-template-columns: repeat(2, 1fr); }
/* .xcard - folded into .card */
/* was 20 (2026-09-12: "reduce card padding") */
/* THE IMAGE HEIGHT CANNOT COME DOWN, and this is the one instruction of the
   2026-09-12 pass that is refused by a check rather than by judgement. "Reduce
   card padding, image height, and row spacing" - the padding and the row
   spacing did; 193 is the floor for the height.
   The four cards sit 2x2, so the frame is 509 wide. experience-utility.webp is
   1.81:1, so at 193 the frame is already 2.64:1 and 69% of the photograph
   survives the crop. 180 keeps 64% and 158 keeps 56%, and check:layout fails
   both: "PHOTOGRAPH STARVED - too little of the frame survives". Taking height
   out of a frame that is already wider than its photograph does not tighten the
   card, it throws away the picture. Measured at 180 and 158, both reverted. */
/* SCOPED TO THE VARIANT, not the component: the drawn frame here is 232 where
   home's is 170, because the card is wider. One component, one default
   (--card-img-h), and a section that measures differently says so. */
.card--panel .card__art { height: calc(232 * var(--u)); }   /* photo y1150-1288 = 135 -> 232 */
/* .card__art img - see the grouped rule above */
/* .card__art .missing - see the grouped rule above */
/* .xcard h3 - folded into .card */
/* .xcard__client - folded into .card */
/* .xcard__metric - folded into .card */
/* .xcard .btn - folded into .card */

/* ---------------------------- Industries ------------------------------- */
.sectors-band { background: var(--paper-warm); text-align: center; }
.sectors-band .rule-short { margin: 0 auto; width: var(--rule-long-w); height: var(--rule-h); }   /* margin-top was the eyebrow gap */
.sectors-band h2 {
  margin-top: calc(14 * var(--u));   /* cap 16 -> 26.7 */
  color: var(--navy)}
.sectors-band__row { margin-top: calc(14 * var(--u)); display: grid; grid-template-columns: repeat(5, 1fr); }
.sector { padding-inline: calc(24 * var(--u)); }
/* The dividers go: the refreshed drawing runs the five sectors as a plain row
   of ringed marks with their labels, and the hairlines were the last thing on
   the page still splitting a row that reads perfectly well on its spacing. */
.sector img { width: var(--circle); height: var(--circle); margin-inline: auto; }
.sector span {
  display: block; margin-top: calc(10 * var(--u));
  font-family: var(--serif); font-size: calc(19 * var(--u)); line-height: calc(26 * var(--u)); color: var(--navy);
}
.sectors-band__more { margin-top: calc(24 * var(--u)); font-size: var(--body-xs); color: var(--ink-body); }

/* ---------------------- Technology environments ------------------------ */
.platforms { text-align: center; }
.platforms h2 {
  color: var(--on-navy)}
.platforms__row {
  /* 13.3 -> 25 (2026-09-12, Jacob: "add a little spacing between the
     Experience across complex... header and the pipe separated company names
     right under it"). The headline sets on two lines here, so 13 read as the
     row being part of it rather than a list under it. */
  margin-top: calc(30 * var(--u));
  display: flex; flex-wrap: wrap; justify-content: center;
  font-size: calc(17 * var(--u)); color: var(--on-navy);
}
.platforms__row span { padding-inline: calc(22 * var(--u)); }
.platforms__row span + span { border-left: var(--hairline) solid var(--rule-on-navy); }

/* -------------------------------- CTA ---------------------------------- */
/* Ground only - see the interior closing block. */
.xcta { background: var(--paper); }
/* .xcta__body is gone: the client cut the subline (Michael Blanchard, 2026-09-08), leaving headline + button. The button takes the home CTA's
   measured headline-to-button gap of 20 rather than the 10 it used under the
   subline -- .cta is the same shape on the same narrative band, so the value
   is measured rather than guessed. */
/* .xcta .btn's 20u and 44u are gone (2026-09-12). It IS in the grouped rule,
   and this line sits 1300 lines below it at equal specificity, so it won on
   source order and defeated both of the group's declarations: the gap stayed
   20u when the token went to 44u, and the height stayed 36.7px against the
   system's 41.7 - the shortest closing button on the site, on the page that
   also has the tightest. Grouping a rule does not group what overrides it. */

/* ------------------------------ Responsive ----------------------------- */
@media (max-width: 1240px) {
  .xhero__inner { gap: calc(28 * var(--u)); }
  .case { grid-template-columns: 1fr; }
/* Same fault as .cexp__art above, same shape of fix. The case photograph is
     a 555-wide panel beside its copy at 1241 (1.33:1); stacked, it runs the
     full 1088 content column and kept its 300, which is a 4.35:1 slot for a
     1.52:1 photograph - 35% surviving, the worst frame on the site after the
     AI hero. It is the CONTENT COLUMN here rather than the viewport, so the
     ramp takes the gutters off. 2.1:1 is this photograph's limit. */
  .case__art {
    height: max(calc(300 * var(--u)), calc((100vw - 2 * var(--gut)) / 2.1));
  }
  .legend { gap: calc(34 * var(--u)); }
}
@media (max-width: 900px) {
/* Stacked: the copy sits above the photographs and the dominant panel runs
     the full width. The bundle manifest asks for exactly this, and for the
     connector overlay to come off below 768 - it is drawn for the desktop
     arrangement and reads as loose gold lines once the group stacks. */
  .xhero__inner { grid-template-columns: 1fr; gap: calc(34 * var(--u)); }
  .xhero__group { margin-right: 0; }
  .xhero__utility { margin-left: 0; width: 100%; }
  .legend { grid-template-columns: 1fr; }
  .case__body { padding: calc(26 * var(--u)) calc(22 * var(--u)); }
  .case__metrics { flex-wrap: wrap; gap: calc(18 * var(--u)) 0; }
  .case__metrics div { flex: 0 0 50%; padding-left: 0; border-left: 0 !important; }
/* The card photo's 232 is a two-up measurement and the grid is one-up here, so the card doubles in width and the photo does not follow: 2.83:1 at 768
     rising to 3.34:1 at 900, against 1.81:1 photographs. Same floor-and-ramp
     as the other stacked bands; 232 still stands below ~600, where the card is
     narrow enough for it. */
  .xcards__grid { grid-template-columns: 1fr; }
/* The floor keeps the card photo from letterboxing as the one-up card grows;
     the CEILING keeps it from going too TALL at 360, where the card is 272 and
     a flat 232 made a 1.17:1 frame for a 1.81:1 photograph. The card is the
     content column less its 40px of inset. */
  .card--panel .card__art { height: clamp(calc((100vw - 2 * var(--gut) - 40px) / 2.6),
                              calc(232 * var(--u)),
                              calc((100vw - 2 * var(--gut) - 40px) / 1.3)); }
  .sectors-band__row { grid-template-columns: repeat(2, 1fr); row-gap: calc(26 * var(--u)); }
  .sector { border-left: 0 !important; }
  .platforms__row span { padding-inline: calc(12 * var(--u)); border-left: 0 !important; }
  .xhero__btns { flex-direction: column; align-items: flex-start; gap: calc(18 * var(--u)); }
}
/* ===========================================================================
   ABOUT PAGE
   Mockup: design/mockup/about-890.png (890px native, x1.6180).
   Measured in design/SPEC-about.md. Shares the header, footer, buttons, eyebrow and arrow-link above.

   Every heading here is sized by MEASURED LINE WIDTH, not cap height. The cap
   reads on this mockup come out inconsistent enough to put the H1 (57px) below
   the section H2s (59-61px), which cannot be right; the width solve gives a
   coherent 65 / 48 / 47 / 40 scale. See SPEC-about.md.
   =========================================================================== */

.page-about {
  --white-band: #FFFFFF;
  /* The five --ed-gap-* tokens moved to :root on 2026-09-12 - see the note
     there. They were declared here, and .ed became a site-wide component. */
}
/* -------------------------------- Hero ---------------------------------
   Copy only, since 2026-09-03. The mockup drew a 52.8 / 47.2 split with the
   portrait panel starting at x470 of the 890 canvas and bleeding to the page
   edge - that is why the split lived on the section rather than inside the
   centred container. With the portrait gone the section is one column and the
   copy sits in the ordinary 1200 shell. */
/* The hero carries the brand art now (2026-09-05 refresh), so it is a
   positioning context and it clips. */
/* ---------------------------- Photograph heroes -------------------------
   .phero: an interior hero with a photograph behind the copy above 900 and a
   crop of it above the copy at 900 and below. Written for /services
   (2026-09-14, Jacob: "use this as the hero, and crop it for when the words
   dont go over the image") and shared with /about the same day, whose image
   has the same shape - a dark left third and the people on the right - so it
   takes the same crop, scrim and floor rather than a copy of them.
   Navy ground, like Home and AI.
   The frame sits in the content shell as Home’s does - photographs stay on
   the shell, grounds bleed - and the copy is on its left 40%, which is the
   dark glass wall of the drawing and holds nobody.

   The floor is 480: the copy is three short blocks, so without one the band
   would be ~220 tall and the 1.78:1 frame would show a strip of heads. Home’s
   430 was tried first and check:layout failed it - 1180x430 is 2.74:1 and kept
   65% of the frame; 480 is 2.46:1 and keeps 72%.
   The copy is centred in it rather than parked on the padding, because a
   photograph wants the picture above and below its words, not below only. */
.phero { position: relative; overflow: hidden; min-height: 480px; display: grid; align-content: center; }
/* width: 100% - a grid item with auto inline margins shrinks to its content
   and centres, which put the 433px copy block in the middle of the photo. */
.phero > :not(.phero__art) { width: 100%; position: relative; z-index: 1; }   /* copy above the art */
.phero__art {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: min(100%, var(--wrap)); pointer-events: none;
}
.phero__art picture { display: block; width: 100%; height: 100%; }
/* 30% vertical: the standing man’s hair is y118 of 941, and at the shell the
   480-tall band shows 480 of the 665 the frame scales to, so the window starts
   at (665 - 480) x .30 = 56 - his head clears the top by ~30. Right-anchored,
   so between 901 and 1240, where the band is narrower than 1.78:1, the crop
   comes off the empty wall rather than off the presenter. */
.phero__art img { width: 100%; height: 100%; object-fit: cover; object-position: 100% 30%; }
/* The left of the frame is already near-black glass, so the scrim only has to
   carry the lede where it reaches the seated man’s shoulder. Navy, clearing
   before the presenter. */
.phero__art::after {
  content: ""; position: absolute; inset: 0;
  /* Measured with the copy hidden, 90th-percentile luminance behind each
     block: .80/.70/.30 left the lede at 4.02:1 at 1440, under 4.5. */
  background: linear-gradient(90deg,
    rgba(4, 35, 77, .88)  0%,
    rgba(4, 35, 77, .82) 36%,
    rgba(4, 35, 77, .45) 50%,
    rgba(4, 35, 77, 0)   64%);
}
@media (max-width: 900px) {
  /* STACKED: the words come off the picture. The band is the crop at its own
     9:5, so nothing further is cut, and the copy sits on solid navy under it -
     no scrim, no floor, no centring. The photo meets the header; the copy
     takes the section’s top padding. */
  .phero { min-height: 0; display: block; padding-top: 0; }
  .phero__art { position: static; transform: none; width: 100%; aspect-ratio: 9 / 5; }
  .phero__art img { object-position: center; }
  .phero__art::after { background: none; }
  .phero > :not(.phero__art) { padding-top: var(--sp-flat); }
}

/* Ground, photograph and floor come from .phero and .sec--navy. This carried
   background: var(--paper), which sits below .sec--navy in the file at the
   same specificity and would have won. */
.ahero-about__inner {
  /* A direct child of the section, so it needs the cap the section used to
     give it - its split panels are content, and content stays at 1200. */
  max-width: var(--wrap); margin-inline: auto;
  /* ONE COLUMN. The founder portrait was removed 2026-09-03 (Jacob), and with
     it the 52/48 split the bundle's IMPLEMENTATION.md called for. Nothing is
     left to sit beside the copy, so the grid would be a one-cell grid. */
  /* The copy has always set this row: 26 words once ran 862px tall because the
     placeholder was a 900x1200 SVG and an in-flow image's intrinsic aspect
     outranks a min-height. With no image the copy decides outright, and this
     floor is what keeps the hero from collapsing to the height of five lines
     of type.
     467 -> 410 (2026-09-12): "reduce the 620px opening hero to approximately
     480-520px". The floor stays for the reason above - there is no image to
     set this row, so without it the hero is five lines tall - it is just lower.
     410 plus the section's 2x35 lands the band at 480. */
  /* THE FLOOR IS GONE (2026-09-14, Jacob: "too much top padding/spacing ...
     akin to the how we engage page"). 410 with the copy centred in it put the
     H1 84px down at 1440 and 57-92 elsewhere, against /services’ 35. The copy
     now sets the band the way it does on /services: section padding, copy,
     section padding. The 480-520 band from 2026-09-12 was a height asked for
     at that time; this asks for the /services spacing instead, so the band
     comes out at whatever the copy is. */
  display: grid; align-content: start;
}
.ahero-about__copy {
  padding-inline: var(--gut);
  /* The right pad was 24 because the portrait panel bled past it. It is the
     full gutter now, so the copy sits in the same 1088 content width as every
     other section on the page. */
}
/* The copy column is wide now that the portrait is not taking half the hero, so the copy carries the measure itself: a headline set 791 wide reads as a
   banner, and its three drawn lines looked stranded in it. 660 is the column
   those line breaks were solved against. */
/* THE COPY STOPS WHERE THE ART STARTS (2026-09-05 refresh). It ran the full
   1088 while the hero's right side was empty; the new drawing ends the
   headline at x537 of a 756 content column and begins the mark at x540.
   820 held the three authored line breaks of the headline this hero shipped
   with. The client replaced it on 2026-09-08 with two shorter lines, the
   longer of which ("Making it stick is harder.") sets well inside the cap.
   The cap stays because what it does is keep the copy clear of the art from
   800 on, which is a property of the drawing and not of the headline.

   THE H1 STAYS AT --t-h1 56, and this is a deliberate drift. Width-solved
   against the drawing it wants 49.9: the drawn line is 494 raster = 711
   build = 592 rendered, and 56 renders it at 664. 49.9 is not a step - it
   falls between --t-h1 56 and --t-h2 46 - and About is a secondary page, which is exactly what --t-h1 is for (STYLE-GUIDE rule 2). So the rule wins
   over the drawing and the headline runs 12% wider than drawn. Recorded in
   SPEC-about.md under Known differences. */
.ahero-about__copy > * { max-width: calc(820 * var(--u)); }   /* was 940; the art takes the column from 800 */
/* The drawing breaks the body after "change": ink x42-413 = 372 raster = 535
   build over two lines. --measure 680 broke it a word and a half later. */
.ahero-about__body { max-width: calc(560 * var(--u)); }   /* was --measure 680 */
.ahero-about h1 {
  margin-top: 0;   /* was 26u; the H1 sits on the section padding, as .shero h1 does */
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h1); line-height: var(--t-h1-lh);   /* Re-solved for the 1200 shell against the
                                           real column, 554px. The binding line is the
                                           THIRD - "and people move together." - which
                                           renders 546 at 46px and 570 at 48. So 46 is
                                           the largest that holds the mockup's three
                                           lines. Leading scaled with it. */
  color: var(--on-navy);   /* was --navy, on ivory */
}
.ahero-about .rule-short { margin-top: calc(30 * var(--u)); width: var(--rule-long-w); height: var(--rule-h); }
.ahero-about__body {
  margin-top: calc(30 * var(--u));   /* measure comes from .ahero-about__copy > * */
  font-size: calc(21 * var(--u)); line-height: calc(34 * var(--u)); color: var(--on-navy);   /* was --ink-body */
}
.ahero-about .btn { margin-top: calc(34 * var(--u)); }
/* A LONGER SCRIM THAN /services’. This headline is two long lines - "Making
   it stick is harder." runs to 53% of the frame, into the lit meeting room -
   where /services’ stops at 36%. On the shared scrim it measured 4.10:1 at
   1440 and 3.28:1 at 901 (90th-percentile backdrop, copy hidden): over the
   3:1 large-text floor, but by 0.28 at 901. The hold moves right to cover
   the headline and still clears before the man at the wall, who starts at 64%. */
/* AND IT STACKS AT 1080, NOT 900. Its headline is 585 wide and the man at the
   wall starts at 64.3% of the frame: 889 at the shell against a headline
   ending at 761, but 707 at 1100, 643 at 1000 and 579 at 901, where "harder."
   ran into his face. The headline clears him by 50 at 1080, so below that the
   photograph goes above the copy - the same stacked band .phero uses at 900.
   The <picture> source in about.html switches to the crop at the same width. */
@media (min-width: 901px) and (max-width: 1079px) {
  .ahero-about.phero { min-height: 0; display: block; padding-top: 0; }
  .ahero-about .phero__art { position: static; transform: none; width: 100%; aspect-ratio: 9 / 5; }
  .ahero-about .phero__art img { object-position: center; }
  .ahero-about > :not(.phero__art) { padding-top: var(--sp-flat); }
}
@media (min-width: 1080px) {
  .ahero-about .phero__art::after {
    background: linear-gradient(90deg,
      rgba(4, 35, 77, .90)  0%,
      rgba(4, 35, 77, .86) 44%,
      rgba(4, 35, 77, .55) 56%,
      rgba(4, 35, 77, 0)   68%);
  }
}
/* the system height, not this hero's measured 76 */

/* ----------------------- Editorial prose sections -----------------------
   The client's About deck (2026-09-08) is seven prose sections and a metric
   band, where the page shipped with six sections of which four were bespoke.
   ".ed" is the ONE component all seven prose sections use, so the page has
   one set of intra-section gaps instead of seven, and adding a section is a
   markup change rather than a new block of CSS.

   Its two-column prose block IS the old founder block: the 442/388 measured
   column widths, the 945 cap and the gold hairline at 49.8% of the gutter are
   that block's measured values, unchanged. The founder section is now one
   ".ed" among seven rather than a section of its own, which is why ".founder"
   is gone from this file.

   ".statement", ".pov" and ".expect" are gone with it: the client's deck has
   no copy for the navy statement band, the three-column point of view or the
   three-item expectation list. See the note at the top of about.html. */
/* .ed with no ground class inherits the site default, which is --paper via
   "main > section" near the top of this file - NOT the body's --tone-canvas.
   That is worth knowing before assigning a tone: the hero is also --paper, so
   the first section after it has to declare a ground or the two merge into
   one surface with no visible boundary (STYLE-GUIDE rule 1). */
.ed--ivory { background: var(--tone-canvas); }
.ed--white { background: var(--white-band); }
/* The eyebrow above this block went 2026-09-12, so --ed-gap-head has nothing
   left to space it from: the block is the section's first element now. */
/* ===========================================================================
   THE BIG TEXT BLOCK, ON /about's FIVE EDITORIAL SECTIONS (2026-09-12, Jacob:
   "remember that fix for that text block ... can you apply that to the about
   page?" - the How we scale treatment from earlier the same day).

   Three measures rather than one, the same three:

     headline            720
     introductory line    700
     the prose            760

   WHAT DOES NOT TRANSFER IS THE CENTRING, and that is not a judgement call
   about this page: How we scale was ALREADY centred before that fix - the fix
   set three max-widths and turned the alignment OFF for the last two
   paragraphs. These sections are left-aligned to begin with, so there is
   nothing to turn off. The three measures are the fix; the centring was the
   section it landed in.

   760 IS WIDER THAN THE 600-680 HE SET FOR INTERIOR BODY COPY on the 12th.
   The big-text-block instruction is the later and more specific of the two, so
   it wins here, and it only wins inside .ed - every other paragraph on the
   site still caps at --measure 640.

   The pull line keeps --measure-lede 620. It is a 34px serif statement, not
   prose, and a 760 measure would run it long - the one part of the block that
   is a different role rather than a different width. */
.ed__inner { margin-top: 0; }/* .ed h2 - every declaration it had is now the shared headline */

.ed__body {
  margin-top: var(--ed-gap-para); max-width: 760px;   /* was --measure 640 */
  font-size: var(--body); line-height: var(--lh-lede); color: var(--ink-body);
}
/* Whatever the headline is followed by takes the wider headline gap. */
/* The introductory sentence - the paragraph directly under the headline -
   takes 700, as it does in How we scale. */
.ed h2 + .ed__body { max-width: 700px; }
/* ...and it is the one paragraph that takes the section's own alignment, so a
   centred section gets a centred opening sentence and left-aligned prose
   under it. That is the three-measure shape he set for How we scale. */
.ed--centred h2 + .ed__body { text-align: center; }
/* THE PROSE COLUMN, and why it is a modifier rather than the default.
   .ed--centred and .ed--centred.ed--prose look like the same section and are
   not. "We focus on the distance" is a headline over five SHORT SENTENCES, one
   per line, and its closing line reads as a centred statement. "The expertise
   required by the transformation" is a headline over two PARAGRAPHS, and those
   want a left-aligned reading column - the 760 he specified on the 12th.

   Rendered with the 760 column as the default, whytaft's two closing sentences
   sat left-aligned in a 760 box while the four lens cards beneath them ran the
   full 1088, so each line started 164px inside the column it was closing. The
   measure was right and the alignment was wrong, which is the tell that these
   are two variants of one component rather than one shape. */
/* .ed--prose is gone (2026-09-12): "the top part is centered? but the bottom
   two paragraphs are left aligned? WTF". Every line in a centred prose section
   is centred - headline, the sentence under it, and every paragraph after it.
   No exceptions and no variant. The 760-left reading column that used to live
   here was the treatment specified for How we scale on the 12th; it is
   superseded by one styling for the whole family. */
.ed h2 + .ed__body,
.ed h2 + .ed__cols,
.ed h2 + .ed__list,
.ed h2 + .ed__sig { margin-top: var(--ed-gap-block); }
/* The sentence a section turns on. --t-h3 is the step below the section
   headline, so it reads as the line being emphasised rather than as a second
   headline. Not a new size (STYLE-GUIDE rule 2). */
.ed__pull {
  margin-top: var(--ed-gap-pull); max-width: var(--measure-lede);
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h3); line-height: var(--t-h3-lh); color: var(--navy);
}
.ed__pull + .ed__body,
.ed__pull + .ed__cols { margin-top: var(--ed-gap-pull); }
/* One step down, for a pull that is a RUN of short sentences rather than a
   single line. The leadership section's is five of them, and at --t-h3 it set
   five lines of 34px serif directly under its own section headline and
   outweighed it. Still a step on the scale, not a new size. */
.ed__pull--sm { font-size: var(--t-h4); line-height: var(--t-h4-lh); }
/* THE GOLD RULE SITS BETWEEN THE COLUMNS, not down the left of the block -
   the 2026-09-05 mockup refresh moved it there and set eyebrow, headline, both body columns and the name flush to the container. Raster x417-418 of
   a 41-796 container = 49.8%, spanning the body rows and nothing else, so it
   is on the row rather than on the section. */
.ed__cols {
  margin-top: var(--ed-gap-block);
  /* ONE COLUMN (2026-09-10, the client). All four of these blocks on /about
     were two paragraphs side by side with a gold rule between them, measured
     off the mockup at 442fr/388fr in a 945 build cap. His note, with four
     screenshots: "Can we make the bottow paragraphs into one column instead
     of two? ... The same with this one ... This is another one that doesnt
     look quite right. And another."

     So the mockup's two-column split is out, and the divider with it - it
     divided two columns and there is one. The paragraphs now stack at the
     site's own --measure, which is what a single column of prose wants. */
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: calc(20 * var(--u));
  max-width: 760px;   /* was --measure 640 */
}
/* max-width: none, because these are plain <p> and main p caps every
   paragraph on the site at --measure 640 - widening the CONTAINER to 760 does
   nothing for the lines inside it. Rendering the page is what showed it: the
   column was still breaking at 624. */
.ed__cols p { max-width: none;
              font-size: var(--body); line-height: var(--lh-lede); color: var(--ink-body); }
/* measured: col 1 runs FIVE lines over 205 build, which is what sets the size - 18px only made four */

/* Three declaratives the client sets on their own lines. A hairline above
   each row and one closing it, no box: a border means "this is one clickable
   thing" (STYLE-GUIDE rule 3), and these are prose. */
.ed__list { margin-top: var(--ed-gap-block); max-width: 760px; }   /* was --measure */
.ed__list li {
  border-top: var(--hairline) solid var(--rule);
  padding: var(--ed-gap-row) 0;
  font-size: var(--body); line-height: var(--lh-lede); color: var(--ink-body);
}
.ed__list li:last-child { border-bottom: var(--hairline) solid var(--rule); }
/* The leadership entry. Both values are the founder signature's own: the name
   takes the h4 step - one above the body, so it reads as a name and not
   another paragraph - and the title is the site eyebrow verbatim. */
.ed__sig { margin-top: var(--ed-gap-block); }
.ed__name {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h4); line-height: var(--t-h4-lh);
  color: var(--navy);
}
.ed__role { margin-top: calc(6 * var(--u)); }
.ed__sig + .ed__cols { margin-top: var(--ed-gap-pull); }
/* A second entry (2026-09-14, Ricardo Williams). A hairline over the name
   separates one person from the next without a box (STYLE-GUIDE rule 3), at
   the 760 prose measure so it ends where the paragraphs do. */
.ed__body + .ed__sig {
  margin-top: var(--ed-gap-block); padding-top: var(--ed-gap-block);
  max-width: 760px; border-top: var(--hairline) solid var(--rule);
}

/* --------------------------------- CTA ---------------------------------- */
/* Ground only - see the interior closing block. */
.about-cta { background: var(--paper-warm); }
/* The client's close is two sentences on two lines (2026-09-08). The second
   takes the page's paragraph gap rather than the 12 that separates the first
   from the headline. */
.about-cta p + p { margin-top: var(--ed-gap-para); }
/* .about-cta .btn's 18u and 62u are gone (2026-09-12), the same override as
   .xcta's: 51.7px tall against the system's 41.7, and an 18u gap. The 62 was
   measured off the drawing (y1647-1688) and the grouped rule's own note
   already says the system height replaces four measured ones - this was the
   fourth, left behind. */

/* ------------------------------ Responsive ------------------------------ */
@media (max-width: 1240px) {
/* was `grid-template-columns: 1fr 1fr` - it restated the two-column split
     the base rule no longer has, and would have put it back below 1240. */
  .ed__cols { gap: calc(18 * var(--u)); }
}
@media (max-width: 900px) {
  .ahero-about__copy { padding-inline: calc(24 * var(--u)); }   /* was 40u top and bottom as well, on top of the section’s own padding */
/* .ed__cols is one column at every width now, so only the gap steps. */
  .ed__cols { gap: calc(24 * var(--u)); }
/* The phone step of the scale, as the hero body and the old founder columns
     already took it. The single-column prose and the list follow them, or the
     page would drop to --body-sm in its two-column block and stay at --body
     everywhere else. */
  .ed__cols p, .ahero-about__body, /* --body-sm is the PHONE step down from --body; above 900 these are --body
     again, and ramped that is 17.5 -> 17.4993 - flat to within a thousandth of
     a pixel. Written as the constant it is rather than a fake ramp. */
  .ed__body, .ed__list li { font-size: 17.5px; line-height: var(--lh-lede); }
}
/* ===========================================================================
   CONTACT PAGE
   Mockup: design/mockup/contact-946.png (946px native, x1.5222).
   Measured in design/SPEC-contact.md. Shares the header, footer, buttons, eyebrow and rule above. The first page on the site with a form.
   =========================================================================== */

/* -------------------------------- Intro --------------------------------- */
/* THE INTRO GOES LEFT (2026-09-05 refresh) and takes the brand art. It was
   centred; the new drawing sets the eyebrow, the headline, the body and the
   gold dash all flush to the container's left edge with the mark filling the
   right, exactly as the About hero does. The two heroes are now one pattern
   rather than two. */
.cintro { position: relative; overflow: hidden; }
/* .cintro .wrap - see the grouped rule above */
.cintro > .wrap > * { max-width: calc(820 * var(--u)); }
.cintro h1 {
  margin-top: 0;   /* was 22u, an eyebrow gap */
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h1); line-height: var(--t-h1-lh);   /* Contact is a secondary page:
     the h1 step, not display. At 70 its headline wanted 1239 against a 1088
     column and broke to four lines; at 56 it holds the drawing's two. */   /* line 1 measures 950 build; base gap 57 -> 87 */
  color: var(--navy);
}
.cintro__body {
  margin-top: calc(24 * var(--u));   /* was 34 */
  font-size: var(--body); line-height: var(--lh-lede);
  color: var(--ink-body);
}
.cintro .rule-short { margin: calc(28 * var(--u)) 0 0;   /* was 42 */ width: var(--rule-long-w); height: var(--rule-h); }

/* ---------------------------- The form panel ---------------------------- */
.cpanel { background: var(--tone-elevated); }
.cpanel__inner {
  display: grid; grid-template-columns: 43.2% 56.8%;   /* navy runs to x413 of the
                                                          911-wide panel */
  border: var(--hairline) solid var(--rule);
  /* NO min-height. The drawing's 1143 was the panel's height in a 1440 shell
     where the form ran two fields to a row; in 1200 the form is the taller
     column on its own, and forcing 952 left ~290px of empty navy under the
     aside's last line. The taller column sets the height, as it should. */
}
/* THE HEADLINE STARTS WHERE THE FORM’S DOES (2026-09-14, Jacob: "this has too
   much top padding on the contact page ‘Start with the work that needs to
   change.’"). 66u of padding plus a 42u margin on the H2 put it 90px into the
   panel, against 35 for "Tell us what you’re working through." beside it.
   Both panels now open on the form’s 42u, so the two headlines share a line. */
.cpanel__aside { background: var(--navy); color: var(--on-navy); padding: calc(42 * var(--u)) calc(56 * var(--u)); }
.cpanel__aside h2 {
  margin-top: 0;   /* was 42u */
  font-size: var(--t-h3); line-height: var(--t-h3-lh);   /* was 36/52. 42/64 MEASURED: the aside
                                           column is 356 wide in the 1200 shell
                                           where 1440 gave 564, and at 42px the
                                           markup's two lines broke to four.
                                           42 x 356/564 is 35, so 36 is the
                                           mockup's own size-to-measure ratio. */
  color: var(--on-navy)}
.cpanel__lede {
  margin-top: calc(42 * var(--u));
  font-size: var(--body-sm); line-height: var(--lh-lede);
  color: var(--on-navy-muted);
}
.cpanel__aside .rule-short { margin-top: calc(70 * var(--u)); width: var(--rule-long-w); height: var(--rule-h); }
.cpanel__focus { margin-top: calc(73 * var(--u)); font-size: calc(19 * var(--u)); color: var(--on-navy); }
.focus-list { margin-top: calc(56 * var(--u)); display: grid; gap: calc(49 * var(--u)); }
.focus-list li {
  display: flex; align-items: baseline; gap: calc(30 * var(--u));
  font-size: calc(19 * var(--u)); color: var(--on-navy);
}
.focus-list span {
  font-family: var(--serif); font-size: calc(30 * var(--u)); color: var(--gold);
  flex: none; min-width: calc(42 * var(--u));
}

.cpanel__form { background: #FFFFFF; padding: calc(42 * var(--u)) calc(46 * var(--u)); }   /* y400-451 = 51 -> 78 */
.cpanel__form h2 {
  font-size: var(--t-h3); line-height: var(--t-h3-lh);   /* was 34; heading measures 493 build */
  color: var(--navy)}

/* --------------------------------- Form --------------------------------- */
.cform { margin-top: calc(22 * var(--u)); }   /* was 30 */
.cform__row + .cform__row { margin-top: calc(18 * var(--u)); }   /* was 24 */   /* field bottom -> next label 19 -> 29 */
.cform label {
  display: block;
  font-size: var(--body-xs); line-height: 1.3; color: var(--ink-body);
}
.cform input[type="text"],
.cform input[type="email"],
.cform textarea {
  display: block; width: 100%;
  margin-top: calc(9 * var(--u));   /* was 12: "reduce form-field and label spacing slightly".
                       The FIELD HEIGHTS ARE UNTOUCHED - "leaving the form comfortable to use". */
  height: calc(50 * var(--u));                        /* y519-551 = 33 -> 50 */
  padding: 0 calc(14 * var(--u));
  font: inherit; font-size: calc(17 * var(--u)); color: var(--ink-body);
  background: #FFFFFF;
  border: 1px solid rgba(4, 35, 77, .22);
  border-radius: 0;                    /* nothing on this site has a radius */
}
.cform textarea { height: calc(116 * var(--u)); padding: calc(12 * var(--u)) calc(14 * var(--u)); resize: vertical; line-height: 1.5; }
.cform input:focus, .cform textarea:focus {
  outline: none; border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(198, 146, 47, .25);
}

.cform__areas { margin-top: calc(20 * var(--u)); border: 0; padding: 0; }   /* was 26 */
.cform__areas legend { padding: 0; font-size: var(--body-xs); color: var(--ink-body); }
.cform__areas label {
  display: flex; align-items: center; gap: calc(12 * var(--u));
  margin-top: calc(8 * var(--u)); font-size: calc(17 * var(--u)); color: var(--navy);
}
.cform__areas input {
  width: calc(18 * var(--u)); height: calc(18 * var(--u)); flex: none; margin: 0;
  accent-color: var(--navy);
}
.cform button { margin-top: calc(20 * var(--u));   /* was 26 */ height: calc(64 * var(--u)); }   /* y1026-1067 = 42 -> 64 */
.cform__privacy { margin-top: calc(16 * var(--u)); font-size: var(--eyebrow); line-height: var(--lh-tight); color: var(--ink-muted); }
/* --- What is missing goes red, and says so ------------------------------
   The border is driven by the ATTRIBUTE rather than by a class, so there is
   one selector, no class anyone can forget to remove, and the state a screen
   reader reads is the same state the eye sees. public/assets/forms.js sets it.

   A border colour alone fails WCAG 1.4.1 and is invisible to roughly one man
   in twelve, so .field__error below is what carries the meaning. */
.cform input[aria-invalid='true'], .cform textarea[aria-invalid='true'], .cform input[aria-invalid='true']:focus, .cform textarea[aria-invalid='true']:focus { border-color: var(--bad); }
/* forms.js creates this next to its input, inside .cform__row, and reuses it
   between submits - so it is present and empty most of the time. */
.field__error {
  margin-top: calc(6 * var(--u));   /* half the 12 above a label, so the
                                       reason reads as part of its field */
  font-size: var(--eyebrow);
  line-height: var(--lh-tight);
  color: var(--bad);
}
.field__error:empty { display: none; }
/* The line that says what actually happened. It is never written
   optimistically - see api/contact.js and the script on contact.html. The
   min-height reserves its row, so a status arriving does not shift the privacy
   line and the page under it. */
.cform__status {
  margin-top: calc(13 * var(--u));
  font-size: var(--eyebrow);
  line-height: var(--lh-tight);
  min-height: calc(20 * var(--u));
}
.cform__status--ok  { color: var(--good); }
.cform__status--bad { color: var(--bad); }

/* ---------------------------- What happens next -------------------------- */
.cnext { text-align: center; }
.cnext h2 {
  margin-top: 0;   /* was 16u, an eyebrow gap */
  font-family: var(--serif);   /* measures 546 build */
  color: var(--navy)}
/* CARDS, and a gold dash under the numeral (2026-09-05 refresh) - the same
   move the About point-of-view columns used to make. The short gold
   connector the drawing runs
   BETWEEN the cards is a ::after on the gap, at the dash's own height, so the
   three read as a sequence rather than three separate panels. */
.cnext__grid {
  margin-top: calc(26 * var(--u));   /* was 40 */
  display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(16 * var(--u));
}
.cnext__grid li {
  position: relative;
  border: var(--hairline) solid var(--rule);
  padding: calc(18 * var(--u)) calc(26 * var(--u)) calc(22 * var(--u));   /* was 26/30/32 */
}
.cnext__grid li + li::before {
  content: ""; position: absolute; top: calc(64 * var(--u));
  left: calc(-16 * var(--u)); width: calc(16 * var(--u));
  height: var(--hairline); background: var(--rule);
}
.cnext__n { font-family: var(--serif); font-size: calc(42 * var(--u)); line-height: 1; color: var(--gold); }
/* The dash under the numeral: --rule-short-w, the site's own dash. */
.cnext__n::after {
  content: ""; display: block; width: var(--rule-short-w); height: var(--rule-h);
  background: var(--gold); margin: calc(12 * var(--u)) auto 0;
}
.cnext__grid h3 {
  margin-top: calc(18 * var(--u));
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h4); line-height: var(--t-h4-lh); color: var(--navy);
}
.cnext__grid p { margin-top: calc(14 * var(--u)); font-size: calc(17 * var(--u)); line-height: calc(26 * var(--u)); color: var(--ink-body); }
/* -------------------------------- Closing -------------------------------- */
/* ------------------------- Partner band --------------------------------
   A SECTION THE REFRESH ADDS. It sits between "What happens next" and the
   closing line: a panel on the content column, a gold rule down its left, the question in the serif and the invitation under it, with a small
   gold-outline button on the right. Copy is read off contact-928 verbatim. */
.cpartner { background: var(--paper); }
.cpartner__inner {
  background: var(--tone-elevated);
  border-left: calc(4 * var(--u)) solid var(--gold);
  padding: calc(26 * var(--u)) calc(30 * var(--u));
  display: flex; align-items: center; gap: calc(30 * var(--u));
}
.cpartner h2 {
  font-size: var(--t-h3); line-height: var(--t-h3-lh)}
.cpartner p { margin-top: calc(6 * var(--u)); font-size: var(--body-sm); line-height: var(--body-sm-lh); color: var(--ink-body); }
.cpartner .btn { margin-left: auto; flex: none; }
/* Ground only - see the interior closing block. It carries no button, because
   the page it closes IS the contact form. */
.cclose { background: var(--paper-warm); }

/* ------------------------------- Responsive ------------------------------ */
@media (max-width: 1240px) {
  .cpanel__inner { min-height: 0; }
  .cpanel__aside, .cpanel__form { padding: calc(44 * var(--u)) calc(36 * var(--u)); }
  .cnext__grid li { padding-inline: calc(24 * var(--u)); }
}
@media (max-width: 900px) {
/* Same as .ed__body above: --body-sm on the phone, --body at 901, and the
     two ends are the same number. */
  .cintro__body { font-size: 17.5px; line-height: var(--lh-lede); }
  .cpanel__inner { grid-template-columns: 1fr; }
  .cpanel__lede, .focus-list li, .cpanel__focus { line-height: var(--lh-lede);
    font-size: calc(17px - 1.1673 * var(--pw) / 510); }                /* 17 -> 15.8327 */
  .focus-list span { line-height: calc(48px - 23.001 * var(--pw) / 510); }
/* 48 -> 24.999 */
  .cpanel__aside .rule-short, .cpanel__focus, .focus-list { margin-top: calc(28 * var(--u)); }
  .focus-list { gap: calc(22 * var(--u)); }
  .cnext__grid { grid-template-columns: 1fr; row-gap: calc(34 * var(--u)); }
  .cnext__grid li + li::before { display: none; }   /* a sideways connector in a stack */
  .cpartner__inner { flex-direction: column; align-items: flex-start; gap: calc(20 * var(--u)); }
  /* The base rule’s margin-left: auto pins the button to the right of the row;
     stacked it pushed it right of the column too, so align-items did nothing
     (2026-09-14, Jacob: "should be left aligned when its on its own row"). */
  .cpartner .btn { margin-left: 0; }
}
/* ===========================================================================
   EXPERIENCE DETAIL - the reusable engagement template
   Mockup: design/mockup/experience__recruiting-workforce-process-transformation-905.png
   Measured in design/SPEC-experience-detail.md. 905 canvas: x1.5912 = build.

   This page is the sixth in a set, not a new design system. Container, type
   scale, circles, dashes, buttons and grounds are the site's existing tokens;
   only this page's own geometry is measured here.
   =========================================================================== */
.page-xdetail { --white-band: #FFFFFF; }

/* --------------------------- Hero (navy) -------------------------------- */
.dhero { position: relative; overflow: hidden; }
/* ground: .sec--navy */
/* THE LIFT GOES ON THE COPY, NOT ON .wrap. What has to sit above the
   photograph is the 48% copy column; .wrap is the full 1200 box, so lifting it
   put a transparent element over the whole art panel - which starts at 48% and
   runs to the wrap's own right edge. Nothing looked wrong, because .wrap paints
   nothing, but it took every hit test in the right half of the hero: the four
   pages still carrying a `<div class="missing">` placeholder reported their
   "Photograph pending" text as covered, and any link ever placed over the
   photograph would have been dead. .dhero__art is positioned against .dhero, which is the `position: relative` above, so .wrap was never its containing
   block and stands down to static here without moving anything. */
/* .dhero__copy - see the grouped rule above */
/* Copy 48%, photo 52% - the photo's left edge measures x436 of 905. */
.dhero__copy { width: 48%;
}
/* The copy ran flush to the photograph, which only showed once a long client
   name arrived: "Major Academic Healthcare System" set its first line to the
   full 48% and touched the photo. The mockup's own gap is measured - the
   rightmost copy ink at x391 against the photo edge at x437, 46 of 905 = 73
   build.
   IT GOES ON THE WRAPPING ELEMENTS, NOT THE COLUMN. Padding the column
   narrows it for everything, and the H1's line breaks are hand-set against
   the full 48%: at 73px less, the recruiting page's headline broke "Workforce
   Process" across two lines and the drawn three became four. The H1 is
   excluded on purpose - its breaks are authored per page and visible in the
   markup, so it does not need a guard. */
.dhero__crumbs, .dhero__client, .dhero__tags, .dhero__kind {
  padding-right: calc(73 * var(--u));
}
/* THE LEDE JOINED THE LIST (2026-09-12), and the note it replaces predicted
   exactly how it would go wrong: "a very long lede is the one case still
   unguarded, and it is visible the moment it is written."

   It was excluded because it carried its own 458 cap from the drawing, so it
   never reached the photograph. That cap came off in this pass - at 382 it set
   the ERP page's lede on five lines and "bring hero text and photography
   closer together" is the instruction - and the moment it had the full 48%
   column it ran under the picture: "affecting employees, processes, systems
   and" crossed the photo edge. Caught by looking at the render, not by a
   check; nothing overflows and no boxes intersect, because the photograph is
   absolutely positioned.

   So the guard, not the cap. 522 less the measured 61 is 461, which is still
   wider than the 382 it had, so the lede keeps the line it gained and the
   recruiting page's two drawn lines are unaffected. The H1 stays excluded: its
   breaks are authored per page and visible in the markup. */
.dhero__lede { padding-right: calc(73 * var(--u)); }
/* THE PHOTOGRAPH IS BEHIND THE COPY NOW (2026-09-14, Jacob: "many of those
   pages have the images on the right, and the words on the left.. is there
   room to put the words on top of the blank images spaces on the left?").
   It was a 52% panel beside the copy - a tall slice of a wide photograph that
   kept 27-65% of it. It now fills the content box, as .hero__art and .phero__art
   do, and the copy sits on its left.

   Six of the nine photographs were re-supplied the same day as extensions of
   the originals with a dark, empty left 40%; the other three (enterprise
   change, digital, utility) already had room on the left. ANCHORED RIGHT, so
   whatever a narrower box has to lose comes off that empty left rather than
   off the people. */
.dhero__art {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: min(100%, var(--wrap)); pointer-events: none;
}
.dhero__art img { object-position: 100% 40%; }
/* The scrim carries the copy: held over the copy column (48% of the box at the
   shell) and clear before the subject. Measured per page with the copy hidden,
   90th-percentile backdrop behind each text block (2026-09-14). */
.dhero__art::after {
  content: ""; position: absolute; inset: 0;
  /* .96/.92 over the copy, because the copy includes the gold eyebrow and gold
     tops out at 5.58:1 on solid navy - there is little headroom to give away.
     At .92/.86 the utility page’s sunset put the eyebrow at 4.3:1. */
  background: linear-gradient(90deg,
    rgba(4, 35, 77, .96)  0%,
    rgba(4, 35, 77, .92) 42%,
    rgba(4, 35, 77, .60) 54%,
    rgba(4, 35, 77, 0)   68%);
}
/* BETWEEN 901 AND 1179 THE COPY STAYS ON THE PHOTOGRAPH (2026-09-14, Jacob:
   "the hero body should go on top of the left of the image, not below").
   The first answer stacked it below 1180, because under cover the photograph
   was cropped to the section’s height: as the box narrowed the section grew
   taller, the crop came off the left, and the subject slid under a copy column
   that did not narrow with it - at 1100 "Model Transformation" ran over the
   woman in white.

   So in this band the photograph stops covering. It keeps its OWN ratio at the
   box’s full width, pinned to the top, which holds the dark left at the ~40% of
   the width it is at the shell whatever the section height does; and it fades
   out at the bottom into the navy ground, so copy that runs below the picture
   sits on navy it was already sitting on.

   Its own ratio, not 16:9 (2026-09-14, Jacob: "you are cropping it.. there is
   plenty of room to his left"). A fixed 16:9 box was right for the six 1672x941
   extensions and cut 22% off the left of digital’s 1888x833 (2.27:1), moving the
   man in the suit from 43% of the box to 27%, under the densest scrim. Height
   auto on the image lets each file set its own box. */
@media (min-width: 901px) and (max-width: 1179px) {
  .dhero__art img { height: auto; }
  .dhero__art {
    bottom: auto; height: auto;
    -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 70%, transparent 100%);
  }
}
/* STACKED AT 900: a band above the copy with no scrim, 3:2 anchored right so it
   leaves out part of the empty left the desktop copy sat on and keeps the
   subject, capped at 560. 3:2, not 4:3: digital’s frame is 2.27:1 and 4:3
   kept 59% of it (check:layout); 3:2 keeps 66%. The copy takes the section’s
   top padding; the photo meets the header. */
@media (max-width: 900px) {
  .dhero { padding-top: 0; display: flex; flex-direction: column; }
  .dhero__art { position: static; transform: none; width: 100%; height: auto; aspect-ratio: 3 / 2; max-height: 560px; }
  .dhero__art::after { background: none; }
  .dhero > .wrap { padding-top: var(--sp-flat); }
  /* --band-zoom, set on one page’s .dhero__art, trims the empty left from the
     stacked band when that page’s subject sits further right than the rest.
     Scaled from the right, so the subject stays put and the dark wall is what
     goes; desktop is untouched because the copy sits on that wall there. Kept
     under the file’s own size at the widest band: 900x560 draws a 1672x941
     file at 995 wide, so 1.6 is 1592. */
  .dhero__art img { transform: scale(var(--band-zoom, 1)); transform-origin: 100% 40%; }
  .dhero > .dhero__art { order: -1; }
  .dhero__copy { width: 100%; max-width: 720px; }
  /* The 73u guard that kept these off a photograph beside them has nothing to
     guard: at 390 it set the lede 13 characters a line in a 342 column. */
  .dhero__crumbs, .dhero__client, .dhero__tags, .dhero__kind, .dhero__lede { padding-right: 0; }
}

.dhero__crumbs { font-size: calc(17 * var(--u)); color: var(--on-navy-muted); }
.dhero__crumbs a { color: var(--on-navy-muted); }
.dhero__crumbs a:hover { color: var(--gold); }
.dhero__crumbs span { margin-inline: calc(10 * var(--u)); }
.dhero__crumbs span[aria-current] { margin-inline: 0; color: var(--on-navy); }
/* THE MARK SITS ABOVE THE CLASSIFICATION, NOT BESIDE IT (2026-09-05, Jacob).
   Drawn inline: a ~40 glyph, then the classification. Built inline it pushed
   the classification 96 right of the H1, the breadcrumb, the dash, the lede
   and the tags - every other line in the hero starts at the column edge and
   the one label that names the page did not. The cause is a departure this
   SPEC already records: the supplied ringed asset renders at 84, not the
   drawn ~40, so a modest lockup became a shove.

   Stacked, the whole hero has one left edge. The two gaps are the drawing's
   own numbers, turned through 90 degrees:
     mark -> classification  drawn horizontally x85-x103 = 18 -> 29 build
     classification -> H1    drawn y153-y185     = 32 -> 51 build
   and each is then leading-corrected the way every other gap in this file is.
   -B/-A are the half-leadings the boxes carry above and below their ink:
   the ring's own transparent margin inside its 96 box (5/96 -> 4.4 at 84), the eyebrow's (16.8 box, 10.1 cap -> 3.35), the H1's (66 lh, 38.4 cap
   -> 13.8, the same 13.8 the inline 18 was solved against). */
.dhero__kind {
  margin-top: calc(20 * var(--u));   /* was 30 */
  display: flex; flex-direction: column; align-items: flex-start;
  gap: calc(14 * var(--u));   /* was 21; 29 -B(ring) 4.4 -A(eyebrow) 3.35 */
}
/* The ring hangs slightly outside the column, so the CIRCLE aligns with the
   flat left edge below it rather than its bounding box doing - which is what
   the drawing does too, setting the glyph at x44 against text at x46.
   -8 = the ring's 4.4 of transparent box + that drawn 2 -> 3.2 overhang. */
.dhero__kind img {
  width: var(--circle); height: var(--circle); flex: none;   /* --circle-lg -> --circle,
                       84 -> 72: "slightly reduce the practice icon" (2026-09-12) */
  margin-left: calc(-8 * var(--u));
}
/* AND THE SAME RING AT THE HERO'S SIZE, for the four capability heroes only.
   .dhero__kind serves two page families: the capability pages hold a practice
   icon, now a file trimmed to its ink, and the five /experience heroes hold
   icon-taft-client-engagement / icon-transformation-leadership-experience,
   which still draw their own ring. Putting the frame on ".dhero__kind img"
   gave those five a ring around a ring and took 6px off all five - caught by
   the snapshot, invisible to every check. So the frame is opted into.

   Both numbers are the ones the capability hero already rendered: the
   superseded drawing put a 92.2%-of-box ring in a 72 box, so the gold circle
   measured 66.4 with 2.8 of transparent box each side, and the -8u pulled that
   box left until the CIRCLE - not its bounding box - sat 3.9 left of the flat
   edge below it. Trimmed art has no such margin, so the box IS the ring at 66
   and the offset drops to what the overhang alone was worth. Same diameter,
   same left edge, new figure. */
.dhero__kind .icon-ring {
  width: 66px; height: 66px;
  --ring-pad: 10px;                       /* figure 63.6% of the ring, as .astep__c */
  margin-left: calc(-4.6 * var(--u));     /* was -8u against a 2.8 transparent margin */
}

.dhero h1 {
  margin-top: calc(24 * var(--u));   /* was 34; 51 -B(eyebrow) 3.35 -A(h1) 13.8; and before that
                                          18, measured off the MARK's bottom
                                          when the two shared a row */
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h1); line-height: var(--t-h1-lh);   /* width-set: "Recruiting &" = 272 */
  color: var(--on-navy);
}
.dhero__client {
  margin-top: calc(6 * var(--u));
  font-family: var(--serif); font-weight: 400;
  /* 41.7 -> --t-h3 34 (2026-09-12, "slightly reduce the ... headline scale").
     It was the one display line on the site that sat on NO step of the scale -
     50 build units, between h1 and h2 - and it is the organization name under
     the headline, which is what --t-h3 is for. Two lines of it lose 20. */
  font-size: var(--t-h3); line-height: var(--t-h3-lh);
  color: var(--gold);
}
/* This page draws a LONGER dash than the home hero: x45-81 = 37x2 -> 59x3, against --dash-w 18. Measured, not inherited. */
.dhero .rule-short, .dtrio__about .rule-short { width: var(--rule-long-w); height: var(--rule-h); }
.dhero .rule-short { margin-top: calc(14 * var(--u)); }   /* was 20 */
.dhero__lede {
  margin-top: calc(16 * var(--u));   /* was 22 */
  /* The 458 cap (382 rendered) came off the drawing and put this lede on five
     lines in a 522 column. "Bring hero text and photography closer together":
     at the column it takes four. */
  font-size: calc(24 * var(--u)); line-height: calc(34 * var(--u)); color: var(--on-navy);
}
.dhero__tags { margin-top: calc(22 * var(--u));   /* was 34 */ font-size: calc(19 * var(--u)); color: var(--on-navy-muted); }
.dhero__tags i { color: var(--gold); font-style: normal; margin-inline: calc(12 * var(--u)); }
/* ------------------------- Outcome stats -------------------------------- */
/* The hairline at y655 is a rule under the band, not a ground change: the
   stats and the challenge share one white ground. */
.dstats { background: var(--white-band); }
.dstats__row { display: grid; grid-template-columns: repeat(4, 1fr); }
.dstats__row div { text-align: center; padding-inline: calc(16 * var(--u)); }
.dstats__row div + div { border-left: var(--hairline) solid var(--rule); }
.dstats__row b {
  display: block; font-family: var(--serif); font-weight: 400;
  font-size: calc(56 * var(--u)); line-height: 1.1; color: var(--navy);   /* width-set: "$360K+" = 154 */
}
.dstats__row span { display: block; margin-top: calc(8 * var(--u)); font-size: calc(19 * var(--u)); line-height: 1.35; color: var(--ink-body); }
.dstats__note {
  max-width: none;   /* the border below is the band rule; it spans the column */
  margin-top: calc(26 * var(--u)); padding-bottom: calc(20 * var(--u));
  border-bottom: var(--hairline) solid var(--rule);
  text-align: center; font-size: var(--note); color: var(--ink-muted);
}

/* --------------------------- The challenge ------------------------------ */
.dchal { background: var(--white-band); }
/* rule x565 of a 46-864 container: 63.9% / 36.1% */
.dchal__grid { display: grid; grid-template-columns: 63.9fr 36.1fr; }
.dchal__main { padding-right: calc(56 * var(--u)); }
/* NO USER SINCE 2026-09-12, AND HELD ON PURPOSE. This is the style for a
   headline this section does not have: the deck gives none, and writing one
   for him is what got nine of them removed on 8 September. The label that used
   to sit here was an <h2> styled as an eyebrow - a block label claiming heading
   rank, which is why npm run verify reported an 11.7px H2 off the scale on
   five pages for a fortnight. It is a <p> now, so this rule waits for a real
   headline rather than dressing a label as one.
   PAGE.md's "nothing dead ships" has an exception for something held back with
   its reason attached; this is that, and the reason is the client's copy deck. */
.dchal h2 {
  margin-top: calc(10 * var(--u));   /* width-set: line 1 = 487 */
  /* The measure, not the column, sets the break. The column is 688 wide but
     the mockup breaks after "too" at 487, so "much" has to be denied line 1:
     510 clears line 1 (493) and refuses "much" (573). */
  max-width: calc(840 * var(--u));   /* was 612, solved at the old 40 build H2 */
}
.dchal__main p {
  margin-top: calc(22 * var(--u)); max-width: calc(644 * var(--u));
  /* The copy does NOT run to the rule: measured ink x46-450 = 405 -> 537,
     inside a 688 column. */
  font-size: calc(21 * var(--u)); line-height: calc(32 * var(--u)); color: var(--ink-body);
}

.dglance { border-left: var(--hairline) solid var(--rule); padding-left: calc(50 * var(--u)); }
.dglance dl { margin: 0; }
.dglance dl > div {
  display: grid; grid-template-columns: 34fr 66fr; align-items: baseline;
  padding-block: calc(31 * var(--u));   /* rules at y749 / y807 - a 58 pitch -> 77 rendered */
}
.dglance dl > div + div { border-top: var(--hairline) solid var(--rule); }
.dglance dt { font-family: var(--serif); font-size: calc(23 * var(--u)); color: var(--navy); }
.dglance dd { margin: 0; font-size: calc(19 * var(--u)); line-height: 1.4; color: var(--ink-body); }

/* --------------------------- The work (warm) ---------------------------- */
.dwork { background: var(--paper-warm); }
.dwork h2 {
  margin-top: calc(10 * var(--u));   /* width-set: the line = 532 */
}
/* dividers x292 / x584 of a 46-864 container - three equal steps */
.dwork__steps { margin-top: calc(30 * var(--u)); display: grid; grid-template-columns: repeat(3, 1fr); }
.dwork__steps li { display: flex; align-items: flex-start; gap: calc(20 * var(--u)); padding-inline: calc(24 * var(--u)); }
.dwork__steps li:first-child { padding-left: 0; }
.dwork__steps li + li { border-left: var(--hairline) solid var(--rule); }
.dwork__n {
  flex: none; width: var(--circle); height: var(--circle); border-radius: 50%;
  border: var(--hairline) solid var(--gold);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-size: calc(30 * var(--u)); color: var(--gold);
}
.dwork__steps h3 { font-family: var(--serif); font-weight: 400; font-size: var(--t-h3); line-height: var(--t-h3-lh); color: var(--navy); }   /* width-set: "Understand" = 163 */
.dwork__steps p { margin-top: calc(6 * var(--u)); max-width: calc(270 * var(--u)); font-size: calc(19 * var(--u)); line-height: calc(28 * var(--u)); color: var(--ink-body); }
/* block ink 170 -> 225 */

/* Full-container band, y1075-1140 = 65 -> 103. A crop of the same photograph
   the hero carries, so it is a band of the drawing, not a second picture. */
.dwork__strip { margin-top: calc(34 * var(--u)); height: calc(103 * var(--u)); overflow: hidden; }
.dwork__strip img { width: 100%; height: 100%; object-fit: cover; object-position: center 42%; }
/* ---------------- Leadership-experience detail variants -----------------
   Three detail pages (utility, academic healthcare, national healthcare) run
   the same template as the recruiting page with three differences, all of
   them drawn in the supplied template:

   1. The classification is the LONG form over two lines, so the hero eyebrow
      wraps where the direct-engagement one does not.
   2. No work strip. The direct-engagement page closes The Work with a
      photograph; these close on the steps.
   3. Their copy deck has not landed, so every unwritten string is an
      uppercase PENDING marker rather than filler prose - an obvious gap, which is what the repo asks for over a plausible placeholder.

   All three are temporary except the first. Delete `.pending` markup with the
   strings as the copy arrives; `.dwork--nostrip` and `--two` stay. */
/* The two-line classification needed no variant once the mark moved above it:
   `align-items: flex-start` is now the default and the 6px optical nudge was
   correcting for a vertical centring that no longer happens. The class stays
   on the markup as a hook and is deliberately empty. */
.dwork--nostrip .dwork__steps { margin-bottom: calc(6 * var(--u)); }
/* THE PENDING MARKERS ARE GONE (2026-09-10). `.pending`, `.dstats__pending`
   and `.dimpact__pending` styled the uppercase PENDING strings that stood in
   for copy that had not landed. No page carries one now: the last of them
   were on the four capability pages, and the two sections holding them were
   deleted rather than filled, because his content document has no prose for
   them. Rules removed per PAGE.md - nothing dead ships. The convention itself
   is in git and in the other repos if a page ever needs it again. */

/* -------------------------- The impact (navy) --------------------------- */
.dimpact h2 {
  margin-top: calc(10 * var(--u)); color: var(--on-navy)}
/* FLEX, NOT GRID, SO A SHORT ROW CENTRES (2026-09-14, Jacob: "center the data
   points ... so if there are 2 or 1 data points in a row they are centered and
   not left aligned"). A grid places a wrapped remainder in its first cells, so
   five figures at 3-up left two hard left and at 2-up left one. Each cell is
   still exactly 1/--cols of the row, so a full row is unchanged; only the
   remainder moves, to the middle. The count per width is --cols below. */
.dimpact__row {
  --cols: 4;
  margin-top: calc(30 * var(--u));
  display: flex; flex-wrap: wrap; justify-content: center;
}
.dimpact__row div { flex: 0 0 calc(100% / var(--cols)); min-width: 0; text-align: center; padding-inline: calc(16 * var(--u)); }
.dimpact__row div + div { border-left: var(--hairline) solid var(--rule-on-navy); }
.dimpact__row b {
  display: block; font-family: var(--serif); font-weight: 400;
  font-size: calc(56 * var(--u)); line-height: 1.1; color: var(--on-navy);
}
.dimpact__row span { display: block; margin-top: calc(8 * var(--u)); font-size: calc(19 * var(--u)); line-height: 1.35; color: var(--on-navy-muted); }
.dimpact__note { max-width: none; margin-top: calc(24 * var(--u)); text-align: center; font-size: var(--note); color: var(--on-navy-muted); }
/* ------------- Counts other than four, and the client's own shape --------
   Added 2026-09-08, building the three leadership-experience pages from the
   client's case-study copy (design/SPEC-experience-detail.md).

   His copy supplies TWO scale figures per case and between zero and five
   results, where the drawn row holds four. Same numeral, same rule, same
   label: a different count is not a different treatment (STYLE-GUIDE
   rule 1). */
.dstats__row--2 { grid-template-columns: repeat(2, 1fr); }
.dimpact__row--2 { --cols: 2; }
.dimpact__row--1 { --cols: 1; }
.dimpact__row--5 { --cols: 5; }
/* Five figures in the 1088 column give a 217 cell. His are all three
   characters ("48%"), so they fit, but at the drawn 56 they crowd their own
   dividers. 44 is the same step the row already drops to below 1240. */
.dimpact__row--5 b { font-size: calc(44 * var(--u)); }
/* THE THREE-STEP BLOCK BECOMES LABELLED LISTS. The mockup draws "The work"
   as three numbered steps, each with a name and a sentence, and the client
   supplied neither for any case. What he did supply is lists: "Scale &
   Complexity", "Transformation Focus", "What Changed". So the block carries
   those, under his own labels, and no headline is invented for it.

   Hairline above each row and no box, as rule 3 asks: these are phrases, not clickable things. The recruiting page keeps its numbered steps. */
.dfocus + .dfocus { margin-top: calc(44 * var(--u)); }
.dfocus__list {
  margin-top: calc(20 * var(--u));
  /* MULTICOLUMN, not a grid, and the reason is the client's ordering. A
     2-column grid fills across rows, so his ten focus areas land 1,3,5,7,9
     down the left column and 2,4,6,8,10 down the right - a reader scanning
     one column takes them out of the sequence he wrote. Multicolumn fills
     the first column before starting the second, so down-then-across reads
     his order. break-inside keeps a row off a column boundary. */
  columns: 2; column-gap: calc(56 * var(--u));
}
.dfocus__list li { break-inside: avoid; }
.dfocus__list li {
  border-top: var(--hairline) solid var(--rule);
  padding-block: calc(14 * var(--u));
  font-size: calc(19 * var(--u)); line-height: 1.4; color: var(--ink-body);
}
.dfocus__lede {
  margin-top: calc(18 * var(--u)); max-width: calc(644 * var(--u));
  font-size: calc(21 * var(--u)); line-height: calc(32 * var(--u)); color: var(--ink-body);
}
/* The client's Result prose, and on one case a list under it. Left, with the
   eyebrow and at the body measure, not centred like the metric cells. */
.dimpact__lede {
  margin-top: calc(18 * var(--u)); max-width: calc(644 * var(--u));
  font-size: calc(21 * var(--u)); line-height: calc(32 * var(--u)); color: var(--on-navy);
}
.dimpact .dfocus__list { margin-top: calc(24 * var(--u)); }
.dimpact .dfocus__list li {
  border-top-color: var(--rule-on-navy); color: var(--on-navy-muted);
}
/* One challenge paragraph and no aside, where the client's Business
   Challenge is a single sentence that the hero already carries. */
.dchal__grid--full { grid-template-columns: 1fr; }
.dchal__grid--full .dchal__main { padding-right: 0; }
/* ------------------------------- Trio ----------------------------------- */
/* rules x346 / x638 of a 46-864 container. NOT normalized: 300/291/225 is a
   real third column, narrower than the other two, holding a shorter block. */
.dtrio { background: var(--white-band); }
/* THREE EQUAL COLUMNS (2026-09-12). They were 36.8 / 35.7 / 27.6, measured
   off the drawing, and the third was the narrowest because its heading was the
   smallest. With both card headings now on one step, the narrow track is what
   breaks: "Transformation" at --t-h3 34 needs 241px of min-content, and at
   901-902 the third column had 222 - check:widths caught scrollWidth +4 on all
   five experience detail pages. An equal third is 282 there, so the word fits
   and the band is clean. It also finishes what he asked for: the two columns
   are now the same WIDTH as well as the same heading step. */
.dtrio__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* A GRID ITEM'S min-width IS auto, SO AN fr COLUMN DOES NOT ACTUALLY SHRINK.
   The three tracks are fr, which reads as "these divide whatever there is", but
   each one still refuses to go below its own min-content - so between 901 and
   933, where the desktop grid is on but the shell has not caught up, the third
   column held 222px it did not have and the page scrolled sideways by up to 32.
   Measured per-pixel 901-933 on all four experience pages; clean across
   320-1600 with this line. Above 933 nothing changes: an fr track that already
   fits sizes identically either way. */
.dtrio__grid > * { min-width: 0; }
.dtrio__grid > * + * { border-left: var(--hairline) solid var(--rule); padding-left: calc(34 * var(--u)); }
.dtrio__grid > *:not(:last-child) { padding-right: calc(40 * var(--u)); }/* .dtrio__about h2 - every declaration it had is now the shared headline */
   /* width-set: 280 */
/* THE RAIL'S THREE SUB-TEXTS TAKE ONE SIZE (2026-09-12). They were 14.17,
   14.17 and 15.83 - three parallel slots at two sizes - so the third column
   differed from its neighbours in size as well as colour, which is most of why
   the colour read as a mistake. All three are --body-sm; only the colour
   separates them now, and it separates them by role. */
.dtrio__about p { margin-top: calc(10 * var(--u)); font-size: var(--body-sm); line-height: var(--body-sm-lh); color: var(--ink-body); }
.dtrio__about .rule-short { margin-top: calc(20 * var(--u)); }
.dtrio__col h3 { margin-top: calc(8 * var(--u)); font-family: var(--serif); font-weight: 400; font-size: var(--t-h3); line-height: var(--t-h3-lh); color: var(--navy); }   /* width-set: 298 */
.dtrio__b { margin-top: calc(10 * var(--u)); max-width: calc(294 * var(--u)); font-size: var(--body-sm); line-height: var(--body-sm-lh); color: var(--ink-body); }
/* The measure holds the two lines the mockup draws: its block is 245 wide in
   a 313 column, so without the cap this ran as one line. */
/* THE TWO COLUMNS TAKE ONE HEADING STEP (2026-09-12, Jacob: "there are two
   sections.. but the second one has a smaller header").

   The last column was --t-h4 27 against --t-h3 34 beside it, and that WAS
   measured - the drawing sets NEXT EXPERIENCE at 265 for line 1 against RELATED
   CAPABILITY's 298, a real difference and not noise. It is superseded: the two
   columns do the same job in the same rail, and on the page the difference read
   as one of them being wrong rather than as a hierarchy. The style guide
   outranks the mockup (repo CLAUDE.md), and he has seen the page. */
/* IN THE RAIL IT TAKES THE SUB-TEXT'S INK, not the client gold (2026-09-14,
   Jacob: the "Leading Clinical Research Organization" sub header "should be
   the same blue as 'prepare the organization for new ways of working'"). It
   sits in the slot .dtrio__b fills in the next column, so it matches that.
   The gold stays on .case__client on /experience, where it labels a card. */
.dtrio__client { margin-top: calc(10 * var(--u)); }
/* Two classes: the shared gold rule is :is(.card .card__meta, ...), and :is
   takes its most specific argument, so one class here would lose to it. */
.dtrio__col .dtrio__client { color: var(--ink-body); }
/* EVERY BUTTON IN THE RAIL SITS ON THE SAME LINE (2026-09-12, Jacob: "all
   those buttons in a rail need to be on the same level").

   The columns are grid items and already stretch to equal height, so the
   buttons were only unaligned because each sat at the end of its own text:
   measured at 1440, one at y2081 and the next at y2000, 81px apart. The column
   becomes a flex column and the button takes margin-top: auto, so the slack
   collects above it and the buttons land on the floor together - the same move
   .card .btn makes with align-self: end.

   The 18u is a margin-BOTTOM on the copy above rather than a margin-top on the
   button, because an auto top margin cannot also guarantee a minimum: in the
   tallest column auto resolves to 0 and the button would touch the text. */
.dtrio__col { display: flex; flex-direction: column; align-items: flex-start; }
/* A FLEX ITEM'S min-width IS auto TOO, and the grid's own min-width: 0 does not
   reach inside it: the column is a flex container now, so its children need
   the same release the tracks got. */
.dtrio__col > * { min-width: 0; max-width: 100%; }
.dtrio__col :is(.dtrio__b, .dtrio__client) { margin-bottom: calc(18 * var(--u)); }
.dtrio__col .btn { margin-top: auto; }
/* ------------------------------- CTA ------------------------------------ */
/* Ground only - see the interior closing block. This one is on all nine detail
   pages, which is why its 366 was the tallest of the five. */
.dcta { background: var(--paper-warm); }

/* .dhero__copy went 54% here while the photograph was a panel beside it; with
   the photograph behind the copy the wider column only carried the headline
   further onto the subject, so it stays 48% until the hero stacks. */
@media (max-width: 900px) {
  .dhero__copy { width: 100%; }
/* STACKED, THIS IS A BAND, AND 260 IS A FIXED HEIGHT ON A FLUID WIDTH - the
     same fault as .cexp__art, .case__art and the rest. At 900 it made a 4.15:1
     slot for the 1.33:1 healthcare photograph, 32% of it left.

     ONE RATIO HAS TO SUIT FIVE PHOTOGRAPHS. These heroes carry 1.33, 1.52, 1.67, 1.81 and 2.38:1 files, and the crop rule wants every frame within
     0.65 of its own image: that is ab <= 2.05 for the 1.33 and ab >= 1.55 for
     the 2.38, so the whole set only agrees between 1.55 and 2.05. 1.8 sits in
     the middle of that window and holds all five at 74% or better.

     It replaces the 260 rather than flooring it: at 390 a 260 band is 1.5:1, which already failed the 2.38 photograph at 63% before this change. */

  .dhero__client { font-size: calc(34px + 7.665 * var(--pw) / 510);    /* 34 -> 41.665  */
                   line-height: calc(42px + 6.3314 * var(--pw) / 510); }
/* 42 -> 48.3314 */
  .dchal__grid, .dtrio__grid { grid-template-columns: 1fr; row-gap: calc(30 * var(--u)); }
  .dchal__main { padding-right: 0; }
  .dglance { border-left: 0; padding-left: 0; border-top: var(--hairline) solid var(--rule); padding-top: calc(24 * var(--u)); }
  .dtrio__grid > * + * { border-left: 0; padding-left: 0; border-top: var(--hairline) solid var(--rule); padding-top: calc(24 * var(--u)); }
  .dtrio__grid > *:not(:last-child) { padding-right: 0; }
  .dwork__steps { grid-template-columns: 1fr; row-gap: calc(24 * var(--u)); }
/* columns, NOT grid-template-columns. The list was a grid once and this line
     was left behind when it became multicolumn (see the note on .dfocus__list):
     grid-template-columns does nothing to a non-grid, so the collapse to one
     column has never happened and the list stayed 2-up all the way down. At 320
     that is two 112px columns holding words like "Transformation", which needs
     130 - the ink ran 33px past .dfocus and the page scrolled sideways by 9.
     check:widths has been failing on this page; the sweep missed it because an
     overflowing WORD does not widen the box it is painted in. */
  .dfocus__list { columns: 1; }
  .dimpact__row--5 { --cols: 2; row-gap: calc(28 * var(--u)); }
  .dimpact__row--5 div:nth-child(odd) { border-left: 0; }
  .dwork__steps li { padding-inline: 0; }
  .dwork__steps li + li { border-left: 0; }
}
/* ===========================================================================
   /404 - built from the /contact intro and the interior closing block, so it
   carries no sizes of its own. The only thing it adds: its content is shorter
   than a desktop screen, which left bare canvas under the footer at 1440x900.
   The intro takes up the slack, so the footer sits on the bottom edge.
   =========================================================================== */
body.page-404 { min-height: 100vh; display: flex; flex-direction: column; }
.page-404 main { flex: 1; display: flex; flex-direction: column; }
.page-404 .cintro { flex: 1; }
/* A flex item with auto inline margins no longer stretches, so without this
   the header, both sections and the footer shrink to their content. */
body.page-404 > *, .page-404 main > * { width: 100%; box-sizing: border-box; }
/* ===========================================================================
   NARROW SCREENS - LAST IN THE FILE ON PURPOSE

   These override per-page grids declared hundreds of lines below the page
   media queries, and a media query does NOT beat a later rule of equal
   specificity - which is exactly why the first attempt at this had no effect.
   Anything that has to win over a page block belongs here, at the bottom.

   None of it is drawn in a mockup: no mobile or tablet mockup was supplied
   (SPEC "Assumptions"). It exists so the page does not scroll sideways.
   =========================================================================== */

/* --- 901 to 1240: THE DESKTOP COMPOSITION IN A SHELL IT WAS NOT MEASURED FOR
   The layout switches to its full desktop arrangement at 901, but --wrap does
   not reach 1200 until 1200 - so between those two the three-column
   compositions run in a content column of 816 rather than the 1088 every
   measurement was taken against. Type cannot solve it: scaling the
   composition down to fit 816 would put body copy near 13px. What breaks is
   always the same shape - a LITERAL ring (--circle-lg is 84 at every width, "an accent's size is a system decision") sitting beside text in a third of
   a column - so the fix is the one the site already makes at 900: the mark
   stacks above the words instead of standing beside them. */
@media (max-width: 1240px) {
/* "About this practice" set one word a line - 6.3 characters over three
     lines in a 168px box - on all four capability pages, because an 84px ring
     and a 46px heading were sharing 36.8fr of 816. */
  .dtrio__about { flex-direction: column; gap: calc(18 * var(--u)); }
/* The three challenge tiles, same fault one page over: ring 84 + gap + a
     fixed arrow left 70px for the question, so "Scaling AI across the
     workforce?" set four lines of one and two words. Stacked, the label gets
     the whole card. The arrow goes to the end of the flow rather than the end
     of the row, which is where it already sits when these stack at 900. */
  .tile { flex-direction: column; align-items: flex-start; gap: calc(16 * var(--u)); }
  .tile__arrow { margin-top: auto; }

}
/* THE METRIC ROWS ARE STILL FIVE AND FOUR ACROSS IN THE BAND. At 901 that is
   123px for "Financial Services & Insurance" and 137 for "Improvement in time
   to productivity" - those are labels, not words, and the label is the part you
   read. Three across gives them 272 at the bottom of the band and 362 at the
   top. The --1 and --2 variants are already narrower than three and keep their
   own count.

   BOTH ENDS ARE BOUNDED ON PURPOSE. Below 901 these rows already have a
   treatment - two across at 900, one at 600 - and this must not reach into it:
   written as a plain max-width:1240 it also applied at 601, where three across
   is 137px and no better than five. :where() keeps the --1/--2 exclusion at a
   single class of specificity, so the narrower blocks below still win inside
   their own range rather than being outranked by a longer selector. */
@media (min-width: 901px) and (max-width: 1240px) {
  .sectors-band__row { grid-template-columns: repeat(3, 1fr); row-gap: calc(26 * var(--u)); }
  .sector { border-left: 0 !important; }
  .dstats__row:where(:not(.dstats__row--2)) {
    grid-template-columns: repeat(3, 1fr); row-gap: calc(28 * var(--u));
  }
  .dimpact__row:where(:not(.dimpact__row--1):not(.dimpact__row--2)) {
    --cols: 3; row-gap: calc(28 * var(--u));
  }
  .dstats__row div:nth-child(3n+1),
  .dimpact__row div:nth-child(3n+1) { border-left: 0; }
}

@media (max-width: 900px) {
/* Below the shell --s is 1, so the detail page's 56px outcome figures render
     at full build size in a 552px column and four of them overflowed at 600. */
  .dstats__row { grid-template-columns: repeat(2, 1fr); row-gap: calc(28 * var(--u)); }
  .dimpact__row { --cols: 2; row-gap: calc(28 * var(--u)); }
  .dstats__row div:nth-child(odd), .dimpact__row div:nth-child(odd) { border-left: 0; }
/* line-height is the 1.1 ratio on the base rule, so it follows this. */
  .dstats__row b, .dimpact__row b { font-size: calc(40px + 6.6648 * var(--pw) / 510); }  /* 40 -> 46.6648 */
  .dimpact__row--5 b { font-size: calc(40px - 3.335 * var(--pw) / 510); }                /* 40 -> 36.665  */
  .chain { flex-wrap: wrap; justify-content: center; }
/* CAPABILITY CARDS RAN TOGETHER (2026-09-05, Jacob). Stacked, the only
     thing between one card's arrow link and the next card's photograph was
     --card-gap 23 - the same 23 that separates three cards SIDE BY SIDE, where a column edge does the separating. Vertically nothing did.

     The fix is the one the site already uses for a stacked card set: .pgrid's
     practice cards take a hairline and a padding step when they unstack, so
     these take the same treatment rather than a new one. 40 above the rule
     and 40 below is the pgrid figure. */
  .caps__row > .card + .card, .caps__row--two > .card + .card {
    border-top: var(--hairline) solid var(--rule);
    padding-top: calc(40 * var(--u));
  }
  .caps__row, .caps__row--two { gap: calc(40 * var(--u)); }
/* THE DETAIL HERO'S 73px RIGHT GUARD IS A DESKTOP MEASUREMENT. It exists to
     keep the copy column off the photograph beside it - and below 900 the
     photograph is not beside it, it is under the whole section. Left on, it
     took 73 of a 342 column and broke the classification onto two lines and
     the tag row mid-phrase. */
  .dhero__crumbs, .dhero__client, .dhero__tags, .dhero__kind { padding-right: 0; }
/* Same shape of thing: the AI hero's label chain is centred inside a
     measured 93px inset, which on a phone is 186 of 342 gone before the first
     word. */
  .ahero__labels { padding-inline: 0; }
/* THE TRIO'S MARK STACKS, for the reason the detail hero's does (see
     .dhero__kind): inline, an 84px ring started "About this practice" at 140
     while every other line in the section started at the gutter. */
  .dtrio__about { flex-direction: column; gap: calc(18 * var(--u)); }

  /* .ed__cols::before was hidden here, and the element itself is gone as of
     2026-09-10: the divider divided two columns and there is one now. */

}
/* THE PRACTICE-CARD TAG ROW STACKS ONCE IT CANNOT FIT. It is a rule-separated
   row of three phrases (/capabilities only), and when it wraps the divider
   lands at the START of a wrapped line - separating a phrase from itself
   rather than from its neighbour. CSS cannot see a line break, so the switch
   is by width.

   MEASURED, not guessed: the longest of the five rows needs 520 at the phone
   eyebrow step, the card is the full content column below 900, and the column
   is 0.906 x viewport once --gut is on its 4.7vw term - so it stops fitting at
   574. The breakpoint is 600 rather than 574 so a font-metric wobble cannot
   land on it; between the two a row that would just have fitted stacks
   instead, which is a list, not a defect. */
@media (max-width: 600px) {
  .tags { flex-direction: column; align-items: flex-start; row-gap: calc(2 * var(--u)); }
  .tags span { padding-inline: 0; }
  .tags span + span { border-left: 0; }
/* THESE STACKED AT 420 AND NEEDED TO STACK HERE. Two columns of a ~420
     content width is 187 a cell, and every one of them holds a label rather
     than a word: "Improvement in time to productivity" set 11.7 characters a
     line, the outcome labels 12.4, and /experience's sector keys 7.0 in 108px.
     A metric's label is the part you read, so the cell has to be wide enough
     to read it - one column from here down. 600 is the same measured
     breakpoint .tags uses above, for the same reason. */
  .legend, .sectors-band__row, .dstats__row { grid-template-columns: 1fr; }
  .dimpact__row { --cols: 1; }
/* And the legend card stacks with them: an 84 ring, a divider and 22 of
     padding left its copy 188 wide at 421. This was at 420, which is one pixel
     of help on a 421 screen. */
  .legend__item { flex-direction: column; gap: calc(14 * var(--u)); padding: calc(20 * var(--u)); }
  .sectors-band__row .sector,
  .dstats__row div, .dimpact__row div { border-left: 0 !important; }
}

@media (max-width: 420px) {
/* THE SUPPLIED MOBILE CROP IS SQUARE, so it belongs where the band is square.
     800x800 against the desktop file's 2.07:1 - it was swapped in below 900, where the band is 3.15:1 by the top of that range, and the square photo was
     cut to a strip across three people's eyes. Both files are supplied and the
     manifest says not to re-crop either, so the fix is to use each where its
     shape fits: the square one here, at 1.43:1 and below, and the wide one from
     421 up, where the 2.8:1 ramp above already suits it. Jacob's call, 2026-09-10. The band goes flat again because the ramp is cut for 2.07:1. */
  /* 5:4 ANCHORED TO THE TOP (2026-09-14). The flat 260 made the square file
     1.32:1 at 390 and 1.05:1 at 320, centre-anchored, which took the top of
     both heads. The heads sit in the top fifth of the 800x800 frame, so a 5:4
     band anchored at the top keeps them and gives up the legs and the table. */
  .dims__art { height: auto; aspect-ratio: 5 / 4; }
  .dims__art img { content: url("/assets/ai-workforce-workshop-mobile-v2.webp"); object-position: 50% 0; }

  .exp__items,
  .legend, .ed__cols, .pgrid,
  .dstats__row { grid-template-columns: 1fr; row-gap: calc(26 * var(--u)); }
  .dimpact__row { --cols: 1; row-gap: calc(26 * var(--u)); }
/* .exp__item + .exp__item, NOT .exp__item, and that is the whole bug
     (found 2026-09-12). The divider is set by , which
     is two classes; this reset named one, so it lost on specificity no matter
     where it sat in the file and could never clear anything. With the band
     stacked to one column, items 2 and 4 kept a 2px gold hairline down their
     LEFT edge - two stray vertical marks against the navy, with items 1 and 3
     clear because the 2-up rule's :nth-child(odd) reset is also two classes
     and did win. Matching the specificity clears all three dividers. */
  .exp__item + .exp__item,
  .dstats__row div, .dimpact__row div { border-left: 0; }
/* .stats__row is not here: the 900 rule already stacks it and owns its
     top hairline, and re-declaring row-gap here would fight the 24 pad. */
  /* A grid item defaults to min-width:auto, so the contact panels refused to
     shrink below their content and pushed 7px past a 320 viewport. */
  .cpanel__aside, .cpanel__form { min-width: 0; padding: calc(24 * var(--u)); }
/* A page gutter exists here (24 since 2026-09-05, 32 before that), so the
     widest fixed component has to come in: icon 85 + gap 20 + padding 56 left
     no room for the label. */
  .tile { padding: calc(20 * var(--u)) calc(18 * var(--u)); gap: calc(14 * var(--u)); }
/* .tile__icon's 56px mobile shrink is gone: it existed because an 84 ring
     sat beside the title and a 320 phone had no room for both. The icon is
     above the title now, and the square is one size everywhere. */
  /* Same trade, and the eyebrow step forced it: /experience's legend keys put
     an 84px ring beside the word TRANSFORMATION, which at 16/.10em needs 171
     and a 320 phone had 166 to give - so the page scrolled sideways by 5. The
     ring comes in to 56, as .tile__icon already does at this width, rather
     than the label going back down.

     The 2026-09-05 mockup refresh then made the key a CARD, and 2x26 of card
     padding put it back over: 24 gutter + 26 pad + 56 ring + 22 gap + the
     inner divider's 22 leaves 118 for a word needing 171. So at this width the
     key stacks - ring above label - and drops the inner divider, which is the
     same move .dhero__kind and .dtrio__about already make on a phone. */
  /* Same for .legend__icon, and .legend__item already stacks at every width
     now, so only its padding and row gap are phone values. */
  .legend__item { row-gap: calc(12 * var(--u)); padding: calc(18 * var(--u)); }
/* The same min-width:auto trap, generalised: a grid item will not shrink
     below its content unless told to, and at 320 several do not fit. */
  .dtrio__grid > *, .dchal__grid > *, .xhero__inner > *, .cexp__inner > *, .case > *, .ed__cols > * { min-width: 0; }
}
/* === /services — the four engagements ==================================
   The only page whose substantive content is entirely the client's own
   words; see the comment at the top of services.html for what is his and
   what is our scaffolding.

   Tonal sequence, rule 1:   soft · ivory · white · warm · navy
   Tier alternation, rule 8: narrative · evidence · narrative
   Headings, rule 2:         --t-h1 hero (secondary page), --t-h2 section, --t-h3 each engagement. No new step. */

/* The photograph, its crop and its scrim are the shared .phero - see
   "Photograph heroes" above /about’s block. This is only the type. */
.shero h1 {
  margin-top: 0;   /* was 10u, an eyebrow gap */
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h1); line-height: var(--t-h1-lh);
  color: var(--on-navy);   /* was --navy, on ivory */
}
.shero .rule-short { margin-top: calc(20 * var(--u)); }
.shero__lede {
  margin-top: calc(18 * var(--u));
  /* 760 -> 520: at 760 the lede ran 633px, across the seated pair. 520 keeps
     it on the glass wall in three lines. */
  max-width: calc(520 * var(--u));
  font-size: var(--body); line-height: var(--lh-lede);
  color: var(--on-navy);   /* was --ink-body, on ivory */
}

/* White, so it parts from the ivory hero above and the warm CTA below.
   Rule 1's test is that no two adjacent sections share a tone. */
.svc { background: var(--tone-elevated); }
.svc h2 {
  margin-top: 0;   /* was 10u, an eyebrow gap */
  font-family: var(--serif)}
/* TWO UP, NOT FOUR DOWN (2026-09-12, Jacob: "arrange the four engagements as
   a two-column grid instead of four expansive stacked blocks"). This section
   was 1, 572 of the page's 2, 915 - the single largest block on the site - and
   it was four full-width rows each holding a headline, a line of copy, ten
   deliverables and a link across 1, 088px, so each row ran about 337 tall with
   the deliverables using half the width and the copy less than that.

   The engagement is the grid cell now: 524 wide, which is what the ten
   deliverables were already being set in. Nothing is dropped - every
   deliverable, every link, all four engagements. */
.svc__list {
  margin-top: calc(30 * var(--u));
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: calc(40 * var(--u)); row-gap: calc(26 * var(--u));
}
/* A divider ABOVE each row, never a box around it — rule 3 reserves a
   border for "one clickable thing", and an engagement is not one. Same
   device .pcard and .hcard uses. */
.svc__row {
  display: grid;
  grid-template-columns: calc(56 * var(--u)) minmax(0, 1fr);   /* was 84; a 524 cell
                                       does not have 70 to give the numeral */
  padding-top: calc(20 * var(--u));                            /* was 30 */
  border-top: var(--hairline) solid var(--rule);
  /* margin-top is gone: the list's row-gap sets the distance between rows now,
     and leaving both would have stacked 30 of margin on 26 of gap. */
}
.svc__n {
  font-size: var(--t-h5); font-weight: 700; letter-spacing: .08em;
  line-height: var(--t-h3-lh);          /* sits on the h3's first baseline */
  color: var(--gold);
}
.svc__main h3 {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h3); line-height: var(--t-h3-lh);
  color: var(--navy);
}
.svc__b {
  margin-top: calc(10 * var(--u));
  max-width: calc(640 * var(--u));
  font-size: var(--body); line-height: var(--lh-lede);
  color: var(--ink-body);
}
.svc__label { margin-top: calc(18 * var(--u)); }
/* was 24 */

/* Two columns, because ten items in one column is a list nobody reads to
   the end.

   COLUMNS, NOT GRID, AND THAT IS THE WHOLE POINT. A two-column grid fills
   ROW-wise, so his ten deliverables read 1, 3, 5, 7, 9 down the left and
   2, 4, 6, 8, 10 down the right — correct only if you read across, which nobody
   does with a list. It shipped that way in the first render and the layout
   checks cannot see it, because every line is intact and nothing overflows.
   `columns` fills and balances COLUMN-wise: 1-5, then 6-10, in his order.
   break-inside stops a two-line item being split across the gap. */
.svc__items {
  margin-top: calc(10 * var(--u));                             /* was 12 */
  columns: 2; column-gap: calc(22 * var(--u));                  /* was 30 */
  list-style: none; padding: 0;
  font-size: var(--body-sm); line-height: var(--body-sm-lh);
  color: var(--ink-body);
}
/* 7 -> 4 ("reduce ... list spacing"). Ten items carry nine of these gaps, so
   three pixels each is 27 off every engagement and 54 off the section. */
.svc__items li { break-inside: avoid; margin-bottom: calc(4 * var(--u)); }
.svc__items li { position: relative; padding-left: calc(16 * var(--u)); }
/* A gold rule rather than a bullet glyph — rule 5 keeps gold to rules and
   marks, and this is the same hairline the dividers use. */
.svc__items li::before {
  content: ""; position: absolute; left: 0; top: calc(12 * var(--u));
  width: calc(7 * var(--u)); height: var(--hairline);
  background: var(--gold);
}
.svc__main .btn { margin-top: calc(16 * var(--u)); justify-self: start; }
/* STRUCTURE ONLY, per PAGE.md: the number column and the two-up
   deliverables both collapse. Nothing here changes a type size except the
   numeral's leading, which existed to align it with an h3 that is no
   longer beside it. */
@media (max-width: 900px) {
  .svc__row { grid-template-columns: minmax(0, 1fr); }
  .svc__n { line-height: 1.2; margin-bottom: calc(8 * var(--u)); }
  .svc__items { columns: 1; }
}
/* --- /capabilities: the engagements band ------------------------------
   Names the four and links to /services. Centred, matching .inter and
   .challenge above it; ivory, because its neighbours are white and navy. */
.engage { text-align: center; }
.engage h2 {
  margin-top: 0;   /* was 10u, an eyebrow gap */
  font-family: var(--serif)}
/* One row at full width, wrapping to two and then one as it narrows. Not a
   grid: the four names differ in length by more than 2:1, so equal columns
   would strand the shortest and shred the longest. */
.engage__list {
  margin-top: calc(26 * var(--u));
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: calc(12 * var(--u)) calc(30 * var(--u));
  list-style: none; padding: 0;
  font-size: var(--body-sm); line-height: var(--body-sm-lh);
  color: var(--ink-body);
}
.engage__list li { position: relative; }
/* A gold hairline between names rather than a bullet — rule 5 keeps gold to
   rules and marks. Not on the first of a row: :not(:first-child) would
   still draw one at the start of a wrapped line, so it is drawn on the
   left of every item and the container's overflow hides nothing — the
   flex gap carries it clear instead. */
.engage__list li + li::before {
  content: ""; position: absolute; left: calc(-16 * var(--u)); top: 50%;
  width: calc(7 * var(--u)); height: var(--hairline);
  background: var(--gold);
}
.engage .btn { margin-top: calc(24 * var(--u)); justify-self: start; }
/* --- /services: engagement models -------------------------------------
   Five names, which is all he supplied. Centred like the /capabilities
   engagements band, and ivory because its neighbours are white and warm. */
.emodels { text-align: center; }
.emodels h2 {
  margin-top: 0;   /* was 10u, an eyebrow gap */
  font-family: var(--serif)}
/* `.emodels__list` IS GONE (2026-09-12), markup and all. Its own comment said
   it was "superseded by the shared .blocks below, which is this rule
   generalised" and that it was "kept as an alias so the markup needs no second
   class" - but it was a full second copy of that rule, not an alias, and the
   shared one below was silently winning every property anyway. The four
   engagement models now carry `blocks blocks--4`, which is the same component
   home uses.
   That also fixes what the alias was hiding: four items on the shared 340
   track land 3 + 1 in a 1088 column, stranding the fourth on a row of its own.
   `.blocks--4` puts them in one row of four, which is where the section's
   504 -> 400 comes from. */

/* === His capability terms, per practice (.covers) ================================
   The TECHNICAL SKILLS list from the client's content document, grouped to
   the practice each page is about. Same idiom as .tags and .platforms__row, which are the site's existing term rows, but at --body-sm: these are read
   rather than scanned as chips, and there are up to nineteen of them. */
.covers__h { line-height: var(--lh-tight); }
.coverlist {
  margin-top: calc(18 * var(--u));
  display: flex; flex-wrap: wrap; align-items: baseline;
  row-gap: calc(9 * var(--u));
  font-size: var(--body-sm); line-height: var(--body-sm-lh);
  color: var(--ink-body);
}
/* THE SEPARATOR TRAILS ITS TERM, it does not lead the next one. .tags and
   .platforms__row both draw a border-LEFT on every span after the first, which is invisible at three items and wrong at nineteen: the row wraps, and every wrapped line then opens with a divider hanging off its left
   edge. Drawn as a trailing mark instead, a break puts it at the end of a
   line where it reads as deliberate. */
/* NOT the prose measure. This list sits inside .dchal__main, whose `p` rule
   caps at 644 build for the readability of a paragraph - which squeezed 18
   terms into 537px. A term row wants the column, not a measure. Two classes, so it out-specifies `.dchal__main p` rather than relying on file order. */
.covers .coverlist { max-width: none; }
.coverlist span { display: inline-flex; align-items: center; }
.coverlist span:not(:last-child)::after {
  content: ""; display: inline-block;
  width: calc(7 * var(--u)); height: var(--hairline);
  background: var(--gold);
  margin-inline: calc(13 * var(--u));
}
/* The band is ivory by default, which is what the capability detail pages
   need between a warm section and a white one. On /capabilities and
   /ai-transformation its neighbours are ivory and warm, so it takes white. */
.covers--white { background: var(--tone-elevated); }
/* --- /capabilities/*: "What this addresses" ----------------------------
   THE LABEL IS A <p> SINCE 2026-09-12, not an <h2>. The specificity note below
   was about a collision between .dchal h2 and .eyebrow; there is no collision
   now, because the label is not a heading. Kept because the rule it describes
   is still the one doing the work. */
   The section is back, filled from COPY-DECK.md section 5. It carries an
   eyebrow and his two paragraphs, and no headline - the deck gives none, and writing one for him is what got nine of them removed on 8 September.

   .dchal h2 is the serif section headline at --t-h2, and it is a more specific
   selector than .eyebrow, so the eyebrow needs to out-specify it rather
   than rely on class order. Same collision .caps h2 caused earlier today. */
.dchal .dchal__eyebrow {
  margin-top: 0; max-width: none;
  font-family: var(--sans); font-weight: 700;
  font-size: var(--eyebrow); line-height: var(--lh-tight);
  letter-spacing: var(--eyebrow-track); text-transform: uppercase;
  color: var(--gold);
}
/* No aside beside it any more - At a glance moved to the terms section. */
.dchal__main--full { padding-right: 0; }
/* === Titled blocks, the shared grid ====================================
   A row of short titled blocks, each with a hairline above it. Five uses:
   the engagement models on /services, and Why Taft, How we work, How we
   scale and Who we work with on the home page. Defined once (PAGE.md).

   408 build = 340 rendered is what lands five items as 3 + 2 in a 1200
   column rather than 4 + 1: four tracks need the min under 300 rendered, three fit at up to 400. auto-fit drops to 2 and then 1 on its own, so the
   column count adapts with no media query restating a size.

   NOT bordered boxes - rule 3 reserves a border for one clickable thing.
   A divider above each, the same device .pcard and .hcard use. */
.blocks {
  margin-top: calc(34 * var(--u));
  display: grid;
  /* min(track, 100%) — NOT the bare track. --u is 1px at and below 390, so a
     408 or 560 min track is wider than a 320 screen and the grid refuses to
     shrink: check:widths caught +264 at 320. min() lets the track collapse
     below its own minimum when the column is narrower than it. */
  grid-template-columns: repeat(auto-fit, minmax(min(calc(408 * var(--u)), 100%), 1fr));
  gap: calc(30 * var(--u)) calc(40 * var(--u));
  list-style: none; padding: 0;
  text-align: left;
}
.blocks > li {
  border-top: var(--hairline) solid var(--rule);
  padding-top: calc(16 * var(--u));
}
.blocks h3 {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--t-h4); line-height: var(--t-h4-lh);
  color: var(--navy);
}
.blocks p {
  margin-top: calc(8 * var(--u));
  font-size: var(--body-sm); line-height: var(--body-sm-lh);
  color: var(--ink-body);
}
/* === Home: the four sections from his copy deck ========================
   Tones run white - ivory - white - warm between the navy experience band
   and the ivory CTA, so nothing adjacent repeats (rule 1); tiers alternate
   narrative - evidence - narrative - evidence (rule 8). */
/* TWO EXPLICIT COLUMN COUNTS, REPLACING auto-fit (2026-09-11, Jacob: the four
   lenses "in one aligned grid", the five principles "as compact columns", the
   five audience groups "into a consistent grid").

   `.blocks--2` was here and gave the four-item lists two tracks of 467. It is
   gone, and so is auto-fit on these four lists, for a reason worth keeping:
   auto-fit picks the column count from whatever the viewport happens to be, and
   for FIVE items the count it picks is sometimes 4 - which strands the fifth on
   a row of its own. The file already records the same failure at the 408 track
   landing four items as 3 + 1. An explicit count says which splits are allowed:
   4 -> 2 -> 1 never strands anything, and 5 -> 3 -> 2 -> 1 keeps the orphan row
   to the 2-column state where it reads as a grid rather than as a mistake.

   The gaps tighten with the counts - a 5-track row cannot carry the 33 gutter a
   2-track row could - and both stay on --u, so they narrow with everything
   else below the shell. */
/* TWO UP IS BACK, EXPLICIT THIS TIME (2026-09-12). The auto-fit `.blocks--2`
   removed on the 11th picked its column count from the viewport; this names it.
   It exists because four items are not always best as a row of four: the
   engagement models on /services are four 110-character paragraphs, and at
   four across they set in 250 and ran SIX lines each - the section came out
   560, taller than the 504 it was asked to reduce. At two across they set in
   524 and run two lines, and the section is 415. Four narrow columns is a
   shape for short titles; two wide ones for anything with a sentence in it. */
.blocks--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: calc(26 * var(--u)) calc(40 * var(--u)); }
/* 24 flat, both axes (2026-09-12, Jacob: "four equal columns, 24px gaps").
   Literal, like --measure: a gutter between cards is a system decision. It was
   23.3 row and 21.7 column, which is 24 to the eye and two different numbers
   on the page. */
.blocks--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; }
.blocks--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: calc(26 * var(--u)) calc(24 * var(--u)); }
/* A FIVE-TRACK COLUMN IS 198 WIDE, WHICH IS NOT A --t-h4 COLUMN.
   check:layout caught it the first time these were built: "HR & Workforce
   Leaders" set on THREE lines at 7.3 characters each, which is a column too
   narrow to read rather than a title too long. --t-h5 is the scale's own small-
   title step and it holds the same words on two lines at 11 characters.
   THIS IS NOT A NEW SIZE - rule 2 says nothing else may SET a heading size, and
   this sets none: it moves these titles onto an existing step, the way Contact's
   hero moved to --t-h1. It is scoped to the 5-track list, so the four-track
   lists beside it keep --t-h4, which their 251 holds. */
/* AND AS A MODIFIER OF ITS OWN (2026-09-12), because the column count is not
   the only thing that makes a column too narrow for --t-h4. The five
   engagement models on /services sit three across at 340, where their titles
   ran to two lines each and the section stood 504 against an instruction
   asking for about 400; on the small step they take one line and it lands at
   446. A list can now take the small title without taking a column count. */
.blocks--5 h3, .blocks--titles-sm h3 { font-size: var(--t-h5); line-height: var(--t-h5-lh); }
/* 1140, not 1200: at 1141 the five tracks are 191 each, which holds the
   longest of these titles on two lines. Below it three is the honest count. */
@media (max-width: 1140px) {
  .blocks--5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1080px) {
  .blocks--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
  .blocks--5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .blocks--2, .blocks--4, .blocks--5 { grid-template-columns: 1fr; }
}
/* CARDS, AND NOT BORDERED ONES - rule 3 reserves a border for the one
   clickable thing, and none of these four is clickable (2026-09-11, Jacob:
   "grouped the four scaling principles into cards beneath the explanatory
   copy"). So the card is a GROUND plus the gold hairline the list already
   carries: --paper inside the white .howscale section, which reads as a
   distinct surface without drawing a box. Nothing else about the list
   changes - same divider, same type, same grid. */
.blocks--cards > li {
  background: var(--paper);
  /* ONE NUMBER ("consistent padding"). It was 13.3 top, 18.3 sides and 18.3
     bottom - three values for the inside of one card. */
  padding: 20px;
}
/* ===========================================================================
   ONE PROSE SECTION (2026-09-12, Jacob)

   > Take a look at "We focus on the distance..." on the homepage... that is
   > different than the "We look further." on the about page... this should be
   > the same component right? ... its not just about using css for each
   > variant. but about aligning each variant to the same component, and
   > controlling that component centrally

   Nine sections were two components. Home had .whytaft, .howwork, .howscale
   and .whowith - four page-named sections with their own slot names
   (__stanza, __point, __lead, __close, __body) - and /about had five .ed
   sections with a different vocabulary for the same jobs (__body, __pull,
   __list, __cols, __sig). Same shape both sides: a headline, a run of prose,
   a line that carries the emphasis, sometimes a list or a grid of cards.

   There is one now. What a section declares for itself is its GROUND and its
   ALIGNMENT, and both are modifiers:

     .ed--white / .ed--warm / .ed--ivory   the ground; bare is --paper
     .ed--centred                          centres the block
     .ed--prose                            a left-aligned 760 reading column
                                           for a body that is paragraphs

   THE SLOTS:
     h2                the headline, from the shared headline rule
     .ed__body         a body paragraph; the first after the headline is 700,
                       the rest 760 - the measures he set for a big text block
     .ed__strong       the line a stanza turns on
     .ed__stanza       a run of short muted lines
     .ed__pull         a serif statement at --t-h3
     .ed__list         a ruled list
     .ed__cols         a prose column
     .ed__sig          a name and a role
     .blocks           the shared card grid, 48 below the prose

   WHAT CHANGED ON THE PAGE, deliberately: home's four sections take the
   700/760 measures instead of one 640, and their headline takes the 720 cap -
   which is the treatment he specified for How we scale and then asked to be
   applied to /about, now applied to the rest of its own family. */
.ed--centred { text-align: center; }
.ed--white { background: var(--tone-elevated); }
.ed--warm  { background: var(--paper-warm); }
/* ivory, the default */



/* 920 = 78% OF THE 1180 SHELL, his number (2026-09-12): "i need this type
   element to take up more width (right now is it 50%? it should be 78%) it can
   stay centered".

   MEASURED FIRST, because six of this section's seven elements are single
   short lines that already fit inside their cap - the longest is 608 of ink in
   a 760 box - so a wider measure does nothing to them at all. The HEADLINE is
   the only element that wraps, and it is what reads as the block's width: at
   720 it broke into three lines of 573 (53% of the column, which is the ~50%
   he was looking at). At 849 it sets as two lines of 725, or 67%.

   67 and not 78: a line breaks between words, and 725 is the natural two-line
   width of that sentence at 48px. No cap reaches 78% of the column - 100% was
   measured and gives the same 725 - so 78% here is the MEASURE, not the ink.
   Getting the ink to 849 would need the headline at 56px, a seventh step on a
   six-step scale.

   PX AND NOT 78%, because a percentage applies on a phone too: --gut is 24
   there, so the column is 342 and a literal 78% would cap this headline at 267
   and shred it. Absolute, like --measure - a reading column is not
   proportional. 920 never binds below a 920 column, which is a 1012 viewport,
   and the old 720 was already doing nothing there.

   849 FIRST, AND IT WAS 22px SHORT. 78% of the 1088 COLUMN is 849, and "The
   expertise required by the transformation - not unnecessary layers." needs
   871 to fit on two lines. text-wrap: balance is on every heading, so at 849
   it did not set two lines with a short second one: it dropped to THREE lines
   and balanced them to 510 each, which is 47% of the column - he came back
   with "why is this section still 50%?". The threshold is between 870 and 872,
   measured. 78% of the 1180 shell is 920, which clears it by 49 and reads the
   same, because every one of these four headlines breaks below 871 anyway: any
   cap from 872 to 1088 renders identically today. A cap sitting 1px from a
   wrap threshold is not a cap - a font substitution or a three-word copy edit
   flips it back to three lines.

   AND IT GIVES BACK THE LINE THE 720 COST. Flagged when the prose sections
   were unified: 720 pushed this headline from two lines to three and
   "Transformation is enterprise work." from one to two. Both are back. The
   other two centred headlines never reached 720 and do not move. */
.ed--centred h2 {
  margin-top: 0;   /* was 10u, an eyebrow gap; the eyebrows are gone from this page */
  /* the measure comes from the one home rule at the end of this file */
}
/* The four centred headlines were sized individually for one afternoon so
   each would FILL 85% of the column - 61/42/51/59px. Reverted on request the
   same day: "i didnt ask you to change font size". They are --t-h2 48 again,
   like every other section headline. The measure stays at 1000, which is what
   actually widened them. SPEC.md keeps the measurements, because the finding
   behind it still holds: one type size cannot give four sentences of different
   lengths the same occupancy, so 85% for all of them is not available without
   changing size. */

/* The four-line stanza is the argument, and the last line is the point of
   it, so it carries the weight rather than being one of four equals. */
/* CENTRING THE TEXT IS NOT CENTRING THE BOX. `main p` caps every paragraph
   at --measure, so these sat at their measure hard against the left of a
   centred section - MOBILE.md rule 5, and visible as a stanza centred 200px
   left of the headline above it. margin-inline is what centres the box. */
.ed__stanza { margin-top: var(--ed-gap-block); }
/* Centring the TEXT is not centring the BOX: main p caps every paragraph, so
   without an auto inline margin a capped paragraph sits hard against the left
   of a centred section - MOBILE.md rule 5. */
.ed--centred :is(p, .ed__stanza, .ed__list, .ed__cols) { margin-inline: auto; }
.ed__stanza p {
  font-size: var(--body); line-height: var(--lh-lede);
  color: var(--ink-muted);
}
/* The line a stanza turns on. It is the only emphasis on this site that is a
   WEIGHT rather than a size or a colour, and it earns that: the four lines
   around it are one sentence each and the last is the point of them.

   TWO CLASSES DELIBERATELY. The old .whytaft__point carried !important, and
   this is what it was for: .ed__stanza p sets the muted grey for the lines
   around this one, and a single class loses to it on specificity, so the
   emphasised line comes out the same grey as the four it is meant to stand out
   from. Scoping to .ed wins on specificity instead, which is the same outcome
   without a declaration that cannot be overridden further down. */
.ed .ed__strong { color: var(--ink-body); font-weight: 600; }


/* 733 -> --measure (2026-09-11, "tightened paragraph widths"). The 880 build
   cap was wider than the 640 `main p` gives each paragraph inside it, so the
   container was doing nothing except centring - and centring is what
   margin-inline is for. One number governs this column now. */
/* ===========================================================================
   A BIG TEXT BLOCK (2026-09-12, Jacob). "We need big text blocks like that to
   be laid out like this."

   Three measures rather than one, because the three parts of the block are
   doing different jobs: a headline that has to hold its own shape, one
   introductory sentence that reads as a statement, and two paragraphs that are
   prose and want a prose column.

     headline    720, centred
     intro       700, centred
     the rest    760, LEFT-ALIGNED inside a centred column

   The section still centres by default, so only the last of those three turns
   the alignment off. Literal px, like --measure: a reading column is absolute, not proportional to a mockup.
   =========================================================================== */


/* The measures, the rhythm and the alignment are all declared in the ONE
   PROSE SECTION block above - .ed__body carries 760, .ed h2 + .ed__body 700,
   and --ed-gap-para is already the 16 this block wanted. Nothing is repeated
   here. The only thing the cards needed was their gap: */
.ed .blocks { margin-top: 48px; }
/* Practice 02's card action is a BUTTON, not an arrow link, and that is the
   client's distinction rather than ours: section 4 of COPY-DECK.md marks four
   of the five practices `LINK:` and this one `BUTTON: Explore AI
   Transformation`. It earns it - AI Transformation is the only practice with
   its own primary-nav page (section 2), where the other four go to capability
   detail pages. The heavier affordance is what tells a reader that card leads
   somewhere different, which is otherwise only discoverable by clicking.

   SECONDARY, not primary: rule 7 allows one primary per section and this
   page's is the closing "Discuss a Transformation". Gold outline on ivory
   sits correctly between an arrow link and that. */
/* ALL FIVE practice cards now, not just 02's. */
/* .pcard__btn - folded into .card */
/* === /services: the engagement grid collapses ==========================
   Two up above 1000, one down below it. The engagement cell is 524 at the
   shell and about 440 at 1000, which still holds the ten deliverables in two
   readable sub-columns; under that the deliverables would be setting in 200
   and the cell is better off with the whole width. */
@media (max-width: 1000px) {
  .svc__list { grid-template-columns: 1fr; row-gap: calc(22 * var(--u)); }
}

/* ===========================================================================
   ONE MEASURE FOR EVERY SECTION HEADLINE AND BODY ON HOME (2026-09-12, Jacob)

   > give them all the same max width of 80% (or you can hardcode the px for
   > responsive purposes) right now they are all different

   872 = 80% of the 1088 column, which is 870.4, plus the 2px that keeps one
   headline off a wrap threshold: "The expertise required by the transformation
   - not unnecessary layers." needs exactly 871 to hold two lines, and at 870 it
   drops to three balanced lines at 47% - the section he had already asked about
   twice. 872 is 80.1%, costs nothing anywhere else, and buys that back.

   HARD PX, as he allowed, because a literal 80%
   resolves against .wrap at every width and --gut is 24 on a phone: the column
   is 342 there, and 80% of it would cap a headline at 274 and shred it.

   WHAT IT REPLACES, measured off the page first (the inventory is in SPEC.md):
   nine section headlines with no measure of their own at all - eight bounded
   only by the 1088 column and one by .exp__head's 833 - over four different
   body measures: .gap__body uncapped at 1088, .ed__body at 700 and at 760, and
   .ed__stanza's lines at main p's 640. That is the "all different": a headline
   at 48% of the column sitting over a body at 99% of it, in one section.

   A MAX-WIDTH BOUNDS THE WIDEST LINE; IT CANNOT LENGTHEN A SHORT ONE. The
   elements that ran past 870 come back to it. The ones whose copy breaks
   narrower than 870 stay where they are, and no measure changes that - it is
   the one thing this cannot fix and the reason the earlier attempt reached for
   font size, which was the wrong answer.

   NOT IN THIS: the hero, whose copy column is 341 beside a photograph, and
   .exp__item's metric labels, which are a four-column grid rather than a
   section body. Home only - /about and the interior pages keep their own. */
body.page-home main :is(h2, .acta),
body.page-home main :is(.gap__body, .ed__body, .ed__stanza, .ed__stanza p) {
  max-width: 872px; margin-inline: auto;
}
/* .exp__head capped the BLOCK at 833, so its headline was bounded by the
   container rather than by a measure of its own. 870, like the rest. */
body.page-home .exp__head { max-width: 872px; }
