/**
 * Sitewide typography contract — Phase 2A foundation, now consumed by
 * Phase 2B1's shared chrome (updated 16 September 2026; see below).
 *
 * This file defines the canonical semantic type roles from
 * docs/typography-audit.md and a `.type-*` utility class for each, under
 * names that were new when Phase 2A added them (verified against every
 * `--type-*` name already in use in src/styles/*.css and assets/*.css before
 * this file was added: --type-body, --type-button, --type-caption,
 * --type-card, --type-eyebrow, --type-feature, --type-h2, --type-h3,
 * --type-hero, --type-label, --type-lead, --type-metric, --type-metric-sm,
 * --type-micro, --type-title-sm — none of which collide with the names
 * below). Phase 2A's own claim that "loading this file changes nothing on
 * any page" was true only of that phase's own scope and is no longer true of
 * the file as a whole: the Phase 2B1 section below actively drives the
 * rendered shared navbar, mega-menu and footer sitewide (all three chrome
 * renderings, including React's PremiumHeader/PremiumFooter, which consume
 * it via the runtime `<link>` src/main.jsx injects). Everything ABOVE the
 * Phase 2B1 section — the roles/tokens/.type-* utilities themselves — is
 * still only reached once something references it with var() or carries the
 * utility class; it's the Phase 2B1 rule set specifically that has live
 * selectors now, not the token definitions in the abstract.
 *
 * Migrating existing PAGE-FAMILY CONTENT rules onto these roles — including
 * legacy aliases such as --type-label and --type-body, which still hold
 * their own literal values — remains a later phase, not this file. See
 * docs/typography-audit.md for the mapping
 * this contract is built from, and for the reasoning behind the load-order
 * strategy: new property names avoid any `:root` redeclaration race with
 * handoff-tokens.css, and any future override of a compiled/frozen surface
 * (the Tailwind bundle, Home's JS bundle) wins by selector specificity, not by
 * link position — see the header/footer rule shapes already proven live on
 * the six membrane pages in src/content/membranes/typography.css.
 *
 * Approved exception: Home's six main section headings (#industries,
 * #capabilities-explorer, #research-leadership, #case-studies,
 * #sustainability-impact, #csr) run larger than --type-heading-section at
 * desktop width (48px vs this contract's 44px ceiling), by deliberate past
 * design. --type-heading-section-home preserves those exact figures under a
 * named role rather than a page-local literal. index.html is not yet wired to
 * it in Phase 2A — that wiring is a page-family migration, deferred to 2B.
 */

:root {
  /*
   * Family tokens the contract owns, so it is self-sufficient on every page
   * that loads it. assets/content.css defines --font-display/--font-body for
   * the 1,029 pages that load it, and about-us/styles.css defines both for
   * its own page — but Home, Careers and CSR load neither (the frozen
   * Tailwind bundle defines --font-display only, never --font-body), so a
   * bare var(--font-body) would resolve to nothing there. These defer to the
   * existing token wherever it exists and otherwise fall back to the same
   * stack content.css already declares, so no page's family changes.
   */
  --type-font-display: var(--font-display, "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif);
  --type-font-body: var(--font-body, "Inter", ui-sans-serif, system-ui, sans-serif);

  /* Display and section headings. Fluid; hierarchy preserved as approved. */
  --type-display-hero: clamp(32px, 4.8vw, 56px);
  --type-heading-section: clamp(30px, 3.6vw, 44px);
  --type-heading-section-home: 30px; /* 48px at >=768px — see --type-heading-section-home-desktop */
  --type-heading-section-home-desktop: 48px;
  --type-heading-subsection: 25px;
  --type-heading-subsection-mobile: 22px;
  --type-title-card: 20px;
  --type-title-card-mobile: 18px;

  /* Body and reading roles. */
  --type-body-lead: clamp(15px, 1.4vw, 17px);
  --type-body-standard: 16px;
  --type-body-article: 17px;
  --type-body-article-mobile: 16px;

  /* Tables. */
  --type-table-heading: 16px;
  --type-table-data: 16px;

  /* Actions, forms, helpers. */
  --type-action: 14px;
  --type-form-label: 14px;
  --type-form-control: 16px;
  --type-helper: 14px;

  /* Navigation. */
  --type-nav-main: 15px;
  --type-nav-main-mobile: 16px;
  --type-nav-utility: 12px;

  /* Metadata, compact labels, captions. */
  --type-metadata: 13px;
  --type-compact: 11px; /* floor — short uppercase labels/identifiers only, never body/table/action text */
  --type-image-caption: 13px;

  /* Footer. */
  --type-footer-link: 15px;
  --type-footer-meta: 13px;
  --type-footer-meta-loose: 12px;
  --type-footer-helper: 14px;

  /* Stat/metric units (the large figure itself stays fluid/local; this is
     only for the unit or caption riding alongside it). */
  --type-stat-unit: 16px;

  /* Weights, shared so a role's weight travels with its size. */
  --type-weight-body: 400;
  --type-weight-action: 600;
  --type-weight-compact: 700;
  --type-weight-heading: 500;
  --type-weight-subheading: 600;

  /* Approved exception: membrane table "Datasheet" action. Defined here so
     it is named and documented centrally; the membrane stylesheet keeps its
     own scoped rule and is not changed to reference this in 2A. */
  --type-action-datasheet-exception: 12px;
}

/*
 * .type-* utility classes. Not yet applied to any markup — available for
 * Phase 2B migrations and for new components going forward. Each sets only
 * typography properties (family, size, weight, line-height, letter-spacing,
 * casing) — no color, spacing or layout.
 */

.type-display-hero {
  font-family: var(--type-font-display);
  font-size: var(--type-display-hero);
  font-weight: var(--type-weight-heading);
  line-height: 1.1;
}

.type-heading-section {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-section);
  font-weight: var(--type-weight-heading);
  line-height: 1.14;
}

.type-heading-subsection {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-subsection-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.25;
}
@media (min-width: 1024px) {
  .type-heading-subsection { font-size: var(--type-heading-subsection); }
}

.type-title-card {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .type-title-card { font-size: var(--type-title-card); }
}

.type-body-lead {
  font-family: var(--type-font-body);
  font-size: var(--type-body-lead);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

.type-body-standard {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

.type-body-article {
  font-family: var(--type-font-body);
  font-size: var(--type-body-article-mobile);
  font-weight: var(--type-weight-body);
  line-height: 1.7;
}
@media (min-width: 1024px) {
  .type-body-article { font-size: var(--type-body-article); }
}

.type-table-heading {
  font-family: var(--type-font-display);
  font-size: var(--type-table-heading);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

.type-table-data {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

.type-action {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.type-form-label {
  font-family: var(--type-font-display);
  font-size: var(--type-form-label);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.type-form-control {
  font-family: var(--type-font-body);
  font-size: var(--type-form-control);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

.type-helper {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

.type-nav-main {
  font-family: var(--type-font-display);
  font-size: var(--type-nav-main-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.35;
}
@media (min-width: 1024px) {
  .type-nav-main { font-size: var(--type-nav-main); }
}

.type-nav-utility {
  font-family: var(--type-font-display);
  font-size: var(--type-nav-utility);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.type-metadata {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

.type-compact {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.type-image-caption {
  font-family: var(--type-font-body);
  font-size: var(--type-image-caption);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

.type-footer-link {
  font-family: var(--type-font-body);
  font-size: var(--type-footer-link);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

.type-footer-meta {
  font-family: var(--type-font-display);
  font-size: var(--type-footer-meta);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

.type-footer-helper {
  font-family: var(--type-font-body);
  font-size: var(--type-footer-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * Phase 2B1 — shared navbar, mega-menu and footer typography (sitewide).
 *
 * Most selectors below are the SAME selector src/content/membranes/typography.css
 * already uses for the identical shared-chrome element, with its
 * `body.membrane-typography` prefix removed and its `--mem-type-*` values
 * replaced by the sitewide roles above (main navigation uses the approved
 * 15px/16px target here, not the membrane family's own 14px exception).
 * That is deliberate, not incidental: dropping the class prefix makes every
 * MIRRORED rule here exactly ONE specificity point below its membrane
 * counterpart (`body.membrane-typography X` is always more specific than
 * `X`), so on the six membrane routes the membrane rule wins regardless of
 * which stylesheet loads first, and their approved computed typography is
 * unchanged — see the Phase 2B1 report in docs/typography-audit.md for the
 * verification. The two selector sets are NOT literally identical, though:
 * the eyebrow rule and `.ix-acc-all` rule below (added in the 16 September
 * repair) have no membrane counterpart at all, and membrane's own
 * eyebrow/description selectors use a looser `div > x` form rather than this
 * file's `div:first-child > x` — functionally equivalent given today's
 * markup, but not the same selector text. See docs/typography-audit.md for
 * the full comparison.
 *
 * Reaches all three chrome renderings without any markup edit:
 *   - the static shell (us/index.html, services/index.html and everything
 *     generated from them) and Home's frozen bundle share the same ids —
 *     #header-utility-bar, #header-desktop-menu, #main-app-header,
 *     #footer-columns, #footer-presence-bar, #footer-licensing,
 *     #contact-form-widget, #newsletter-segment, #impact-metrics-row,
 *     #footer-directory, #contact;
 *   - the injected mega-menu (#ie-mega-navigator, assets/megamenu.js) and
 *     Home's own native mega-menu (#mega-menu-navigator) share the same
 *     internal class names except their rail-tab marker — the injected
 *     clone uses [data-mega-tab], Home's original uses id="rail-tab-*" —
 *     so both ids are listed wherever that marker is used.
 * React's PremiumHeader/PremiumFooter (dev-preview only; no static page
 * loads it) use entirely different class names for almost everything, and
 * are mapped separately by editing src/styles/premium-chrome.css directly,
 * which is a normal maintained source, not a compiled one. Correction
 * (2026-09-16): they do NOT have "no ids in common" — PremiumFooter.jsx
 * renders <footer id="contact">, so the #contact h2/h3/h4 and #contact
 * p/input/select/button family rules below also reach React's footer. This
 * is harmless only because every value they set agrees with what
 * premium-chrome.css sets directly on the same elements (confirmed by the
 * React rendered verification) — not because the id is actually unreachable.
 *
 * Scope: only the chrome's own primary navigational and transactional text.
 * The mega-menu panel's descriptive column HEADLINE and DESCRIPTION
 * paragraph are deliberately NOT touched here — the description already
 * clips at its current 13.5px on the two routes that render it open (Home,
 * /us/), a pre-existing baseline finding; enlarging it toward a
 * reading-body role would make that clipping worse, which this batch
 * reports rather than repairs. See the Phase 2B1 report. The column's
 * EYEBROW is a separate, short, all-caps identifier line above the
 * headline — not reading text — and IS mapped below, to the compact role,
 * as part of the Phase 2B1 repair (2026-09-16): it was sub-11px (10px) and
 * is a short label, exactly what --type-compact exists for.
 *
 * The column's "Explore All X" / "View All X" CTA link, and the accordion
 * rendering's equivalent .ix-acc-all link, each need their OWN rule below:
 * both are still <a> tags, so the generic `a`/`.ix-acc-link` rules two
 * blocks up would otherwise sweep them up to the nav-main size too —
 * caught empirically by diffing a before/after render, not assumed. The
 * membrane reference has exactly the same problem for the terminal CTA and
 * solves it exactly this way, with its own more specific selector
 * overriding the generic one.
 *
 * #main-app-header itself is deliberately NOT a bare selector below (unlike
 * its `button`/`a` companions): #ie-mega-navigator/#mega-menu-navigator
 * nest inside #main-app-header, and a bare `#main-app-header` selector's
 * font-family would inherit down into every descendant with no explicit
 * font-family of its own — including the mega-menu's plain <span>/<h3>/<p>
 * text, silently changing the description paragraph's computed font
 * fallback stack even though it's meant to be untouched (found and fixed
 * 2026-09-16). `#main-app-header button`/`#main-app-header a` alone still
 * reach every anchor and button in the header, including inside
 * #header-utility-bar and #header-desktop-menu (both nest inside
 * #main-app-header too) and inside the mega-menu panel — nothing needs a
 * compensating rule for losing the bare selector.
 */

#main-app-header button,
#main-app-header a {
  font-family: var(--type-font-display);
}

/* No font-family here: #header-utility-bar is nested inside #main-app-header,
   so the rule above already reaches its anchors/buttons. */
#header-utility-bar a,
#header-utility-bar button {
  font-size: var(--type-nav-utility);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/* Likewise nested inside #main-app-header — family already applied. */
#header-desktop-menu > button,
#header-desktop-menu > a {
  font-size: var(--type-nav-main-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.35;
}
@media (min-width: 1024px) {
  #header-desktop-menu > button,
  #header-desktop-menu > a {
    font-size: var(--type-nav-main);
  }
}

/*
 * font-family is needed here (unlike the two rules above): .ix-acc-sum is a
 * <summary>, [data-mega-tab] > span and the rail span are <span>s, none of
 * which the #main-app-header rule's `button`/`a` selectors reach even though
 * the mega-menu panel nests inside #main-app-header too. font-weight is
 * deliberately NOT set — the rail label's native font-bold/font-medium
 * (700/500) distinguishes the active tab from inactive ones, and flattening
 * both to one weight would erase that distinction. This matches the
 * membrane reference exactly: it doesn't set font-weight here either.
 */
#ie-mega-navigator [data-mega-tab] > span,
#mega-menu-navigator [id^="rail-tab-"] > span,
#ie-mega-navigator .ix-acc-sum,
#mega-menu-navigator .ix-acc-sum,
#ie-mega-navigator .ix-acc-link,
#mega-menu-navigator .ix-acc-link,
#ie-mega-navigator a,
#mega-menu-navigator a {
  font-family: var(--type-font-display);
  font-size: var(--type-nav-main-mobile);
  line-height: 1.4;
}
@media (min-width: 1024px) {
  #ie-mega-navigator [data-mega-tab] > span,
  #mega-menu-navigator [id^="rail-tab-"] > span,
  #ie-mega-navigator .ix-acc-sum,
  #mega-menu-navigator .ix-acc-sum,
  #ie-mega-navigator .ix-acc-link,
  #mega-menu-navigator .ix-acc-link,
  #ie-mega-navigator a,
  #mega-menu-navigator a {
    font-size: var(--type-nav-main);
  }
}

/*
 * More specific than the bare `a` rule above (an extra child-combinator
 * level), so it wins for this one link and pulls it off the nav-main size
 * onto the complete action role instead — matching the membrane
 * reference's own treatment of this exact link. Family and weight added
 * 2026-09-16: the first pass only set size/line-height, leaving this link
 * computing Plus Jakarta Sans 700 instead of the action role's own Inter
 * 600.
 */
#ie-mega-navigator > div > div > div:nth-child(2) > div:last-child > a,
#mega-menu-navigator > div > div > div:nth-child(2) > div:last-child > a {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * The accordion rendering's own "View All Products" link (assets/megamenu.js
 * marks it with both ix-acc-link and ix-acc-all — see localUrl()'s isAll
 * branch). More specific than the .ix-acc-link rule above (two classes vs
 * one under the same id ancestor), so it wins there. Added 2026-09-16: this
 * link was still landing on the generic nav-main-mobile/nav-main size the
 * .ix-acc-link rule sets, the same class of bug the CTA link above already
 * had fixed.
 */
#ie-mega-navigator .ix-acc-all,
#mega-menu-navigator .ix-acc-all {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * The descriptive column's eyebrow (e.g. "TAILORED PRODUCTS" — see the
 * `eyebrow` field in assets/megamenu.js's SECTIONS data). Native markup sets
 * it in a monospace Tailwind utility (font-mono) at 10px with no explicit
 * line-height — sub-11px and off the sitewide type family. Mapped to the
 * existing --type-compact role (2026-09-16): family/size/weight/line-height
 * only, matching the role's own definition (see .type-compact above);
 * letter-spacing and uppercase casing are left as authored (tracking-[0.2em]
 * uppercase in the source markup), per this batch's casing/tracking rule.
 * Selector verified against Home's own rendered DOM — the description
 * paragraph immediately after (`p`, next sibling) is NOT part of this
 * selector and stays untouched.
 */
#ie-mega-navigator > div > div > div:nth-child(2) > div:first-child > span:first-child,
#mega-menu-navigator > div > div > div:nth-child(2) > div:first-child > span:first-child {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/* Shared footer. */
#contact h2,
#contact h3,
#contact h4 {
  font-family: var(--type-font-display);
}

#contact p,
#contact input,
#contact select,
#contact button,
#footer-columns a,
#footer-address address {
  font-family: var(--type-font-body);
}

/* Footer type (user, 3 Nov 2026): one size for all footer text, 14px, with
   nothing smaller (tagline and address were 16px, links and the bottom bar
   13px, labels 10 to 13px). The four column headings and the Address and
   Newsletter labels are 15px and one step bolder (700), in Home's darker
   brand blue (#005691, they were slate-500), so they read above their lists. #contact lifts these rules above the
   membrane family's body.membrane-typography sizes, so every page type
   matches. */
#contact #footer-directory > div > p,
#contact #footer-address address,
#contact #footer-columns a,
#contact #newsletter-segment > span,
#contact #footer-presence-bar span,
#contact #footer-presence-bar a,
#contact #footer-presence-bar > div,
#contact #footer-licensing div,
#contact #footer-licensing span,
#contact #footer-licensing a {
  font-size: 14px;
}

#contact #footer-columns h4,
#contact #footer-address > div:first-child,
#contact #newsletter-segment > div:first-child {
  font-size: 15px;
  font-weight: 700;
  color: #005691;
}

/* Home and most pages space the heading 12px above its list (Tailwind
   space-y-3); a page-level list reset (Careers, CSR) dropped it to 0. */
#contact #footer-columns h4 + ul {
  margin-top: 12px;
}

#impact-metrics-row > div > div > div:last-child,
#impact-metrics-row > div:last-child > div:last-child {
  font-family: var(--type-font-display);
  font-size: var(--type-footer-meta);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/* No font-family below: #contact-form-widget, #newsletter-segment and
   #footer-directory all nest inside #contact, whose p/input/select/button
   rule above already applies it. */
#contact-form-widget p,
#footer-directory > div:first-child > p,
#footer-address address {
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

/* Footer address and social links under the tagline (scripts/apply-footer.mjs
   on the static pages, the Home bundle's own footer on Home). The label and
   the address take the Newsletter label and tagline rules beside them. */
#footer-address {
  padding-top: 8px;
}

#footer-address > * + * {
  margin-top: 8px;
}

#footer-address address {
  font-style: normal;
}

#footer-social {
  display: flex;
  align-items: center;
  gap: 16px;
  padding-top: 4px;
}

#footer-social a {
  display: inline-flex;
  color: #64748b;
  transition: color 0.2s ease;
}

#footer-social a:hover,
#footer-social a:focus-visible {
  color: #0284c7;
}

#footer-social svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

/* Country menu (Home and assets/region-selector.js): the country names are
   11px since 2 Nov, and "United Kingdom" needs a little more than the
   600px panel to show whole. */
#global-language-directory-dropdown {
  width: 640px;
  max-width: calc(100vw - 32px);
}

#contact-form-widget input,
#contact-form-widget select,
#newsletter-segment input {
  font-size: var(--type-form-control);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

#contact-form-widget button,
#newsletter-segment button {
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

#consultation-form > p,
#newsletter-segment > span {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

#newsletter-segment > div,
#footer-address > div:first-child,
#footer-columns h4,
#footer-presence-bar,
#footer-licensing {
  font-family: var(--type-font-display);
  font-size: var(--type-footer-meta);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/* No font-family: #footer-columns a is already one of the selectors in the
   #contact p/input/select/button, #footer-columns a family rule above. */
#footer-columns a {
  font-size: var(--type-footer-link);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

#footer-presence-bar span,
#footer-licensing span,
#footer-licensing a {
  font-size: inherit;
  line-height: inherit;
}

#footer-presence-bar > div:last-child {
  font-size: var(--type-footer-meta);
  line-height: 1.45;
}

/* ---------------------------------------------------------------------------
 * Careers page-content typography — /careers/ only
 * (17 September 2026; scope moved from <body> to #main-content itself on
 * 17 September 2026 during the closeout repair — see below)
 *
 * Scope: `#main-content.careers-typography`. The class is the one
 * Careers-only hook on the page, carried directly on the existing
 * <main id="main-content"> element rather than on <body>. It originally
 * lived on <body>, but careers/index.html also donates its <head> and
 * <body> tag verbatim to scripts/build-csr-static.mjs when building the CSR
 * page, so a class on <body> would have been copied onto CSR's <body> the
 * next time that generator ran — even though every rule below is scoped to
 * #main-content and CSR has none of this matching content. Putting the
 * class on #main-content instead removes the coupling at the source: the
 * generator's verbatim <body> copy has nothing left to carry, and
 * #main-content is not part of the shell it reuses.
 *
 * The (1,2,0) specificity is still deliberate: careers/index.html carries a
 * "requested text-size increases" block of id-scoped rules such as
 * `#opportunities .minilabel` and `#life .open__title` at (1,1,0). A bare
 * `.careers-typography .minilabel` would lose to those and leave the page
 * split between two scales. Anchoring on the #main-content id keeps the id
 * count equal (1 = 1) while the extra `.careers-typography` class still
 * outweighs it on class count (2 > 1), so one canonical rule wins across
 * every section exactly as it did when the class lived on <body>.
 *
 * Only font-family, font-size, font-weight and line-height are set here.
 * Casing and letter-spacing stay with the component, per design.md 2.2.5, so
 * .type-compact's uppercase/0.1em default is deliberately NOT applied.
 *
 * Approved, temporary Careers-specific exceptions (design.md 2.2.7 item 7;
 * docs/typography-audit.md) — rendered typography preserved exactly, not
 * mapped to a canonical role in this repair: .grow__no, .csr2__val (large
 * statistic numerals) and .vox__quote (pull quote); .csr2__plus inherits
 * .csr2__val's tuple and only sets color. No canonical role covers a
 * display statistic numeral or an editorial pull quote.
 *
 * The #apply heading ("Three ways in.") previously carried a 34px inline
 * style attribute that no stylesheet rule could outrank; that attribute was
 * removed in the closeout repair, so `.open__title` below now applies to it
 * like every other section heading.
 * ------------------------------------------------------------------------- */

/* Display hero */
#main-content.careers-typography .hero__title {
  font-family: var(--type-font-display);
  font-size: var(--type-display-hero);
  font-weight: var(--type-weight-heading);
  line-height: 1.1;
}

/* Section heading */
#main-content.careers-typography .h2,
#main-content.careers-typography .open__title {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-section);
  font-weight: var(--type-weight-heading);
  line-height: 1.14;
}

/* Subsection heading */
#main-content.careers-typography .disc__name,
#main-content.careers-typography .pol__cat {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-subsection-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.25;
}

/* Card title */
#main-content.careers-typography .evpc__name,
#main-content.careers-typography .grow__name,
#main-content.careers-typography .awd__title,
#main-content.careers-typography .role__name,
#main-content.careers-typography .vox__name,
#main-content.careers-typography .conv__what {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}

@media (min-width: 1024px) {
  #main-content.careers-typography .disc__name,
  #main-content.careers-typography .pol__cat {
    font-size: var(--type-heading-subsection);
  }

  #main-content.careers-typography .evpc__name,
  #main-content.careers-typography .grow__name,
  #main-content.careers-typography .awd__title,
  #main-content.careers-typography .role__name,
  #main-content.careers-typography .vox__name,
  #main-content.careers-typography .conv__what {
    font-size: var(--type-title-card);
  }
}

/* Lead body */
#main-content.careers-typography .lead,
#main-content.careers-typography .opensplit__note,
#main-content.careers-typography .vox__note {
  font-family: var(--type-font-body);
  font-size: var(--type-body-lead);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

/* Standard body */
#main-content.careers-typography .disc__body,
#main-content.careers-typography .pol__what,
#main-content.careers-typography .pol__list li,
#main-content.careers-typography .grow__body,
#main-content.careers-typography .vox__smallquote,
#main-content.careers-typography .evpc__body,
#main-content.careers-typography .evpc__keys li,
#main-content.careers-typography .evp__quote,
#main-content.careers-typography .glass-note p {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

/* Metadata */
#main-content.careers-typography .role__meta,
#main-content.careers-typography .conv__meta,
#main-content.careers-typography .awd__year,
#main-content.careers-typography .awd__org {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/* Action */
#main-content.careers-typography .btn,
#main-content.careers-typography .routechip,
#main-content.careers-typography .disc__go,
#main-content.careers-typography .role__go,
#main-content.careers-typography .finder__more,
#main-content.careers-typography .finder__reset {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/* Form control */
#main-content.careers-typography .filterfield select {
  font-family: var(--type-font-body);
  font-size: var(--type-form-control);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

/* Compact label */
#main-content.careers-typography .eyebrow,
#main-content.careers-typography .minilabel,
#main-content.careers-typography .disc__no,
#main-content.careers-typography .evpc__no,
#main-content.careers-typography .csr2__lab,
#main-content.careers-typography .glass-note__cap {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/*
 * Shared primitives — Batch 5 (18 September 2026).
 *
 * `.ds-label-system` and `.ds-caption` are two of the sitewide design-system
 * primitives. Their only served definition on the 1,029 static routes that
 * link assets/content.css is inside that manually-published, deliberately
 * stale bundle (content.css is NOT rebuilt as part of this batch — the
 * emitted Vite output is a documented strict superset of unrelated classes,
 * per the NOTE on content.css in scripts/build-services-static.mjs — editing
 * or refreshing it here would land unrelated rules on every page that loads
 * it). src/styles/design-system.css, the maintained source, reaches none of
 * those routes today: it is only pulled in via src/main.jsx's Vite import,
 * and that bundle is not linked by any canonical static page.
 *
 * These plain bare-class rules rely on cascade ORDER, not specificity, to
 * win over content.css's own same-specificity (0,1,0) definitions: every one
 * of the 11 route-family templates that link both files places this file's
 * <link> after assets/content.css's — verified directly in the served head
 * markup of a representative page from every family, not assumed. Two
 * existing, more specific scoped overrides continue to win where they
 * already do — `.ir-drawer__header .ds-label-system` and friends in
 * assets/indion-resins.css, and `#mem-page#mem-page .ds-caption` in
 * assets/membrane-typography.css — both outrank a bare class regardless of
 * load order and are untouched by this rule.
 *
 * Verified route reach (real markup, not just stylesheet linkage): exactly
 * 12 routes render `.ds-label-system` (csr, indion-resins, investor-relation,
 * 7 market-segments case-study pages, 1 engineering leaf page) and 16 render
 * `.ds-caption` (14 country-landing routes including /us/, downloads,
 * investor-relation) — 27 unique routes, 8 distinct structural signatures.
 * Careers does not render either class in its live markup (confirmed by a
 * fresh grep of its real markup, not just its stylesheet); the Careers
 * closeout's own already-approved implementation is unaffected. Home does
 * not link assets/content.css and its frozen bundle does not define either
 * class, so Home is unaffected. Casing, letter-spacing, color, spacing and
 * layout are left exactly as content.css already sets them — only family,
 * size, weight and line-height are mapped onto an existing canonical role.
 *
 * `.ds-label-system` (10px today) is mapped to Compact — a short uppercase
 * identifier/label role, matching its name and existing usage.
 * `.ds-caption` (9px today, already Plus Jakarta Sans) is mapped to
 * Metadata — a 13px Plus Jakarta Sans role, the closer family match of the
 * two 13px roles to what content.css already sets.
 */
.ds-label-system {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.ds-caption {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/*
 * Shared primitives — Batch 5B (18 September 2026).
 *
 * `.field__label` (Form Label role) and `.field__control` (Form Control
 * role): both already match their target role's family. Live markup:
 * `.field__label` on the 17 contact-us pages plus /investor-relation/;
 * `.field__control` only on /investor-relation/. `#main-content` is
 * added as a specificity anchor (Mega Cycle III repair) so this rule
 * beats `.handoff-page .field__label` (content.css, (0,2,0)) outright
 * via the ID rather than losing to it on the 17 contact-us routes as a
 * bare class did originally — that gap left size/weight (9.5px/700)
 * owned by content.css there, with only line-height actually applied by
 * this rule; the full canonical tuple now applies uniformly on all 18
 * routes. Confirmed by real rendered evidence, not assumed from either
 * rule's specificity alone. The footer's own
 * newsletter `.field__control` stays governed by the pre-existing,
 * higher-specificity `#newsletter-segment input` shared-chrome rule (an id
 * beats a bare class regardless of load order), so this addition does not
 * reach shared chrome. Careers, Home and the six membrane routes render
 * neither class in real markup — confirmed by exact class-token search,
 * not assumed.
 *
 * `.link-list__link` (Action role): the one live use (35 elements on
 * /investor-relation/) is a list of CTA-style document links, matching
 * design.md's "Standard CTA/button -> Action role" mapping. No other route
 * renders this class.
 *
 * Two other candidates from this batch's brief — `.btn` and `.ds-eyebrow` —
 * were analyzed, found to have a real live use on an explicitly protected
 * route, and deliberately NOT implemented. See docs/typography-audit.md's
 * Batch 5B entry for the exact rendered evidence and reasoning:
 * `.btn` reaches Home's own "Explore Our Innovation" R&D CTA (its frozen
 * bundle's `.btn--primary` line-height leaked through because typography.css
 * loads after that bundle); `.ds-eyebrow` reaches all six membrane routes
 * because their own protective override (`#mem-page#mem-page .ds-eyebrow`
 * in membrane-typography.css) sets only `font-size`, not `line-height` —
 * the one property this batch would have changed. Both were caught by the
 * required rendered before/after sweep, not by the static specificity
 * analysis alone, which is exactly why that check is required before
 * trusting a specificity argument on a large-reach selector.
 */
#main-content .field__label {
  font-family: var(--type-font-display);
  font-size: var(--type-form-label);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.field__control {
  font-family: var(--type-font-body);
  font-size: var(--type-form-control);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

.link-list__link {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * "Handoff pages" cluster (Mega Cycle III, 19 September 2026) —
 * /research-and-development/, /sustainability/, /contact-us/ (the
 * standalone family's own bespoke, hand-authored marketing pages; none
 * of the three has a dedicated family stylesheet — every selector below
 * lives in the shared, frozen assets/content.css under a `.handoff-page`
 * wrapper). /csr/ shares the exact same `.handoff-page .X` rule text in
 * its own markup's inline styles but its LIVE rendered DOM uses none of
 * it — confirmed by a rendered check, not static grep, after the same
 * kind of false-positive grep hit this engagement already learned to
 * distrust once on Home: `grep -rl` finds these class names in
 * csr/index.html and careers/index.html too, but in both cases that is
 * only the CSS *rule definition* sitting unused in an inline `<style>`
 * block, with zero matching `class="..."` in real markup and zero
 * matching element in the live DOM at any width. Both routes are
 * therefore excluded from this cluster's implemented reach, not
 * silently assumed clear.
 *
 * `#main-content` anchors every rule below to clear `.handoff-page .X`'s
 * (0,2,0) specificity — confirmed present once on all three real routes.
 *
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .hubx__kicker ("Hub 02 / Application"), .msq__no ("01", a real
 *     accessible ordinal — has its own data-field attribute, not
 *     aria-hidden, unlike the sibling .vstage__node/.sdgo__num below),
 *     .msq__stop-year ("1965" timeline-stop years), .recog-panel__kicker
 *     ("Newest - Commissioned 2019") — all four on /research-and-
 *     development/.
 *   .rshow__kicker ("3rd Annual ESG Summit..."), .rlogo ("Transformance
 *     Forums", a partner-logo name badge) — both on /sustainability/.
 *   .glass-note__cap ("Talk to us" / "Free Consultation") — reaches both
 *     /research-and-development/ and /contact-us/.
 *   .info-panel__kicker ("Prefer to talk?"), .offcard__info-label ("Tel")
 *     — both on /contact-us/ only.
 *   All nine are already Plus Jakarta Sans/700 at 8.5-10px (content.css's
 *   own `--type-caption`/`--type-label` tokens, both 10px), below the
 *   sitewide 11px floor; family/weight already match Compact, only size
 *   and line-height move to canonical.
 * ---- Action (14px/Inter/600/1.4) ----
 *   .sdgo__navbtn ("Prev"/"Next" goal-carousel buttons, /sustainability/)
 *     — a real interactive control (its own aria-label, arrow icon
 *     separately aria-hidden), so mapped by function per the standing
 *     "don't use Compact merely because current values are small" rule
 *     even though its own 9.5px/700/display numerically coincides with
 *     Compact, the same reasoning already applied to .pback and
 *     .eng-sidebar-tree a elsewhere in this contract.
 * Excluded, decorative (not a candidate): .vstage__node ("01"-"04" stage
 *   markers, /research-and-development/) and .sdgo__num ("01"-"17" goal
 *   numbers, /sustainability/) — both carry `aria-hidden="true"` directly
 *   on the same element, confirmed in the real markup, not inferred.
 * Correction (sub-11px normalisation pass, 19 September 2026): this
 * block originally blocked every Plus Jakarta Sans/600 "caption" tier
 * selector below Metadata's 13px floor, reasoning that no role pairs
 * Plus Jakarta Sans with weight 600 under that floor. That reasoning was
 * too strict — this contract already accepts a below-floor size jump
 * wherever family+weight match exactly (the same precedent used
 * throughout: .ctry-strip__label, .eng-partners__pill, .soltrain__name).
 * All of .darkstat__label, .hubx__loc, .hubx__sub, .msq__range,
 * .msq__count, .msq__hint, .pstat__label, .recog-panel__badge
 * (/research-and-development/), .apply-card__label, .rmeta__label,
 * .rshow__ptitle, .sdgo__count, .sdgo__hint, .wlcstage__name,
 * .wlcycle__cap (/sustainability/), and .direct__label, .offcard__label
 * (/contact-us/ family) are now migrated to Metadata further down this
 * file, each with its own reach/cascade evidence. Only two of the
 * original group remain genuinely blocked: .pstat__kicker (a different
 * shape — Inter/400, migrated to Image caption instead, also below) and
 * .sdgp__chip (Inter/600, no role pairs Inter with weight 600 — see its
 * own note below). .sdgo__hub-name still carries a `-webkit-line-clamp`,
 * the same regression shape design.md 3.5.1 and this contract's own
 * .cp-card__desc precedent already flag as unsafe to touch without
 * separately approved layout work — left blocked, not force-migrated.
 */
#main-content .hubx__kicker,
#main-content .msq__no,
#main-content .msq__stop-year,
#main-content .recog-panel__kicker,
#main-content .rshow__kicker,
#main-content .rlogo,
#main-content .glass-note__cap,
#main-content .info-panel__kicker,
#main-content .offcard__info-label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content .sdgo__navbtn {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * "Handoff pages" cluster completion — sub-11px normalisation pass
 * (19 September 2026). Re-derived fresh from assets/content.css, not
 * carried forward from the prior "no canonical role pairs Plus Jakarta
 * Sans with weight 600 under Metadata's 13px floor" finding — that
 * finding predates this pass's mandate to accept a below-floor size jump
 * where family+weight already match exactly, the same standing exception
 * already used throughout this contract (.ctry-strip__label,
 * .eng-partners__pill, .soltrain__name, etc.).
 *
 * Every selector below has BOTH a bare `.X` rule and an identical
 * `.handoff-page .X` compound rule in content.css (0,2,0) — the same
 * duplicate-rule shape already handled for .field__label/.offcard__info-
 * label; `#main-content` (confirmed present once on each route) clears
 * the compound rule's specificity regardless of which of the two content.
 * css rules the browser resolves. Reach re-verified against real markup
 * this pass (not the earlier static grep, which false-matched csr/
 * index.html's own dead inline `<style>` block the same way Home's and
 * Careers' did in prior cycles — confirmed zero real `class="...X..."`
 * usage on /csr/ for every one of these 16 selectors). No fixed-height or
 * overflow:hidden ancestor found for any of them — confirmed by scanning
 * their containing components (.darkstat, .hubx, .msq__stage, .wlcstage,
 * .rmeta, .rshow__logos, .apply-card, .sdgo) in content.css — so none
 * needs a component-local fit repair to grow to Metadata's size.
 *
 * ---- Metadata (13px/Plus Jakarta Sans/600/1.45) ----
 * /research-and-development/ (7): .darkstat__label ("Founded" stat-block
 *   caption), .hubx__loc ("Mumbai, India" hub location), .hubx__sub
 *   (hub sub-caption), .msq__range ("1965 - 1980" milestone-stage year
 *   range), .msq__count ("6 milestones" stage count), .msq__hint
 *   ("Scroll to explore" hint), .recog-panel__badge ("Recognition"
 *   panel badge — padding-based, not fixed-height, confirmed safe to
 *   grow).
 * /sustainability/ (7): .apply-card__label ("Deadline" application-card
 *   field label), .rmeta__label ("Founded" / "Focus" leadership-card
 *   meta label), .rshow__ptitle (partner-logo-row pre-title), .sdgo__count
 *   ("Goal 06" SDG overview counter), .sdgo__hint ("Tap a goal to learn
 *   more" hint), .wlcstage__name (welcome-cycle stage name, e.g.
 *   "Awareness" — the .wlc-bloom-text animation only affects opacity/
 *   transform, confirmed no font-size keyframe), .wlcycle__cap (welcome-
 *   cycle caption).
 * All 14 are already Plus Jakarta Sans/600 at 8-10px; only size and
 * line-height move to canonical.
 *
 * .pstat__label (partner-stat caption, /research-and-development/) is
 * already exact Plus Jakarta Sans/600/1.45 (9px only differs from
 * Metadata by size) -> Metadata, the same pattern as the group above,
 * listed separately only because its line-height was already correct.
 */
#main-content .darkstat__label,
#main-content .hubx__loc,
#main-content .hubx__sub,
#main-content .msq__range,
#main-content .msq__count,
#main-content .msq__hint,
#main-content .recog-panel__badge,
#main-content .apply-card__label,
#main-content .rmeta__label,
#main-content .rshow__ptitle,
#main-content .sdgo__count,
#main-content .sdgo__hint,
#main-content .wlcstage__name,
#main-content .wlcycle__cap,
#main-content .pstat__label {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/*
 * .pstat__kicker (partner-stat top label, e.g. "Water treated", above
 * .pstat__value's big figure; /research-and-development/) is a different
 * shape from its .pstat__label sibling above — already Inter/400 (the
 * banned weight is NOT in play here; 400 is Standard body's own weight),
 * not Plus Jakarta Sans/600 -> Image caption (13px/Inter/400/1.45), the
 * closer family+weight match. No fixed-height ancestor (.darkstat only
 * sets a left border/padding).
 *
 * Blocked (Bucket D), evidence recorded: .sdgp__chip ("Water Treatment" /
 * "Zero Liquid Discharge" / "Recycling" SDG solution-category chips,
 * /sustainability/, src/components/sustainability/SDGSection.jsx) —
 * Inter/600/10.5px, no canonical role pairs Inter with weight 600: Action
 * matches weight but is reserved for real interactive controls (these
 * chips render inside a read-only `<span>` list, no click handler,
 * confirmed in SDGSection.jsx) and Standard body/Helper both require
 * weight 400. The identical "non-interactive Inter/600 tag, no role fits"
 * finding already made for .us-case-study-card__tag elsewhere in this
 * contract. Also a fixed-height (25px) pill — even if a role fit existed,
 * Metadata's 1.45 line-height at 13px (18.85px) would need a component-
 * local height repair; not reached because no role fit was found first.
 */
#main-content .pstat__kicker {
  font-family: var(--type-font-body);
  font-size: var(--type-image-caption);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * .metric__label / .vstage__where — sub-11px normalisation pass
 * (19 September 2026). Two shared hero-stats primitives, both declared
 * in assets/content.css with a duplicate `.handoff-page .X` compound
 * override (0,2,0) each, the same shape as the group above —
 * `#main-content` anchors both to clear it.
 *
 * `.metric__label` ("First R&D centre" / "Years of innovation" /
 * "Water Recycled" hero-stat captions) reaches 4 distinct contexts,
 * reach-checked via real `class="..."` markup, not the static grep alone
 * (which also false-matched careers/index.html and csr/index.html —
 * zero real usage confirmed on both, the same dead-inline-style-block
 * pattern already caught repeatedly this engagement):
 *   - /research-and-development/ and /sustainability/ (content.css,
 *     loads before typography.css).
 *   - /market-segments/ (2 of its 4 real instances — a "60+ Years of
 *     Practice / 6 Continents Served" stat pair OUTSIDE the `#msl`
 *     landing scope entirely, sharing the same bare content.css rule;
 *     the other 2 instances are already-migrated `#msl`-scoped ones).
 *   - /about-us/ — a SEPARATE, page-local declaration in its own
 *     about-us/styles.css (near-identical 10px/Jakarta/600/tracked/
 *     uppercase tuple), not the content.css rule at all — confirmed
 *     safe from the previously-documented Home-collision risk: neither
 *     milestones.css nor rnd-leadership.css (the two files actually
 *     shared with Home) declares .metric__label/.metric-strip/
 *     .hero-stats at all (grep-confirmed, 0 matches in both), and
 *     about-us's own styles.css is not linked by Home. typography.css
 *     loads after styles.css on /about-us/, so the bare selector wins
 *     there too.
 * No fixed-height or overflow:hidden ancestor on `.metric`/`.metric-
 * strip` in either declaration (padding-only) — safe to grow without a
 * fit repair. `.metric__value` (the adjacent large statistic figure) is
 * deliberately untouched — the existing, unrelated "no role for a
 * display statistic numeral" finding.
 *
 * `.vstage__where` (R&D's milestone-pipeline location caption, e.g.
 * "Patancheru R&D Centre") is /research-and-development/-exclusive
 * (real-markup-checked; the csr/careers file matches are the same dead-
 * rule false positive as above). Already Plus Jakarta Sans/600/tracked/
 * uppercase; only size (9px) and line-height move. Its sibling
 * `.vstage__node` (the "01"-"04" pipeline-step ordinal) carries
 * `aria-hidden="true"` directly on the element (confirmed in the live
 * markup) and stays excluded as decorative, unchanged from the existing
 * .vstage__node/.sdgo__num precedent already established for this
 * cluster.
 *
 * `.direct__label` ("Telephone"/"Email" field labels, the root /contact-
 * us/ page's own "direct line" card) and `.offcard__label` (office-panel
 * field labels on the 16 country /contact-us/ pages) reach exactly the
 * 17-route contact-us family — real-markup-checked; csr/careers are the
 * same dead-rule false positive as above. Both already Plus Jakarta
 * Sans/600/tracked/uppercase; no fixed-height ancestor (`.direct__row`
 * is padding-based, `.offcard__info-row` is grid with no row-height).
 *
 * ---- Metadata (13px/Plus Jakarta Sans/600/1.45) ----
 */
#main-content .metric__label,
#main-content .vstage__where,
#main-content .direct__label,
#main-content .offcard__label {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/*
 * Family-scoped primitives — Batch 6 (18 September 2026): country
 * sub-pages and the corporate blog (assets/country-pages.css,
 * assets/blog-pages.css — both hand-maintained assets with no Vite/src
 * counterpart, generated by build-country-subpages.mjs and
 * build-corporate-blog.mjs). Every selector below is grep-verified
 * against every other src/styles/*.css and assets/*.css file with zero
 * collisions, and its exact route reach was confirmed either from
 * docs/typography-manifest.json's per-class fingerprint reach (the cp-*
 * class family as a whole: 851 routes, exactly country-sub + blog, zero
 * elsewhere) or by scanning all 871 country/blog/country-landing route
 * files directly for the class token (blog-card__badge: 244 routes, all
 * under blog/; the unique .blog-filter chip row: 1 route, the shared
 * /blogs/ index). The selectors actually implemented below reach a
 * narrower union of 835 of those 851 routes (591 country-sub + 244
 * blog) — the remaining 16 country-sub /contact-us/-family routes carry
 * only deferred selectors (see below) and receive no implemented change,
 * nor do any of the 20 country-landing routes. None of these classes
 * reach Home, Careers, membranes, engineering, market-segments,
 * speciality-chemicals, country-landing, or shared chrome. typography.css
 * loads after both family stylesheets on every verified route, so
 * same-specificity bare/compound-class rules here win by source order,
 * matching the mechanism proven in Batches 5A/5B.
 *
 * Role assignments (design.md 2.2.6, "map by what they are"):
 *   Article body   — .cp-prose p, ul, ol (flowing article copy)
 *   Image caption   — .cp-prose figcaption ("Captions -> Image caption")
 *   Table heading   — .cp-prose th
 *   Table data      — .cp-prose table (governs inherited <td> text)
 *   Card title      — .cp-card__title ("Card heading -> Card title")
 *   Standard body   — .cp-empty (search empty-state message)
 *   Metadata        — .cp-card__foot, .cp-meta (both publish-date/read-
 *                      time labels), .cp-count ("29 articles" — 2.2.2's
 *                      own example is "dates and short identifier
 *                      labels")
 *   Compact label   — .cp-tag, .cp-backlink ("<- All insights", already
 *                      11px/700; only line-height was missing),
 *                      .blog-card__badge (category badge on a card)
 *   Action          — .cp-more button ("Load more"), .blog-filter
 *                      (category filter chip — "Standard CTA/button ->
 *                      Action role")
 *   Form control    — .cp-search input (the blog/article search box)
 * No letter-spacing or text-transform is set on any rule below, even for
 * the Compact-label group: 2.2.5 reserves the uppercase/tracking default
 * for new use and requires preserving component-authored casing during a
 * migration, and none of the source rules being replaced set either
 * property, so there is nothing to preserve away from.
 *
 * Deferred, NOT implemented (reported, not silently dropped):
 *   - .cp-card__desc — mapped to Standard body (16px/400/1.6) in an
 *     earlier pass of this batch, then reverted. Its existing markup is a
 *     3-line `-webkit-line-clamp`; real rendered before/after evidence
 *     across the tested country/blog signatures showed the 16px tuple
 *     (up from the current 14px/300) pushed 57 additional card
 *     descriptions past their clamp's line budget, each becoming newly
 *     clipped that was not clipped before. The clamp itself, the card
 *     height, and every other layout property were and remain untouched
 *     — this is a real consequence of the larger role's line-count fit
 *     against a fixed-height card, not a bug in the migration. Design.md
 *     3.5.1 anticipates exactly this ("a clamp introduced for a 14px
 *     description must be re-checked once that copy grows toward the
 *     16px Standard body role") but re-checking found it unsafe as a
 *     typography-only change. Fixing it requires separately approved
 *     clamp/layout work (e.g. raising the card's fixed height or the
 *     clamp's line count) before the Standard body tuple can be applied
 *     here; until that is approved, `.cp-card__desc` keeps its existing
 *     14px/300/1.65 literal, unchanged.
 *   - .cp-prose h2/h3/h4, blockquote — nested headings/pull-quotes inside
 *     free-form article body copy. Real sampled articles show h2 marking
 *     the article's own top-level sections and h3 nested inside an h2
 *     section, but neither reading produces a clean tuple match to
 *     Section heading or Subsection heading, and no canonical role covers
 *     a third heading tier or an editorial pull-quote — design.md 2.2.7
 *     item 7 records the identical gap for Careers' pull quotes rather
 *     than inventing a role. Forcing today's numeric coincidence (h3's
 *     20px/600/1.3 happens to equal Card title's desktop tuple) would
 *     misrepresent semantic intent, since an article h3 is not a card.
 *   - .cp-contact__card (h3/p/a), .cp-enquiry__address, .cp-region(__code)
 *     — the 16 country-sub /contact-us/-family routes. Scanning all 871
 *     route files found inline font-size/font-weight overrides on sibling
 *     <p> tags inside this exact card on several routes (e.g. /ae/contact-
 *     us/'s "REGIONAL REACH" paragraph), so a class-based rule would apply
 *     inconsistently within the same card; .cp-region is also a clickable
 *     country-switcher link, not clearly body copy or an Action.
 *   - country-landing (.ctry-strip__label, .ctry-industries__pill,
 *     .ctry-placeholder__label; both templates, 20 routes) — analyzed but
 *     not implemented for two different reasons. The first two have no
 *     confidently-matching canonical role (nearest fits, Statistic unit
 *     and Compact label, both require a substantial size/weight change,
 *     and Statistic unit has never had a live consumer). The third,
 *     .ctry-placeholder__label, already matches Compact label almost
 *     exactly (11px/700/display; only line-height was missing) but the
 *     preferred mechanism does not work for this family: on every
 *     country-landing:new-template route, assets/country-landing.css
 *     loads AFTER assets/typography.css (the reverse of every other
 *     Batch 6 family), so an additive rule here would be silently
 *     outranked by that file's own rule rather than winning — verified on
 *     both the hand-authored /us/ shell and a generated route (/ae/). No
 *     file other than typography.css was in scope to fix the load order,
 *     so this is reported as a mechanism gap rather than forced.
 *   - .ctry-strip__label/.ctry-industries__pill share that same reversed
 *     load order, which is an independent reason they could not have been
 *     implemented here even if a role fit had been found.
 *   - .blog-filter__n (the small in-chip count badge) — nearest role
 *     (Compact label, weight 700) would bold a currently-regular (400)
 *     cosmetic number with no confidently-matching alternative.
 *   - .btn / bare .ds-eyebrow — unchanged, per Batch 5B: blocked from
 *     unscoped sitewide mapping because rendered evidence proved leakage
 *     onto Home and the six membrane routes. Not revisited this batch.
 */
.cp-prose p,
.cp-prose ul,
.cp-prose ol {
  font-family: var(--type-font-body);
  font-size: var(--type-body-article-mobile);
  font-weight: var(--type-weight-body);
  line-height: 1.7;
}
@media (min-width: 1024px) {
  .cp-prose p,
  .cp-prose ul,
  .cp-prose ol {
    font-size: var(--type-body-article);
  }
}

.cp-prose figcaption {
  font-family: var(--type-font-body);
  font-size: var(--type-image-caption);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

.cp-prose th {
  font-family: var(--type-font-display);
  font-size: var(--type-table-heading);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

.cp-prose table {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

.cp-card__title {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .cp-card__title {
    font-size: var(--type-title-card);
  }
}

.cp-empty {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

.cp-card__foot,
.cp-meta,
.cp-count {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

.cp-tag,
.cp-backlink,
.blog-card__badge {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.cp-more button,
.blog-filter {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.cp-search input {
  font-family: var(--type-font-body);
  font-size: var(--type-form-control);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

/*
 * Family-scoped primitives — Batch 7 (18 September 2026): Engineering,
 * non-membrane Products, Market Segments, Speciality Chemicals and
 * Services (assets/engineering.css, assets/market-segments.css,
 * assets/market-segments-landing.css, assets/speciality-chemicals.css,
 * assets/product-pages.css, assets/services-lifecycle.css,
 * assets/content-pages.css — all hand-maintained, no Vite/src
 * counterpart). Scope was derived from the current filesystem and
 * docs/typography-manifest.json, not from memory: 89 engineering routes
 * (82 on assets/engineering.css, 7 leaf pages on assets/content-pages.css
 * with page-unique class prefixes — purity-/cpu-/desal-/aop-/oilgas-),
 * 34 market-segments routes (25 mainstream + 8 case-study + 1 landing),
 * 14 speciality-chemicals routes, 1 products-index route (/products/),
 * and 3 services routes (manifest family "standalone", isolated here by
 * route path since the fingerprint field does not separate them).
 *
 * Mechanism gap (majority of this batch's routes): the preferred additive
 * mechanism only works where typography.css loads AFTER the family
 * stylesheet. Checked in the real <head> of a representative file per
 * distinct signature (42 signatures), not assumed from the manifest's
 * alphabetised stylesheets list:
 *   - engineering: typography.css loads BEFORE engineering.css on all 82
 *     mainstream routes (blocked); AFTER content-pages.css on the 7 leaf
 *     routes (usable).
 *   - market-segments: order is inconsistent per hand-authored route, not
 *     just per sub-family — typography.css loads AFTER market-segments.css
 *     on the 8 case-study routes and on 10 of the "industrial/*" routes
 *     (23 routes usable), but BEFORE it on both "institutions/*" and
 *     "communities/*" routes and the /market-segments/ landing page (11
 *     routes blocked).
 *   - speciality-chemicals: typography.css loads AFTER
 *     speciality-chemicals.css on all 14 routes (fully usable).
 *   - products-index: typography.css loads BEFORE product-pages.css on
 *     the one route (blocked).
 *   - services: typography.css loads AFTER services-lifecycle.css on
 *     /services/ (usable); the other 2 services routes load no family
 *     stylesheet at all (their svc-om-, svc-feature- and svc-sidebar-
 *     prefixed rules live in the frozen, sitewide assets/content.css, out
 *     of scope).
 * No file other than typography.css was in scope to fix any load order,
 * so every blocked case above is reported as a mechanism gap, not forced.
 *
 * Reach analysis (design.md-required, done by grepping real class="..."
 * tokens across all 1033 manifest routes, not by fingerprint alone):
 * caught that #main-content and #msl — the two "container" IDs the family
 * stylesheets scope under — are not family-exclusive (#main-content is on
 * 1032 of 1033 routes; it is the sitewide <main> landmark). Scoping was
 * therefore verified by the class name alone for every candidate below.
 * That check also found assets/speciality-chemicals.css duplicates several
 * class names 1:1 from assets/market-segments-landing.css (.indx__name,
 * .civcol__title, .insttile__name, .resrow__title) with identical values,
 * but the underlying HTML for those classes exists only on the single
 * /market-segments/ landing route, which is cascade-blocked — so those
 * four rules are dead CSS on every speciality-chemicals route and are not
 * implemented. The same check found .metric__value/.metric__label reach
 * /about-us/, /research-and-development/ and /sustainability/ — all
 * protected or explicitly-tested standalone pages — so both are excluded
 * outright, not just deferred.
 *
 * Role assignments implemented (design.md 2.2.6, "map by what they are"):
 *   Image caption   — .ms-shot figcaption (gallery photo captions in
 *                      market-segments/industrial/*, e.g. "RO", "UF");
 *                      raised to a photo title on 30 Oct 2026, see the rule
 *   Card title      — .ms-index-card__title (hub-index card headings,
 *                      e.g. "Power", "Refinery & Petrochemical")
 *   Compact label   — #main-content .section-label-row > .section-label,
 *                      #main-content .cap__label (speciality-chemicals
 *                      eyebrow labels, e.g. "Overview", "INDION® Autochem").
 *                      In both selectors #main-content is present ONLY to
 *                      match the source rule's specificity (an ID selector
 *                      always outranks any number of classes, so without
 *                      it these rules parse but silently lose the cascade
 *                      — verified empirically, not assumed) — it is not
 *                      family scope and is not claimed as such: it is the
 *                      sitewide <main> landmark present on 1032 of 1033
 *                      routes. The real, verified structural scope is
 *                      `.section-label-row > .section-label` and the class
 *                      name `.cap__label`. `.section-label-row` was
 *                      grepped across all 1033 routes and found on exactly
 *                      13, all speciality-chemicals, none elsewhere; every
 *                      one of the 62 real `.section-label` elements across
 *                      those 13 routes is confirmed a direct child of it
 *                      (zero orphans outside the wrapper), so the narrower
 *                      `>` combinator reaches exactly the same elements as
 *                      the bare class did, while no longer matching a
 *                      hypothetical, unrelated future `.section-label`
 *                      that isn't inside this specific wrapper.
 *   Lead body       — .svc-lifecycle__stage-summary (/services/ lifecycle
 *                      rail's one-sentence stage summary paragraph)
 * Every implemented selector was confirmed real (not vestigial) and
 * clamp/overflow-free in its own rule and immediate container before
 * being added. No letter-spacing or text-transform is set on any rule
 * below; none of the source rules being replaced set either property.
 *
 * Partial reach, implemented anyway (mechanism-gap routes for the SAME
 * class are simply inert there, not incorrect): .ms-index-card__title
 * appears on 3 hub pages (communities/industrial/institutions) but only
 * /market-segments/industrial/ has a winning cascade; the other two keep
 * their current market-segments.css tuple, unchanged.
 *
 * Deferred, NOT implemented (reported, not silently dropped):
 *   - .ms-index-card__lead — same 14px/weight-300/1.65,
 *     `-webkit-line-clamp: 3` pattern that caused Batch 6's .cp-card__desc
 *     regression (design.md 3.5.1). Not even attempted: migrating to
 *     Standard body (16px/400/1.6) would almost certainly repeat that
 *     regression, and it is already one of this batch's pre-existing
 *     clipped elements at its current, smaller size.
 *   - .cap__title, .feat__title, .pbens__text (speciality-chemicals),
 *     .ms-brochure__label (market-segments), .svc-lc-card__title,
 *     .svc-lc-card__detail-title, .svc-why__subheading,
 *     .svc-why__stat-label (services), .desal-thermal li (engineering
 *     leaf) — real, non-vestigial, cascade-usable where they appear, but
 *     no canonical role tuple is a confident match (weight and/or size
 *     deltas large enough that normalizing would be a design judgment
 *     call, not a verified migration). .svc-why__stat-label is the
 *     closest fit to design.md's Statistic unit role, which design.md
 *     itself records has never had a live consumer; wiring it for the
 *     first time here was left for an explicit follow-up decision rather
 *     than assumed.
 *   - .cpu-benefits__list li::before — decorative `content: '✓'` glyph,
 *     not real text; not a typography candidate.
 *   - .membranes-page-banner .btn — 0 routes contain this class today;
 *     not applicable.
 *   - .solrel__name, .soltrain__name and the remaining
 *     assets/engineering.css / assets/product-pages.css / bare
 *     assets/market-segments.css and assets/market-segments-landing.css
 *     selectors on mechanism-blocked routes — real candidates, several
 *     with clean role fits (e.g. #msl .hero__title already matches
 *     Display hero, #msl .h2 already matches Section heading — both
 *     literal-equivalent, not migration candidates either way), but
 *     cascade-blocked per the mechanism-gap note above.
 *   - .btn / bare .ds-eyebrow / .h2 / .lead — unchanged, per Batch 5B:
 *     blocked from unscoped sitewide mapping (proven leakage), and this
 *     batch's own reach analysis reconfirmed .metric__value/.metric__label
 *     reach protected standalone pages. Not revisited.
 */
/* User review, 30 Oct 2026: the gallery captions name the photo and must
   stand out, so they are set as a photo title (display face, standard body
   size, subheading weight) rather than the 13px image caption. */
.ms-shot figcaption {
  font-family: var(--type-font-display);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

.ms-index-card__title {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .ms-index-card__title {
    font-size: var(--type-title-card);
  }
}

#main-content .section-label-row > .section-label,
#main-content .cap__label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.svc-lifecycle__stage-summary {
  font-family: var(--type-font-body);
  font-size: var(--type-body-lead);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

/*
 * Cascade/load-order recovery — Batch 8 (18 September 2026).
 *
 * Re-derived the blocked set fresh from all 1033 routes' real <head> order
 * (not the manifest's alphabetised stylesheets list), and every route is
 * accounted for in exactly one of three buckets that sum to 1033: 901
 * routes have >=1 family stylesheet, and it loads BEFORE typography.css
 * (typography.css already wins ties); 17 routes load no family stylesheet
 * at all (nothing to compete with typography.css there either); together
 * these 918 are unblocked. The remaining 115 have >=1 family stylesheet
 * loading AFTER typography.css (typography.css loses ties): 82
 * engineering-mainstream, 11 market-segments, 14 country-landing:new-
 * template, 6 membranes, 1 products-index, 1 standalone (/indion-resins/).
 * The 2 services routes reported earlier are a different case: they load
 * no family stylesheet at all, so there is no order to fix — their
 * svc-om-, svc-feature- and svc-sidebar-prefixed rules live in the frozen,
 * sitewide assets/content.css.
 *
 * Ownership: build-engineering-static.mjs, build-products-static.mjs and
 * build-country-landing-static.mjs all insert their family stylesheet with
 * `.replace('</head>', ...)`, i.e. unconditionally last, by construction —
 * a maintained-generator pattern, not a per-route accident. Editing that
 * insertion point and regenerating 82+1+14 = 97 routes was evaluated and
 * rejected for this batch: it is a broad, higher-blast-radius action than
 * a typography.css-only fix, and every route below is fully recoverable
 * without it, so mechanism (1) (reorder + regenerate) was not needed and
 * was not attempted. Membranes (accepted, closed — membrane-typography.css
 * is its own deliberate, separate contract) and /indion-resins/ (its own
 * standalone stylesheet, never in scope for any prior batch) are excluded
 * outright, not evaluated further.
 *
 * Mechanism used — never (1) reorder, never `!important`, never a
 * specificity arms race: for every selector below, the property that
 * actually needed to change (always line-height, sometimes font-size) has
 * NO competing declaration anywhere else that reaches it (checked against
 * the owning family stylesheet, assets/content.css and
 * assets/index-DC2gwfO6.css), so a plain bare-class rule here is enough —
 * cascade order stops mattering once there is nothing left to lose to.
 * Two selectors (.solrel__name, .resrow__title) needed a real font-size
 * change too, which DOES compete; both use a genuine DOM-structural
 * compound selector (a real parent already in the markup, not an
 * arbitrary specificity anchor) that naturally out-specifies the source
 * rule — `.solrel__card .solrel__name` (0,2,0) beats bare `.solrel__name`
 * (0,1,0) in assets/engineering.css; `#msl .reslist .resrow__title`
 * (1,2,0) beats `#msl .resrow__title` (1,1,0) in
 * assets/market-segments-landing.css. Neither claims a container ID as
 * family scope; `#msl` is the market-segments-landing page's own unique
 * root id (confirmed on exactly 1 route sitewide), used the same way
 * `#main-content` was used as a pure specificity anchor in the repaired
 * Batch 7 `.section-label` rule.
 *
 * Implemented (all reach-checked across all 1033 routes; zero elsewhere):
 *   Compact label — .ctry-placeholder__label (14 country-landing:new-
 *     template routes; "Image pending client validation" — already
 *     11px/700/display, only line-height was missing, exactly as Batch 6
 *     found and left blocked); .eng-sidebar-group__label (69 of 82
 *     blocked engineering routes; sidebar-accordion group labels, e.g.
 *     "Water Treatment"); .eng-tile__step (3 routes; short step-number
 *     identifier, e.g. "01"); .solrel__kicker (1 route; card eyebrow,
 *     e.g. "Water Treatment") — all already 11px/700/display, only
 *     line-height added. .eng-partners__label (1 route; section label,
 *     "Technology Partners") is a PARTIAL normalization only, not a
 *     complete canonical Compact-label tuple: family/weight/line-height
 *     are brought to canonical, but font-size is intentionally NOT set
 *     here. engineering.css authors this selector at 12px, not the
 *     canonical 11px, and on this blocked route engineering.css's own
 *     rule wins font-size by cascade order regardless of anything this
 *     file could assert, so the 12px stays owned by, and stated only in,
 *     the family stylesheet — this file does not duplicate or override
 *     it.
 *   Card title — .solrel__card .solrel__name (1 route; related-solutions
 *     card heading, e.g. "Process Water Treatment"); #msl .reslist
 *     .resrow__title (1 route; hub-index card heading, e.g. "Homes &
 *     Apartments") — both real <h3> card headings already at
 *     600/display and within a few px of the canonical range.
 *
 * Rejected as numeric coincidence, not a real semantic match (same
 * property values as Compact label, but the element is a CTA, not a
 * label) — left untouched: .sol-over__link ("Download the Brochure"),
 * .soltrain__link ("Learn More"). Both are real Action-role content, but
 * migrating them to Action's own tuple (14/600/body) is a materially
 * larger, unvetted change than this batch's line-height-only mechanism
 * and was not attempted here.
 *
 * Deferred — real, cascade-usable-in-principle, but no confident role
 * (component-tier size differences, not just literal drift, so forcing a
 * single tuple onto all of them would erase an apparently intentional
 * size hierarchy across component types): #msl .civcol__title, #msl
 * .insttile__name, #msl .indx__name (weight/size far outside Card
 * title's range — a deliberately larger index-tile style, not a card).
 * .eng-partners__pill, .eng-sidebar-tree a (weight 600/500, matching no
 * canonical weight token cleanly). .soltrain__name (16px, below Card
 * title's 18px floor).
 *
 * Not evaluated for implementation this batch: assets/product-pages.css's
 * .ix-* and .ixd-* selectors (/products/, the 1 blocked products-index
 * route).
 * Re-derivation found products/index.html also carries its own large
 * inline <style> block (bare, unscoped .ix-* rules covering layout/visual
 * properties only — confirmed no typography-property overlap with
 * assets/product-pages.css's higher-specificity `.resins-designer .ix-*`
 * rules, so not itself a cascade risk to the properties this contract
 * touches) plus several tab/pill/CTA elements without a confident
 * canonical-role match. Given that complexity and the lack of a clean
 * semantic decision, this was left for a dedicated pass rather than
 * folded into a load-order batch. The 2 services routes (content.css-
 * sourced svc-om-, svc-feature- and svc-sidebar-prefixed classes) were
 * likewise
 * left alone: no prior batch ever decided a canonical mapping for them,
 * so there is nothing "blocked" to recover — only a first-time semantic
 * decision to make, which is out of this batch's mechanism-recovery scope.
 */
.ctry-placeholder__label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/*
 * Mega Cycle II completion (18 September 2026) of the three selectors
 * above: Batch 8 asserted family/weight/line-height only, leaving
 * font-size unset because on the 69 (of 82) engineering.css routes where
 * it loads AFTER typography.css, a same-specificity bare-class rule here
 * would tie and lose — the values happened to already match (11px in
 * engineering.css itself) but this file was not the true winner there.
 * `#main-content` (confirmed present on every engineering.css route)
 * clears that tie uniformly, so this file now truly owns all four
 * properties on both the 13 unblocked and 69 blocked routes alike.
 * `.eng-partners__label`'s font-size is completed the same way — this is
 * the exact gap the Batch 8 repair explicitly left unresolved ("the 12px
 * stays owned by, and stated only in, the family stylesheet"); it is now
 * centrally owned like the other three.
 */
#main-content .eng-sidebar-group__label,
#main-content .eng-tile__step,
#main-content .solrel__kicker,
#main-content .eng-partners__label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/*
 * Remaining Engineering candidates (Mega Cycle II), all via the same
 * `#main-content` anchor for the same load-order reason above.
 *   .eng-partners__pill ("Technology Partners" partner-name pill; 13.5px/
 *     600/Plus Jakarta Sans) -> Metadata (13px/600/Plus Jakarta Sans/
 *     1.45) — weight already matched exactly; a static badge, not a
 *     control (no click handler on this class in the source).
 *   .eng-sidebar-tree a (engineering sub-page sidebar navigation link;
 *     13.5px/500/Inter) -> Action (14px/Inter/600/1.4) — family already
 *     matched; a real navigational link, not a label.
 *   .sol-over__link ("Download the Brochure"), .soltrain__link ("Learn
 *     More") — both 11px/700/Plus Jakarta Sans, numerically matching
 *     Compact label by coincidence (Batch 8's own finding) but both are
 *     real link controls -> Action (14px/Inter/600/1.4), the completion
 *     Batch 8 flagged as "a materially larger... change" and left for a
 *     later pass.
 *   .soltrain__name (training-card heading, e.g. a card title in the
 *     rail; 16px/600/Plus Jakarta Sans) -> Card title (18px<1024px,
 *     20px>=1024px/600/1.3) — family and weight already matched exactly;
 *     Batch 8 noted it sits "below Card title's 18px floor" but did not
 *     find a better-fitting role, and none of the other 22 roles matches
 *     a 600-weight Plus Jakarta Sans card heading better.
 */
#main-content .eng-partners__pill {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

#main-content .eng-sidebar-tree a,
#main-content .sol-over__link,
#main-content .soltrain__link {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

#main-content .soltrain__name {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .soltrain__name {
    font-size: var(--type-title-card);
  }
}

.solrel__card .solrel__name {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .solrel__card .solrel__name {
    font-size: var(--type-title-card);
  }
}

#msl .reslist .resrow__title {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #msl .reslist .resrow__title {
    font-size: var(--type-title-card);
  }
}

/*
 * Engineering mainstream family repair (Mega Cycle III, 18 September
 * 2026) — the 82-route family on assets/engineering.css. Re-derived by
 * parsing every selector in that file carrying a font-family, font-size
 * or font-weight declaration (24 total), not by trusting any prior
 * batch's own candidate list, because that full parse turned up several
 * real candidates no earlier batch had ever evaluated. typography.css
 * loads BEFORE engineering.css on this family (the same blocked order
 * Batch 8 already established), so `#main-content` is used as the same
 * pure specificity anchor as the existing .eng-sidebar-group__label
 * group above; it is not family scope.
 *
 * Already resolved, not new work: .eng-partners__pill, .eng-sidebar-tree
 * a, .sol-over__link, .soltrain__link, .soltrain__name (Mega Cycle II,
 * above). .eng-sidebar-children a and .eng-sidebar-tree a.is-current are
 * both real descendants of the already-migrated `.eng-sidebar-tree a`
 * selector (`.eng-sidebar-children` is a nested wrapper inside
 * `.eng-sidebar-tree`, and `.is-current` is a state modifier on the same
 * `a`), so the existing `#main-content .eng-sidebar-tree a` rule already
 * reaches and wins on both — confirmed live (14px/600/Inter/1.4 on both,
 * not their own source rules' 13px/400 and unset-family respectively).
 * Neither needed a rule of its own.
 *
 * Already an exact match (Bucket B, no rule needed): .eng-overview__lede,
 * .eng-overview__more, .eng-tile__text, .soltrain__body, .sol-over__body
 * p — all five are `clamp(15px,1.4vw,17px)/Inter/400/1.6` verbatim,
 * identical to Lead body's own definition.
 *
 * Not a typography candidate: .sol-accent (a gradient-clipped inline
 * accent span inside a heading, weight 700 only — the sitewide "accent
 * spans stay bold" convention, not a role migration; it sets no family/
 * size/line-height of its own and inherits its heading's).
 *
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .eng-leaf-page .page-banner__pending-label ("Image pending client
 *     validation"; 17 routes, all under products/engineering/, confirmed
 *     the only routes and the only CSS declaration for this class
 *     sitewide) — already 11px/700/display/uppercase/0.12em tracking,
 *     only line-height was missing, the same completion pattern as the
 *     .eng-sidebar-group__label group.
 * ---- Card title (18px<1024px, 20px>=1024px/Plus Jakarta Sans/600/1.3) -
 *   .eng-tile__title (engineering-hub tile headings, e.g. "Plant Layout
 *     Modelling") — already an exact 20px/600/display match at and above
 *     1024px; flat today with no mobile step, so the <1024px 18px step
 *     is new, not just a line-height fill (1.3 was already correct).
 * Blocked (Bucket D), no defensible role:
 *   .eng-overview__icon-label (an icon-adjacent section label, e.g.
 *     "Plant Layout Modelling" under a pictogram; Plus Jakarta Sans/16px/
 *     600) — numerically matches three different roles at once (Statistic
 *     unit, Table heading, and Main navigation's <1024px half all use
 *     16/600/display) but is semantically none of the three: not a
 *     statistic figure, not a table header, not a nav item. Forcing any
 *     of them would borrow an unrelated role's identity on a numeric
 *     coincidence alone, the same reasoning Batch 8 already used to
 *     reject .sol-over__link/.soltrain__link as Compact-label
 *     coincidences.
 * Dead code (zero live consumers, confirmed by grep across every route
 * sitewide, not just this family): .soltrain__ends, .soltrain__ends b,
 * .soltrain__no — all three are declared in assets/engineering.css but
 * matched by zero elements anywhere.
 */
#main-content .page-banner__pending-label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content .eng-tile__title {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .eng-tile__title {
    font-size: var(--type-title-card);
  }
}

/*
 * Engineering leaf-page family completion (Mega Cycle III, 18 September
 * 2026) — the 7 routes on assets/content-pages.css whose page-unique
 * class prefixes (purity-/cpu-/desal-/aop-/oilgas-) were left as an
 * unresolved "partial, zero hard debt" note in Mega Cycle II: this batch
 * rebuilds the ledger from scratch by re-grepping the real markup of all
 * 7 routes (confirmed exactly 7 via grep -rl "content-pages.css"
 * products/engineering/), not by trusting that prior note's inventory.
 * content-pages.css loads BEFORE typography.css on all 7 (confirmed in
 * each route's own <head>), so a same-specificity bare rule here already
 * wins by load order; no #main-content anchor is needed anywhere in this
 * block.
 *
 * Several page-unique wrapper classes carry no typography properties of
 * their own and are pure ambient inheritance (.purity-benefit p,
 * .purity-tile p — margin:0 only) or already reach a shared, sitewide
 * `.ds-*` utility class applied directly on the same element
 * (.cpu-benefits__list li p, .desal-membrane__grid p, .oilgas-offerings p
 * all also carry .ds-body, already an exact Lead-body match; .ds-h2 is
 * already an exact Section-heading match; .ds-h3 is already an exact
 * Subsection-heading match at 25px/20px; .ds-label-system is already
 * migrated to Compact sitewide; .ds-eyebrow is already blocked sitewide
 * for proven leakage) — none of these six are new candidates, and none
 * is touched again here.
 *
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .desal-membrane__number, .oilgas-offerings__number (step ordinals
 *     "01"/"02"/"03"; already 11px/700/display, only line-height was
 *     missing — the same completion pattern as Batch 8's .eng-* group).
 *   .purity-contact__details span, .purity-contact__details small
 *     (contact-card field labels "Telephone"/"Email" and the
 *     "9:00 AM to 6:00 PM INDIA IST" note; family/size already matched
 *     but weight was the banned 300, not 700 — a real fix, not just a
 *     gap-fill). Uppercase casing and the existing 0.12em letter-spacing
 *     are preserved and now scale off the unchanged 11px, so neither
 *     actually changes.
 * ---- Card title (18px<1024px, 20px>=1024px/Plus Jakarta Sans/600/1.3) -
 *   .desal-thermal li (a short bulleted technology-name list —
 *     "Multi-Stage Flash Evaporation (MSF)" etc. — already an EXACT
 *     18px/600/Plus Jakarta Sans match; only line-height (1.4 -> 1.3) and
 *     the responsive >=1024px step are added). Reverses Batch 7's
 *     original "no confident role" finding for this selector: that
 *     finding predates this cycle's stricter complete-tuple mandate and
 *     did not have this exact a family/weight/size match in hand — every
 *     property but line-height already matched precisely, which is a
 *     materially stronger case than a fresh, unmatched candidate.
 * ---- Article body (16px<1024px, 17px>=1024px/Inter/400/1.7) ----
 *   .aop-tradeoffs ul, .oilgas-approach li (flowing prose bullet lists —
 *     AOP advantages/disadvantages; oil-and-gas process-approach
 *     statements). Matched on the role's distinctive 1.7 line-height,
 *     unique among all 23 roles, and on the live precedent this same
 *     contract already sets for exactly this shape of content
 *     (.cp-prose p/ul/ol, Batch 6). .oilgas-approach li is an exact 17px
 *     match at the >=1024px step; .aop-tradeoffs ul is 15px flat today,
 *     1px under the <1024px step — accepted on family/weight/line-height
 *     confidence, the same size-tolerance already used for .chain__name
 *     (19px) and similar Card-title candidates elsewhere in this
 *     contract. Both were flat, unresponsive sizes before this rule; the
 *     responsive step is new.
 * ---- Helper/response (14px/Inter/400/1.45) ----
 *   .aop-methods ol (two short numbered procedure lists — "Formation of
 *     OH" / "UV/H2O2" etc.; exact 14px size match; weight was the banned
 *     300).
 * Blocked (Bucket D), no defensible role, evidence recorded:
 *   .aop-equations span (raw chemical equations, e.g. "Fe2+ + H2O2 ->
 *     Fe3+ + HO. + OH-"; inherits Plus Jakarta Sans/20px from its parent
 *     .aop-equations div) — no canonical role uses 20px in Plus Jakarta
 *     Sans (nearest are Card title at 18/20px and Subsection heading at
 *     22/25px, neither a semantic match for a formula) and the content
 *     itself is not a heading, title, or label.
 *   .purity-contact__details a (the tel:/mailto: link text itself, e.g.
 *     "+91-22-6610-0678"; Inter/16px/600) — no role combines Inter, 16px
 *     and weight 600: Standard body matches size+family but is weight
 *     400; Table heading and Statistic unit match size+weight but are
 *     Plus Jakarta Sans, not Inter. Forcing any of the three would
 *     override an explicitly-authored property this contract's own scope
 *     does not license changing without a real match.
 */
.desal-membrane__number,
.oilgas-offerings__number,
.purity-contact__details span,
.purity-contact__details small {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.desal-thermal li {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .desal-thermal li {
    font-size: var(--type-title-card);
  }
}

.aop-tradeoffs ul,
.oilgas-approach li {
  font-family: var(--type-font-body);
  font-size: var(--type-body-article-mobile);
  font-weight: var(--type-weight-body);
  line-height: 1.7;
}
@media (min-width: 1024px) {
  .aop-tradeoffs ul,
  .oilgas-approach li {
    font-size: var(--type-body-article);
  }
}

.aop-methods ol {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * Products-index typography completion — Mega Cycle Phase B (18 September
 * 2026), /products/ only. Supersedes Batch 9's partial rules for
 * .ixd-sec-h, .ixd-kicker, .ixd-mini-h, .ixd-comp-c, .ixd-pair-k,
 * .ix-eyebrow and .ix-reslink: those asserted family/weight/line-height
 * only, leaving font-size locally owned by the inline <style> block. Every
 * selector below now carries a complete four-property tuple; none is
 * described as migrated while any of the four remains owned elsewhere.
 *
 * Cascade (unchanged from Batch 9's finding): three sources compete on
 * /products/ — assets/product-pages.css ("PP", compound
 * `.resins-designer .ix-X` selectors, (0,2,0), loads immediately after
 * typography.css in <head>), a ~1,480-line inline <style> block physically
 * in the body (bare `.ix-X`, (0,1,0), parsed later in document order than
 * PP but of lower specificity so it still loses to PP), and this file. The
 * real Products scope is `.resins-designer`; `#main-content` below is used
 * only as a specificity anchor, never as the scope. For any property PP
 * already declares for a selector, a same-specificity `.resins-designer
 * .ix-X` rule here would tie PP and lose (PP loads later), so those groups
 * are anchored with `#main-content` to clear PP's (0,2,0) — the minimum
 * needed, an ID versus any count of classes. Two selectors (.ix-acc-title,
 * .ix-acc-reslabel) have a PP ceiling of (1,2,0) because PP additionally
 * targets them via `#ixResinAcc`; those add `.ix-root` — already present
 * on the same wrapping `<section class="resins-designer ix-root">`, no
 * markup change — to reach (1,3,0), clearing that ceiling. Where PP
 * declares nothing for the property, a plain `.resins-designer .ix-X`
 * (beats the inline block's (0,1,0) outright) is used instead, with no
 * anchor. Reach: every rule below is scoped under `.resins-designer` or
 * `#main-content .resins-designer`, both confirmed present in exactly one
 * served route (products/index.html) across all 1,033 routes' actual
 * markup; a like-named, unbuilt, unlinked fragment at
 * src/content/products/resins.html is not a served route.
 *
 * ---- Compact label (Plus Jakarta Sans / 11px / 700 / 1.3) ----
 * design.md 2.2.6: short labels, ordinals, and tertiary metadata. Casing/
 * tracking are left exactly as authored (already uppercase/tracked
 * locally on every one of these); only family/size/weight/line-height are
 * asserted.
 *   .ixd-sec-h — drawer section marker ("I // Product Overview").
 *   .ixd-kicker, .ixd-mini-h, .ixd-comp-c, .ixd-pair-k — drawer micro-
 *     labels (feature kicker, mini-headers, companion category, spec-pair
 *     key). Sizes rise from 10.5/10/9/9.5px to the 11px floor.
 *   .ix-eyebrow — "FREQUENTLY ASKED" above the FAQ heading; 2.2.6 names
 *     eyebrows explicitly. Size moves from 13px down to 11px — the only
 *     decrease in this group.
 *   .ix-prodcount — "16 PRODUCTS" tertiary counter beside the hero heading.
 *   .ix-acc-num — resin-group ordinal ("01", "02"...) in the accordion
 *     header; PP's weight (600) is overridden to Compact's 700.
 *   .ix-appimg-badge — case-study card sector badge; same 600->700 fix.
 * .ix-tag (industry taxonomy chip inside the spec drawer; design.md 3.6:
 *   chips indicate filters/taxonomy attributes) gets the identical tuple
 *   via a plain `.resins-designer` rule below — PP declares nothing for
 *   it, so no anchor is needed. Static, not a control (contrast
 *   .ix-catpill below, which is clickable and therefore mapped to Action).
 */
#main-content .resins-designer .ixd-sec-h,
#main-content .resins-designer .ixd-kicker,
#main-content .resins-designer .ixd-mini-h,
#main-content .resins-designer .ixd-comp-c,
#main-content .resins-designer .ixd-pair-k,
#main-content .resins-designer .ix-eyebrow,
#main-content .resins-designer .ix-prodcount,
#main-content .resins-designer .ix-acc-num,
#main-content .resins-designer .ix-appimg-badge {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.resins-designer .ix-tag {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/*
 * ---- Metadata (Plus Jakarta Sans / 13px / 600 / 1.45) ----
 *   .ix-acc-reslabel — short resin sub-group tag ("Gel", "Macroporous",
 *     "Nuclear / UPW"...) inside the resin accordion, and the static
 *     "Resins Used" caption inside the industry accordion (verified from
 *     the RESIN_PRODUCTS data and renderAccordion()) — a short identifying
 *     label preceding a grid/list, not a heading. This is the same pattern
 *     design.md 3.8 assigns to Metadata for the Timeline component's
 *     "short identifier labels". Family (Plus Jakarta Sans) and weight
 *     (600) already matched; size (20px) and line-height (1.3) did not.
 */
#main-content .resins-designer.ix-root .ix-acc-reslabel {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

/*
 * ---- Card title (Plus Jakarta Sans / 18px<1024px, 20px>=1024px / 600 /
 * 1.3) ---- design.md 2.2.6: card heading. All four below already carry
 * PP's complete 20px/600/1.3/Plus Jakarta Sans tuple at every width; only
 * the missing <1024px step to 18px is added here (mirrors .type-title-card
 * exactly).
 *   .ix-acc-title — accordion row's own title ("Strong Acid Cation
 *     Resins"), the heading of each resin-group/industry-group row.
 *   .ix-strip-title — "Find resins by your industry" strip heading.
 *   .ix-appimg-title — case-study card's own <h3> title.
 *   .ix-faq-q — FAQ question text, the heading of each FAQ row.
 *   .ixd-comp-n — companion-product name inside a clickable companion
 *     tile in the drawer ("Recommended Companions") — a card heading for
 *     a card-shaped control; PP declares nothing for it, so it takes a
 *     plain `.resins-designer` rule, no anchor.
 * .ix-acc-title's PP ceiling is (1,2,0) (the `#ixResinAcc` compound), so
 * it is anchored with `.ix-root` in addition to `#main-content` (see the
 * file header note above); the other three only need to clear PP's plain
 * (0,2,0).
 */
#main-content .resins-designer .ix-strip-title,
#main-content .resins-designer .ix-appimg-title,
#main-content .resins-designer .ix-faq-q {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .resins-designer .ix-strip-title,
  #main-content .resins-designer .ix-appimg-title,
  #main-content .resins-designer .ix-faq-q {
    font-size: var(--type-title-card);
  }
}

#main-content .resins-designer.ix-root .ix-acc-title {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .resins-designer.ix-root .ix-acc-title {
    font-size: var(--type-title-card);
  }
}

.resins-designer .ixd-comp-n {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .resins-designer .ixd-comp-n {
    font-size: var(--type-title-card);
  }
}

/*
 * ---- Section heading (Plus Jakarta Sans / clamp(30px,3.6vw,44px) / 500 /
 * 1.14) ---- design.md 2.2.6/2.2.2. Both are the page's major <h2>-level
 * section introductions.
 *   .ix-ov-h2 — "What are Resins?" section heading.
 *   .ix-faq-h2 — "Frequently Asked Questions" section heading.
 * Family (Plus Jakarta Sans) and weight (already var(--weight-medium)=500
 * via PP) already matched; size (previously clamp(30px,4vw,48px)) and
 * line-height (1.2) did not exactly match the canonical clamp/1.14.
 */
#main-content .resins-designer .ix-ov-h2,
#main-content .resins-designer .ix-faq-h2 {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-section);
  font-weight: var(--type-weight-heading);
  line-height: 1.14;
}

/*
 * ---- Subsection heading (Plus Jakarta Sans / 22px<1024px, 25px>=1024px /
 * 600 / 1.25) ---- design.md 2.2.6.
 *   .ixd-name — the spec drawer's own product-name header (e.g. "INDION
 *     2250 H"), the heading of the drawer's content, not a card within a
 *     grid. Current flat 25px/700/1.25 already sits at Subsection
 *     heading's desktop ceiling; weight moves 700->600 and the <1024px
 *     step to 22px is added.
 */
#main-content .resins-designer .ixd-name {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-subsection-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.25;
}
@media (min-width: 1024px) {
  #main-content .resins-designer .ixd-name {
    font-size: var(--type-heading-subsection);
  }
}

/*
 * ---- Action (Inter / 14px / 600 / 1.4) ---- design.md 2.2.6: standard
 * CTA/button. Explicitly not Compact merely because current sizes are
 * small (10.5-15.5px) — every one below is a real interactive control
 * (`<a>`/`<button>`), not a label.
 *   .ix-reslink — resin-group link inside the industries accordion.
 *   .ix-appimg-link, .ix-whylink, .ix-enq — "Explore case studies",
 *     "Why do we use it?", "Enquire for..." inline links.
 *   .ix-cta — hero/strip primary call-to-action.
 *   .ix-catpill — industry filter pill (`<button>`, toggles the active
 *     industry filter) — contrast the static, non-interactive .ix-tag
 *     above, which stays Compact label.
 *   .ixd-cta — drawer footer CTA buttons ("Consult a Specialist",
 *     "Download MSDS", "Download TDS").
 *   .ix-rescode — resin-code chip (`<button>`, opens the spec drawer for
 *     that resin) — its text is an identifier, but it is a real button
 *     triggering navigation, matching the same "control, not a label"
 *     reasoning as .ix-catpill rather than the static .ix-tag.
 * .ixd-comp-go ("Load specs ->" inside a companion tile), .ixd-cta-ghost
 * and .ixd-cta-primary (the same drawer CTA buttons' own modifier
 * classes) get the identical tuple via a plain `.resins-designer` rule —
 * PP declares nothing for them, so no anchor is needed.
 */
#main-content .resins-designer .ix-reslink,
#main-content .resins-designer .ix-appimg-link,
#main-content .resins-designer .ix-whylink,
#main-content .resins-designer .ix-enq,
#main-content .resins-designer .ix-cta,
#main-content .resins-designer .ix-catpill,
#main-content .resins-designer .ixd-cta,
#main-content .resins-designer .ix-rescode {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.resins-designer .ixd-comp-go,
.resins-designer .ixd-cta-ghost,
.resins-designer .ixd-cta-primary {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * ---- Standard body (Inter / 16px / 400 / 1.6) ---- design.md 2.2.6:
 * card/list copy. PP explicitly sets `font-weight: var(--weight-light)`
 * (300) for the first five — design.md's banned weight — plus a 13.5px
 * size matching no canonical body role; this is that debt's resolution.
 *   .ix-ov-p — "What are Resins?" lead/body paragraphs.
 *   .ix-acc-desc — resin-group description paragraph in the accordion.
 *   .ix-appimg-desc — case-study card description.
 *   .ix-faq-a — FAQ answer paragraph.
 *   .ixd-overview — drawer's own product-overview paragraph.
 *   .ixd-feat li — drawer feature-list bullet items (list copy).
 * .ixd-appitem (drawer application-list bullet items — the same "list
 * copy" pattern, already weight 600 rather than 300, PP declares nothing
 * for it) gets the identical tuple via a plain `.resins-designer` rule.
 */
#main-content .resins-designer .ix-ov-p,
#main-content .resins-designer .ix-acc-desc,
#main-content .resins-designer .ix-appimg-desc,
#main-content .resins-designer .ix-faq-a,
#main-content .resins-designer .ixd-overview,
#main-content .resins-designer .ixd-feat li {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

.resins-designer .ixd-appitem {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

/*
 * ---- Table data (Inter / 16px / 400 / 1.5), value half of a key/value
 * pair ---- design.md 2.2.6.
 *   .ixd-pair-v — the value span of each drawer spec-pair ("Matrix
 *     Formulation" -> e.g. "Styrene DVB"); its key counterpart
 *     (.ixd-pair-k) is in the Compact-label group above. PP declares
 *     nothing for this class, so a plain `.resins-designer` rule is used.
 */
.resins-designer .ixd-pair-v {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

/*
 * ---- Drawer spec table (.ixd-table): header cells -> Table heading,
 * data cells -> Table data ---- Neither cell was captured by Batch 9's
 * live-DOM inventory (it queried `[class*="ix-"], [class*="ixd-"]`; the
 * real `<thead><tr><th>` has no class at all, and `<td class="k">`/
 * `<td class="v">` don't match that substring either — confirmed from the
 * drawerBodyHTML() template, which emits exactly
 * `<table class="ixd-table"><thead><tr><th>...</th><th>...</th></tr></thead>
 * <tbody><tr><td class="k">...</td><td class="v">...</td></tr>...`).
 *   `.ixd-table thead th` — column headers ("Parameter Metric", "Standard
 *     Value") -> Table heading. PP's ceiling here is (0,2,2) (it already
 *     forces the header's font-family to Plus Jakarta Sans, coincidentally
 *     matching Table heading's family, but does not touch size/weight/
 *     line-height, which stay on the inline block's 10px/700/monospace-
 *     turned-display/unset-line-height); `#main-content` clears that
 *     ceiling so this rule — not PP's coincidental family match — is the
 *     true four-property winner.
 *   `.ixd-table td.k`, `.ixd-table td.v` — the key and value cells of each
 *     spec row -> Table data uniformly (PP declares nothing for either, so
 *     a plain `.resins-designer` rule is enough; both currently inherit
 *     13.5px/undeclared-weight from the table's own base rule, and td.v
 *     additionally has its own inline `font-family: ui-monospace, Menlo,
 *     monospace`).
 * Existing uppercase/letter-spacing on the header cells and the mono
 * family on td.v's own inline rule are untouched — this migration asserts
 * only family/size/weight/line-height.
 */
#main-content .resins-designer .ixd-table thead th {
  font-family: var(--type-font-display);
  font-size: var(--type-table-heading);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

.resins-designer .ixd-table td.k,
.resins-designer .ixd-table td.v {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

/*
 * Blocked (Bucket D) — no defensible canonical role, not silently forced:
 *   .ix-tab — the three main-tab buttons ("Products", "Industry",
 *     "Case Studies"; `<button id="ixtab-*">`, aria-selected). Current
 *     computed tuple: Plus Jakarta Sans / 20px (18px at <=680px, via PP's
 *     own media query) / 600 / 1.3. Function: page-local segmented tab
 *     control switching the three main content views. Checked against all
 *     23 roles: Card title fails on function (a tab is not a card
 *     heading, despite sharing Card title's exact 20px/600/1.3 numbers —
 *     the trap this batch was warned against). Main navigation is the
 *     next-closest by function (2.2.6: "primary navigation"), but its
 *     canonical responsive direction is 16px<1024px shrinking to
 *     15px>=1024px, the OPPOSITE of what a segmented tab bar would want,
 *     and the opposite of .ix-tab's own current direction (20px desktop,
 *     18px<=680px) — forcing Main navigation's numbers here would make the
 *     tabs bigger on mobile and smaller on desktop, inverted from their
 *     current, evidently intentional behavior. Utility navigation (12px)
 *     and every heading/body/form/footer role fail on both size and
 *     function. No role in the 23-row matrix covers a page-local tab
 *     control; none is invented here. Left entirely as-is (PP's existing
 *     complete tuple governs, unchanged).
 *
 * Bucket F (no live rendered use on /products/, confirmed by grep across
 * the entire file, not just the 8 captured interaction states):
 * .ix-app-reslabel, .ix-app-bestfor-label, .ix-app-bestfor-text,
 * .ix-app-title, .ix-arrowlink, .ix-card-title, .ix-card-desc, .ix-h2,
 * .ix-hero-title, .ix-hero-text, .ix-intro (all defined in the inline
 * block, never emitted by any render function or present in static
 * markup) plus every purely structural/icon wrapper class (.ix-acc-header,
 * .ix-acc-icon, .ix-acc-row, .ix-card-icon, .ix-faq-icon, .ix-faq-item,
 * .ix-faq-q-btn, .ix-ico, .ix-whylink-ic, .ixd-close, .ixd-comp,
 * .ixd-feat-ic and similar) that carries no typography contract of its
 * own — text, where present, belongs to a child element already covered
 * above. `.ix-root` (Bucket G) already sets the page's ambient base family
 * to Inter via the inline block, matching Standard body's family; it is
 * not itself a text leaf and is not a migration target.
 */

/*
 * Mega Cycle Phase C — Market Segments family (18 September 2026).
 * assets/market-segments.css is the maintained family stylesheet for all
 * 21 /market-segments/* routes (confirmed via grep of every route's
 * markup), but its load order relative to typography.css is NOT uniform
 * across the family — checked per route, not assumed from one sample:
 * the 14 /market-segments/industrial/* routes load market-segments.css
 * BEFORE typography.css (unblocked; a plain bare-class rule wins the
 * cascade tie by document order), while the 7 /market-segments/
 * institutions/* and /market-segments/communities/* routes load it
 * AFTER (blocked; a same-specificity bare-class rule here would tie and
 * lose). All four target classes are bare-class (0,1,0) in
 * market-segments.css on every route, so `#main-content` (present on
 * every one of these routes, confirmed) uniformly clears that ceiling
 * regardless of which of the two load orders a given route uses — one
 * rule shape instead of two. No other stylesheet declares any of these
 * four classes (grep-checked across assets/*.css and src/styles/*.css).
 * Reach-checked: all four classes are used only on /market-segments/*
 * routes across all 1,033 routes' actual markup.
 *   .ms-case__label -> Compact label (11px/Plus Jakarta Sans/700/1.3).
 *     `<h3 class="ms-case__label">Introduction</h3>` etc. — already
 *     10px/Plus Jakarta Sans/700/uppercase/0.18em-tracked; the real
 *     content of each case-study block is the full paragraph(s) beneath
 *     it, so despite the <h3> tag this functions as a small section
 *     marker label (2.2.5's "short labels...tertiary metadata"), the same
 *     pattern as /products/'s .ixd-sec-h. Only size and line-height were
 *     missing.
 *   .ms-brochure__note -> Compact label. "PDF — sector capability
 *     overview", a short caption under the brochure download link.
 *     Family (Plus Jakarta Sans) already matched; size (10px), weight
 *     (600, not Compact's 700) and line-height did not.
 *   .ms-index-card__cta -> Action (14px/Inter/600/1.4). "View sector",
 *     the visible call-to-action text inside each whole-card
 *     `<a class="ms-index-card">` link on the /market-segments/ and
 *     /market-segments/industrial/ index grids — a real action, not a
 *     label, despite currently sharing the other three classes' small
 *     10px/Plus Jakarta Sans/700 tuple (the same "don't use Compact
 *     merely because it's small" reasoning applied to /products/'s
 *     .ix-catpill and .ix-rescode).
 * Deferred (Bucket E, decorative, not a migration target):
 *   .ms-download__icon — `<span class="ms-download__icon"
 *     aria-hidden="true">PDF</span>`, explicitly aria-hidden, a visual
 *     badge substituting for a PDF icon, not accessible text content.
 */
#main-content .ms-case__label,
#main-content .ms-brochure__note {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content .ms-index-card__cta {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * Mega Cycle II — remaining Market Segments content (18 September 2026).
 * Covers the classes and structural cells not reached by the four already
 * migrated above: `.ms-abbr table`/`th`/`td` (abbreviation tables, 12
 * routes) and the case-study-detail template (assets/case-studies.css +
 * assets/case-studies-overrides.css, exclusively the 8 nested case-study
 * detail routes under /market-segments/ — reach-checked: no
 * other stylesheet or route declares any of these five selectors).
 * `#main-content` is used uniformly as the anchor because this family's
 * load order is not uniform (proven in the prior cycle: 14
 * /market-segments/industrial/* routes load their family sheet before
 * typography.css; the 7 /market-segments/institutions/* and
 * /market-segments/communities/* routes load it after) — the anchor wins
 * against a bare-class competitor on either order without needing to
 * track which routes use which.
 * (.ms-shot figcaption was found already migrated by the pre-existing
 * Batch 7 block above — same Image caption tuple, bare selector, no
 * anchor needed there since market-segments.css already loads before
 * typography.css for that selector's own routes. Not duplicated here.)
 *   .ms-abbr th -> Table heading (16px/Plus Jakarta Sans/600/1.4).
 *   .ms-abbr table, .ms-abbr td -> Table data (16px/Inter/400/1.5). The
 *     table itself only set 14px/Inter; weight was unset and fell back to
 *     #main-content's banned 300.
 *   .case-study-fact > p:last-child -> Card title (18px<1024px,
 *     20px>=1024px/Plus Jakarta Sans/600/1.3). The value half of each
 *     project-facts pair ("Andhra Pradesh", "STP 900 m³/day..."); family,
 *     weight and line-height already matched Card title exactly, only the
 *     fluid clamp(17px,1.7vw,22px) size differed.
 *   .case-study-outcomes__list p -> Standard body (16px/Inter/400/1.6).
 *     Outcome-list prose; family already matched, weight was unset
 *     (fell back to 300), size was 14.5px.
 *   .case-study-outcomes__footer -> Statistic unit (16px/Plus Jakarta
 *     Sans/600/1.2 — design.md 2.2.2/2.2.4, a token defined but not yet
 *     wired to any live selector before this). "Approximately 92%
 *     overall water recovery" is exactly the highlighted-statistic case
 *     this token was reserved for; family/weight already matched, only
 *     size (12px) and line-height (unset) did not.
 */
#main-content .ms-abbr th {
  font-family: var(--type-font-display);
  font-size: var(--type-table-heading);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

#main-content .ms-abbr table,
#main-content .ms-abbr td {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

/*
 * .ms-chem (7 routes) is a second, parallel abbreviation/spec table
 * pattern, structurally identical to .ms-abbr but under a different
 * ancestor class (Ceramics/Process Chemicals sections) — caught by
 * rendered verification after the .ms-abbr fix above, not by the static
 * grep alone. No other stylesheet declares .ms-chem's th/td/table.
 */
#main-content .ms-chem th {
  font-family: var(--type-font-display);
  font-size: var(--type-table-heading);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

#main-content .ms-chem table,
#main-content .ms-chem td {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

#main-content .case-study-fact > p:last-child {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .case-study-fact > p:last-child {
    font-size: var(--type-title-card);
  }
}

#main-content .case-study-outcomes__list p {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

#main-content .case-study-outcomes__footer {
  font-family: var(--type-font-display);
  font-size: var(--type-stat-unit);
  font-weight: var(--type-weight-subheading);
  line-height: 1.2;
}

/*
 * /market-segments/ landing page (route-unique id `#msl`, its own
 * self-contained ~77-block design system in assets/market-segments-
 * landing.css, no relation to assets/market-segments.css). `#msl` sits
 * inside `<main id="main-content">`, after the shared `</header>` closes
 * — confirmed page content, not shared chrome. Batch 8 already migrated
 * `#msl .reslist .resrow__title` (untouched here). market-segments-
 * landing.css loads AFTER typography.css on this route, so a same-
 * specificity `#msl .x` rule here would tie and lose; `#main-content` is
 * added (a real ancestor of `#msl`, confirmed) to reach two IDs and
 * clear that tie without inventing a selector, mirroring Batch 8's own
 * `.reslist`-compounding solution to the identical problem.
 * `.h2` and `.lead` are already an EXACT, complete Section-heading and
 * Lead-body match respectively (clamp(30px,3.6vw,44px)/500/1.14 and
 * clamp(15px,1.4vw,17px)/400/1.6, both Plus Jakarta Sans/Inter as
 * required) — Bucket B, no rule added, so as not to duplicate an
 * already-correct literal value.
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .eyebrow (11px/700/display already; only line-height was missing).
 *   .resrow__no, .civcol__no, .indxnav__no (11px/700/display ordinal
 *     labels already; only line-height was missing).
 *   .metric__label, .indx__label, .footer__col h3, .brand__since,
 *     .footer__legal span (10px/9px, weight 500-600) — short uppercase
 *     labels/tertiary metadata, size and weight both move to the floor.
 * ---- Action (14px/Inter/600/1.4) ----
 *   .btn — a real button/link control; `#msl`-scoped, so this does not
 *     touch the sitewide bare `.btn` you asked me to leave alone.
 *   .indx__cta, .indx__foot-link, .indxnav__name (industry-switcher
 *     control text inside `.indxnav__btn`), .inst__close-text (dialog
 *     close-button label) — real interactive-control text, not labels.
 * ---- Standard body (16px/Inter/400/1.6) ----
 *   .res__points li, .civcol__body, .resrow__body, .instcap__body,
 *     .indx__foot-text — list/card copy.
 *   .footer__blurb — explicitly weight 300 (design.md's banned weight)
 *     in the source; moved to 400.
 * ---- Footer link (15px/Inter/400/1.5) ----
 *   .footer__col a, .footer__col span — footer column links/labels,
 *     also explicitly weight 300 in the source.
 * Blocked (Bucket D), no defensible role among the 23, evidence kept
 * brief given the volume — full markup/tuple on request:
 *   .indx__name (clamp(26px,3.1vw,44px)/700, an uppercase display
 *     industry name) and .indx__no (clamp(20px,1.9vw,26px)/700, a large
 *     decorative ordinal) — weight 700 matches no heading role (500/600)
 *     and size matches no role at that weight.
 *   .metric__value (clamp(30px,4vw,44px)/500/line-height:1) — a large
 *     statistic numeral; design.md 2.2.7 item 7 already documents that no
 *     canonical role covers this shape.
 *   .instcap__name (14px/600 but explicitly Inter, unlike the matching
 *     display-family pattern on every sibling title class on this page —
 *     inconsistent evidence, not forced into Card title).
 *   .nav a (13.5px/500/Inter) — closest role by function is Main
 *     navigation, but its canonical family (Jakarta) and weight (600)
 *     both mismatch the current Inter/500.
 *   .brand__mark (18px/700) — a wordmark/logotype; no canonical role
 *     covers logotype text.
 * Decorative/structural, not migrated: .metric__value .suffix,
 * .indxnav__arrow, .insttile__arrow, .civcol__arrow, .resrow__arrow,
 * .inst__close-icon, .indx__cta-dot, .indx__foot-icon, .instcap__icon —
 * icon/arrow glyphs, color-only, no independent typography of their own.
 */
#main-content #msl .eyebrow,
#main-content #msl .resrow__no,
#main-content #msl .civcol__no,
#main-content #msl .indxnav__no {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content #msl .metric__label,
#main-content #msl .indx__label,
#main-content #msl .footer__col h3,
#main-content #msl .brand__since,
#main-content #msl .footer__legal span {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content #msl .btn,
#main-content #msl .indx__cta,
#main-content #msl .indx__foot-link,
#main-content #msl .indxnav__name,
#main-content #msl .inst__close-text {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

#main-content #msl .res__points li,
#main-content #msl .civcol__body,
#main-content #msl .resrow__body,
#main-content #msl .instcap__body,
#main-content #msl .indx__foot-text,
#main-content #msl .footer__blurb {
  font-family: var(--type-font-body);
  font-size: var(--type-body-standard);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

#main-content #msl .footer__col a,
#main-content #msl .footer__col span {
  font-family: var(--type-font-body);
  font-size: var(--type-footer-link);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

#main-content #msl .civcol__title,
#main-content #msl .insttile__name {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content #msl .civcol__title,
  #main-content #msl .insttile__name {
    font-size: var(--type-title-card);
  }
}

/*
 * Services family (18 September 2026) — assets/services-lifecycle.css,
 * exclusively /services/ (the only route linking it; /services/
 * consumables-spares/ and /services/operations-maintenance/ carry no
 * family stylesheet of their own and use only already-migrated shared
 * .ds- and .svc- components, so nothing further was found there). Loads
 * BEFORE typography.css on /services/ (confirmed from <head> order), so
 * plain bare-class rules win the cascade tie by document order — no
 * anchor needed. No other stylesheet declares any of these selectors.
 * `.svc-lc-card__text` and `.svc-lc-card__detail-text` are already an
 * EXACT, complete Lead-body match (clamp(15px,1.4vw,17px)/Inter/400/1.6)
 * — Bucket B, no rule added.
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .svc-lifecycle__tab-badge ("01" stage ordinal; already 11/700/
 *     display, only line-height missing).
 *   .svc-lc-card__eyebrow ("Install & Commission" card eyebrow).
 *   .svc-lc-card__sys ("Install & Commission · 01 / 03" status line).
 *   .svc-lc-card__tag ("Site Installation" capability tag; weight 500
 *     moves to 700).
 * ---- Card title (18px<1024px, 20px>=1024px/Plus Jakarta Sans/600/1.3) --
 *   .svc-lc-card__title ("Commissioning" card heading; weight 700->600,
 *     line-height 1.2->1.3).
 *   .svc-why__subheading (clamp(18px,1.8vw,21px)/600 already — closer to
 *     Card title's range than Subsection heading's 22-25px; weight
 *     already matched).
 * ---- Subsection heading (22px<1024px, 25px>=1024px/Plus Jakarta
 * Sans/600/1.25) ----
 *   .svc-lc-card__detail-title (clamp(22px,2vw,28px)/700 expanded-view
 *     title — bigger than the card title above; weight 700->600).
 * ---- Action (14px/Inter/600/1.4) ----
 *   .svc-lc-card__cta ("Talk To A Commissioning Engineer" CTA button).
 *   .svc-lc-card__more ("Explore" expand link).
 * (.svc-lifecycle__stage-summary was found already migrated to Lead body
 * by the pre-existing Batch 7 block above, with the identical tuple; not
 * duplicated here.)
 * ---- Statistic unit (16px/Plus Jakarta Sans/600/1.2) ----
 *   .svc-why__stat-label ("24×7 Support", 15px/600) — Batch 7 flagged
 *     this by name as "the closest fit to design.md's Statistic unit
 *     role" but left it unwired pending a follow-up decision. This cycle
 *     already wires the same, previously-unused token for
 *     .case-study-outcomes__footer (Market Segments, above); wiring it
 *     here too is that follow-up. Family and weight already matched
 *     exactly; only size (15px) and line-height (unset) did not.
 * Blocked (Bucket D), evidence kept brief given the volume:
 *   .svc-lifecycle__tab-label ("Install & Commission" tab-switch text) —
 *     same reasoning as /products/'s .ix-tab: a content-switching tab
 *     control, not a heading or nav item; no canonical role covers it.
 *   .svc-lc-card__spine (11.5px/600, the collapsed-rail vertical label,
 *     styled only inside `.svc-lc-rail.has-open ... :not(.is-open)`) —
 *     between Compact (11px/700) and no other role at that weight; a
 *     narrow, state-conditional variant of the already-migrated card
 *     title, not itself forced into either role.
 *   .svc-lc-card__related-label ("Erection & Installation", 13px/500/
 *     Inter, plain `<span>`, ambiguous whether it is itself interactive)
 *     — family/weight match neither Metadata (Jakarta/600) nor Action
 *     (14px) cleanly.
 */
.svc-lifecycle__tab-badge,
.svc-lc-card__eyebrow,
.svc-lc-card__sys,
.svc-lc-card__tag {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.svc-lc-card__title,
.svc-why__subheading {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .svc-lc-card__title,
  .svc-why__subheading {
    font-size: var(--type-title-card);
  }
}

.svc-lc-card__detail-title {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-subsection-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.25;
}
@media (min-width: 1024px) {
  .svc-lc-card__detail-title {
    font-size: var(--type-heading-subsection);
  }
}

.svc-lc-card__cta,
.svc-lc-card__more {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.svc-why__stat-label {
  font-family: var(--type-font-display);
  font-size: var(--type-stat-unit);
  font-weight: var(--type-weight-subheading);
  line-height: 1.2;
}

/*
 * Speciality Chemicals family (Mega Cycle II, 18 September 2026) —
 * assets/speciality-chemicals.css, 14 routes; loads BEFORE typography.css
 * (unblocked), but every one of its own selectors is already
 * `#main-content`-scoped by its own author (matching Batch 7's existing
 * `#main-content .cap__label` precedent above), so `#main-content` is
 * needed here purely to match that specificity, not for load order.
 * `--font-mono` (content.css :root) is an ALIAS for Plus Jakarta Sans,
 * not a real monospace stack — every "mono" class below already renders
 * in the canonical display family; nothing here overrides an intentional
 * typeface choice.
 * Reach: grepped the real markup of all 14 routes, not the shared CSS
 * file's selector list — assets/speciality-chemicals.css duplicates many
 * class names 1:1 from assets/market-segments-landing.css (Batch 7's own
 * finding, re-confirmed and extended here: .indx__*, .civcol__* except
 * -no, .insttile__* except -name is also unused, .instcap__*,
 * .indxnav__*, .metric__*, .eyebrow, .h2, .lead, .btn, .hero__title,
 * .resrow__no, .resrow__title, .resrow__body, .techcard__* — none of
 * these has real HTML on any of the 14 routes; all confirmed dead here).
 * `.chain__body`, `.bens__body`, `.papp-row__body`, `.pcard__desc` are
 * already an EXACT, complete Lead-body match (clamp(15px,1.4vw,17px)/
 * Inter/400/1.6, inherited from the sitewide `body` rule once the
 * selector's own explicit weight:400 overrides #main-content's ambient
 * 300) — Bucket B, no rule added.
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .chain__no, .papp-row__no (step ordinals), .pcard__no (product-card
 *     ordinal), .pbens__no (benefit ordinal), .spec__key (spec-row key,
 *     e.g. "pH Range"), .dg-label (a short section marker), .bens__label
 *     (benefit-card eyebrow) — all already Jakarta/700 via --font-mono;
 *     only size and line-height changed.
 *   .broch__name ("INDION Autochem Leaflet" — a document name/label
 *     inside a hoverable row, not an action phrase; contrast .cap__btn
 *     below, which is a real "Explore"-style CTA).
 * ---- Card title (18px<1024px, 20px>=1024px/Plus Jakarta Sans/600/1.3) -
 *   .chain__name (19px flat), .bens__name (18px flat) — family/weight
 *     already matched; the <1024px step is added.
 * ---- Section heading (Plus Jakarta Sans/clamp(30px,3.6vw,44px)/500/
 * 1.14) ----
 *   .feat__title (clamp(28px,3.4vw,44px)/500 already — weight (Heading
 *     token) already matched exactly).
 * ---- Lead body (clamp(15px,1.4vw,17px)/Inter/400/1.6) ----
 *   .feat__lead, .cap__body, .papps__row — all explicitly weight 300
 *     (design.md's banned weight) in the source; moved to 400.
 * ---- Table data (16px/Inter/400/1.5) ----
 *   .spec__val (the value half of the .spec__key pair; explicitly weight
 *     300 in the source).
 * ---- Helper/response (14px/Inter/400/1.45) ----
 *   .pdoc__pending ("pending" datasheet-status text; explicitly weight
 *     300 in the source).
 * ---- Action (14px/Inter/600/1.4) ----
 *   .cap__btn (a real CTA button; family Inter and weight 600 already
 *     matched, only size 12px and line-height changed).
 *   .pback (Mega Cycle III repair, real <a> back-navigation link, the
 *     one live route products/speciality-chemicals/autochem/; was
 *     10px/--font-mono alias/700/uppercase/0.18em tracking — a genuine
 *     interactive control, not a static label, so mapped to Action
 *     rather than Compact per the standing "don't use Compact merely
 *     because current values are small" rule; casing and the em-based
 *     tracking are preserved and now scale with the new 14px size as
 *     the documented permitted side effect).
 * ---- Image caption (13px/Inter/400/1.45) ----
 *   .chain__note (a short note under each chain step; size already
 *     matched, weight fell back to #main-content's banned 300).
 * Blocked (Bucket D), no defensible role:
 *   .cap__title (20-25px/700) — weight matches neither Card title nor
 *     Subsection heading (both 600); Batch 7's original finding stands.
 *   .pbens__text (17-20px/500) — weight (the Heading token) matches no
 *     role at this size; Card title's weight is 600, not 500.
 *   .pcard__name ("Cooling Water Treatment Programmes", 15px/600/Inter)
 *     — a genuine card/product name, but explicitly Inter rather than
 *     the display family every other card-title-shaped class here uses;
 *     forcing Plus Jakarta Sans would override that explicit choice.
 *   .pdoc__link ("INDION Chlogen Brochure ", 15px/500/Inter) — weight
 *     500 matches no role cleanly (Footer link is 400, Action is 600).
 */
#main-content .chain__no,
#main-content .papp-row__no,
#main-content .pcard__no,
#main-content .pbens__no,
#main-content .spec__key,
#main-content .dg-label,
#main-content .bens__label,
#main-content .broch__name {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content .chain__name,
#main-content .bens__name {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .chain__name,
  #main-content .bens__name {
    font-size: var(--type-title-card);
  }
}

#main-content .feat__title {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-section);
  font-weight: var(--type-weight-heading);
  line-height: 1.14;
}

#main-content .feat__lead,
#main-content .cap__body,
#main-content .papps__row {
  font-family: var(--type-font-body);
  font-size: var(--type-body-lead);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

#main-content .spec__val {
  font-family: var(--type-font-body);
  font-size: var(--type-table-data);
  font-weight: var(--type-weight-body);
  line-height: 1.5;
}

#main-content .pdoc__pending {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

#main-content .cap__btn,
#main-content .pback {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

#main-content .chain__note {
  font-family: var(--type-font-body);
  font-size: var(--type-image-caption);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * Country-landing:new-template family (Mega Cycle II, 18 September 2026)
 * — assets/country-landing.css, 14 routes (re-derived fresh from disk;
 * an earlier batch's count of 20 no longer matches current routes).
 * A prior batch found all three candidates below but left them
 * unimplemented: `.ctry-placeholder__label` already matched Compact
 * label almost exactly but was blocked because country-landing.css
 * loads AFTER typography.css on every one of these routes (the reverse
 * of most other families) and only a same-specificity bare-class
 * mechanism was tried; `.ctry-strip__label` and `.ctry-industries__pill`
 * were reported as having no confident role at all. `#main-content`
 * (confirmed present on all 14 routes) resolves the load-order problem
 * mechanically (an ID beats any bare class regardless of order), and
 * this cycle's semantic latitude — already used the same way for
 * .svc-why__stat-label and .case-study-outcomes__footer (Statistic
 * unit) and .ix-catpill/.ms-index-card__cta (Action for a clickable
 * pill/chip despite small current values) — resolves the other two.
 * Reach-checked: none of the three is declared in any other stylesheet.
 *   .ctry-placeholder__label -> Compact label (11px/700/Plus Jakarta
 *     Sans/1.3); already exact except line-height.
 *   .ctry-strip__label -> Statistic unit (16px/Plus Jakarta Sans/600/
 *     1.2); weight and family already matched exactly, only size (15px)
 *     and line-height (1.35) did not.
 *   .ctry-industries__pill -> Action (14px/Inter/600/1.4); a real
 *     `<button>` filter control (has an `.is-active` state), not a
 *     label, despite its current small Plus Jakarta Sans styling —
 *     family and size both change.
 */
#main-content .ctry-placeholder__label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content .ctry-strip__label {
  font-family: var(--type-font-display);
  font-size: var(--type-stat-unit);
  font-weight: var(--type-weight-subheading);
  line-height: 1.2;
}

#main-content .ctry-industries__pill {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

/*
 * /contact-us/ and /ae/contact-us/ (Mega Cycle II, 18 September 2026).
 * The root /contact-us/ loads no family stylesheet and no candidate
 * class from this family — its content is already on shared, migrated
 * `.ds-*` components. /ae/contact-us/ (and 15 other country-contact
 * routes — reach-checked: exactly 16 routes carry .cp-contact__card)
 * loads assets/country-pages.css BEFORE typography.css (unblocked; a
 * plain bare-class rule wins the tie). A prior batch grouped
 * `.cp-contact__card` (h3/p/a), `.cp-enquiry__address` and `.cp-region
 * (__code)` together and left the whole cluster unimplemented for two
 * reasons that apply only to PART of it: `.cp-contact__card p, a` has
 * inline font-size/font-weight overrides on several routes' sibling <p>
 * tags (that finding is not re-verified here and is taken as still
 * valid — left untouched), and `.cp-region` itself is an ambiguous
 * clickable country-switcher link (also left untouched). The other
 * three members of that cluster have no such objection on their own:
 *   .cp-contact__card h3 -> Compact label ("PHONE"/"EMAIL"-style card
 *     label; already 10px/700/Plus Jakarta Sans/uppercase/tracked, only
 *     size and line-height changed).
 *   .cp-region__code -> Compact label (short country code inside the
 *     clickable .cp-region row, e.g. "AE" — the row's own action-vs-
 *     label ambiguity does not extend to this short identifier, the
 *     same reasoning already applied to /products/'s .ix-acc-num inside
 *     its own clickable header).
 *   .cp-enquiry__address -> Helper/response (14px/Inter/400/1.45); was
 *     explicitly weight 300 (design.md's banned weight) at 13.5px.
 */
.cp-contact__card h3,
.cp-region__code {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.cp-enquiry__address {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * /international-associates/ (Mega Cycle II, 18 September 2026; corrected
 * Mega Cycle III repair, 19 September 2026) — assets/associates.css, a
 * single route; loads BEFORE typography.css (unblocked). All 8 candidates
 * are reach-checked as declared only in this one file/route (grep across
 * every other src/styles/*.css and assets/*.css: zero collisions; grep
 * across all 1,033 manifest routes' real markup: exactly 1 route,
 * international-associates/index.html).
 *   .ia-region__n ("6 companies" region count), .ia-card__territory
 *     ("Singapore" location tag) -> Compact label; weight 600->700 for
 *     the first, already 700 for the second.
 *   .ia-partner__note ("Silicon carbide ceramic membranes..." partner
 *     descriptor) -> Helper/response (14px/Inter/400/1.45); was
 *     explicitly weight 300 at 13.5px.
 *   .ia-region__name ("Asia Pacific"), .ia-partner__name ("LiqTech
 *     International"), .ia-card__name ("Ion Exchange Asia Pacific Pte.
 *     Ltd.") -> Card title; family/weight already matched, sizes (19px,
 *     15px, 16px) move to the 18/20px step — the same below-floor jump
 *     already accepted for /products/engineering/'s .soltrain__name.
 * Correction (Mega Cycle III): the prior note that .ia-card__cta was
 * "empty in the static markup" was wrong — a direct read of the real
 * markup shows `<a class="ia-card"><span class="ia-card__territory">...
 * </span><span class="ia-card__name">...</span><span class="ia-card__cta">
 * <span>Visit the Singapore site</span><span aria-hidden="true">-></span>
 * </span></a>`, repeated for all 14 country cards: real, visible,
 * accessible CTA text (the trailing arrow glyph alone is aria-hidden, the
 * "Visit the X site" text is not). Declared today at Plus Jakarta Sans/
 * 10px/600/no own line-height (inherits #main-content's ambient 1.65,
 * computing 16.5px) — the same "small current value" trap this contract
 * has repeatedly refused elsewhere: this is a real click-affordance string
 * inside a card that is itself the whole `<a>` target, functionally
 * identical to .us-card-link ("Read article") and .ms-index-card__cta
 * ("View sector") elsewhere in this file, both already mapped to Action
 * despite an even closer numeric coincidence with Compact than this one.
 * -> Action (14px/Inter/600/1.4): weight already matches; family (Jakarta
 * -> Inter) and size (10 -> 14px) both change, the same two-property jump
 * already accepted for .ctry-industries__pill. The hover-state color-only
 * rule (`.ia-card:hover .ia-card__cta`) sets no font property and is
 * untouched.
 */
.ia-region__n,
.ia-card__territory {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.ia-card__cta {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.ia-partner__note {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

.ia-region__name,
.ia-partner__name,
.ia-card__name {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .ia-region__name,
  .ia-partner__name,
  .ia-card__name {
    font-size: var(--type-title-card);
  }
}

/*
 * /about-us/ (Mega Cycle II, 18 September 2026) — investigated, NOT
 * implemented. assets/milestones.css (`.iexms__*`) and assets/rnd-
 * leadership.css (`.rnd-leadership-section .*`/`.rdl__*`/`.rdlcard__*`)
 * are also `<link>`ed on `/` (Home) and `/light/`. A static grep of
 * Home's source HTML found zero matches for either component and this
 * was initially treated as proof they are about-us-exclusive — that was
 * wrong. Home's content is a client-rendered bundle with no readable
 * static source for its injected sections; a live rendered check (real
 * headless browser, JS executed) shows both `#research-leadership`
 * (`.rdl__*`, `.rdlcard__*`, `.btn`) AND the full `.iexms__*` milestones
 * strip actually render on `/` at runtime. Excluded outright, not
 * deferred: every candidate in both files reaches the protected Home
 * route, and no selector-only differentiator was found in the time
 * available (the one existing `.iexms--home .iexms__h2` modifier
 * protects that single selector via higher specificity, but does not
 * cover .iexms__eyebrow/.iexms__year/.iexms__desc or any
 * .rnd-leadership-section descendant, none of which has a Home-specific
 * override). No rule was written for any selector in either file.
 */

/*
 * /awards/ (Mega Cycle II, 18 September 2026) — assets/awards.css, a
 * single route (reach-checked: not linked on Home, Careers, or any
 * membrane route — re-verified against the live-rendered DOM this time,
 * not the static source alone, after the /about-us/ finding above).
 * Loads BEFORE typography.css (unblocked).
 *   .aw-filter ("All"), .aw-filter--body ("Water Digest", a modifier for
 *     longer publication-name filters) -> Action (14px/Inter/600/1.4).
 *     Both are the same clickable filter control (`.is-active`/`:hover`
 *     states); casing and tracking are untouched by this rule (only
 *     family/size/weight/line-height are asserted), so the two
 *     variants' different text-transform/letter-spacing survive intact.
 *   .aw-item__body ("UBS Forums: Net Zero Summit & Awards 2024", an
 *     award-entry heading) -> Card title; family and weight already
 *     matched, size (17px) moves to the 18/20px step.
 *   .aw-item__citations li -> Helper/response (14px/Inter/400/1.45);
 *     was explicitly weight 300 at 14.5px.
 *   .aw-empty ("No awards match that filter.") -> Lead body; family
 *     already matched, size (15px) already matched Lead body's floor,
 *     weight was unset (fell back to #main-content's banned 300).
 * Not migrated:
 *   .aw-filter__n ("56", a cosmetic in-filter count) — the same gap a
 *     prior batch already recorded for the sitewide-equivalent
 *     `.blog-filter__n`: Compact label's 700 weight would bold a
 *     currently-regular (400) cosmetic number with no confident
 *     alternative role.
 *   .aw-year__label ("2024", a large year heading, 22-26px/700) — the
 *     same "no canonical role covers a display numeral" gap as
 *     .rdlcard__value/.iexms__title above and design.md 2.2.7 item 7.
 * ---- Image caption (13px/Inter/400/1.45) ----
 *   .aw-shot figcaption — sub-11px normalisation pass correction
 *     (19 September 2026): a prior cycle attempted this same migration
 *     and reverted it after finding the size increase pushed captions
 *     past their `-webkit-line-clamp: 3` container (21 clipped rows in
 *     that cycle's own 87-route sample; 7 on /awards/ itself in this
 *     pass's own before-measurement). This pass makes the component-
 *     local fit repair that finding calls for: assets/awards.css's
 *     clamp widened from 3 to 6 lines in two verified steps (4, then 5,
 *     then 6 — each step measured live, stopped once further widening
 *     showed diminishing return), with `min-height: calc(3 * 1.5em)`
 *     recalculated to `calc(6 * 1.45em)` to match Image caption's own
 *     line-height — both scoped to exactly this one selector, no width/
 *     grid/breakpoint change. assets/awards.css's own font-size/line-
 *     height (10px/1.5) were removed so this rule's 13px/1.45 is the
 *     only declaration; family/weight/letter-spacing/uppercase stay in
 *     assets/awards.css unchanged (Image caption's own Inter/400
 *     overrides them the same way it does everywhere else). Verified
 *     live at all four widths: clipped captions on /awards/ went from 7
 *     to 1 — the gallery's single longest caption ("CorpComm and PR
 *     Excellence Awards Best Multi Channel Communication of the Year:
 *     NDTV Campaign") still overflows 6 lines at 390px. Recorded as a
 *     named remaining blocker rather than widening further: each
 *     additional clamp line adds real reserved empty space beneath the
 *     other 20, mostly-shorter captions, which is exactly the broader
 *     visual-redesign trade-off this pass's own narrow-repair rule
 *     excludes — a real fix needs either a genuinely dynamic (non-
 *     clamped) caption height or a copy edit to the one long caption,
 *     both out of typography-only scope.
 */
.aw-filter,
.aw-filter--body {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.aw-item__body {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .aw-item__body {
    font-size: var(--type-title-card);
  }
}

.aw-item__citations li {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

.aw-shot figcaption {
  font-family: var(--type-font-body);
  font-size: var(--type-image-caption);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * /csr/ — sub-11px normalisation pass (19 September 2026). Live rendered
 * sweep found exactly one real remaining candidate (every other sub-11px
 * Tailwind-utility text spotted by an earlier static read of this page's
 * markup — "Years"/"Countries"/etc. — did not reach the live sub-11px
 * report at any of the 4 widths in the resting state, so none is treated
 * as a candidate here without live confirmation).
 * Blocked (Bucket D), no defensible role, evidence recorded:
 *   #journey-showcase-footer span ("ION FOUNDATION - A CULTURE OF CARE -
 *     ESTABLISHED 2010", the CSR page's own scrolling credential ticker;
 *     /csr/-exclusive, confirmed by id uniqueness) — Plus Jakarta Sans/
 *     10px/400/tracked, styled entirely by the parent `#journey-showcase-
 *     footer`'s own Tailwind utilities (no reusable class). Not aria-
 *     hidden (confirmed in the live markup) — a real, if secondary,
 *     credential line. No canonical role pairs Plus Jakarta Sans with
 *     weight 400: Heading is 500, Subheading/Action/Metadata/Compact are
 *     600-700; only Body/Helper/Lead/Standard/Article/Image-caption use
 *     400, and all of those are Inter, not Jakarta.
 */
/*
 * /about-us/ — sub-11px normalisation pass (19 September 2026). This
 * pass's own live 4-width sweep found a large, previously uninventoried
 * candidate set entirely in about-us's own page-local about-us/
 * styles.css (not content.css) — the earlier "about-us fully excluded"
 * finding covered only the two shared files reused on Home (milestones.
 * css, rnd-leadership.css); it never claimed about-us/styles.css itself
 * was inventoried, and it wasn't. Re-confirmed safe: neither milestones.
 * css nor rnd-leadership.css declares any of the selectors below (grep-
 * checked), and about-us/styles.css is not linked by Home (`/`'s own
 * <link> list has no styles.css at all). typography.css loads last on
 * /about-us/, so bare selectors win by load order; no anchor needed.
 * ---- Metadata (13px/Plus Jakarta Sans/600/1.45) ----
 *   .cert-card__label ("Quality Management" certification-card caption),
 *   .gp-chip__n ("50" a stat-chip's small leading number — .gp-chip
 *     itself is `height: 42px`, generous for a single short number,
 *     confirmed no clipping live), .origin-metric__label ("Years of
 *     Expertise" company-origin stat caption), .hall-stat__l ("Awarding
 *     institutions" recognition-hall stat caption), .sus__hero-badge
 *     ("Integrated Water Facility - Goa, India" facility photo badge —
 *     `height` changed to `min-height` above so the badge can grow).
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .hall-stage__no ("Era 07 - 2020-Today" hall-of-recognition era
 *     marker), .hall-list-label ("Further Honours" list heading),
 *     .origin-strip__label ("Six decades in one line" timeline-strip
 *     caption), .hall-feat__cat ("Sustainability" featured-award
 *     category chip — `height` changed to `min-height` above),
 *     .hall-node__label ("1960" / "Present" hall-of-recognition timeline
 *     year markers — base rule is 12px, already above floor, but a
 *     narrower-viewport media query in about-us/styles.css drops it to
 *     10px; Compact's flat 11px normalises both contexts to one value
 *     rather than leaving the narrow one under-floor).
 * Not a candidate: .bio-toggle__chev ("v" leadership-bio expand/collapse
 *   chevron) carries `aria-hidden="true"` directly on the element
 *   (confirmed in the live markup) — decorative, excluded. Likewise the
 *   `.hall__caption` timeline strip ("1964-2024 - 70+ Honours - 20+
 *   Awarding Institutions") carries `aria-hidden="true"` on its wrapper.
 * Blocked (Bucket D), no defensible role, evidence recorded:
 *   .mfg-cert-badge ("ISO 9001" / "ISO 45001" manufacturing-certification
 *     badges) — Inter/700/10.5px; no canonical role pairs Inter with
 *     weight 700 (Action is Inter/600, Compact is Plus Jakarta Sans/700).
 */
.cert-card__label,
.gp-chip__n,
.origin-metric__label,
.hall-stat__l,
.sus__hero-badge {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

.hall-stage__no,
.hall-list-label,
.origin-strip__label,
.hall-feat__cat,
.hall-node__label {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/*
 * Home (`/`) — sub-11px normalisation pass (19 September 2026). Per
 * design.md's own standing exception, Home's content has no readable
 * static source (a frozen, hydrated bundle) and is otherwise fully
 * excluded from this contract; this pass's live rendered sweep is the
 * only way any Home candidate has ever been inventoried, and it found
 * exactly one, real, page-exclusive live control worth recording:
 * Blocked (Bucket D), no defensible role, evidence recorded:
 *   #hero-slides-nav button span (the 5 hero-carousel pager labels —
 *     "TOTAL WATER SOLUTIONS" / "6 DECADES OF LEGACY" / "GLOBAL
 *     PRESENCE" / "SUSTAINABLE INNOVATION" / "SOLUTIONS ACROSS SECTORS")
 *     — a real, clickable slide-switch control, Plus Jakarta Sans/
 *     weight 800/tracked/uppercase, already fluid via Tailwind
 *     responsive utilities (9px mobile -> 10px tablet -> 11px desktop,
 *     so it clears the floor at 1024px+ on its own). No canonical role
 *     uses weight 800 anywhere in the 23-row matrix (the heaviest is
 *     Compact at 700); forcing a role's own weight onto it would be
 *     exactly the "invent an arbitrary value" this pass's rules forbid.
 *     `#hero-slides-nav` is a real, page-unique id (confirmed absent
 *     from every other route's markup), so a scoped rule would be safe
 *     to add if a role ever does fit — recorded for that reason rather
 *     than dismissed as unreachable.
 */
.aw-empty {
  font-family: var(--type-font-body);
  font-size: var(--type-body-lead);
  font-weight: var(--type-weight-body);
  line-height: 1.6;
}

/*
 * /indion-resins/ standalone React page (Mega Cycle III, 19 September
 * 2026) — the guided resin finder (src/pages/IndionResins.jsx, hydrated
 * client-side; assets/indion-resins.css). The one route this whole
 * engagement had never yet touched. Inventoried entirely from the live
 * rendered DOM across every interactive state (industry dropdown open,
 * an option chosen, technical filters unlocked and one applied, a
 * product drawer open, its gap-details disclosure expanded), not from
 * static source — the page's real markup only exists after hydration,
 * the same lesson this engagement already learned the hard way on Home.
 * assets/indion-resins.css loads AFTER typography.css on this route (the
 * one standalone case Batch 8 already flagged as blocked and never
 * revisited), so every rule below is anchored on `#main-content` purely
 * for specificity — confirmed present once in this route's markup, not
 * claimed as page scope.
 *
 * Structural finding: `.indion-resins-page button, input, select {
 * font: inherit }` is a deliberate reset so form controls stop using the
 * browser's UA font and instead inherit from their container. Because it
 * targets `button`/`input`/`select` directly, it OUTRANKS a same-file
 * single-class rule like the existing `.ir-clear-button, .ir-text-button,
 * .ir-card-link { font-size: 12px; font-weight: 600 }` (a class alone is
 * lower specificity than a class+tag) — so those three buttons' real,
 * live typography is `#main-content`'s own ambient 14px/300/Inter/1.65,
 * not their own rule's 12px/600. Confirmed live, not assumed from the
 * source CSS alone; this is exactly why the task specifies computed
 * styles, not declared ones.
 *
 * No role exists for a short (~13px), regular-weight, Inter secondary
 * line below a heading or inside a card (a "hook", a filtered-state
 * summary, a source citation) — design.md's 23 roles have no "small body"
 * entry between Image caption (13px) and Helper/response (14px). Image
 * caption is used below for every one of these, on family+weight-target
 * match and the closest available size, the same way this contract has
 * already reused Metadata and Statistic unit slightly outside their most
 * literal reading elsewhere. Where a candidate's weight is intentional
 * and not a stand-in for the banned 300 — .ir-structured-facts > div > dd
 * (Inter/500, no role uses Inter+500) — it is left alone, not forced.
 *
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .ir-product-card__number ("001" reference ordinal), .ir-journey__
 *     steps span ("01" step ordinal) — both already exact 11px/700/
 *     display; only line-height was missing.
 *   .ir-drawer__header .ds-label-system, .ir-drawer__section >
 *     .ds-label-system, .ir-drawer__section-head .ds-label-system — all
 *     three are this page's own scoped overrides of the sitewide,
 *     already-canonical `.ds-label-system` (Batch 7), knocking its size
 *     down to 10px/10.5px inside this drawer specifically; family/weight/
 *     line-height are already correct via the base rule, only size is
 *     restated here to cancel the local override.
 *   .ir-active-filters > span ("Active filters"), .ir-select-group > p
 *     ("Primary industries"), .ir-match-reason > span ("Why it matches"),
 *     .ir-evidence-box dt / .ir-fact-pairs dt ("Technical data sheet
 *     (TDS)" / "Chemistry"), .ir-structured-facts > div > dt ("Resin
 *     type"), .ir-governance__rules > div span ("01"), .ir-control-label
 *     ("01 Industry"), .ir-technical-filter ("Resin chemistry" filter
 *     label, text carried by an unclassed child <span> that inherits
 *     this rule) — all nine are already Plus Jakarta Sans/700 at
 *     8-10px, below the sitewide 11px floor; family/weight already
 *     match, only size and line-height move to canonical.
 * ---- Metadata (13px/Plus Jakarta Sans/600/1.45) ----
 *   .ir-select-group [role='option'] strong ("Power & Utilities" option
 *     label, 12.5px), .ir-select-trigger strong ("Choose or search
 *     industry", 13.5px) — both already 600/display; size moves 0.5px.
 *   .ir-load-more > span ("Showing 12 of 117" result count) — sub-11px
 *     normalisation pass, 19 September 2026 correction: re-read from
 *     assets/indion-resins.css, this is Plus Jakarta Sans/9px/var(--
 *     weight-semibold)=600, an exact Metadata family+weight match, not
 *     the "doesn't match Compact's 700" mismatch this contract's own
 *     prior note claimed — that note compared it to the wrong role.
 *   .ir-technical-refinements__action b ("2 active" filter-count badge
 *     inside the technical-filters toggle) — same correction, same
 *     Plus Jakarta Sans/10px/600 shape; the badge's own `border-radius:
 *     pill` padding (space-6/space-8) has no fixed height, confirmed
 *     safe to grow.
 * ---- Form label (14px/Plus Jakarta Sans/600/1.4) ----
 *   .ir-technical-refinements__toggle strong ("Technical filters" toggle
 *     label, 15px/600/display) — a real form-control label, the role
 *     this contract has not yet had a live consumer for.
 * ---- Card title (18px<1024px, 20px>=1024px/Plus Jakarta Sans/600/1.3) -
 *   .ir-product-card h3 ("INDION GS 3000 (Type 1)"), .ir-drawer__section
 *     h3 ("Documented specifications") — both already an exact 20px/600/
 *     display match, flat with no responsive step; the <1024px step is
 *     new.
 * ---- Subsection heading (22px<1024px, 25px>=1024px/Plus Jakarta Sans/
 * 600/1.25) ----
 *   .ir-compact-finder__head h3 ("Search, select and compare."),
 *     .ir-journey__steps h3 ("Establish the application") — both 600/
 *     display already; sizes (21-23px, 21px) and the line-height (1.65)
 *     move to canonical. Supersedes this page's own bespoke 640px-
 *     breakpoint step on the first with the role's own 1024px contract.
 * ---- Helper/response (14px/Inter/400/1.45) ----
 *   .ir-evidence-box dd / .ir-fact-pairs dd ("Open TDS PDF" / values),
 *     .ir-governance__rules > div p, .ir-journey__steps p — all four
 *     already exact 14px/Inter; weight was the banned 300 (the shared
 *     `.ir-fact-pairs dd, .ir-evidence-box dd` rule itself sets no
 *     weight; a minority of `.ir-fact-pairs dd` elements are additionally
 *     matched by the separate, more specific `.ir-structured-facts > div
 *     > dd` rule, which keeps winning its own explicit 500 regardless of
 *     this rule).
 * ---- Image caption (13px/Inter/400/1.45) ----
 *   .ir-compact-finder__head p, .ir-results__header p:not(.ds-eyebrow),
 *     .ir-product-card__hook, .ir-evidence-box .ir-source, .ir-match-
 *     reason p — five short secondary/descriptive lines at 12.5-13.5px/
 *     Inter/300; see the role note above.
 *   .ir-document-card small (document-card secondary caption), .ir-
 *     select-trigger small ("Choose an industry first" / "{n} options
 *     available" / the selected option's own description — single-line,
 *     text-overflow:ellipsis, so growing the font only changes how much
 *     text is visible before the ellipsis, not a clipping risk), .ir-
 *     select-group [role='option'] small (each dropdown option's own
 *     description line, e.g. under "Power & Utilities") — sub-11px
 *     normalisation pass correction: all three are the identical Inter/
 *     ambient-300/10-10.5px/1.35-1.45 shape as the five above, missed by
 *     the original inventory. `[role='option']` itself is `min-height:
 *     54px` (not fixed), confirmed safe to grow.
 * ---- Action (14px/Inter/600/1.4) ----
 *   .ir-card-link ("View reference specs"), .ir-clear-button ("Clear
 *     finder") — real CTA buttons, exact 14px/Inter match once the
 *     `font: inherit` reset (not either button's own dead-for-this-
 *     property rule) is beaten; only weight/line-height change.
 *   .ir-gap-details summary ("7 fields require controlled TDS
 *     validation") — a native disclosure control, already 600/Inter;
 *     size moves 0.5px.
 *   .ir-active-filters button ("Industry: X ×" / "Goal: X ×" / "Search:
 *     X ×" active-filter-removal buttons, each with its own onClick in
 *     IndionResins.jsx) — sub-11px normalisation pass correction: the
 *     original inventory logged this as "no role fits" without checking
 *     for the real onClick handler each one carries. Computed tuple hits
 *     the same `font: inherit` reset as .ir-card-link/.ir-clear-button
 *     above (Inter/inherited weight 300, the banned weight); size and
 *     weight both move.
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .ir-status / .ir-status--review / .ir-status--verified / .ir-
 *     status--regional (product-card status pills, e.g. "Verified" /
 *     "Under Review") — sub-11px normalisation pass correction: re-read
 *     from assets/indion-resins.css, the pill uses `min-height: 24px`,
 *     not a fixed `height` as this contract's own prior note assumed —
 *     confirmed live at all four widths, growing to Compact's 1.3 line-
 *     height (14.3px at 11px) causes zero clipping or visible height
 *     change, the box already accommodates it. Family/weight already
 *     exact (Plus Jakarta Sans/700); only size and line-height move. The
 *     `--status--*` modifiers set color only, not font, so the base
 *     rule alone reaches every variant.
 * Blocked (Bucket D), no defensible role, evidence recorded:
 *   .ir-tag-row--compact > span (undecorated-tag-row's compact variant)
 *     — Inter/ambient weight 300/9.5px, same "no role pairs Inter with
 *     this size" gap as its sibling .ir-tag-row > span (which sits AT
 *     the 11px floor already and is therefore out of this pass's scope).
 *   .ir-structured-facts > div > dd — explicit Inter/500; no canonical
 *     role uses weight 500 with Inter.
 *   .ir-drawer__section > p (bare) — left alone: every element it
 *     reaches that has a confident role is already covered by a more
 *     specific sibling selector above (.ir-drawer__intro > p, .ir-
 *     evidence-box .ir-source); the bare selector's own remaining reach
 *     is ambiguous between contexts and not solely attributable to one
 *     role.
 *   .ir-control-label b ("01") — left alone: an intentional weight:900
 *     emphasis on the ordinal, not a stand-in for a banned weight; also
 *     a fixed `height: 24px` (not min-height), a real fit-repair risk
 *     even if a role fit had been found.
 * Already at or above the 11px floor, out of this pass's scope (left
 * unchanged, not evaluated further here): .ir-technical-refinements__
 * toggle small (11.5px), .ir-technical-refinements__head p (12px), .ir-
 * gap-details li (13px), .ir-structured-facts > div > dd (14px), .ir-
 * drawer__section > p (14px), .ir-tag-row > span (11px), .ir-spec-table
 * tbody th (13.5px), .ir-spec-table td (14px).
 *
 * Two further candidates missed by the original inventory (surfaced by
 * this pass's live 4-width sweep of the open product drawer, not the
 * static source): .ir-spec-table thead th ("Parameter" / "Reference
 * value" column headers, Plus Jakarta Sans/9.5px/700/uppercase/tracked)
 * and .ir-data-count ("8" documented-parameter count badge, Plus Jakarta
 * Sans/10px/700/uppercase/tracked) — both an exact Compact family+weight
 * match, added to the Compact group below. Table heading was considered
 * for the former given its `<thead><th>` tag, but its own declared style
 * (small, uppercase, tracked, no cell background beyond a light tint) is
 * a Compact-shaped caption-style header, not Table heading's 16px/600
 * reading-table style; Compact needs no weight change where Table
 * heading would need both size and weight forced. Neither has a fixed-
 * height ancestor (`.ir-spec-table-wrap` only sets `overflow-x: auto`).
 */
#main-content .ir-product-card__number,
#main-content .ir-journey__steps span,
#main-content .ir-drawer__header .ds-label-system,
#main-content .ir-drawer__section > .ds-label-system,
#main-content .ir-drawer__section-head .ds-label-system,
#main-content .ir-active-filters > span,
#main-content .ir-select-group > p,
#main-content .ir-match-reason > span,
#main-content .ir-evidence-box dt,
#main-content .ir-fact-pairs dt,
#main-content .ir-structured-facts > div > dt,
#main-content .ir-governance__rules > div span,
#main-content .ir-control-label,
#main-content .ir-technical-filter {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

#main-content .ir-select-group [role='option'] strong,
#main-content .ir-select-trigger strong,
#main-content .ir-load-more > span,
#main-content .ir-technical-refinements__action b {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}

#main-content .ir-technical-refinements__toggle strong {
  font-family: var(--type-font-display);
  font-size: var(--type-form-label);
  font-weight: var(--type-weight-subheading);
  line-height: 1.4;
}

#main-content .ir-product-card h3,
#main-content .ir-drawer__section h3 {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  #main-content .ir-product-card h3,
  #main-content .ir-drawer__section h3 {
    font-size: var(--type-title-card);
  }
}

#main-content .ir-compact-finder__head h3,
#main-content .ir-journey__steps h3 {
  font-family: var(--type-font-display);
  font-size: var(--type-heading-subsection-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.25;
}
@media (min-width: 1024px) {
  #main-content .ir-compact-finder__head h3,
  #main-content .ir-journey__steps h3 {
    font-size: var(--type-heading-subsection);
  }
}

#main-content .ir-evidence-box dd,
#main-content .ir-fact-pairs dd,
#main-content .ir-governance__rules > div p,
#main-content .ir-journey__steps p {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

#main-content .ir-compact-finder__head p,
#main-content .ir-results__header p:not(.ds-eyebrow),
#main-content .ir-product-card__hook,
#main-content .ir-evidence-box .ir-source,
#main-content .ir-match-reason p,
#main-content .ir-document-card small,
#main-content .ir-select-trigger small,
#main-content .ir-select-group [role='option'] small {
  font-family: var(--type-font-body);
  font-size: var(--type-image-caption);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

#main-content .ir-card-link,
#main-content .ir-clear-button,
#main-content .ir-gap-details summary,
#main-content .ir-active-filters button {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

#main-content .ir-status,
#main-content .ir-spec-table thead th,
#main-content .ir-data-count {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

/*
 * Country-landing shared primitives + the last 2 Services routes (Mega
 * Cycle III, 19 September 2026). All in assets/content.css (frozen,
 * shared); typography.css loads AFTER content.css on every route below,
 * so same-specificity bare/compound rules win by load order alone — no
 * anchor needed anywhere in this block.
 *
 * `.us-card-link`, `.us-image-card__body h3/p`, `.us-case-study-card__
 * body h3/p` reach BOTH country-landing templates at once (verified:
 * 15 and 20 routes respectively, the exact union of the 6 old-template
 * + 14 new-template routes) — a gap in this engagement's own earlier
 * "country-landing:new-template complete" claim, since that pass's
 * reach analysis covered only assets/country-landing.css's `.ctry-*`
 * classes, not these content.css-sourced ones. Corrected here rather
 * than left silently wrong.
 *
 * The whole `.us-product-card*` cluster (h3, p, __meta, __areas span,
 * .us-card-link's compound override) is DEAD on every route: `grep -rl
 * "us-product-card"` (the bare wrapping class, not a suffixed variant)
 * matches zero HTML files sitewide — confirmed, not assumed, before
 * excluding it. `.us-products__title span` / `.us-statement__copy h2
 * span` are gradient-clipped inline accent spans inside a heading (the
 * sitewide "accent spans stay bold" convention), not candidates.
 * `.us-intro__body p`, `.us-product-card p` (dead, see above),
 * `.us-statement__copy p:not(.ds-eyebrow)`, `.us-image-card__body p`,
 * `.us-case-study-card__body p`, `.us-service-note` are all already an
 * exact `clamp(15px,1.4vw,17px)/Inter/400/1.6` Lead-body match — Bucket
 * B, no rule added.
 *
 * ---- Compact label (11px/Plus Jakarta Sans/700/1.3) ----
 *   .us-location__label ("Phone"/office-panel field labels on old-
 *     template's location strip; 6 routes) — already 10px/700/display;
 *     only size+line-height move.
 *   .us-contact__details span ("Phone"/"Email" field labels; 694 routes
 *     — the full country-sub + country-landing contact-card union) —
 *     already exactly 11px/700/display/tracked; only line-height was
 *     missing.
 * ---- Statistic unit (16px/Plus Jakarta Sans/600/1.2) ----
 *   .us-location__value (the office-panel's own value text, e.g. a
 *     city name or time; 6 routes) — already 600/display; size moves
 *     15->16px, the closest available role once Compact (wrong family
 *     token) and Table heading (wrong semantic fit) are ruled out.
 * ---- Card title (18px<1024px, 20px>=1024px/Plus Jakarta Sans/600/1.3) -
 *   .us-image-card__body h3, .us-case-study-card__body h3 (both 20
 *     routes, the full country-landing union) — already an exact 18px/
 *     600/display match; only line-height (1.28->1.3) and the missing
 *     >=1024px step move.
 * ---- Action (14px/Inter/600/1.4) ----
 *   .us-card-link ("Read article" card-CTA affordance text; 15 routes),
 *     .us-case-study-card__link (the same affordance inside a case-
 *     study card; 20 routes) — both real, visible call-to-action text
 *     inside a card that is itself the real `<a>`/click target; already
 *     600/Inter, only size (12->14px) and line-height move.
 *   .svc-feature-block__sub (a feature-block description line on the
 *     last 2 previously-undecided Services routes, /services/
 *     consumables-spares/ and /services/operations-maintenance/ —
 *     Batch 7's own note that these needed "a first-time semantic
 *     decision, out of [that] batch's mechanism-recovery scope") ->
 *     mapped to Helper/response instead: exact 14px match, banned
 *     weight 300 fixed. Supersedes the source's own non-canonical
 *     639px-breakpoint 12px step with Helper/response's flat, non-
 *     responsive tuple.
 * ---- Metadata (13px/Plus Jakarta Sans/600/1.45) ----
 *   .us-case-study-card__metric-label ("COD REDUCTION" / "RESISTIVITY"
 *     stat captions inside speciality-chemicals' and old-template's
 *     case-study carousels; 20 routes, the exact union of speciality-
 *     chemicals' 14 + old-template's 6). Correction (sub-11px
 *     normalisation pass, 19 September 2026): this contract's own prior
 *     note called it "Inter or display at weight 500" — re-read directly
 *     from assets/content.css, the actual declared tuple is Plus Jakarta
 *     Sans/10px/var(--weight-semibold)=600/line-height:1/tracked, an
 *     exact family+weight Metadata match, not a weight-500 case at all.
 *     Confirmed safe to grow: the JS carousel renderer (autochem/index.
 *     html and the shared old-template component) wraps each metric in
 *     a `py-1 px-2 flex flex-col justify-center` box with no fixed
 *     height, so raising line-height from 1 to 1.45 has room to grow —
 *     verified live, zero new clipping at any of the 4 widths.
 * Blocked (Bucket D), no defensible role, evidence recorded:
 *   .us-product-card h3 (dead, see above — not evaluated further).
 *   .us-statement__visual li, .us-contact__details a / .us-contact__
 *     details p — Inter at weight 500; no canonical role uses weight
 *     500 at all. (.us-statement__visual and its `li` are additionally
 *     dead code sitewide — grep across every route's real markup finds
 *     zero elements — confirmed this pass, not evaluated further either
 *     way.)
 *   .us-case-study-card__metric-value (12px/700/display/line-height:1) —
 *     already above the 11px floor; out of this pass's sub-11px scope.
 *     Left as its own separate, still-open finding for a future pass:
 *     forcing Compact's 11px would shrink already-compliant text, and no
 *     other role fits a small bold stat figure riding beside a Metadata
 *     label.
 *   .us-case-study-card__tag (12px/600/Inter, a static non-interactive
 *     category label, not a control — Action's semantic basis for
 *     accepting a weight change does not apply, and no role covers a
 *     static Inter/600 label). Also already above the 11px floor.
 *   .us-statement__copy h2 — already Plus Jakarta Sans/500 like Display
 *     hero, but its own clamp formula (40-56px, vs Display hero's
 *     32-56px) and an independent, different max-width override at
 *     <1024px (44px, not a simple step of the same formula) make this a
 *     component-tier size difference, not a literal-value migration.
 */
.us-case-study-card__metric-label {
  font-family: var(--type-font-display);
  font-size: var(--type-metadata);
  font-weight: var(--type-weight-subheading);
  line-height: 1.45;
}
.us-location__label,
.us-contact__details span {
  font-family: var(--type-font-display);
  font-size: var(--type-compact);
  font-weight: var(--type-weight-compact);
  line-height: 1.3;
}

.us-location__value {
  font-family: var(--type-font-display);
  font-size: var(--type-stat-unit);
  font-weight: var(--type-weight-subheading);
  line-height: 1.2;
}

.us-image-card__body h3,
.us-case-study-card__body h3 {
  font-family: var(--type-font-display);
  font-size: var(--type-title-card-mobile);
  font-weight: var(--type-weight-subheading);
  line-height: 1.3;
}
@media (min-width: 1024px) {
  .us-image-card__body h3,
  .us-case-study-card__body h3 {
    font-size: var(--type-title-card);
  }
}

.us-card-link,
.us-case-study-card__link {
  font-family: var(--type-font-body);
  font-size: var(--type-action);
  font-weight: var(--type-weight-action);
  line-height: 1.4;
}

.svc-feature-block__sub {
  font-family: var(--type-font-body);
  font-size: var(--type-helper);
  font-weight: var(--type-weight-body);
  line-height: 1.45;
}

/*
 * Country-landing old-template "solutions spotlight" cards (/bd/, /in/,
 * /my/, /np/, /pt/, /sg/) — sub-11px normalisation pass (19 September
 * 2026). Live sweep found the family's single largest remaining
 * contributor: "01 / PRODUCT LINE" (font-mono text-[9.5px] font-bold
 * tracking-[0.25em] uppercase), "EXPLORE PRODUCT" (text-[10.5px]
 * font-bold tracking-wider uppercase font-mono) and its trailing arrow
 * glyph, repeated once per spotlight card across all 6 routes — ~592 of
 * this pass's ~1,167 remaining instances.
 * Blocked (Bucket D) — structurally unreachable within scope, not a
 * semantic gap: every element in this card is styled with inline
 * Tailwind arbitrary-value utility classes only (`text-[10.5px]`,
 * `font-bold`, etc.) with no shared class name, id, or other stable
 * selector anywhere in its ancestor chain (confirmed by reading the
 * surrounding markup directly). A typography.css rule needs a selector
 * to attach to; the only way to give this component one is adding a
 * class name in the page's own markup/component source, which is an
 * HTML/component edit this pass's scope explicitly excludes ("do not
 * edit generated HTML/CSS directly"). Recorded as a named blocker whose
 * required follow-up is exactly that: a small, separately-scoped
 * component change adding a class hook, after which this contract could
 * migrate it the same way as every other card CTA (Action) and label
 * (Compact) in this family.
 */

/* =========================================================================
 * Header desktop nav — laptop-width density (2026-09-29).
 *
 * The compiled Tailwind sheet was purged without a rule for the nav's
 * text-[13px] utility, so the eight items render at the inherited 15px.
 * At >=1280px viewports the row has ~45px of slack and reads fine; between
 * 1024-1279px it collides: PRODUCTS touches the wordmark, R&D LEADERSHIP
 * and ABOUT US wrap to two lines, and the row touches the hamburger.
 * The Home bundle header and the static shell header share these ids and
 * compiled classes, so this one block corrects every page.
 * ------------------------------------------------------------------- */
@media (min-width: 1024px) {
  /* A nav item never breaks internally; the row manages its own density. */
  #header-desktop-menu button,
  #header-desktop-menu a {
    white-space: nowrap;
  }
}

@media (min-width: 1024px) and (max-width: 1279.98px) {
  /* The id selector outweighs the compiled space-x-5 utility (20px). */
  #header-desktop-menu > * + * {
    margin-left: 13px;
  }
  #header-desktop-menu button,
  #header-desktop-menu a {
    font-size: 12px;
  }
}

@media (min-width: 1024px) and (max-width: 1151.98px) {
  /* Tightest band: without this, unwrapped R&D LEADERSHIP + ABOUT US push
   * the row into the wordmark and hamburger. 11px is the sitewide floor. */
  #header-desktop-menu > * + * {
    margin-left: 10px;
  }
  #header-desktop-menu button,
  #header-desktop-menu a {
    font-size: 11px;
    letter-spacing: 0.02em;
  }
}

/* "Lifecycle Services" (3 Nov 2026, was "Services") made the row 57 to 90px
 * wider: it filled the whole space between the wordmark and the menu button,
 * touched both, and squeezed the wordmark. Tighter item spacing gives back
 * about 25px either side at every desktop width; text sizes are unchanged.
 * The compiled space-x utilities put their gap in margin-inline-end; the
 * margin-left of the bands above is cleared so only this gap applies. */
@media (min-width: 1024px) {
  #header-desktop-menu > * + * {
    margin-left: 0;
  }
  #header-desktop-menu > :not(:last-child) {
    margin-inline-end: 18px;
  }
}
@media (min-width: 1024px) and (max-width: 1279.98px) {
  #header-desktop-menu > :not(:last-child) {
    margin-inline-end: 22px;
  }
}
@media (min-width: 1024px) and (max-width: 1151.98px) {
  #header-desktop-menu > :not(:last-child) {
    margin-inline-end: 20px;
  }
}

/* Pages on the membrane type scale (assets/membrane-typography.css) set the
 * menu size with a body.membrane-typography selector that outweighs the two
 * bands above, so at 1024 to 1279px their menu stayed at 14px and squeezed
 * the logo. Same compact sizes here; the html prefix outweighs that sheet,
 * which loads after this one. */
@media (min-width: 1024px) and (max-width: 1279.98px) {
  html body.membrane-typography #header-desktop-menu > button,
  html body.membrane-typography #header-desktop-menu > a {
    font-size: 12px;
  }
}
@media (min-width: 1024px) and (max-width: 1151.98px) {
  html body.membrane-typography #header-desktop-menu > button,
  html body.membrane-typography #header-desktop-menu > a {
    font-size: 11px;
    letter-spacing: 0.02em;
  }
}

@media (min-width: 1024px) {
  /* Compact desktop header (~94px total, was ~112px): 30px utility bar,
   * 52px wordmark, and a constant 6px of nav padding. Both headers swap
   * py-* utility classes between open/scrolled/top states; these id rules
   * outweigh all of them, so every page keeps one compact height and the
   * bg/shadow state transitions still run. The logo was 40px until 2 Nov
   * 2026, when Kanchan asked for it larger and sharper ("Navigation bar",
   * 31 Oct); the file itself is now rendered from her vector PDF. */
  #header-utility-bar {
    height: 30px;
  }
  #header-logo-image {
    height: 52px;
  }
  #header-nav-container {
    padding-top: 6px;
    padding-bottom: 6px;
  }
  /* The wordmark keeps its full width; the menu row gives way instead. */
  #header-logo-link {
    flex-shrink: 0;
  }

  /* Mega menu: a subtopic list longer than 9 (Industries: 14, About: 11)
   * rendered as one column ran past the viewport (901px panel on a 900px
   * screen). Long lists lay out as two aligned columns instead, and the
   * panel itself can never outgrow the screen. Used by both the Home
   * bundle header and the static pages' menu (assets/megamenu.js).
   * Solutions (9 links since 2 Nov 2026) stays one column with 8px rows. */
  #mega-menu-navigator,
  #ie-mega-navigator {
    max-height: calc(100vh - 110px);
    overflow-y: auto;
  }
  .ix-two-col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 24px;
  }
  .ix-two-col > a {
    font-size: 12.5px;
    padding-block: 8px;
  }
  .ix-two-col > a:nth-last-child(-n + 2) {
    border-bottom: none;
  }
}

/* ---------------------------------------------------------------------------
 * Headline accent — one gradient sitewide, matched to the Home page
 * (client review 2026-10-01, updated 2026-10-07: light-section accents now
 * match Home's INDUSTRIES WE SERVE heading ("Water Solutions Across Every
 * Industry"), the reference the client pointed at).
 *
 * Canonical values, taken from the live Home bundle:
 *   light sections: linear-gradient(90deg, #0284c7 0%, #007cc3 55%, #06b6d4 100%)
 *                   (IndustriesCarousel heading accent)
 *   dark sections:  linear-gradient(90deg, #38bdf8, #00a2ff)  (hero)
 * The shared footer heading already matches Home's footer exactly and is
 * deliberately NOT touched here.
 *
 * !important because the page stylesheets declare these on selectors up to
 * #mem-page#mem-page specificity and load-order differs per template.
 * ------------------------------------------------------------------------- */
:root,
.handoff-page {
  --grad-headline: linear-gradient(90deg, #0284c7 0%, #007cc3 55%, #06b6d4 100%);
  --grad-headline-dark: linear-gradient(90deg, #38bdf8 0%, #00a2ff 100%);
  --rdl-grad-headline: linear-gradient(90deg, #0284c7 0%, #007cc3 55%, #06b6d4 100%);
  --iexms-grad-headline: linear-gradient(90deg, #0284c7 0%, #007cc3 55%, #06b6d4 100%);
}

.h2-accent,
.h2-accent-inline,
.iexms__h2-accent,
.sol-accent,
.svc-accent,
.mfg-title-accent {
  font-weight: 700;
  background: linear-gradient(90deg, #0284c7 0%, #007cc3 55%, #06b6d4 100%) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  color: transparent !important;
}

.h2-accent--on-dark,
.h2-accent-inline--on-dark,
.h2--on-dark .h2-accent,
.h2--on-dark .h2-accent-inline,
.open--on-dark .open__title em,
.us-statement__copy h2 span {
  background: linear-gradient(90deg, #38bdf8 0%, #00a2ff 100%) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  color: transparent !important;
}

@supports not (background-clip: text) {
  .h2-accent,
  .h2-accent-inline,
  .iexms__h2-accent,
  .sol-accent,
  .svc-accent,
  .mfg-title-accent {
    background: none !important;
    color: #007cc3 !important;
  }
  .h2-accent--on-dark,
  .h2-accent-inline--on-dark,
  .us-statement__copy h2 span {
    background: none !important;
    color: #2ea8ff !important;
  }
}

/* Narrowest desktop band (1024 to 1151px): a slightly smaller wordmark than
 * the 52px set in the compact header block keeps clear air between it and
 * the first menu item; the extra padding keeps the bar at 64px. Kept last so
 * it follows that block. */
@media (min-width: 1024px) and (max-width: 1151.98px) {
  #header-logo-image {
    height: 44px;
  }
  #header-nav-container {
    padding-top: 10px;
    padding-bottom: 10px;
  }
}
