/* ============================================================
   UPSCALE R3 — site.css
   ALL shared styling for the six-page R3 site. Follows the conventions of
   ui_kits/marketing_site/assets/site.css: tokens only (no inline hex outside the one marked
   :root block below), structural class names, and every hiding rule gated on html.motion.
   ============================================================

   READ FIRST — the four settled decisions this file encodes (DESIGN-R3.md, "four decisions"):
     1. Headings are SENTENCE CASE. There is exactly ONE `text-transform: uppercase` in this
        file — .eyebrow — and it is set at 14px (--text-sm), the brand's ceiling for caps.
        Never raise it to --fs-meta (16px) and never add a second uppercase rule.
     2. Radius is 0-2px. --radius-sm (2px) is the maximum on any card, button, input or badge.
        The comps draw ~12px; they lose. The one exception is a true circle (--radius-pill) where the
        shape IS a circle: the footer socials, the advisory logo badges, the carousel dots.
     3. LIME IS NEVER TEXT. Not a heading colour, not body copy, never on Ivory. It appears as:
        button fills, hairlines, rings, rail dots, timeline connectors and focus rings, on dark
        surfaces only. Every Lime declaration in this file is one of those.
     4. Type comes from five tokens and no more: --fs-statement, --fs-sec-head, --fs-sub-head,
        --fs-lead, --fs-body (+ --fs-meta for labels). No per-section sizes.

   MOBILE-FIRST. Base rules are the 320px layout; everything widens through min-width queries at
   640 / 901 / 1181px. 901px is the header/rail boundary (nav sheet below it, full nav and scene
   rail above). No rule in this file may produce horizontal overflow: every clip-path element,
   oversized glyph and marquee track is either inside an overflow:hidden parent or positioned
   with percentages of its own container.
   ============================================================ */

/* ------------------------------------------------------------
   NEW TOKENS
   tokens.css is FROZEN. These seven are the only additions, and each exists because the R3 comps
   need a value the token layer has no name for.
   ------------------------------------------------------------ */
:root {
  /* THE PHOTOGRAPHIC SCRIM — and read the alpha note before touching it.
     Body copy over photography must clear 4.5:1 against the LIGHTEST composite the crop can
     produce, i.e. the scrim over a blown-out white highlight. The scrim is TRANSLUCENT, so the
     old comment here ("cream text at ~11:1 regardless of the crop") was false twice over: the
     photo reads through, and the copy on these bands was --light-grey (#aaa99f), not cream.
     Measured: #aaa99f over 0.72 on white tops out at 2.95:1 — a hard WCAG 1.4.3 failure.
     The fix is both halves together: copy on .band--photo is promoted to --ivory (see TYPE), and
     the alphas are raised so Ivory clears the floor over ANY crop. Ivory needs a composited
     background no lighter than ~rgb(116), which is alpha >= 0.61 against pure white; 0.78 /
     0.66 leaves margin. --scrim-soft is for hero photography where the image must still read;
     --scrim-deep is the testimonial band, where faces read through a 0.78 wash behind the copy. */
  --scrim:      rgba(25, 25, 25, 0.78);
  --scrim-soft: rgba(25, 25, 25, 0.66);
  --scrim-deep: rgba(25, 25, 25, 0.88);
  /* The raised panel on Eerie Black — the About carousel plate and the collapsed cluster cards.
     One step lighter than the band, no border, no shadow: the brand is flat. */
  --panel:      #1f1f1f;
  --panel-hi:   #262626;
  /* The dark-green wedge behind the three-card cluster (comp m_350). It is Lime at low alpha
     over Eerie Black, i.e. a surface, not a text colour — legal. */
  --wedge:      rgba(52, 197, 42, 0.14);
  /* Header height, so anchor landings and the rail can clear the fixed bar from one place. */
  --chrome-h:   72px;
  /* HOW TALL A PANE ACTUALLY IS. C3's hold pins a band with its top at CHROME_CLEAR, not at 0
     (site.js:1005), so the strip a reader sees while a band is held is `viewport - this`. This
     mirrors CHROME_CLEAR's own arithmetic exactly: site.js:46-49 computes max(72, barHeight + 16)
     and publishes barHeight back as --chrome-h precisely so this file never duplicates a number
     it cannot re-measure. Consumed by .sec--pane and by the .why pane block. */
  --pane-clear: max(72px, calc(var(--chrome-h) + 16px));
  /* Named for the Home stat figures, which left with the canvas landing; stays so a stat-sized figure never gets an anonymous clamp(). */
  --fs-stat:    clamp(44px, 9vw, 86px);
  /* THE GUTTER AND THE SECTION RHYTHM, named so a full-bleed child can cancel them exactly.
     .wrap's horizontal padding and .sec's padding-block used to be authored inline at each
     breakpoint, which left the Home "problem" diagonal with no way to compute the negative
     margin that bleeds it to the band edge as the comp draws it. Both now resolve from here and
     are re-declared per breakpoint at the foot of this file. Change them THERE, not on .wrap. */
  --gutter:         24px;
  --sec-pad-block:  clamp(64px, 11vw, 128px);
  /* THE HOLD-SCREEN PULSE. The only ambient animation in the build with a cadence of its own —
     the duration tokens are interaction speeds (180/340/640ms) and a pulse is not an
     interaction. Named here for the same reason --fs-stat is: one name, one place. */
  --dur-pulse:      3200ms;
}

*, *::before, *::after { box-sizing: border-box; }

/* NO `scroll-behavior: smooth` — Lenis owns scrolling in motion mode and CSS smooth scroll
   fights it (two animations against the same scrollTop). scroll-padding-top keeps a native
   plain-mode hash jump clear of the fixed header. */
html {
  background: var(--eerie-black);
  scroll-padding-top: calc(var(--chrome-h) + 16px);
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  /* The single guard that makes "no horizontal overflow at any width" true even if a clip-path
     wedge is authored a percent too wide. It is a backstop, not a licence. */
  overflow-x: hidden;
  background: var(--eerie-black);
  color: var(--ivory);
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

/* ---------------- Lenis ---------------- */
/* Lenis stamps these on <html>. `height: auto` matters: a hard 100% height clamps the
   scrollable range Lenis translates. */
html.lenis, html.lenis body { height: auto; }
html.lenis-smooth { scroll-behavior: auto !important; }
html.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
html.lenis-stopped { overflow: hidden; }
html.lenis-smooth iframe { pointer-events: none; }

/* ============================================================
   LAYOUT PRIMITIVES
   ============================================================ */

/* .band is a full-width horizontal band. Every top-level <section> is one, and carries
   data-scene-id (which also supplies its id for anchors and the scene rail). */
.band { position: relative; width: 100%; z-index: 1; }

/* Surface variants. Only three, and they are the only backgrounds in this file:
     --ink   Eerie Black. Lime is unlocked here and ONLY here.
     --cream Ivory. Lime is forbidden — buttons fall back to Eerie Black.
     --photo photography behind a scrim. Treated as dark: Lime is unlocked. */
.band--ink   { background: var(--eerie-black); color: var(--ivory); }
.band--panel { background: var(--panel);       color: var(--ivory); }
.band--cream { background: var(--cream);       color: var(--eerie-black); }
.band--photo { background: var(--eerie-black); color: var(--ivory); }

.band--ink h1, .band--ink h2, .band--ink h3, .band--panel h1, .band--panel h2, .band--panel h3,
.band--photo h1, .band--photo h2, .band--photo h3 { color: var(--ivory); }
.band--cream h1, .band--cream h2, .band--cream h3 { color: var(--eerie-black); }

/* --green  Lime Green. The one Lime SURFACE in the build (Home #connect). Lime is the GROUND
   here, so nothing on it may be Lime: copy, glyphs, rules and buttons are all Eerie Black, and
   .btn--lime falls back exactly as it does on cream. data-tone="light" (Eerie Black rail dots);
   the rail's label plate is swapped to Eerie Black by .rail.is-green — cream on Lime is the one
   adjacency the brand forbids. It must never share an edge with a cream band. */
.band--green { background: var(--lime-green); color: var(--eerie-black); }
.band--green h1, .band--green h2, .band--green h3 { color: var(--eerie-black); }
.band--green .lede, .band--green .body-copy, .band--green .meta { color: var(--eerie-black); } /* 7.67:1; a 0.72 tint measures 4.50 exactly — never use it */
.band--green .hr { background: rgba(25, 25, 25, 0.35); }
.band--green .btn--lime { background: var(--eerie-black); color: var(--ivory); border-color: var(--eerie-black); }
.band--green .btn--lime:hover { filter: brightness(0.92); }
.band--green .btn--hair { border-color: var(--eerie-black); color: var(--eerie-black); }
.band--green .link-arrow, .band--green .link-arrow [data-icon] { color: var(--eerie-black); }
.band--green :focus-visible { outline-color: var(--eerie-black); }
.band--green .todo-note { border-color: rgba(25, 25, 25, 0.4); color: var(--eerie-black); }

.wrap {
  max-width: 1440px;
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* Section rhythm. Nothing is sized to a stage; sections are as tall as their content needs.
   PADDING-BLOCK, never the `... 0` shorthand: `.sec` is authored after the >=901px `.wrap`
   gutter rule, so a shorthand here zeroes the horizontal gutters of any single element that
   carries BOTH classes (`<div class="wrap sec">`, which CONTRACT's hero snippet uses) and puts
   its copy flush against the viewport edge. The block axis is the only one `.sec` owns. */
.sec { padding-block: var(--sec-pad-block); }
.sec--tight { padding-block: clamp(48px, 8vw, 88px); }
/* A BAND THAT OWNS ONE PANE, AND CENTRES ITS CONTENT IN IT.
   Why this exists: C3's scroll-lock cannot hold a band shorter than the viewport without the
   next band creeping up under a frozen one, so holdBands() appends a plain .hold-fill to the
   FOOT of any short band to pad it out (site.js, extendHoldBand). That is structurally right and
   visually top-heavy: on #connect it added 230px of empty Lime below the copy, which is exactly
   what "dynamically center this" was pointing at. Padding is the wrong lever — it would have to
   be recomputed per viewport and would still be guesswork.
   The fix removes the REASON for the filler instead of fighting it. The band declares the pane's
   true height itself, so naturalHeightOf() reads >= minH, holdBands() takes its `tall enough on
   its own` branch, and no .hold-fill is ever appended. Then justify-content does the centring,
   at every viewport, with no numbers to keep in sync.
   min-height, never height: content taller than a pane grows the box rather than being clipped,
   so this can never cost a reader a line. */
.sec--pane {
  min-height: calc(100svh - var(--pane-clear));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* THE FIXED HEADER'S OWN SPACE.
   .chrome is fixed, so it takes no room in the flow. A HERO section wants that — the bar sits
   transparently over its photography, which is the whole composition. Every other page's first
   section does not: without this rule its heading renders underneath an opaque header. Keyed off
   [data-hero] so it resolves itself from the markup the page already has to author, with no
   per-page padding class for a builder to forget. */
/* ADDITIVE, not a replacement: this selector is more specific than `.sec`, so a bare
   `padding-top: var(--chrome-h)` overrode the section's own top rhythm and left the page head
   sitting ~27px under the opaque bar on every hero-less page (About, Projects, Advisory). The
   clamp is `.sec`'s own, so the head clears the bar and then keeps its rhythm. */
main > .band:first-child:not([data-hero]) {
  padding-top: calc(var(--chrome-h) + clamp(48px, 8vw, 96px));
}

/* ============================================================
   BAND HOLDS — the .pin-spacer GSAP wraps a held band in
   ============================================================
   site.js gives every eligible band a short scrubbed pin (~30% of a viewport height) and GSAP
   wraps the pinned <section> in a generated <div class="pin-spacer">. That wrapper is the only
   structural change the holds make to the page, and three rules have to survive it.

   None of these is gated on html.motion, and that is deliberate: the wrapper only ever exists
   in motion mode (site.js returns before the hold block in every plain path), so a gate would
   be decoration. What they actually guard is the one frame in which html.motion has been
   stripped and the spacers have not yet been reverted — which is exactly what the blank-page
   watchdog does. */

/* 1. THE FIRST-BAND HEADER RULE, THROUGH THE WRAPPER. The rule above pads a hero-less page's
      opening band clear of the opaque bar. site.js never holds a first band, so this should
      never fire — it is here so that exclusion stops being load-bearing for the layout. */
main > .pin-spacer:first-child > .band:not([data-hero]) {
  padding-top: calc(var(--chrome-h) + clamp(48px, 8vw, 96px));
}

/* 2. THE SPACER IS THE BAND'S FLOW BOX, so it is full-bleed like the band it stands in for.
      GSAP writes an inline width; this is the fallback for the frame before it does. */
main > .pin-spacer { width: 100%; }

/* 3. NO DEAD SCROLL IN A PLAIN MODE. If html.motion is stripped while a spacer is still in the
      DOM, the spacer's padding is an unexplained gap in a page that is no longer holding
      anything. */
html:not(.motion) .pin-spacer { padding-bottom: 0 !important; }

.hr { border: 0; height: 1px; background: var(--hairline); margin: 0; }
.band--ink .hr, .band--panel .hr, .band--photo .hr { background: var(--hairline-cream); }
/* The Lime hairline — the sanctioned Lime-as-accent rule. Dark surfaces only. */
.band--ink .hr--lime, .band--panel .hr--lime, .band--photo .hr--lime { background: var(--hairline-green); }

/* visually hidden — real text for screen readers behind a decorative treatment */
.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
      clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ============================================================
   TYPE
   Five tokens. .stmt / .sec-head / .sub-head / .lede / body, plus .meta.
   ============================================================ */

/* .stmt — the display exception. Home hero H1 and the final CTA statement ONLY. */
.stmt {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--fs-statement);
  line-height: 1.04; letter-spacing: var(--tracking-tight);
  margin: 0; text-wrap: balance;
}
/* .sec-head — every section head, on every page. */
.sec-head {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--fs-sec-head);
  line-height: 1.1; letter-spacing: var(--tracking-tight);
  margin: 0; text-wrap: balance;
}
/* .sub-head — card, pillar and row titles. */
.sub-head {
  font-family: var(--font-sans); font-weight: 700;
  font-size: var(--fs-sub-head);
  line-height: 1.16; letter-spacing: var(--tracking-tight);
  margin: 0; text-wrap: balance;
}
/* .lede and .body-copy share ONE measure (60ch) because after R4b C1 they share one size.
   The lede used to run 52ch; at --fs-lead's old 20-22px that came out within 2% of
   .body-copy's 60ch at --fs-body, so the two read as one column. Flatten both to 18px and
   52ch drops to 573px against .body-copy's 661px — an 88px step between two paragraphs of
   identical type, which reads as a bug in every stack that carries a lede above a body
   paragraph (#hero, System's hero, System's cluster, About's two-up). One size, one measure. */
.lede {
  font-family: var(--font-sans); font-weight: 300;
  font-size: var(--fs-lead); line-height: 1.6;
  max-width: 60ch; margin: 0;
  color: var(--light-grey);
}
.band--cream .lede { color: var(--mid-grey); }
.body-copy {
  font-family: var(--font-sans); font-weight: 300;
  font-size: var(--fs-body); line-height: 1.6;
  max-width: 60ch; margin: 0;
}
.band--ink .body-copy, .band--panel .body-copy, .band--photo .body-copy { color: var(--light-grey); }
.meta { font-size: var(--fs-meta); font-weight: 300; line-height: 1.5; color: var(--light-grey); margin: 0; }
.band--cream .meta { color: var(--mid-grey); }

/* COPY ON PHOTOGRAPHY IS IVORY, NOT LIGHT GREY.
   --light-grey (#aaa99f) measures a comfortable 7.44:1 on the OPAQUE --ink and --panel bands,
   and that is where it stays. Over a TRANSLUCENT scrim it cannot reach 4.5:1 at any alpha this
   build would tolerate (2.95:1 at 0.72, 1.83:1 at 0.58), because the photograph reads through.
   Ivory over --scrim / --scrim-soft clears the floor against the lightest crop in the set.
   Same specificity as the --light-grey rule above and authored after it, so it wins by order —
   do not "tidy" it upward. */
.band--photo .lede,
.band--photo .body-copy,
.band--photo .meta { color: var(--ivory); }

/* THE TWO-WEIGHT HEADLINE DEVICE — the comps' strongest move, kept.
   `Upscale <span class="lt">through our value creation system</span>` — Bold 700 beside Light
   300 in one line. It is a WEIGHT change, never a colour change: .lt inherits `color` and there
   is deliberately no colour declaration here. Writing one would put Lime (or anything else) in a
   heading, which is the rule this device is most often used to break. */
.lt { font-weight: 300; letter-spacing: var(--tracking-snug); }

/* THE R4b LIME-ON-DARK ACCENT — sanctioned by the R4b brief and by nothing wider.
   The standing rule is "Lime is never a heading colour". The user supplied the reference that
   breaks it, so the rule is AMENDED, narrowly: Lime may accent ONE heading line, and only on a
   dark surface. It is still never body text, never on cream, never on Ivory, never on the Lime
   ground.
   THE AMENDMENT IS STRUCTURAL, NOT REMEMBERED. Both modifiers below are COLOURLESS on their
   own — the Lime lives only in the .band--ink / --panel / --photo branch — so pasting either
   class onto a cream or a Lime-ground band cannot produce Lime. That is the same shape as
   .btn--lime's cream fallback, and for the same reason. Deliberately absent: any .band--cream or
   .band--green branch.
   .hl-accent is display:block so it breaks the heading's second line WITHOUT breaking
   textContent: the audit's copy probe reads textContent, so "For growth <span>at every
   scale.</span>" still normalises to the required string. Keep the space before the span.
   Consumers, and there are exactly two: #problem's "Slower growth." (C4) and #audiences'
   "at every scale." (C5) — see DESIGN-R3.md's "R4b" section. #aiq is .band--panel and could
   reach this rule too; it must not — every Lime inside #aiq stays a border, background or glow,
   never a heading colour (see the AiQ restyle notes in DESIGN-R3.md). */
.hl-accent    { display: block; color: inherit; }
.glyph-accent { color: inherit; }
.band--ink .hl-accent,    .band--panel .hl-accent,    .band--photo .hl-accent    { color: var(--lime-green); }
.band--ink .glyph-accent, .band--panel .glyph-accent, .band--photo .glyph-accent { color: var(--lime-green); }

/* Eyebrow labels are the ONLY uppercase in this build, and the size is 14px — NOT --fs-meta.
   The brand rule is "sentence case, caps only for eyebrow labels <=14px" and --fs-meta is 16px,
   so setting an uppercase label from --fs-meta breaks the one rule this component exists inside.
   14px is the ceiling, and the audit asserts no ALL-CAPS text above it. */
.eyebrow {
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: 500;
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
  color: var(--light-grey); margin: 0;
  display: inline-flex; align-items: center; gap: 12px;
}
.band--cream .eyebrow { color: var(--mid-grey); }

/* Copy-level stacks. Use these instead of margins on p/h. */
.stack   { display: flex; flex-direction: column; gap: 20px; align-items: flex-start; }
.stack-l { display: flex; flex-direction: column; gap: 32px; align-items: flex-start; }

/* A deliberate open item (DESIGN-R3.md "Open items"). Marked, never silently cleared.
   THE MARKER IS THE data-todo ATTRIBUTE, NOT A PRINTED CAPTION. The dashed caption boxes were
   rendering inside the composition — eleven on Home alone, two of them splitting a cluster card
   between its body and its "Learn more" — which reads as debug output printed into the middle of
   the design. The attribute is what the brief requires and what the audit counts, so the note
   keeps its markup and its text and is simply not painted in a normal build.
   TO SEE THEM: add `todo` to <html> (`document.documentElement.classList.add('todo')`) and every
   marker paints exactly as before. Nothing is deleted, nothing is silently cleared. */
[data-todo] { position: relative; }
.todo-note { display: none; }
/* WITH ONE EXCEPTION, AND IT IS THE BRIEF'S OWN. Where the note is the placeholder's ONLY
   content — the three empty Advisory cards, the two blank-badge rows, the five unwritten carousel
   slides and Home's four headshot holds — hiding it leaves an empty bordered box, and DESIGN-R3.md is explicit that those render
   as "visible, LABELLED placeholders". So the note stays painted exactly where it is the label
   and stands down where it was a caption printed over finished composition. */
.tl-card > .todo-note,
.slide-copy > .todo-note,
.leader-photo > .todo-note,
html.todo .todo-note {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--fs-meta); font-weight: 400; line-height: 1.4;
  padding: 6px 10px; border: 1px dashed var(--hairline-cream-strong);
  border-radius: var(--radius-none); color: var(--light-grey);
}
.band--cream .todo-note { border-color: var(--hairline-strong); color: var(--mid-grey); }

/* ============================================================
   ICONS
   The <span data-icon> placeholder carries the icon's FINAL BOX before icons.js runs, so
   hydration cannot shift the layout. --icon-size is the single knob.
   ============================================================ */
[data-icon] {
  display: inline-block; flex: none;
  width: var(--icon-size, 24px);
  height: var(--icon-size, 24px);
  line-height: 0;
  vertical-align: middle;
}
[data-icon] > svg { display: block; width: 100%; height: 100%; }
.icon-sm { --icon-size: 20px; }
.icon-md { --icon-size: 28px; }
/* SIZED TO THE COMPS, not to the box they sit in. Every badge and glyph in the first pass came
   out at 55-75% of drawn size, which is the single biggest reason the build read flatter and
   denser than the comp: comp value glyphs ~79px against 44, mission/vision ~143px against 96. */
.icon-lg { --icon-size: 72px; }
/* The oversized About mission/vision glyphs. */
.icon-xl { --icon-size: clamp(72px, 12vw, 140px); }

/* THE ICON RING — the comps draw a Lime-FILLED circular badge with a white glyph. The brand
   rule wins: a Lime HAIRLINE ring with a white 1.5-stroke glyph. Same silhouette, Lime stays an
   accent, and the glyph keeps full cream-on-dark contrast instead of white-on-Lime (2.2:1).
   The ring is the ONE place --radius-pill is legal: the shape is a circle. */
/* 112px, not 72: the comps draw this badge at ~126px at 1440 with a ~40px glyph inside it, and
   an undersized ring is what makes the Projects and cluster cards read as dense tiles. */
.icon-ring {
  display: inline-flex; align-items: center; justify-content: center;
  width: 112px; height: 112px; flex: none;
  border: 1px solid var(--hairline-green);
  border-radius: var(--radius-pill);
  color: var(--ivory);
  background: transparent;
}
/* On cream, Lime is illegal — the ring falls back to an Eerie Black hairline. */
.band--cream .icon-ring { border-color: var(--hairline-strong); color: var(--eerie-black); }
.icon-ring [data-icon] { --icon-size: 40px; }
/* THE FORMULAS IN THE RING. README's ICONOGRAPHY is explicit: the Formulas replace bullet and
   feature icons — `+` "we add", `x` "we multiply", `=` "the outcome" — and a general icon set is
   only the flagged substitute for system UI glyphs. The Efficiency / Effectiveness / Expertise
   cluster is exactly that feature list, so it carries plus / multiply / equals in Ivory rather
   than three hand-drawn line glyphs (which also de-duplicates hand-coins and the clipboard family
   between the cluster and the Projects cards). Same 40px box as a hydrated glyph. */
.icon-ring img { width: 40px; height: 40px; object-fit: contain; }

/* ============================================================
   BUTTONS
   Two only: .btn--lime (primary, Lime fill, dark surfaces) and .btn--hair (secondary,
   hairline). Both 0 radius, both ≥44px tall.
   ============================================================ */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--font-sans); font-weight: 400; font-size: var(--fs-meta);
  letter-spacing: 0; text-align: center;
  min-height: 48px; padding: 12px 22px;
  border: 1px solid transparent;
  border-radius: var(--radius-none);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.btn > * { position: relative; z-index: 1; }
.btn [data-icon] { --icon-size: 18px; }

/* THE PRESS STATE. README: "Press: scale 0.98 + filter brightness 0.88, 120ms". There was no
   :active rule anywhere in this file, so nothing in the build had one. One shared rule for every
   pressable control; neutralised under prefers-reduced-motion at the foot of this file. */
.btn:active, .carousel-arrow:active, .nav-toggle:active,
.play-btn:active, .carousel-dot:active {
  transform: scale(0.98); filter: brightness(0.88); transition-duration: 120ms;
}

/* PRIMARY — the Lime fill. Eerie Black label on Lime is 6.6:1. This is Lime doing exactly what
   the brand allows: a fill on a dark surface, never a text colour. */
.btn--lime {
  background: var(--lime-green); color: var(--eerie-black);
  border-color: var(--lime-green); font-weight: 500;
}
/* HOVER DARKENS, IT DOES NOT CHANGE HUE. README's hover rule is "background darkens ~8%
   (filter: brightness(0.92)), or border strengthens". This used to flip Lime to Ivory outright,
   which is a different button, not a hover state. */
.btn--lime:hover { filter: brightness(0.92); }
/* Lime is forbidden on Ivory — on a cream band the primary becomes Eerie Black. Same weight,
   same role, legal surface. */
.band--cream .btn--lime {
  background: var(--eerie-black); color: var(--ivory); border-color: var(--eerie-black);
}
.band--cream .btn--lime:hover { filter: brightness(0.92); }

/* SECONDARY — a hairline that fills on hover. */
.btn--hair { background: transparent; color: var(--ivory); border-color: var(--hairline-cream-strong); }
.btn--hair:hover { background: var(--ivory); color: var(--eerie-black); border-color: var(--ivory); }
.band--cream .btn--hair { color: var(--eerie-black); border-color: var(--hairline-strong); }
.band--cream .btn--hair:hover { background: var(--eerie-black); color: var(--ivory); border-color: var(--eerie-black); }

/* A text link that carries the arrow motif. */
.link-arrow {
  display: inline-flex; align-items: center; gap: 10px;
  min-height: 44px; font-size: var(--fs-body); font-weight: 400;
}
.link-arrow [data-icon] { --icon-size: 18px; color: var(--lime-green); }
.band--cream .link-arrow [data-icon] { color: var(--eerie-black); }
.link-arrow:hover { text-decoration: underline; text-underline-offset: 6px; }

/* ============================================================
   INPUTS
   ============================================================ */
.field { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.field-label { font-size: var(--fs-meta); font-weight: 400; color: var(--light-grey); }
.band--cream .field-label { color: var(--mid-grey); }
.input {
  font-family: var(--font-sans); font-weight: 300; font-size: var(--fs-meta);
  min-height: 48px; width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--hairline-cream-strong);
  border-radius: var(--radius-none);
  background: transparent; color: var(--ivory);
}
.input::placeholder { color: var(--light-grey); opacity: 1; }
.input:focus { outline: 2px solid var(--lime-green); outline-offset: 2px; border-color: var(--lime-green); }
.band--cream .input { border-color: var(--hairline-strong); color: var(--eerie-black); }
.band--cream .input::placeholder { color: var(--mid-grey); }
.band--cream .input:focus { outline-color: var(--dark-cyan); border-color: var(--dark-cyan); }

/* ============================================================
   HEADER (.chrome)
   Fixed. Two tones:
     .is-transparent — over hero photography (the page's first section is .band--photo)
     .is-opaque      — once scrolled past the hero, and on every page with no hero
   site.js owns the class; CSS only reacts to it. That is the one-place-for-one-decision rule
   the marketing site learned the hard way.
   ============================================================ */
.chrome {
  position: fixed; top: 0; left: 0; right: 0; z-index: 80;
  min-height: var(--chrome-h);
  border-bottom: 1px solid transparent;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base),
              opacity var(--dur-slow) var(--ease-out), visibility 0s linear 0s;
}
.chrome-inner {
  max-width: 1440px; margin: 0 auto;
  padding: 12px 24px;
  display: flex; align-items: center; gap: 16px;
}
.chrome.is-transparent { background: transparent; border-bottom-color: transparent; }
.chrome.is-opaque { background: var(--eerie-black); border-bottom-color: var(--hairline-cream); }
/* WHILE THE DOOR IS CLOSED (html.motion.hold, index.html only) the bar and the rail are hidden:
   the hold screen is the wordmark on a blank page, and a second wordmark in the corner is not
   blank. Hidden by DEFAULT under the hold and revealed by a runtime class, so the first frame
   never flashes the bar. Gated on BOTH classes: if the libraries fail and site.js strips
   html.motion, this rule stops matching and the bar is back. visibility, not just opacity, takes
   the nav links and the Let’s talk button out of the tab order while they cannot be seen. The
   visibility delay equals the fade so the bar stays paintable for the whole fade-OUT; .is-shown
   zeroes it. Runtime classes are ignored by the audit's header signature. */
html.motion.hold .chrome:not(.is-shown),
html.motion.hold .rail:not(.is-shown) {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition-delay: 0s, 0s, 0s, var(--dur-slow);
}
/* No-JS: the tone listener never runs, so default to opaque — a transparent bar over an
   unknown surface is the one state that can be unreadable. */
html:not(.js) .chrome { background: var(--eerie-black); border-bottom-color: var(--hairline-cream); }

.chrome .logo { display: inline-flex; align-items: center; min-height: 44px; margin-right: auto; }
.chrome .logo img { height: 22px; width: auto; }

/* --- desktop nav (≥901px; below that the sheet replaces it) --- */
.nav { display: none; }
.nav-list { list-style: none; display: flex; align-items: center; gap: clamp(14px, 1.8vw, 30px); margin: 0; padding: 0; }
.nav-list a {
  display: inline-flex; align-items: center; min-height: 44px; min-width: 44px;
  font-size: var(--fs-meta); font-weight: 400; color: var(--ivory);
}
/* min-width keeps a short label ("AiQ") at a 44px hit target; centring it in the bar hides the
   slack. The sheet's items are width:100% and left-aligned, so this is scoped to the bar. */
.nav .nav-list a { justify-content: center; }
.nav-list a:hover { text-decoration: underline; text-underline-offset: 6px; }
/* The active item is Lime — the one place the brief sanctions Lime on a text-like element. It
   is a NAV STATE on a dark surface at 16px, not a heading and not body copy, and it is not
   colour-alone: the item also carries a 2px Lime underline and weight 500 (WCAG 1.4.1).
   DORMANT since 2026-09-14: no nav item is authored with aria-current any more (every item is a
   section of Home; the scene rail shows the current section). Kept so the state is defined if
   a page-level nav ever returns. */
.nav-list a[aria-current="page"] {
  color: var(--lime-green); font-weight: 500;
  border-bottom: 2px solid var(--lime-green);
}

/* --- header CTA: the chrome's one outward action (replaces the R3 search form) ---
   .btn--hair is the kit's OUTLINE button (§BUTTONS): transparent fill, Ivory label, cream
   hairline, radius 0 — exactly the boxed button the C4 reference draws in the bar. Nothing new
   is invented here; the two overrides below are size only.

   THE 44px IS LOAD-BEARING, not a tap-target floor (.btn is already 48px). At ≥901px
   .chrome-inner pads 14px top and bottom, so the tallest child sets the bar's height. The search
   shell it replaces measured 46px — 44px controls inside its own 1px border — which made the bar
   74px while site.css declares --chrome-h: 72px and site.js re-measures it. A 44px CTA makes the
   bar exactly 72px and the static token correct for the first time. Do NOT let this button fall
   back to .btn's 48px: 48 + 28 = 76px and the no-JS #navSheet (inset: var(--chrome-h) 0 0 0)
   would tuck 4px under the bar.

   HIDDEN BELOW 901px, where #navSheet carries the CTA instead — the same split the search form
   used, and the only one that fits: at 360px the bar has ~81px between the wordmark and the
   toggle, and this button needs ~132px. */
.chrome-cta { display: none; min-height: 44px; padding: 10px 18px; white-space: nowrap; }
/* The → is a CHARACTER, so it is TEXT (CONTRACT rule 3) and takes the button's own colour —
   Ivory at rest, Eerie Black on the .btn--hair hover fill. Never Lime; aria-hidden hides it from
   a screen reader, not from the rule. No transform: it is upright by construction. */
.chrome-cta .ar, .sheet-cta .ar { color: currentColor; font-weight: 400; }

/* --- mobile: hamburger + sheet (≤900px) --- */
.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: none;
  background: transparent; border: 1px solid var(--hairline-cream-strong);
  border-radius: var(--radius-none); cursor: pointer; color: var(--ivory);
}
.nav-toggle [data-icon] { --icon-size: 22px; }
/* Which glyph shows is driven by aria-expanded — the state lives in the attribute the
   assistive tech reads, so there is no second source of truth to keep in sync. */
.nav-toggle .i-close { display: none; }
.nav-toggle[aria-expanded="true"] .i-menu { display: none; }
.nav-toggle[aria-expanded="true"] .i-close { display: inline-block; }

.nav-sheet {
  position: fixed; inset: var(--chrome-h) 0 0 0; z-index: 79;
  background: var(--eerie-black);
  border-top: 1px solid var(--hairline-cream);
  padding: 24px;
  overflow-y: auto;
  /* `display: none` when closed, not opacity or visibility: it is the only mechanism that also
     drops the closed sheet out of the accessibility tree, so the six nav links are never
     tabbable behind a closed panel. */
  display: none;
}
.nav-sheet.is-open { display: block; }
.nav-sheet .nav-list { flex-direction: column; align-items: stretch; gap: 0; }
.nav-sheet .nav-list li + li { border-top: 1px solid var(--hairline-cream); }
.nav-sheet .nav-list a { min-height: 56px; font-size: var(--fs-body); width: 100%; }
.nav-sheet .nav-list a[aria-current="page"] { border-bottom: 0; }
/* The sheet's CTA sits under the six links, full width, on the sheet's own 56px row rhythm
   (.nav-sheet .nav-list a is 56px) — well past the 44px floor probeTaps fails on the three touch
   profiles. The sheet already scrolls (overflow-y: auto), so the extra 80px is safe at
   mobileLand, where six 56px rows already exceed the panel. */
.nav-sheet .sheet-cta { display: flex; width: 100%; min-height: 56px; margin-top: 24px; }
/* No-JS: the toggle cannot work, so hide it and let the sheet stand open as a plain list. */
html:not(.js) .nav-toggle { display: none; }
html:not(.js) .nav-sheet { position: static; inset: auto; display: block; padding: 0 24px 16px; border-top: 0; }

/* The page behind an open full-height nav sheet must not scroll under it. site.js sets this
   on <html>; a class rather than an inline style, so it cannot clobber Lenis's own
   lenis-stopped handling. */
html.nav-locked { overflow: hidden; }

/* ============================================================
   SCENE RAIL
   Right margin, hero pages only, one dot per band, active one Lime. Hidden ≤900px — a 24px dot
   is a real tap-target failure on touch, and it competes with content for the gutter.
   ============================================================ */
.rail { display: none; }

/* ============================================================
   ENTRY — THE HOLD SCREEN (index.html only)
   Ported from marketing_site's entry runway WITHOUT its WebGL stage. The runway is scroll
   distance; .entry is sticky and holds the viewport while the scrub plays; when the scrub ends
   the black frame slides up and the hero comes up underneath — the door opening. A true scrub:
   geometry is a pure function of scroll offset and reverses at any point.
   Rendered ONLY under html.motion.hold — the head script sets .hold pre-paint and site.js
   strips it with .motion on any plain fallback, so in every plain case (no JS, reduced motion,
   a library missing, the blank-page watchdog) this whole block is display:none and the page
   starts on the intro. The pin is position: sticky, never a ScrollTrigger pin (no .pin-spacer).
   ============================================================ */
.entry-runway { display: none; }
html.motion.hold .entry-runway {
  display: block;
  position: relative; height: 220vh; z-index: 2;
  background: var(--eerie-black);
}
/* Shorter where 220vh would cost two-plus screens before any content: phones, coarse pointers,
   and any window too short to give the zoom room to read. 639 is R3's own phone boundary. */
@media (pointer: coarse), (max-width: 639px), (max-height: 620px) {
  html.motion.hold .entry-runway { height: 150vh; }
}
.entry {
  position: sticky; top: 0;
  height: 100vh; height: 100svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 28px;
  background: var(--eerie-black);
  overflow: hidden;
  cursor: pointer;
}
/* The handle. A real <button> so the door is keyboard-operable; padding grows the hit box past
   44px on both axes without touching .entry-logo, whose % geometry the arrow depends on. No
   hover filter: dimming the brand mark is not a hover state. */
.entry-pass {
  display: block; padding: 24px;
  background: transparent; border: 0; border-radius: var(--radius-none);
  color: inherit; cursor: pointer;
}
.entry-logo {
  display: block; position: relative;
  width: clamp(240px, 28vw, 430px);
  max-width: calc(100vw - 112px);
  aspect-ratio: 1180 / 168;
}
.entry-text {
  display: block; width: 100%; height: 100%;
  object-fit: contain; user-select: none; -webkit-user-drag: none;
  will-change: opacity;
}
/* Green-pixel bounding box measured in the 1180x168 wordmark. site.js drives left/top/width/
   height during the scrub — NEVER transform. */
.entry-arrow {
  position: absolute;
  left: 56.02%; top: 1.79%; width: 15.08%; height: 98.21%;
  color: var(--lime-green);
  will-change: width, height, left, top;
  backface-visibility: hidden;
}
.entry-mark { display: block; width: 100%; height: 100%; overflow: visible; shape-rendering: geometricPrecision; }
.entry-mark path { fill: currentColor; }
/* THE PULSE (D6). Scale only: a symmetric breath between 1 and 1.08 on a 3.2s cadence. PAUSED,
   not removed, the instant the scrub starts — `animation: none` snapped the arrow back to
   scale(1) on the first wheel tick. The scrub measures the OUTER .entry-arrow box, which the
   inner scale never touches, so a paused mid-phase is invisible. */
@media (prefers-reduced-motion: no-preference) {
  .entry-mark { animation: arrowPulse var(--dur-pulse) var(--ease-in-out) infinite; transform-origin: 50% 50%; }
}
@keyframes arrowPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
.entry.is-scrubbing .entry-mark { animation-play-state: paused; }
/* The tagline — sentence case, Light. Fades with the wordmark (site.js writes the same
   opacity). --light-grey is legal here: an opaque Eerie Black ground. */
.entry-tag {
  margin: 0; text-align: center;
  font-size: var(--fs-lead); font-weight: 300; letter-spacing: var(--tracking-normal);
  color: var(--light-grey);
  will-change: opacity;
}
/* PAST THE DOOR (progress >= ZOOM_END) the handle and the tagline leave the tab order and the
   accessibility tree, so Tab cannot land on a transparent button. Toggled by site.js. */
.entry.is-passed .entry-pass,
.entry.is-passed .entry-tag { visibility: hidden; pointer-events: none; }
/* The door hands focus to the hero when it opens; the ring is suppressed on the section only. */
[data-hero][tabindex="-1"]:focus, [data-hero][tabindex="-1"]:focus-visible { outline: none; }

/* ============================================================
   SECTION BANDS: PHOTOGRAPHY + THE CUTS
   ============================================================ */

/* .band--photo holds a .band-photo child (the <img>) and a .scrim over it. The scrim is a flat
   wash, not a gradient: body copy has to clear 4.5:1 against the DARKEST value it can land on,
   and a gradient's light end is a contrast lottery decided by the crop. */
.band-photo {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}
/* PARALLAX HEADROOM. site.js runs a parallaxed element from y:0 to y:-(f * viewportHeight)
   across its band's pass, so the travel is always UPWARD and always leaves f*vh of the band
   uncovered at the BOTTOM — a bare strip under the photograph. The largest factor in the build
   is 0.06, so 8vh is headroom with margin. The correction is height/offset and never a CSS
   transform: `data-parallax` hands `transform` entirely to GSAP. `bottom: auto` un-constrains
   the `inset: 0` above so `height` wins. */
.band-photo[data-parallax] { height: calc(100% + 8vh); bottom: auto; }

/* THE SCRIM MUST KEEP ITS OWN position: absolute — do not add .scrim back to this selector.
   `.band--photo > .wrap, .band--photo > .scrim { position: relative }` was MORE SPECIFIC than
   `.scrim`'s own `position: absolute`, so the scrim resolved to position:relative, `inset: 0`
   stopped applying and the element measured 1440x0 on all seven photographic bands. The whole
   contrast strategy this file documents was structurally inoperative and copy sat on raw
   photography (measured 1.01:1 on Home's "solution" lede). This rule now touches .wrap only. */
.band--photo > .wrap { position: relative; }
.scrim { position: absolute; inset: 0; z-index: 1; background: var(--scrim); pointer-events: none; }
.scrim--soft { background: var(--scrim-soft); }
.scrim--deep { background: var(--scrim-deep); }
/* THE LANE SCRIM. A flat 0.78 wash met the contrast floor but printed the photograph almost
   black, which loses the photographic read the comps are built on. Where the copy occupies the
   left half only — the three glyph-free Partnership pillars and the System hero — the scrim
   becomes a lane: full strength across the copy column, easing off over the rest of the image.
   The contrast probe samples the pixel composited behind each text box, and every text box
   sits inside the 0.86 plateau, so the floor still holds where it is load-bearing.
   NOT on Partnership's #value-creation: its Lime multiply glyph sits over the right of the
   photograph, and lightening that side would put Lime on a light ground. */
.scrim--lane {
  background: linear-gradient(to right,
    rgba(25, 25, 25, 0.88)  0%,
    rgba(25, 25, 25, 0.86) 42%,
    rgba(25, 25, 25, 0.52) 78%,
    rgba(25, 25, 25, 0.42) 100%);
}
/* THE FOOT SCRIM (Home #hero). The lane scrim's idea turned vertical, for a hero whose copy is
   CENTRED: the lane's light right half would sit under the H1's second line, so instead the
   copy is anchored low (#hero carries .hero-photo — System's low-head grammar) and the wash
   deepens toward it. Under the transparent bar the wash is the old --scrim-soft 0.66 (the nav
   reads exactly as it does today); just below the bar it eases to 0.46 so the boardroom reads
   through the top third; from 32vh down it is the 0.86 plateau every text box sits inside
   (Ivory over a 255 pixel at 0.86 = 10.9:1). The copy's top is held at or below 38vh by the
   wrap's padding, so text never enters the easing. Stops in px/vh, not %, so the plateau does
   not move when the band grows taller than the viewport on a phone. */
.scrim--foot {
  background: linear-gradient(to bottom,
    rgba(25, 25, 25, 0.66) 0,
    rgba(25, 25, 25, 0.66) var(--chrome-h),
    rgba(25, 25, 25, 0.46) calc(var(--chrome-h) + 72px),
    rgba(25, 25, 25, 0.86) 32vh,
    rgba(25, 25, 25, 0.88) 100%);
}
.band--photo > .scrim--foot ~ .wrap { padding-top: max(var(--sec-pad-block), 38vh); }
/* PHONES. The geometry holds at every width — with #hero { min-height: 100vh } and flex-end the
   band resolves to max(100vh, anchor + copy + pad-bottom), so the copy's top lands at exactly the
   anchor in the first case and above it in the second, text never enters the easing, and the
   px/vh stops keep the plateau put. But at 393x659 with an H1, a lede AND a body paragraph the
   band measures ~830px, so ~170px of hero copy sits below the fold at the #hero landing —
   immediately after a 150vh runway. Pulling the anchor to 28vh (plateau from 24vh, so the copy's
   top is still inside it) recovers ~66px. The residue is accepted, not hidden: it is
   hand-checked at 393, and no probe catches it (probeCopy's paintedEl does not require viewport
   intersection), so the acceptance line is the only guard there is. */
@media (max-width: 639px) {
  .scrim--foot {
    background: linear-gradient(to bottom,
      rgba(25, 25, 25, 0.66) 0,
      rgba(25, 25, 25, 0.66) var(--chrome-h),
      rgba(25, 25, 25, 0.46) calc(var(--chrome-h) + 56px),
      rgba(25, 25, 25, 0.86) 24vh,
      rgba(25, 25, 25, 0.88) 100%);
  }
  .band--photo > .scrim--foot ~ .wrap { padding-top: max(var(--sec-pad-block), 28vh); }
}
/* z-index 1 on the scrim (over .band-photo's 0), 2 on the copy. */
.band--photo > .wrap { z-index: 2; }

/* ============================================================
   HERO COMPOSITIONS
   Two photographic heroes: Home (#hero, copy centred over the frame, the oversized brand mark
   bleeding out of the bottom-left) and System (.hero-photo, head sitting low over a full-bleed
   frame, comp hero_07). Both are FLOORS, never stages: the band still grows with its content
   and no type is sized to the window.
   ============================================================ */
#hero {
  /* FILL THE VIEWPORT. This was clamp(560px, 80vh, 860px), which in a 900px window resolved to
     720px and left ~180px of the *next* band showing under the fold — the photograph read as
     cut off halfway down the screen. A hero that ends mid-viewport is the one composition where
     a floor is not enough, because the fold is part of the composition.
     Still a FLOOR, not a stage: min-height, so the band grows if the copy needs more, and no
     type is sized to the window. svh is the honest unit on a phone — 100vh there measures the
     viewport *without* the browser's own chrome, so the CTA lands under the address bar. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex; align-items: center;
  /* The mark bleeds by NEGATIVE offsets. An unclipped negative inset lands in
     documentElement.scrollWidth and fails the no-horizontal-overflow assertion at every width. */
  overflow: hidden;
  /* .chrome is fixed and transparent here, so it takes no room in the flow — on a phone the
     centred copy would clear the bar by ~56px, which is not clearance. */
  padding-top: var(--chrome-h);
}
#hero > .wrap { width: 100%; }
/* pack/boardroom.jpg is landscape 3:2; centre keeps the table. */
#hero > .band-photo { object-position: 50% 50%; }
/* Home hero and the final-CTA statement are both centred over their band (comp g_Group_326). */
.hero-copy, .cta-copy { align-items: center; text-align: center; margin-inline: auto; }
/* The hero H1's two clauses each take their own line. Left to wrap freely, the em-dash landed
   mid-line after "marketing" and the four lines ragged badly ("…marketing — without / the
   headcount / or the handoffs."). A block-level .lt breaks at the dash, which is where the
   sentence turns anyway, and lets each half balance on its own. Not a <br>: this stays correct
   at every width, and the span is already the two-weight device. */
#hero .stmt .lt { display: block; }
/* The hero's measure is set on the CONTAINER, and `ch` here resolves against .hero-copy's own
   inherited 20px body font — so 62ch was ~620px, a narrow column for a 66px headline. It forced
   "Enterprise-grade marketing" to break at its own hyphen with ~600px of band still unused.
   The container now runs wide and each child takes its own measure: the H1 gets the room, while
   the lede took its own 56ch override (of 20px) where body copy actually reads. Not a type
   change — the headline is still --fs-statement.
   R4b C1 REMOVED THAT OVERRIDE. --fs-lead and --fs-body are now the same flat 18px, and .lede's
   own rule (site.css:~204) already carries the shared 60ch measure every lede and body-copy
   paragraph reads at — a hero-local 56ch would just reopen the inversion C1 exists to close
   (§ RULING R5 / R4). The hero lede now inherits that global measure with no override here. */
.hero-copy { max-width: min(1100px, 100%); }
.cta-copy { max-width: 46ch; }

/* The System hero: head sitting LOW over a full-bleed frame (comp hero_07).
   THE ID SELECTOR IS DELIBERATE. System's hero carries BOTH id="hero" and .hero-photo, and
   `#hero { align-items: center }` above outranks a class, so the plain .hero-photo rule lost and
   the head landed in the upper third. Do not "simplify" this back to a single class. */
/* NO min-height HERE. System's hero carries both id="hero" and .hero-photo, so `#hero`'s
   min-height above (an id, 100) outranks anything this class could set (10) — the
   clamp(560px, 82vh, 860px) that used to sit here never applied to a single element and was
   read as live for the whole build. Both photographic heroes fill the viewport from `#hero`. */
.hero-photo { display: flex; align-items: flex-end; }
#hero.hero-photo { align-items: flex-end; }
.hero-photo > .wrap { width: 100%; }

/* THE DIAGONAL CUT — the comps' defining layout move (Home "problem", comp m_350/hero_00).
   A photo filling the right of a cream band, its left edge a straight diagonal.
   On mobile the cut is a horizontal band across the top of the media instead: a 45-degree
   hypotenuse across a 320px column eats most of the image. */
.cut { position: relative; overflow: hidden; }
.cut img, .cut > picture > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cut--diagonal { clip-path: polygon(0 22%, 100% 0, 100% 100%, 0 100%); }
/* THE X CUT — the Partnership pillars' defining device (comps hero_11-hero_13). The comp masks
   each photographic band with a full X: a black triangle biting DOWN from the top centre, a
   matching one biting UP from the bottom centre, and black chevrons biting inward from the left
   and right edges, so the visible photograph reads as an X/bowtie. The first pass carried only
   the left-and-right bite, which reads as a thin ribbon, and only two of the four pillars had it
   at all. ONE mask, carrying all the bites, applied to EVERY .pillar--photo photograph — the
   selector is what keeps the four pillars agreeing with each other. */
.cut--chevron,
.pillar--photo > .band-photo {
  clip-path: polygon(0 0, 42% 0, 50% 16%, 58% 0, 100% 0, 88% 50%, 100% 100%,
                     58% 100%, 50% 84%, 42% 100%, 0 100%, 12% 50%);
}
/* Under reduced motion the cuts stay — they are composition, not motion. */

/* ============================================================
   THE THREE-CARD OFFSET CLUSTER
   Home §4 and System §2. Efficiency / Effectiveness / Expertise, offset vertically, over a
   diagonal dark-green wedge (comp m_350).

   Expansion is HOVER **and** FOCUS-WITHIN in CSS, plus an .is-open class site.js sets from a
   real <button aria-expanded>. Three consequences, all deliberate:
     · it works with JS off, because :hover/:focus-within need no script;
     · it is keyboard-operable, because the toggle is a button and :focus-within fires on it;
     · the DRAG the comps annotate is NOT implemented. A drag-only affordance is unreachable by
       keyboard, and the brief settles it: hover/focus expansion, skip the drag.
   ============================================================ */
.cluster-layout { position: relative; display: grid; grid-template-columns: 1fr; gap: clamp(40px, 6vw, 72px); align-items: start; }
.cluster { position: relative; display: grid; grid-template-columns: 1fr; gap: 20px; }
/* The wedge is a child of .cluster-layout, which is not a positioned-stacking context of its
   own, so the headline column has to be lifted out of the normal-flow layer or the wedge paints
   over it. */
.cluster-layout > *:not(.cluster-wedge) { position: relative; z-index: 1; }

/* The wedge. Absolutely positioned behind the cards and clipped to a diagonal.
   HORIZONTAL INSET IS ZERO, and that is load-bearing. `.cluster` does NOT clip — it cannot,
   because an overflow:hidden ancestor also clips the focus rings on the cards inside it, and
   there is exactly one focus style in this file (see FOCUS). So the wedge is not allowed to
   bleed sideways in the first place: any negative left/right inset here lands in
   documentElement.scrollWidth and fails the no-horizontal-overflow assertion at EVERY width
   (measured: 1469 vs 1440 at desktop, 333 vs 320 at 320px, when this read `inset: -8% -4%`).
   Vertical bleed is free — it costs no horizontal scroll — so the wedge takes its overhang
   above and below, and the diagonal comes from the clip-path, not from extra width.

   IT IS A CHILD OF .cluster-layout, NOT OF .cluster. Confined to the cluster column it measured
   655x983 at x=713 — exactly the card column — and read as a stub behind the cards' left edge
   instead of the long 45-degree band comp m_350/hero_08 draws across the whole section, which
   also left ~350px of dead black under the headline column. */
.cluster-wedge {
  position: absolute; inset: -8% 0; z-index: 0;
  background: var(--wedge);
  clip-path: polygon(0 100%, 62% 0, 100% 0, 38% 100%);
  pointer-events: none;
}
.cluster > .ccard { position: relative; z-index: 1; }

.ccard {
  background: var(--panel);
  border: 1px solid var(--hairline-cream);
  border-radius: var(--radius-sm);
  padding: 28px 24px;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  text-align: center;
}
.ccard-toggle {
  /* The whole card head is the control. A button, so Tab reaches it and Enter/Space toggle it. */
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  width: 100%; min-height: 44px;
  background: transparent; border: 0; padding: 0; cursor: pointer;
}
.ccard-title { font-family: var(--font-sans); font-weight: 700; font-size: var(--fs-sub-head);
                line-height: 1.16; letter-spacing: var(--tracking-tight); color: var(--ivory); margin: 0; }
.ccard-body {
  /* max-height, not grid-template-rows: the 1fr row transition is Chrome-117+ and this build
     has to behave identically in WebKit (the audit drives it for the touch profiles). The cap
     is generous enough for the longest card body at 320px. */
  max-height: 0; overflow: hidden; width: 100%;
  transition: max-height var(--dur-slow) var(--ease-out), opacity var(--dur-base) var(--ease-out);
  opacity: 0;
}
/* visibility, not just max-height: `max-height: 0; overflow: hidden` leaves the "Learn more"
   link inside a collapsed card FOCUSABLE, so Tab landed on a 44px rect inside a 0px-tall panel
   with nothing painted (WCAG 2.4.3 / 2.4.7 / 4.1.2). visibility:hidden drops it out of the tab
   order and is still animatable, and every reveal branch below restores it. The toggle button is
   OUTSIDE .ccard-body, so :focus-within still fires and the keyboard path is unchanged. */
.ccard-body { visibility: hidden; }
.ccard-body > * { margin-top: 16px; }
.ccard:hover > .ccard-body,
.ccard:focus-within > .ccard-body,
.ccard.is-open > .ccard-body,
.ccard--open > .ccard-body { max-height: 42em; opacity: 1; visibility: visible; }
/* .ccard--open is the AUTHORED default-expanded card: the middle one on Home, all three on
   System. It expands with no interaction at all, which is what makes the cluster read as a
   composition rather than as three closed drawers. */
.ccard--open { background: var(--panel-hi); }
/* No-JS and reduced motion: every body is open and nothing transitions. Content first. */
html:not(.js) .ccard-body { max-height: none; opacity: 1; visibility: visible; }

/* ============================================================
   CAPABILITY ROWS
   Home §5. Five full-width rows, Lime arrow-up-right right-aligned, one open at a time.
   site.js drives aria-expanded; CSS reacts to it. No <details> element: its open/close is not
   animatable and the summary marker fights the arrow motif.
   ============================================================ */
.caps { display: flex; flex-direction: column; gap: 12px; }
.cap { border: 1px solid var(--hairline-cream); border-radius: var(--radius-sm); background: var(--panel); }
.cap-btn {
  display: flex; align-items: center; gap: 16px; width: 100%;
  min-height: 64px; padding: 16px 20px;
  background: transparent; border: 0; cursor: pointer; text-align: left;
}
.cap-btn .cap-label {
  font-family: var(--font-sans); font-weight: 500; font-size: var(--fs-body);
  line-height: 1.3; color: var(--ivory); margin-right: auto;
}
/* 40px collapsed, 52px and Ivory on the open row — the comps draw ~43px and ~58px. */
.cap-btn [data-icon] { --icon-size: 40px; color: var(--lime-green); flex: none;
  transition: transform var(--dur-base) var(--ease-out); }
.cap-btn:hover [data-icon] { transform: translate(3px, -3px); }
.cap-btn[aria-expanded="true"] { background: var(--panel-hi); }
.cap-btn[aria-expanded="true"] .cap-label { font-weight: 700; }
.cap-btn[aria-expanded="true"] [data-icon] { --icon-size: 52px; color: var(--ivory); }
.cap-panel {
  max-height: 0; overflow: hidden;
  transition: max-height var(--dur-slow) var(--ease-out);
}
/* Same reason as .ccard-body above: a collapsed row held a focusable "Learn more" that could
   never be made visible, so Home's four closed rows gave four invisible tab stops. */
.cap-btn[aria-expanded="false"] + .cap-panel { visibility: hidden; }
.cap-btn[aria-expanded="true"] + .cap-panel { max-height: 44em; visibility: visible; }
.cap-panel-inner { padding: 0 20px 22px; display: flex; flex-direction: column; gap: 18px; align-items: flex-start; }
/* No-JS: every panel open. The copy is the point; the accordion is a convenience. */
html:not(.js) .cap-panel { max-height: none; visibility: visible; }

/* THE CONTENT UNITS' REGIONS. Each marker below is replaced by exactly one unit with its own
   self-contained rules (its own @media and prefers-reduced-motion blocks inside its region).
   Nothing else in this file belongs to them, and none of them writes outside its own marker. */
/* ============================================================
   UNIT B — HOME SECTIONS 1, 2, 3 AND 9
   The hero needs no rules of its own (.band--photo, .hero-photo and .scrim--foot are the
   shell's). What follows is the fragmented stack (§2), the audiences trio (§3), and the
   contact email link (§9).
   ============================================================ */

/* THE PROBLEM — the fragmented stack (Home §2).
   R5/A: PORTED from the old production site rather than reinterpreted. See the long note above
   this section's markup in index.html for where each half came from and for the full list of
   deviations. This block is a transcription of
     ../../Superseded/website-prod/ui_kits/marketing_site/assets/site.css
   with --cream mapped to --ivory, the font-size literals mapped to the kit's tokens, and the
   motion gating re-expressed in site_r3's idiom.
   Written for --ink only; there is deliberately no .band--cream branch. */

.why { position: relative; }
.why .wrap { position: relative; z-index: 1; }

.why-head { max-width: 68ch; margin-bottom: clamp(48px, 7vw, 96px); }
/* .hl-accent is display:block, so "Slower growth." takes its own line without a <br>. The Lime
   itself comes from the .band--ink branch at the top of this file — never hardcoded here, or the
   class stops being colourless on light grounds and the audit's Lime probe stops being a proof. */
.why-head .lede { margin-top: 24px; max-width: 58ch; }
.lede-emph { color: var(--ivory); font-weight: 500; }

/* The three-column composition: stack, hinge, cost. 104px is the source's hinge column. */
.why-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(36px, 5vw, 64px);
  align-items: center;
}

/* --- LEFT: the twelve-cell grid ---------------------------------------------------------- */
/* 2px rules, not hairlines. The source draws this heavier than anything else on the page on
   purpose: it is a picture of a wall of boxes, and a hairline version reads as a data table. */
.dept-panel { min-width: 0; }
.dept-panel .eyebrow { margin-bottom: 22px; }
.dept-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 2px solid rgba(254, 255, 234, 0.42);
  border-left: 2px solid rgba(254, 255, 234, 0.42);
}
.dept-cell {
  border-right: 2px solid rgba(254, 255, 234, 0.42);
  border-bottom: 2px solid rgba(254, 255, 234, 0.42);
  padding: 15px 16px;
  min-height: 56px;
  display: flex;
  align-items: center;
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: var(--fs-body);
  line-height: 1.3;
  color: rgba(254, 255, 234, 0.74);
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base);
}
.dept-cell:hover { background: rgba(254, 255, 234, 0.04); color: var(--ivory); }
.dept-cap {
  margin: 24px 0 0;
  font-family: var(--font-sans); font-weight: 300;
  font-size: var(--fs-body); line-height: 1.6;
  color: var(--light-grey); max-width: 48ch;
}
.dept-cap b { color: rgba(254, 255, 234, 0.92); font-weight: 500; }

/* --- MIDDLE: the hinge ------------------------------------------------------------------- */
.concentrate {
  position: relative;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 14px; padding: 0 6px;
}
.concentrate svg { width: 100%; max-width: 120px; height: auto; }
.concentrate .clabel {
  position: absolute; left: 50%; top: 89%; transform: translateX(-50%);
  white-space: nowrap;
  /* The source sets this Lime. It is scoped back to grey inside .why there, and grey is also what
     the reference screenshot shows — so grey is the ported value, not a concession to the probe. */
  color: var(--light-grey);
}

/* --- RIGHT: the cost panel --------------------------------------------------------------- */
/* The source frames this in Lime; its own .why scope then re-tones the frame to cream hairlines,
   "because the glyphs are the calculation grammar, not the sentiment". That scoped form is what
   shipped and what the reference shows, so it is what is transcribed. */
.model-panel {
  border: 1px solid var(--hairline-cream-strong);
  background: rgba(254, 255, 234, 0.03);
  padding: clamp(24px, 2.6vw, 38px);
  min-width: 0;
}
.model-panel .eyebrow { margin-bottom: 22px; }
.model-row {
  display: grid;
  grid-template-columns: 38px 1fr;
  gap: 18px;
  padding: 20px 0;
  align-items: start;
}
.model-row + .model-row { border-top: 1px solid var(--hairline-cream); }
/* The glyph. An ICON, not text — see the deviation note in index.html. --icon-size replaces the
   source's 30px font-size, so it is sized without a type literal. */
.mop { --icon-size: 30px; margin-top: 2px; }
.model-row h3 { margin: 0 0 8px; font-family: var(--font-sans); font-size: var(--fs-body); font-weight: 500; line-height: 1.3; letter-spacing: -0.01em; color: var(--ivory); }
.model-row p { margin: 0; }
.model-row.outcome {
  border: 1px solid var(--ivory);
  background: rgba(254, 255, 234, 0.06);
  padding: 22px;
  margin-top: 14px;
}

/* --- MOTION ------------------------------------------------------------------------------ */
/* All three of the source's animations, gated on html.motion exactly as the source gated them.
   .in is added by the reveal observer in site.js (line 696/702) to the OBSERVED element — which
   here is .why-layout, the one carrying .beat. The source keyed these off .dept-panel.in and
   .concentrate.in because in the old markup those were the observed nodes; re-pointing them at
   .why-layout.in is the adaptation. Get this wrong and the twelve cells and the whole hinge stay
   at opacity 0 / dashoffset 240 forever — which is exactly what the first pass shipped. */
html.motion .concentrate svg path { stroke-dasharray: 240; stroke-dashoffset: 240; }
.why-layout.in .concentrate svg path { animation: drawLine 1.6s var(--ease-out) both; }
.why-layout.in .concentrate svg path:nth-child(1) { animation-delay: 0.08s; }
.why-layout.in .concentrate svg path:nth-child(2) { animation-delay: 0.20s; }
.why-layout.in .concentrate svg path:nth-child(3) { animation-delay: 0.32s; }
.why-layout.in .concentrate svg path:nth-child(4) { animation-delay: 0.44s; }
.why-layout.in .concentrate svg path:nth-child(5) { animation-delay: 0.56s; }
.why-layout.in .concentrate svg path:nth-child(6) { animation: none; opacity: 0; animation: fadeArrow 0.5s var(--ease-out) 0.68s both; }
.concentrate svg .pl-pulse { stroke: var(--lime-green); stroke-width: 1.6; stroke-linecap: round; opacity: 0; }
.why-layout.in .concentrate svg .pl-pulse {
  stroke-dasharray: 14 600;
  animation: clPulse 3.2s linear 1.5s infinite;   /* starts after the lines draw in. 3.2s, not the
     source's 9s: the dash is the only thing on this page that says "continuously", and at 9s a
     reader met the hinge, watched it draw, and left before the second pulse ever arrived. One
     keyframe drives both rate and travel speed, so a shorter cycle is also a faster dash — which
     is the intended reading, a current rather than a drip. */
}
html.motion .dept-panel .dept-cell { opacity: 0; }
.why-layout.in .dept-cell { animation: cellIn 0.8s var(--ease-out) both; }
.why-layout.in .dept-cell:nth-child(1)  { animation-delay: 0.03s; }
.why-layout.in .dept-cell:nth-child(2)  { animation-delay: 0.06s; }
.why-layout.in .dept-cell:nth-child(3)  { animation-delay: 0.09s; }
.why-layout.in .dept-cell:nth-child(4)  { animation-delay: 0.12s; }
.why-layout.in .dept-cell:nth-child(5)  { animation-delay: 0.15s; }
.why-layout.in .dept-cell:nth-child(6)  { animation-delay: 0.18s; }
.why-layout.in .dept-cell:nth-child(7)  { animation-delay: 0.21s; }
.why-layout.in .dept-cell:nth-child(8)  { animation-delay: 0.24s; }
.why-layout.in .dept-cell:nth-child(9)  { animation-delay: 0.27s; }
.why-layout.in .dept-cell:nth-child(10) { animation-delay: 0.30s; }
.why-layout.in .dept-cell:nth-child(11) { animation-delay: 0.33s; }
.why-layout.in .dept-cell:nth-child(12) { animation-delay: 0.36s; }

@keyframes drawLine  { from { stroke-dashoffset: 240; } to { stroke-dashoffset: 0; } }
@keyframes fadeArrow { from { opacity: 0; } to { opacity: 1; } }
@keyframes cellIn    { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes clPulse   { from { stroke-dashoffset: 614; opacity: 0.9; } to { stroke-dashoffset: 0; opacity: 0.9; } }

/* PLAIN MODE / NO JS. The .in class never arrives, so every animated part must be visible at
   rest — otherwise the cells and the hinge are simply missing. */
html:not(.motion) .dept-panel .dept-cell { opacity: 1; }
html:not(.motion) .concentrate svg path { stroke-dasharray: none; stroke-dashoffset: 0; }
html:not(.motion) .concentrate svg .pl-pulse { display: none; }

@media (prefers-reduced-motion: reduce) {
  .dept-panel .dept-cell { opacity: 1 !important; animation: none !important; }
  .concentrate svg path { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; animation: none !important; opacity: 1 !important; }
  .concentrate svg .pl-pulse { display: none !important; }
}

/* --- >=1024px: the three-column composition ---------------------------------------------- */
/* 1024, NOT 901: at 901 the rail steals the right gutter and the twelve-cell grid loses its
   longest items to mid-word breaks. Measured in R4b, and the grid here is no narrower. */
@media (min-width: 1024px) {
  .why-layout { grid-template-columns: 1fr 104px 1fr; gap: clamp(24px, 3vw, 48px); }
}

/* --- <=1023px: the hinge lies down ------------------------------------------------------- */
/* The source rotates it into a horizontal row rather than deleting it, and wraps the svg in
   .hinge so the ROTATED footprint gets a layout box that matches what is painted — rotate()
   repaints without resizing the box flex lays out around, which used to collide the drawing with
   its label. Transcribed as-is. */
@media (max-width: 1023px) {
  .concentrate { flex-direction: row-reverse; justify-content: center; align-items: center; gap: 18px; padding: 4px 0; }
  .concentrate .hinge { position: relative; width: 240px; height: 90px; flex: none; }
  .concentrate .hinge svg {
    position: absolute; left: 50%; top: 50%;
    width: 90px; max-width: none;
    transform: translate(-50%, -50%) rotate(-90deg);
  }
  .concentrate .clabel { position: static; transform: none; }
}

@media (max-width: 639px) {
  .dept-cell { padding: 12px 12px; min-height: 52px; }
  .concentrate .hinge { width: 160px; height: 60px; }
  .concentrate .hinge svg { width: 58px; }
}

/* --- SINGLE PANE: the whole section fits one viewport ------------------------------------ */
/* "This section all needs to fit on a single pane, resize as required (dynamic)."
   CLAUDE.md:168 records exactly why v3's every-section-is-a-100vh-stage was torn out: ~550 lines
   of stacked breakpoint blocks that made content fit by SHAVING TYPE, and a site that read as a
   slide deck. Both failure modes are avoided deliberately here:
     1. It is ONE section, not a layout mode. Nothing outside .why is touched.
     2. It compresses SPACING ONLY. No font-size appears below, so C1's flat 18px body/lede
        standard survives whole — every value here is a margin, a padding or a min-height.
   Where spacing alone cannot do it the rule simply does not apply and the section flows at its
   natural height, which is the honest outcome rather than a shaved one. Hence the double gate:
     - min-width 1024px, because below it the three columns become one and the hinge lies down;
       a stacked composition is ~1340px tall and no spacing budget reaches a pane.
     - min-height 800px, because that is where the measured fit runs out (a 1280x800 window lands
       at ~765px of content; a 680px-tall window would still need ~75px more than spacing has to
       give). Short windows scroll the section, as they did before.
   The pane is min-height, not height: content is centred in it and never clipped, so a viewport
   that grows only opens up more air. Knock-on for C3: naturalHeightOf() now always reads >= minH
   for this band, so holdBands() takes its `tall enough on its own` branch and never appends a
   .hold-fill here — the hold still forms, it just no longer needs padding out. */
@media (min-width: 1024px) and (min-height: 800px) {
  .why {
    /* THE PANE IS NOT THE VIEWPORT. C3's hold pins this band with its top at CHROME_CLEAR, not
       at 0 (site.js:1005, `start: "top " + CHROME_CLEAR + "px"`), so the strip a reader actually
       sees while the band is held is `viewport - CHROME_CLEAR` tall. Measuring against 100svh
       instead left the last ~90px of the band below the fold on the very windows this rule
       exists to serve. --pane-clear mirrors CHROME_CLEAR's own arithmetic exactly — site.js:46-49
       computes max(72, barHeight + 16) and publishes barHeight as --chrome-h precisely so this
       file never has to duplicate the number. --pane-clear is declared once at :root. */
    min-height: calc(100svh - var(--pane-clear));
    display: flex; flex-direction: column; justify-content: center;
    /* No chrome clearance in this pad: while the band is held it already sits below the bar, and
       adding the clearance a second time is what ate the budget. In transit (and in plain mode,
       where no hold exists) the heading passes under an opaque header for a moment, which is what
       every other band on the site already does. */
    padding-top: clamp(20px, 3vh, 96px);
    padding-bottom: clamp(20px, 3vh, 96px);
  }
  .why-head { margin-bottom: clamp(20px, 2.8vh, 96px); }
  .why-head .lede { margin-top: clamp(12px, 1.8vh, 24px); }
  .dept-panel .eyebrow,
  .model-panel .eyebrow { margin-bottom: clamp(10px, 1.6vh, 22px); }
  .dept-cell { padding-block: clamp(8px, 1.3vh, 15px); min-height: clamp(44px, 6vh, 56px); }
  .dept-cap { margin-top: clamp(12px, 2vh, 24px); }
  .model-panel { padding-block: clamp(18px, 2.6vh, 38px); }
  /* Horizontal, not vertical, but it buys height: at 1024-1120px the cost rows were wrapping to
     three lines. Narrowing the glyph column and its gutter hands ~12px back to the text measure,
     which is the difference between a wrap and no wrap at the tight end of this range. */
  .model-row { padding-block: clamp(10px, 1.5vh, 20px); grid-template-columns: 30px 1fr; gap: 14px; }
  .model-panel { padding-inline: clamp(20px, 2.2vw, 38px); }
  .model-row h3 { margin-bottom: clamp(4px, 0.7vh, 8px); }
  .model-row.outcome { padding: clamp(14px, 2vh, 22px); margin-top: clamp(8px, 1.2vh, 14px); }
}


/* AUDIENCES (Home §3). R4b/C5: the reference's editorial grid — statement left, lede right, a
   full-width rule across both, then three equal columns divided by VERTICAL hairlines. The band
   stays dark, so the accent is Lime; on any other ground .hl-accent/.glyph-accent are colourless
   and the composition falls back to Ivory-on-dark with no Lime at all. The client montage that
   used to sit below this was removed in R4b/C2 — the canvas asked for URBNSURF / RSYS / Nanuk
   footage, none arrived, and a strip of two labelled holds beside one still was not carrying its
   space. */
.aud-head { display: grid; grid-template-columns: 1fr; gap: 24px; }
.aud-rule { grid-column: 1 / -1; margin-top: clamp(20px, 2.6vw, 32px); }

.audiences {
  list-style: none; margin: clamp(32px, 4vw, 56px) 0 0; padding: 0;
  display: grid; grid-template-columns: 1fr;
  gap: 0;                                   /* the hairlines, not a gap, do the dividing */
}
.audience {
  display: flex; flex-direction: column; gap: 16px;
  padding-block: clamp(24px, 3vw, 36px);
  border-top: 1px solid var(--hairline-cream);
  min-width: 0;
}
.audience:first-child { border-top: 0; padding-top: 0; }
.aud-glyph { --icon-size: 28px; }
.audience .body-copy { max-width: 40ch; }

/* --- >=901px: three equal columns, divided vertically ------------------------------------ */
/* The divider is a border-left on a STRETCHED grid item, so it runs the full height of the
   tallest column automatically and needs no pseudo-element and no fixed height. The gap is 0 and
   the breathing room is symmetric padding, so the hairline sits in the optical middle between
   two columns rather than hard against one of them. */
@media (min-width: 901px) {
  .aud-head {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    column-gap: clamp(32px, 4vw, 72px);
    align-items: end;
  }
  .aud-head .lede { justify-self: end; max-width: 40ch; }
  .audiences { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .audience {
    border-top: 0;
    border-left: 1px solid var(--hairline-cream);
    padding-block: 0;
    padding-inline: clamp(20px, 2.6vw, 44px);
  }
  .audience:first-child { border-left: 0; padding-left: 0; }
  .audience:last-child  { padding-right: 0; }
}

/* CONTACT (Home §9). The email link is the CTA; nothing competes with it. */
.contact-email { font-size: var(--fs-sub-head); font-weight: 500; letter-spacing: var(--tracking-tight); overflow-wrap: anywhere; }
/* THE BAND'S GROUND — deck .grid-bg and .glow. Both are pure gradients on absolutely-positioned
   spans: no image, no request, nothing to 404. The grid is masked to a radial fade so it never
   reaches the band's edges as a hard seam, and the mask is centred on the diagram's own side of
   the layout. pointer-events:none on both; .wrap is already z-index 1 above them. */
.aiq { overflow: hidden; }
.aiq-bg-grid, .aiq-bg-glow { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.aiq-bg-grid {
  background:
    linear-gradient(rgba(254, 255, 234, 0.06) 1px, transparent 1px) 0 0 / 72px 72px,
    linear-gradient(90deg, rgba(254, 255, 234, 0.06) 1px, transparent 1px) 0 0 / 72px 72px;
  -webkit-mask-image: radial-gradient(ellipse at 72% 55%, #000 26%, transparent 76%);
          mask-image: radial-gradient(ellipse at 72% 55%, #000 26%, transparent 76%);
}
.aiq-bg-glow { background: radial-gradient(circle at 76% 55%, rgba(52, 197, 42, 0.16), transparent 55%); }
/* Both exist to sit BEHIND the diagram. Under 640px the compass is swapped for the list form and
   there is no diagram to sit behind, so a green wash across a stack of text rows is just noise. */
@media (max-width: 639px) { .aiq-bg-grid, .aiq-bg-glow { display: none; } }
.aiq > .wrap { position: relative; z-index: 1; }

/* ============================================================
   AIQ DIAGRAM (Home §6). AiQ at the centre, four arms, the 4Ps hanging off Execution.
   Five tabs, one selected, the centre selected at rest. ONE layer selector per breakpoint:
   .aiq-compass at >=640px, .aiq-list at <=639px, swapped with display:none and nothing else.

   R4b: restyled from the Sonder deck slide 06 ("The growth operating model"). The deck is a
   1920x1080 slide of hard px and nine infinite loops; what is carried over is its TREATMENT —
   dashed orbits, a dark radial core wearing a Lime edge + halo + glow, the inline AiQ lockup,
   a letterspaced caption, direction letters on the satellites, dashed segment-work on the 4Ps
   rail, and the pulse bead. What R4b did NOT carry over: every animation, every transform, the
   slide's grid-bg/glow furniture, and the satellites' permanent Ivory fill. R5/R6 then brought
   back the Ivory discs and the slide's ground, and R7 (2026-09-14) brought back the four loops
   — see THE LOOPS at the foot of this component. In every plain path the R6 picture is still
   exactly what is painted.

   GEOMETRY IS UNCHANGED and stays the mechanism it was: .aiq-compass is the WHOLE footprint
   (ring + node diameter + the 4Ps tier), placed with left/top calc() from --ring / --pt /
   --core — NO transform anywhere in this component. Every deck px is re-expressed as a ratio
   of a property that already existed: halo = 0.03 x --ring (deck: inset -24 on a 300 core),
   glow blur = 0.40 x --core (deck: 80px), lockup = 0.36 x --core (deck: 118px on 300).
   --pt is the node diameter, sized so the longest label ("Effectiveness", ~112px at --fs-meta
   400 in Lexend) sits whole inside the node with 16px of side clearance — no soft hyphen.

   THE RING FLOOR IS 340px, NOT 300 (spec §4.6 wrote 300; measured defect, reported to
   integration). A spoke runs from the core's edge to a node's edge, so its length is
   (--ring - --core - --pt) / 2 = (--ring - 304px) / 2. At a 300px floor that is -2px, which CSS
   clamps to 0: the four spokes vanish and the core overlaps the nodes by 2px — measured at
   640-700px and again at 901-1153px (1024 among them, a §12 #45 hand-check width), taking the
   Lime active-arm spoke of §12 #46 with them. 340px is the smallest floor that still reads:
   an 18px spoke at the floor, a 484px footprint inside the 576px content box at 640px, and a
   238.9px panel at 901px against a 213.5px "04 Effectiveness" head. --pt (144px, fixed by §4.6
   and CONTRACT.md) and --core (160px, so the hub is never smaller than an arm) are untouched.
   R4b raises only the >=901px clamp MAX, 400px -> 480px, so the connector reaches 88px on a
   real ultrawide monitor; 26vw governs below a ~1847px viewport, so no audit profile is
   affected by that line.
   ============================================================ */
.aiq-layout { display: grid; grid-template-columns: 1fr; gap: clamp(32px, 5vw, 48px); margin-top: clamp(40px, 6vw, 72px); align-items: center; }
.aiq-compass {
  /* R6: RE-PROPORTIONED TO THE DECK, and this is the whole of why the R4b/R5 version read as
     "nothing like" slide 3. The old geometry grew from --ring (the orbit) and hung a fixed 144px
     node off it, which inverted the source's hierarchy: 144px satellites around a 160px core,
     the two nearly equal, with a 2px gap of dead air between them. The deck's engine is a BIG
     hub with SMALL satellites held well off it — 300px core and 108px satellites in a 680px box.
     Everything below is now that box's own ratio, measured off the slide, so the whole diagram
     scales as one object instead of four fixed numbers drifting apart at each breakpoint:
        core        300/680 = 0.441      inner orbit  396/680 = 0.582
        satellite   108/680 = 0.159      4P radius    198/680 = 0.291
     --w IS the deck's engine box (680px at its ceiling, the slide's exact size).
     THE ONE DEPARTURE, and it is not cosmetic: --pt carries a 112px FLOOR. "Effectiveness" is
     103.1px at --fs-meta, so below ~112px the label stops fitting inside its disc. The slide
     lets it spill onto the dark ground, where black-on-near-black is unreadable; a floor keeps
     the label inside the Ivory it was contrasted against. 124px, not 112: measured, 112 left the
     word touching the disc edge on both sides with no optical margin at all.
     --w's own floor follows FROM that: the connector measures 0.2795w - pt, so with pt held at
     124 the arms are 32px at 560 and gone entirely by 444 — the same class of defect as the old
     340px ring floor recorded above, found the same way (measured at 1024, where a 500px floor
     left 16px arms and satellites sitting on the core's halo). 560 is the floor, and the
     two-column breakpoint moved from 901px to 1200px to pay for it: under 1200 the diagram gets
     the whole content width to itself rather than splitting it with the pane. */
  --w: clamp(560px, 46vw, 680px);
  --pt: max(124px, calc(var(--w) * 0.159));   /* satellite diameter */
  --core: calc(var(--w) * 0.441);
  --orbit-in: calc(var(--w) * 0.582);
  --pmix-r: calc(var(--w) * 0.291);           /* the 4P labels sit ON the inner orbit */
  --sat-r: calc((var(--w) - var(--pt)) / 2);  /* satellite CENTRE radius; discs sit inside the box */
  --halo: clamp(8px, calc(var(--w) * 0.03), 20px);      /* deck: .core::after inset -24 */
  --dot: 8px;                                           /* deck: .pulse-dot 8px */
  --sp: 2px;                                            /* deck .conn is 2px on EVERY arm, at rest */
  /* The deck's .pmix .spoke is rgba(52,197,42,.55) at 2px AT REST — it is a surface, not text,
     so Lime is legal here. Cream hairlines at 1px were invisible against the band and the glow,
     which is why the 4Ps read as missing rather than as quiet. */
  --sat-line: rgba(52, 197, 42, 0.55);
  --sat-w: 2px;                                         /* 4P ray thickness; brightens on Execution */
  position: relative; width: var(--w); height: var(--w); margin-inline: auto;
}

/* ORBITS — deck .orbit / .orbit.o3, dashed. The span is the GEOMETRY (the 4P labels are placed
   from the inner one) and it never transforms; the dashed stroke is its ::before, filling the
   span exactly, and R7 rotates THAT — see THE LOOPS. A rotated square's bounding box grows by
   root 2, so spinning the span itself would have moved every measurement in this component. */
.aiq-ring { position: absolute; border-radius: 50%; }
.aiq-ring::before { content: ""; position: absolute; inset: 0; border-radius: 50%; border: 1px dashed rgba(254, 255, 234, 0.2); }
/* the outer orbit is the box: tangent to the satellites' outer edges, as the slide draws it */
.aiq-ring--outer { inset: 0; }
/* the inner orbit is the 4P ring — the labels are placed at exactly half its diameter */
.aiq-ring--inner {
  left: calc(50% - var(--orbit-in) / 2); top: calc(50% - var(--orbit-in) / 2);
  width: var(--orbit-in); height: var(--orbit-in);
}
.aiq-ring--inner::before { border-color: rgba(52, 197, 42, 0.25); }

/* CONNECTORS — deck .conn. Hairline at rest; the SELECTED arm doubles to 2px, goes Lime, and
   shows the bead. Thickness lives in --sp so the centring calc() follows it with no transform.
   The bead (::after) rests at the arm's midpoint; in motion mode R7 sends it travelling the arm
   — THE LOOPS at the foot of this component. */
.aiq-spoke { position: absolute; background: rgba(52, 197, 42, 0.55); transition: background var(--dur-base); }
.aiq-spoke::after {
  content: ""; position: absolute; width: var(--dot); height: var(--dot); border-radius: 50%;
  background: var(--lime-green); box-shadow: 0 0 calc(var(--dot) * 1.5) rgba(52, 197, 42, 0.55);
  opacity: 1; transition: opacity var(--dur-base);
}
/* Length is the gap the arm has to cross: from the core's edge out to a satellite's inner edge.
   --sat-r is the satellite CENTRE, so its inner edge is --sat-r - --pt/2, and the run is
   (--sat-r - --pt/2) - --core/2. Authored as one --arm so the four rules cannot drift apart. */
.aiq-compass { --arm: calc(var(--sat-r) - var(--pt) / 2 - var(--core) / 2); }
.aiq-spoke--n, .aiq-spoke--s { width: var(--sp); left: calc(var(--w) / 2 - var(--sp) / 2); height: var(--arm); }
.aiq-spoke--e, .aiq-spoke--w { height: var(--sp); top: calc(var(--w) / 2 - var(--sp) / 2); width: var(--arm); }
.aiq-spoke--n::after, .aiq-spoke--s::after { left: calc(var(--sp) / 2 - var(--dot) / 2); top: calc(50% - var(--dot) / 2); }
.aiq-spoke--e::after, .aiq-spoke--w::after { top: calc(var(--sp) / 2 - var(--dot) / 2); left: calc(50% - var(--dot) / 2); }
.aiq-spoke--n { top: calc(var(--w) / 2 - var(--core) / 2 - var(--arm)); }
.aiq-spoke--s { top: calc(var(--w) / 2 + var(--core) / 2); }
.aiq-spoke--e { left: calc(var(--w) / 2 + var(--core) / 2); }
.aiq-spoke--w { left: calc(var(--w) / 2 - var(--core) / 2 - var(--arm)); }
.aiq-layout[data-active="strategy"]      .aiq-spoke--n,
.aiq-layout[data-active="brand"]         .aiq-spoke--e,
.aiq-layout[data-active="execution"]     .aiq-spoke--s,
.aiq-layout[data-active="effectiveness"] .aiq-spoke--w { --sp: 2px; background: var(--lime-green); }
.aiq-layout[data-active="strategy"]      .aiq-spoke--n::after,
.aiq-layout[data-active="brand"]         .aiq-spoke--e::after,
.aiq-layout[data-active="execution"]     .aiq-spoke--s::after,
.aiq-layout[data-active="effectiveness"] .aiq-spoke--w::after { opacity: 1; }

.aiq-tablist { position: absolute; inset: 0; }
.aiq-tab { font-family: var(--font-sans); cursor: pointer; }

/* THE FIVE CONTROLS. Shared shell; the core and the nodes diverge on fill, size and content. */
.aiq-node, .aiq-core {
  position: absolute; z-index: 2; display: flex; flex-direction: column; align-items: center; justify-content: center;
  border-radius: 50%; color: var(--ivory); text-align: center;
  transition: border-color var(--dur-base), background var(--dur-base), color var(--dur-base), filter var(--dur-fast);
}
/* R5/C: the deck's satellites are IVORY DISCS, not dark chips. This one line is most of why the
   R4b version read as "nothing like" the source: dark nodes on a dark band behind hairline
   connectors gave a diagram with no figure-ground at all, where the deck has four bright discs
   held on Lime arms. */
.aiq-node { width: var(--pt); height: var(--pt); gap: 4px; padding: 0; background: var(--ivory); border: 1.5px solid var(--ivory); color: var(--eerie-black); }
/* Placed from --sat-r, the centre radius, so the four read from one number. With --sat-r at
   (--w - --pt)/2 each disc's outer edge lands exactly on the box, i.e. on the outer orbit. */
.aiq-node--n { left: calc(var(--w) / 2 - var(--pt) / 2); top: calc(var(--w) / 2 - var(--sat-r) - var(--pt) / 2); }
.aiq-node--s { left: calc(var(--w) / 2 - var(--pt) / 2); top: calc(var(--w) / 2 + var(--sat-r) - var(--pt) / 2); }
.aiq-node--e { top: calc(var(--w) / 2 - var(--pt) / 2); left: calc(var(--w) / 2 + var(--sat-r) - var(--pt) / 2); }
.aiq-node--w { top: calc(var(--w) / 2 - var(--pt) / 2); left: calc(var(--w) / 2 - var(--sat-r) - var(--pt) / 2); }

/* THE CORE — deck .core: a dark radial disc wearing a Lime edge, a halo ring and a soft glow. */
.aiq-core {
  width: var(--core); height: var(--core);
  left: calc(var(--w) / 2 - var(--core) / 2); top: calc(var(--w) / 2 - var(--core) / 2);
  gap: calc(var(--core) * 0.055); padding: 0 calc(var(--core) * 0.10);
  background: radial-gradient(circle, #242424 0%, #141414 100%);
  border: 2px solid var(--lime-green);
  /* deck: 0 0 80px on a 300px core = 0.27; at 0.50 this bloomed into a blob that swallowed the
     4P labels and the inner orbit behind it. */
  box-shadow: 0 0 calc(var(--core) * 0.27) rgba(52, 197, 42, 0.40),
              inset 0 0 calc(var(--core) * 0.13) rgba(52, 197, 42, 0.12);
}
/* the halo — deck .core::after at its RESTING geometry, and static on purpose: its border
   doubles on .is-active, so it is a selection channel and cannot be allowed to fade out. The
   breath itself is .aiq-pulse (R7, THE LOOPS below): two separate rings growing from this edge. */
.aiq-core::after {
  content: ""; position: absolute; inset: calc(-1 * var(--halo));
  border-radius: 50%; border: 1px solid rgba(52, 197, 42, 0.5);
  transition: border-color var(--dur-base), border-width var(--dur-base);
}
/* the lockup — a LOGO authored in the page, not an icons.js glyph: filled, not 24x24, not g().
   currentColor is what lets it flip to Eerie Black on the selected Lime core. */
.aiq-core-mark { width: calc(var(--core) * 0.36); height: auto; display: block; }
/* the Upscale wordmark above the lockup — deck .core > img, 20px on a 300px core. Ratio, not a
   literal, so it tracks --core like everything else in the hub. */
.aiq-core-brand { width: calc(var(--core) * 0.30); height: auto; display: block; }
.aiq-core-mark-i { fill: var(--lime-green); transition: fill var(--dur-base); }
/* the Lime dot-and-bar of the AiQ mark stays Lime: the core never turns Lime under it now. */
/* the caption — deck's CENTRAL INTELLIGENCE. 14px is the ALL-CAPS ceiling; --tracking-caps not
   the deck's 0.26em, which measures 144px against 131.2px of content inside a 160px core.
   Ivory-at-opacity, NEVER Lime. */
.aiq-core-cap {
  font-size: var(--text-sm); font-weight: 500; line-height: 1.35;
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
  color: var(--light-grey); transition: color var(--dur-base);
}
.aiq-core.is-active .aiq-core-cap { color: var(--ivory); }

/* LABELS. --tracking-caps is scoped to the compass nodes so .aiq-row and .aiq-pane-head keep
   the tracking they already had. The direction letter is GENERATED CONTENT inside an
   aria-hidden span: it cannot reach the accessible name and it costs the nodes no markup.
   "N 01", not the deck's "N · 01" — U+00B7 is not one of the two blessed characters. */
.aiq-n { font-size: var(--text-sm); font-weight: 500; letter-spacing: var(--tracking-wide); color: var(--light-grey); transition: color var(--dur-base); }
/* On the Ivory disc the label is Eerie Black, NOT the deck's #1FA619. Measured: that green on
   Ivory is 3.16:1, under the 4.5:1 small-text floor — and it is off-palette besides. The label
   stays distinguishable from the name by size, weight and tracking rather than by hue. */
.aiq-node .aiq-n { letter-spacing: var(--tracking-caps); color: var(--eerie-black); }
.aiq-node--n .aiq-n::before { content: "N "; }
.aiq-node--e .aiq-n::before { content: "E "; }
.aiq-node--s .aiq-n::before { content: "S "; }
.aiq-node--w .aiq-n::before { content: "W "; }
.aiq-node .aiq-l { font-size: var(--fs-meta); font-weight: 400; line-height: 1.2; max-width: calc(100% - 16px); white-space: nowrap; }
/* .aiq-core-name is the button's accessible name and carries .vh in the markup; the lockup is
   the visual. No rule here — .vh (site.css:175) does the work. */

.aiq-node:hover, .aiq-core:hover { border-color: var(--ivory); }
.aiq-node:active, .aiq-core:active, .aiq-row:active { filter: brightness(0.88); }
/* SELECTED: Lime fill, Eerie Black label — the .btn--lime palette. Exactly one of the five is
   ever filled. R4b adds the second, colour-independent channel: the border DOUBLES to 2px, on
   the control, on its connector, on the halo and on the 4Ps rule (WCAG 1.4.1). */
/* R5/C: the SATELLITE fills Lime when selected — it is Ivory at rest, so the flip is unmistakable.
   The CORE deliberately does NOT: in the deck it is always a dark disc wearing a Lime edge and a
   glow, and AiQ is the arm selected at rest, so filling it Lime meant the diagram was a solid Lime
   blob on arrival — the single loudest reason the last pass read as "nothing like" the source.
   The core's selected state is a brighter, thicker edge and a deeper glow instead. */
.aiq-node.is-active { background: var(--lime-green); border-width: 2px; border-color: var(--lime-green); color: var(--eerie-black); }
.aiq-core.is-active { border-width: 3px; border-color: var(--lime-green); }
/* NOT COLOUR ALONE (WCAG 1.4.1): the selected disc also gains a detached Ivory ring, and its name
   goes to weight 500 below. Either cue identifies it with the hue removed. */
.aiq-node.is-active { box-shadow: 0 0 0 3px var(--eerie-black), 0 0 0 4.5px var(--ivory); }
.aiq-core.is-active { box-shadow: 0 0 calc(var(--core) * 0.40) rgba(52, 197, 42, 0.55),
                                  inset 0 0 calc(var(--core) * 0.25) rgba(52, 197, 42, 0.16); }
.aiq-core.is-active::after { border-width: 2px; border-color: var(--lime-green); }
.aiq-node.is-active .aiq-l { font-weight: 500; }
.aiq-node.is-active .aiq-n { color: var(--eerie-black); }

/* THE 4Ps — deck .pmix, now DIAGONAL as the slide draws it, not a rail under the diagram.
   R4b hung them off Execution on a horizontal dashed rule below the footprint, on the reasoning
   that ours belong to Execution rather than radiating from the hub. That reasoning was sound and
   the picture it produced was wrong: it reads as a footnote, and the slide's four labels sitting
   out in the quadrants ON the inner orbit are one of the two things that make the diagram
   recognisable at a glance (the other being the big-hub/small-satellite hierarchy above).
   They are placed from --pmix-r, the same radius that sizes .aiq-ring--inner, so a label can
   never drift off the ring it is meant to sit on. 0.7071 is cos 45deg: the leg of the right
   triangle whose hypotenuse is --pmix-r.
   THE TRANSFORM EXCEPTION. The header of this component says no transform anywhere, and that
   still holds for everything that carries geometry — every ring, node, core and arm is placed by
   left/top calc() and stays measurable. The four rays are the exception for a PLACED element:
   a 45deg line cannot be expressed as a box offset at all, and rotate() is what the slide itself
   uses. translate(-50%,-50%) on the labels is centring, not geometry. R7 adds transforms on two
   pseudo-elements that nothing is placed from — the ring strokes (::before) rotate and the arm
   beads (::after) travel — and on .aiq-pulse, decoration with no geometric role. THE LOOPS says
   why those three are safe and what they may never touch.
   The Execution highlight survives the move: the arithmetic is still "the 4Ps hang off
   Execution", it is just drawn in the round. */
.aiq-sats {
  position: absolute; inset: 0; margin: 0; padding: 0; list-style: none;
}
.aiq-sat {
  position: absolute; transform: translate(-50%, -50%); white-space: nowrap;
  font-size: var(--text-sm); font-weight: 500;
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--light-grey); transition: color var(--dur-base);
}
.aiq-sat--ne { left: calc(50% + var(--pmix-r) * 0.7071); top: calc(50% - var(--pmix-r) * 0.7071); }
.aiq-sat--se { left: calc(50% + var(--pmix-r) * 0.7071); top: calc(50% + var(--pmix-r) * 0.7071); }
.aiq-sat--sw { left: calc(50% - var(--pmix-r) * 0.7071); top: calc(50% + var(--pmix-r) * 0.7071); }
.aiq-sat--nw { left: calc(50% - var(--pmix-r) * 0.7071); top: calc(50% - var(--pmix-r) * 0.7071); }

/* The dashed diagonals. Drawn from the centre exactly as the deck draws them; the core sits at
   z-index 2 and covers the inner run, so what is seen is the stretch between hub and label. */
.aiq-ray {
  position: absolute; left: 50%; top: 50%;
  width: var(--pmix-r); height: var(--sat-w);
  transform-origin: 0 50%;
  background: repeating-linear-gradient(90deg, var(--sat-line) 0 7px, transparent 7px 15px);
  transition: background var(--dur-base);
}
.aiq-ray--ne { transform: rotate(-45deg); }
.aiq-ray--se { transform: rotate(45deg); }
.aiq-ray--sw { transform: rotate(135deg); }
.aiq-ray--nw { transform: rotate(-135deg); }
.aiq-layout[data-active="execution"] .aiq-compass { --sat-line: var(--lime-green); --sat-w: 3px; }
.aiq-layout[data-active="execution"] .aiq-sat { color: var(--ivory); }
/* the list (phone selector) — UNCHANGED IN FULL. Its selected state is already thickness +
   weight + colour, and the restyle does not reach below 640px. */
.aiq-list { display: none; grid-template-columns: 1fr 1fr; gap: 0 clamp(12px, 4vw, 24px); }
.aiq-row { display: flex; flex-direction: column; justify-content: center; gap: 4px; min-height: 56px; padding: 14px 12px 14px 0; text-align: left; background: none; border: 0; border-top: 1px solid var(--hairline-cream); color: var(--light-grey); transition: color var(--dur-base), border-color var(--dur-base); }
.aiq-row[data-aiq="aiq"] { grid-column: 1 / -1; }
.aiq-row .aiq-l { font-size: var(--fs-body); font-weight: 300; color: inherit; }
.aiq-row:hover { color: var(--ivory); }
.aiq-row.is-active { color: var(--ivory); border-top: 2px solid var(--lime-green); padding-top: 13px; }  /* a Lime RULE, never Lime text */
.aiq-row.is-active .aiq-l { font-weight: 500; }
/* panel: five panes in one grid cell, cross-fading; visibility drops the inactive four out of
   the accessibility tree and the tab order; the stack is as tall as the tallest pane */
.aiq-panel { display: grid; }
.aiq-pane { grid-area: 1 / 1; opacity: 0; visibility: hidden; transition: opacity var(--dur-slow) var(--ease-out), visibility 0s linear var(--dur-slow); display: flex; flex-direction: column; gap: 20px; align-items: flex-start; }
.aiq-pane.is-active { opacity: 1; visibility: visible; transition-delay: 0s; }
.aiq-pane-head { font-family: var(--font-sans); font-weight: 700; font-size: var(--fs-sub-head); line-height: 1.16; letter-spacing: var(--tracking-tight); color: var(--ivory); margin: 0; display: flex; align-items: baseline; gap: 16px; }
.aiq-pane-copy { font-family: var(--font-sans); font-weight: 300; font-size: var(--fs-body); line-height: 1.6; color: var(--light-grey); max-width: 44ch; margin: 0; }
.aiq-ps { display: none; margin: 0; padding: 0; list-style: none; gap: 8px 12px; flex-wrap: wrap; }
.aiq-ps li { font-size: var(--text-sm); font-weight: 400; color: var(--ivory); padding: 8px 12px; border: 1px solid var(--hairline-cream); border-radius: var(--radius-sm); }
/* reveal choreography: opacity only, staggered, forward-only via .beat-on and :focus-within */
html.motion .aiq-layout .aiq-ring,  html.motion .aiq-layout .aiq-spoke, html.motion .aiq-layout .aiq-core,
html.motion .aiq-layout .aiq-node,  html.motion .aiq-layout .aiq-ray,   html.motion .aiq-layout .aiq-sats { opacity: 0; transition: opacity var(--dur-slow) var(--ease-out); }
html.motion .aiq-layout.beat-on .aiq-ring,  html.motion .aiq-layout:focus-within .aiq-ring,
html.motion .aiq-layout.beat-on .aiq-spoke, html.motion .aiq-layout:focus-within .aiq-spoke,
html.motion .aiq-layout.beat-on .aiq-core,  html.motion .aiq-layout:focus-within .aiq-core,
html.motion .aiq-layout.beat-on .aiq-node,  html.motion .aiq-layout:focus-within .aiq-node,
html.motion .aiq-layout.beat-on .aiq-ray,   html.motion .aiq-layout:focus-within .aiq-ray,
html.motion .aiq-layout.beat-on .aiq-sats,  html.motion .aiq-layout:focus-within .aiq-sats { opacity: 1; }
html.motion .aiq-layout .aiq-core    { transition-delay: 0.12s; }
html.motion .aiq-layout .aiq-spoke   { transition-delay: 0.24s; }
html.motion .aiq-layout .aiq-node--n { transition-delay: 0.36s; }
html.motion .aiq-layout .aiq-node--e { transition-delay: 0.44s; }
html.motion .aiq-layout .aiq-node--s { transition-delay: 0.52s; }
html.motion .aiq-layout .aiq-node--w { transition-delay: 0.60s; }
html.motion .aiq-layout .aiq-ray, html.motion .aiq-layout .aiq-sats { transition-delay: 0.72s; }
/* NO JS: neither selector can work, so neither is shown; the five panes stack, all visible. */
html:not(.js) .aiq-compass, html:not(.js) .aiq-list { display: none !important; }
html:not(.js) .aiq-panel { display: grid; grid-template-columns: 1fr; gap: 40px; }
html:not(.js) .aiq-pane { grid-area: auto; opacity: 1; visibility: visible; }
html:not(.js) .aiq-ps { display: flex; }
/* THE SELECTOR SWAP — on R3's own phone boundary. display:none on each side, never opacity or
   aria-hidden, so exactly one selector is exposed to everyone. */
@media (max-width: 639px) {
  .aiq-compass { display: none; }
  .aiq-list { display: grid; }
  .aiq-ps { display: flex; }
}
@media (min-width: 640px) { .aiq-panel { max-width: 560px; margin-inline: auto; width: 100%; } }
/* 640-1199px: one column. The head and the pane centre under a centred diagram rather than
   hanging off its left edge, which is what a single-column stack reads as. */
@media (min-width: 640px) and (max-width: 1199px) {
  .aiq-head { align-items: center; text-align: center; }
  .aiq-head > * { margin-inline: auto; }
  .aiq-pane { align-items: center; text-align: center; }
  .aiq-pane-copy { margin-inline: auto; }
}
@media (min-width: 1200px) {
  /* R6: the slide reads COPY LEFT, DIAGRAM RIGHT, and so does this now. The swap is done with
     grid-column, not `order` and not by moving the markup: the tablist must keep coming before
     the panel it controls in the DOM, or a screen reader meets five panes before the controls
     that switch them. Visual order changes; reading order does not. */
  /* Two rows in the left column: the section head above, the live pane below it; the diagram
     spans both on the right. The head is FIRST IN THE DOM and placed here, so reading order is
     heading -> tablist -> panel, which is also the order a screen reader wants. */
  /* 1fr/1fr with the head bottom-aligned and the pane top-aligned straddles the row boundary,
     which is the diagram's own vertical centre — so the copy sits level with the hub instead of
     hanging from the top with the diagram's lower half beside nothing. */
  .aiq-layout { grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: 1fr 1fr; gap: clamp(24px, 3vw, 40px) clamp(40px, 5vw, 80px); }
  .aiq-head { grid-column: 1; grid-row: 1; align-self: end; }
  .aiq-panel { grid-column: 1; grid-row: 2; max-width: none; margin-inline: 0; align-self: start; }
  .aiq-compass { grid-column: 2; grid-row: 1 / span 2; align-self: center; margin-inline: 0; }
}
@media (max-width: 390px) { .aiq-list { grid-template-columns: 1fr; } }
/* Two mechanics that will look like mistakes and are not:
   1. --sp is read by width/left in the base spoke rule but written by the [data-active] rule
      above. Custom properties cascade first, then var() substitutes the winning value, so the
      selected spoke resolves width:2px AND left:calc(--w/2 - 1px) together and stays optically
      centred — no transform, no second selector per arm. It will not animate (an unregistered
      custom property is not interpolable); the 1px snap is intended.
   2. .aiq-node.is-active { border-width: 2px } shrinks the content box by 2px (border-box). The
      label's max-width: calc(100% - 16px) re-measures to 126px from 128px; "Effectiveness" at
      --fs-meta still measures ~112px. Do not "fix" this by removing the border-width bump — it
      is one of the two colour-independent selection channels (§ WCAG 1.4.1 above). */
/* THE LOOPS — R7 (2026-09-14). Slide 3 runs four ambient loops on its engine, and the user asked
   for them back after R6 carried the geometry over without them ("we lost the animations present
   in the source"). Each is the deck's own keyframes and cadence, re-expressed on the element that
   already plays that part in this component:
     · the two orbits SPIN   — deck .orbit.spin1 80s and .orbit.o3 60s (eng-spin), on the ring
                               STROKES, never the ring boxes;
     · the hub BREATHES      — deck .core::after and ::before (eng-pulse): two rings growing from
                               the halo's edge to 1.68x and fading out, 3.6s, 1.2s apart;
     · the beads TRAVEL      — deck .pulse-dot (pulse-n/s/e/w): core -> satellite -> core in 2.8s,
                               fading in over the first fifth and out over the last, one arm after
                               another at 0.7s — N, S, E, W, the deck's order;
     · the 4P rays FLOW      — deck .pmix .spoke (pmix-flow): the dashes drift 30px outward and
                               back over 3.2s.
   THREE RULES, each structural rather than remembered:
   1. GATED on html.motion AND .aiq-layout.beat-on. The loops start the frame the diagram reveals
      (the focus latch in site.js writes the same class, so a keyboard arrival starts them too)
      and nothing runs while the section is still below the fold. html.motion is never set under
      prefers-reduced-motion; the blanket animation: none below is the second lock; and the phone
      list variant has none of these elements at all.
   2. COMPOSITOR-ONLY PROPERTIES. transform and opacity everywhere, background-position on the
      four rays. Nothing here writes a layout property, and nothing here moves a box that other
      geometry is placed from: the rotation is on the ring's ::before, the travel on the bead's
      ::after, the breath on .aiq-pulse — three things no calc() in this component reads. Every
      getBoundingClientRect() measures exactly what it measured in R6.
   3. R6 IS THE REST STATE. In every plain path the pulse rings are display:none, the bead sits at
      its arm's midpoint and the dashes at 0 0 — the still picture the review approved.
   Cadences are the deck's, named on the component: they are this diagram's own, not interaction
   speeds, which is the same reason --dur-pulse is not --dur-slow. Easings are the tokens. */
.aiq-compass {
  --dur-orbit-outer: 80s;                          /* deck eng-spin on .orbit.spin1 */
  --dur-orbit-inner: 60s;                          /* deck eng-spin on .orbit.o3    */
  --dur-breath: 3.6s;                              /* deck eng-pulse                */
  --dur-bead:   2.8s;                              /* deck pulse-n/s/e/w            */
  --dur-flow:   3.2s;                              /* deck pmix-flow                */
  --run: calc((var(--arm) - var(--dot)) / 2);      /* a bead's travel each way from its midpoint: its far edge lands exactly on the core, then on the satellite */
}
/* the breath — two rings sized off the core: one at the halo's radius (deck inset -24 on a 300
   core), one further out (deck inset -50 = 0.167). Out of the tree until the diagram reveals in
   motion mode; never a hit target. */
.aiq-pulse {
  display: none; position: absolute; pointer-events: none; border-radius: 50%;
  border: 1px solid rgba(52, 197, 42, 0.5);
  left: calc(var(--w) / 2 - var(--core) / 2 - var(--pr)); top: calc(var(--w) / 2 - var(--core) / 2 - var(--pr));
  width: calc(var(--core) + var(--pr) * 2); height: calc(var(--core) + var(--pr) * 2);
  transform-origin: 50% 50%;
}
.aiq-pulse--1 { --pr: var(--halo); }
.aiq-pulse--2 { --pr: calc(var(--core) * 0.167); border-color: rgba(52, 197, 42, 0.3); }
html.motion .aiq-layout.beat-on .aiq-pulse    { display: block; animation: aiqBreath var(--dur-breath) var(--ease-out) infinite; }
html.motion .aiq-layout.beat-on .aiq-pulse--2 { animation-delay: 1.2s; }
/* the spin — on the strokes, never the boxes */
html.motion .aiq-layout.beat-on .aiq-ring--outer::before { animation: aiqSpin var(--dur-orbit-outer) linear infinite; }
html.motion .aiq-layout.beat-on .aiq-ring--inner::before { animation: aiqSpin var(--dur-orbit-inner) linear infinite; }
/* the beads — one keyframe set per arm, because "outward" is a different axis and sign on each.
   The rest position is the arm's midpoint (the base rule above), so +/- --run reaches both ends
   without a second placement rule per arm. */
html.motion .aiq-layout.beat-on .aiq-spoke--n::after { animation: aiqBeadN var(--dur-bead) var(--ease-in-out) 0s   infinite; }
html.motion .aiq-layout.beat-on .aiq-spoke--s::after { animation: aiqBeadS var(--dur-bead) var(--ease-in-out) 0.7s infinite; }
html.motion .aiq-layout.beat-on .aiq-spoke--e::after { animation: aiqBeadE var(--dur-bead) var(--ease-in-out) 1.4s infinite; }
html.motion .aiq-layout.beat-on .aiq-spoke--w::after { animation: aiqBeadW var(--dur-bead) var(--ease-in-out) 2.1s infinite; }
/* the flow — the rays' repeating gradient is 15px a period, so 30px is two dashes outward */
html.motion .aiq-layout.beat-on .aiq-ray { animation: aiqFlow var(--dur-flow) var(--ease-in-out) infinite; }
@keyframes aiqSpin   { to { transform: rotate(360deg); } }
@keyframes aiqBreath { from { transform: scale(1); opacity: 0.7; } to { transform: scale(1.68); opacity: 0; } }
@keyframes aiqBeadN  { 0%, 100% { transform: translateY(var(--run));             opacity: 0; } 20%, 80% { opacity: 1; } 50% { transform: translateY(calc(-1 * var(--run))); opacity: 1; } }
@keyframes aiqBeadS  { 0%, 100% { transform: translateY(calc(-1 * var(--run))); opacity: 0; } 20%, 80% { opacity: 1; } 50% { transform: translateY(var(--run));             opacity: 1; } }
@keyframes aiqBeadE  { 0%, 100% { transform: translateX(calc(-1 * var(--run))); opacity: 0; } 20%, 80% { opacity: 1; } 50% { transform: translateX(var(--run));             opacity: 1; } }
@keyframes aiqBeadW  { 0%, 100% { transform: translateX(var(--run));             opacity: 0; } 20%, 80% { opacity: 1; } 50% { transform: translateX(calc(-1 * var(--run))); opacity: 1; } }
@keyframes aiqFlow   { 0%, 100% { background-position: 0 0; } 50% { background-position: 30px 0; } }
/* REDUCED MOTION. The transition kill list gains the two R4b carriers, and the component keeps
   its blanket animation: none. R7 brought four of the deck's nine loops back ON PURPOSE, every
   one gated on html.motion, which is never set under prefers-reduced-motion — this block is the
   second lock, so no loop reaches a reduced-motion reader by any route, and the pulse rings
   stay out of the tree entirely. */
@media (prefers-reduced-motion: reduce) {
  .aiq-pane, .aiq-tab, .aiq-spoke, .aiq-spoke::after, .aiq-stem, .aiq-sats, .aiq-sat,
  .aiq-core::after, .aiq-core-mark-i, .aiq-core-cap { transition: none !important; }
  .aiq-layout, .aiq-layout *, .aiq-layout *::before, .aiq-layout *::after { animation: none !important; }
  .aiq-pulse { display: none !important; }
}
/* ============================================================
   THE FORMULA ROW  (Home §4 "How we solve it", the Lime page)
   ============================================================ */
/* THE FORMULA ROW (Home §4, the Lime page). Four math operators × + = > as THIN STROKED
   GLYPHS from icons.js — R4b/C6 replaced the heavy formulas/*-black.png here, and only here.
   The PNGs stay on disk and keep their five other consumers: .ribbon-plus (index #operators),
   .icon-ring img (system's three plates) and .pillar-glyph (partnership's two). #connect now
   makes zero image requests.
   The row is aria-hidden: the sentence beneath it carries the meaning.
   NO COLOUR IS SET HERE AND NONE MAY BE. currentColor inherits Eerie Black from
   .band--green (:129) — 7.67:1 on the Lime ground — which is the same mechanism that keeps
   Lime off its own surface: an icon cannot be Lime unless something sets `color` on it. */
/* R5/B: #connect is centred — the user asked for "center nothing compounds until it connects".
   .stack-l means "stack LEFT", so it is not repurposed: .stack-centred is an additive modifier
   that flips the flex cross-axis and the text, and centres the block itself. Used with .stack-l
   rather than replacing it, so the 32px rhythm is inherited from one place.
   The measure is capped because centred copy with no measure is the worst of both: a 1440px band
   would run the lede edge to edge and the ragged centre would have nothing to hang on. */
.stack-centred { align-items: center; text-align: center; }
.stack-centred > * { margin-inline: auto; }
.stack-centred .lede { max-width: 62ch; }
/* The glyph row is centred to match — it is the same composition, and a left-aligned formula over
   centred copy reads as a mistake rather than a choice. */
#connect .formula { justify-content: center; }

.formula { display: flex; align-items: center; gap: clamp(4px, 0.8vw, 14px); margin: 0 0 clamp(40px, 6vw, 72px); }
/* THE GAP IS SMALL BECAUSE THE GLYPHS CARRY THEIR OWN. A 24-unit icon box holds a 17-unit
   mark, so 3.5 units of empty box already sit at each edge before the authored gap starts —
   the measured visual gaps at 1440 are 54 / 43 / 55 px against a 76.5px glyph. The PNGs were
   edge-to-edge and needed clamp(20px, 3.5vw, 56px); restoring that here doubles the row's
   width for no reason. Sized so 4 boxes + 3 gaps clear the narrowest supported content box
   (320px viewport, 18px gutter, 284px content): 4*60 + 3*4 = 252. */
.formula > [data-icon] { --icon-size: clamp(60px, 7.5vw, 116px); }
/* Emphasis on Lime is a RULE, not italic: the kit ships no Lexend italic and a synthesised
   oblique is the one thing Lexend does badly. */
.em-rule { font-style: normal; text-decoration: underline; text-decoration-thickness: 0.08em; text-underline-offset: 0.12em; text-decoration-skip-ink: none; }
/* The row's own transform belongs to GSAP (data-parallax="0.05"); the glyph spans animate
   THEIR OWN transform — a different element, so ownership is not shared. Unchanged from the
   PNG form; only the selector moved from .formula-glyph to the hydrated icon span. */
html.motion .formula > [data-icon] { opacity: 0; }
html.motion .formula.beat-on > [data-icon] { animation: glyphIn 0.7s var(--ease-out) both; }
html.motion .formula.beat-on > [data-icon]:nth-child(2) { animation-delay: 90ms; }
html.motion .formula.beat-on > [data-icon]:nth-child(3) { animation-delay: 180ms; }
html.motion .formula.beat-on > [data-icon]:nth-child(4) { animation-delay: 270ms; }
@keyframes glyphIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .formula > [data-icon] { animation: none !important; } }

/* ============================================================
   ARROW BULLETS + THE ARROW-MASKED KEY IMAGE  (Home §5 "What Upscale delivers")
   .diffs is shared with Home §7 "Who we are"; it is declared once, here.
   ============================================================ */
/* ARROW-BULLET LIST. The brand's bullet is →. The arrow is a CHARACTER, so it is text, so it is
   never Lime (CONTRACT rule 3) — Ivory at weight 400, the lead-in Ivory at 500, the sentence
   Light. aria-hidden only keeps a screen reader from announcing "right arrow" five times; it is
   not a licence for a colour the rule forbids. */
.diffs { list-style: none; margin: clamp(24px, 3vw, 40px) 0 0; padding: 0; display: grid; grid-template-columns: 1fr; gap: 18px; }
.diffs li { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 14px; font-size: var(--fs-body); font-weight: 300; line-height: 1.6; color: var(--light-grey); max-width: 46ch; }
.diffs .ar { color: var(--ivory); font-weight: 400; }
.diffs b { color: var(--ivory); font-weight: 500; }
.band--cream .diffs li { color: var(--mid-grey); }
.band--cream .diffs .ar, .band--cream .diffs b { color: var(--eerie-black); }
/* WHAT UPSCALE DELIVERS — copy left, the arrow-masked key image right. The mask is an inline
   SVG clipPath carrying the entry mark's path verbatim: no rotation, no flip, no tilt, and no
   transform anywhere in these rules — the arrow is upright by construction, not by correction.
   It is also script-free, so the composition holds with JS off and under reduced motion. */
.delivers-layout { display: grid; grid-template-columns: 1fr; gap: clamp(40px, 6vw, 80px); align-items: center; }
.arrow-figure { margin: 0; width: 100%; max-width: 320px; }
.arrow-image { display: block; width: 100%; height: auto; aspect-ratio: 447 / 415; }
@media (min-width: 901px) {
  .delivers-layout { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
  .arrow-figure { max-width: 520px; justify-self: end; }
}

/* ============================================================
   WHO WE ARE  (Home §7) — copy, the operators' photograph, the ribbon head
   ============================================================ */
/* WHO WE ARE (Home §7): copy + three arrow bullets on the left, the operators' photograph inside
   the brand's × on the right at >=901px (above the ribbon, below the copy, on narrower screens),
   then the client ribbon under a small h3. R8: the photograph moved from a plain 3:2 frame into a
   multiply-glyph clipPath — the same grammar as §5's arrow image, so the two pictures on the ink
   bands read as one set. The mask is square (the glyph's own 1667 canvas), colour (Upscale
   imagery), and, like the arrow, carries no transform anywhere: upright by construction. */
.operators-layout { display: grid; grid-template-columns: 1fr; gap: clamp(32px, 5vw, 64px); align-items: center; }
.multiply-figure { margin: 0; width: 100%; max-width: 320px; }
.multiply-image { display: block; width: 100%; height: auto; aspect-ratio: 1; }
.ribbon-head { display: flex; align-items: center; gap: 12px; margin: clamp(48px, 6vw, 80px) 0 0; font-size: var(--fs-meta); font-weight: 500; letter-spacing: var(--tracking-normal); color: var(--light-grey); }
.ribbon-plus { width: 20px; height: 20px; flex: none; }
#operators .ribbon-viewport { margin-top: clamp(20px, 3vw, 32px); }
@media (min-width: 901px) {
  .operators-layout { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); gap: clamp(40px, 6vw, 80px); }
  .multiply-figure { max-width: 520px; justify-self: end; }
}

/* ============================================================
   LEADERSHIP  (Home §8) — four cards on Ivory with headshot holds
   ============================================================ */
/* LEADERSHIP (Home §8), on Ivory. Four operators, four portraits at 4:5. The photo cell is a
   hold until the real set lands — a charcoal field the tone of the eventual backdrop, framed,
   with one quiet label. On cream the dark field reads as a framed print. */
.leaders { display: grid; grid-template-columns: 1fr; gap: clamp(32px, 4vw, 48px); list-style: none; margin: clamp(40px, 5vw, 64px) 0 0; padding: 0; }
.leader { display: flex; flex-direction: column; gap: 14px; align-items: flex-start; }
.leader .meta { margin-top: -8px; font-weight: 400; }
.leader .body-copy { max-width: none; }
/* R6: THE PORTRAIT IS DELIBERATELY SMALL. It used to be a full-column 4:5 plate — ~296x370 at
   the four-up desktop grid, the largest thing in the band by a wide margin. The photographs that
   arrived are interim, not a commissioned shoot, and the brief with them was "don't highlight
   them too much", so the plate became a modest round avatar: the face is present and identifies
   the person, and the copy beside it stays the thing being read.
   A circle, and 96px, for a specific reason beyond taste: all four frames are shot on plain
   white, and a large white rectangle on the Ivory band reads as a printing error — two
   almost-but-not-quite whites meeting on a straight edge. At avatar scale, rounded and ringed in
   a hairline, that same white reads as the portrait's own ground. The source files are
   pre-cropped square on the head (2.6x head width, measured per face), so object-fit has no work
   left to do here and the four faces land at the same scale. */
.leader-photo {
  position: relative;
  width: clamp(76px, 7vw, 96px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--hairline-cream);
  border: 1px solid var(--hairline-strong);
  padding: 0;
  overflow: hidden;
  flex: none;
}
.leader-photo > img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; display: block; }
@media (min-width: 640px)  { .leaders { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1181px) { .leaders { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* ============================================================
   INNER-PAGE CONTRAST REMEDY (Unit E) — Partnership #value-creation, 640-900px
   ============================================================
   probePhotoContrast, run for the first time on 2026-09-09, measured Partnership's
   #value-creation p.body-copy at 2.61:1 at 760x900: the brightest pixel composited under the copy
   was rgb(4, 185, 4) — the Lime multiply glyph, which is a direct child of the band and therefore
   stays in the composite (a reader sees it; hiding it from the probe would be measuring a page
   nobody is served).
   The @media (max-width: 900px) rule further down already shrinks that glyph and drops it to the
   lower right for exactly this reason, but it was written before any contrast probe existed and
   it does not go far enough. Measured across 640-899px: .body-copy is a constant 563px wide from
   the 32px gutter, so its right edge sits at x=595, while the glyph's left edge runs 410px (at
   640) to 575px (at 899) — left of the copy at EVERY width in the range — and the two boxes
   overlap vertically as well. There is no horizontal clearance to be had: a glyph that started
   right of 595px would have 45px of room at 640.
   So the glyph clears VERTICALLY instead, into the empty band below the copy, as a corner motif:
   the copy's bottom edge sits 104px (at 640) to 182px (at 899) above the band's, which is the
   budget this width/offset pair is sized against. Measured intersection with every .sec-head /
   .body-copy rect: 0 square pixels from 640 to 899, clearance 17px at the tightest width and
   growing; the mark stays whole and fully inside the band's overflow:hidden. Re-measured after:
   15.15:1 (narrow), 15.11 (tablet), 15.10 (mobileLand).
   It is smaller here than at <=639px, where the glyph returns to the FLOW above the copy and can
   afford to be bigger. That is the cost of there being no room beside the copy in this range, and
   a smaller motif is the right trade against a hidden one (DESIGN-R3 "Accessibility floor").
   THREE classes deliberately: this region sits ABOVE the @media (max-width: 900px) block in the
   file, so an equal-specificity rule would lose on source order. Offsets and width only — the
   base .pillar-glyph rule owns `transform`, and nothing here may hand a second writer that
   property. Only #value-creation matches: it is the one .band--photo section on any inner page
   that carries a .pillar-glyph. */
@media (min-width: 640px) and (max-width: 900px) {
  .pillar--photo.band--photo .pillar-glyph--right {
    width: clamp(76px, 12vw, 140px);
    right: var(--gutter);
    top: auto; bottom: 10px; margin-block: 0;
  }
}

/* ============================================================
   CLIENT RIBBON
   Home §6 / System §3. A continuous marquee. In motion mode site.js drives it from the GSAP
   ticker; in plain mode the CSS keyframe below runs it. Paused on hover/focus and OFF entirely
   under prefers-reduced-motion (see the reduced-motion block at the foot of this file).
   ============================================================ */
/* No hairline rules top or bottom — the comps draw the ribbon as an open band, and the borders
   fenced it off as a widget. */
.ribbon-viewport {
  position: relative; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}
.ribbon-track { display: flex; width: max-content; align-items: center; }
/* The comp band is ~180px tall with ~58px marks; the first pass ran 90px with 44px marks. */
.ribbon-item {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 32px clamp(28px, 5vw, 72px);
  flex: none;
}
/* The five client PNGs used to carry opaque black or white PLATES, so every mark read as a
   pasted rectangle on the Eerie Black band and Colonial First State read inverted against the
   other three. The plates are alpha'd out in the assets themselves (assets/img/clients/*.png),
   not masked with a blend mode here — a blend mode would have to be undone anywhere the same
   mark lands on a light surface, which is exactly what the Advisory badges do. */
.ribbon-item img { height: clamp(40px, 5vw, 58px); width: auto; object-fit: contain; }
/* OPTICAL, not mechanical. At equal pixel height the RSYS crest-and-two-line lockup and the CFS
   boxed lockup both read much smaller than the Samsung and Abbott wordmarks, because most of
   their height goes to a crest or a border rather than to letterforms. .ribbon-item--tall gives
   those two the extra height that makes the four marks read as one row. */
.ribbon-item--tall img { height: clamp(52px, 6.6vw, 76px); }
/* C9 (2026-09-10): EY and Nanuk Asset Management join RSYS and CFS on --tall — the EY beam
   device takes the top third of its box, and Nanuk's bear-in-circle device sits over two stacked
   type lines, so both would read markedly smaller than a pure wordmark at the base height.
   Diageo and the URBNSURF wordmark stay base — single-line letterforms at full cap height, the
   Samsung/Abbott case. Assigned by eye against a rendered strip, not by aspect ratio: see
   CONTRACT.md's ribbon note. */

/* Plain mode (no JS / motion libs absent): a CSS marquee. The track is authored TWICE in the
   markup, so translating by -50% is one seamless lap. */
html:not(.motion) .ribbon-track { animation: ribbonRun 46s linear infinite; }
html:not(.motion) .ribbon-viewport:hover .ribbon-track,
html:not(.motion) .ribbon-viewport:focus-within .ribbon-track { animation-play-state: paused; }
@keyframes ribbonRun { to { transform: translateX(-50%); } }

/* ============================================================
   MISSION / VISION TWO-UP  (About §1)
   ============================================================ */
/* margin-top, because the page h1 is now a sibling above it inside .wrap (see about.html). */
.twoup { display: grid; grid-template-columns: 1fr; gap: clamp(48px, 8vw, 88px); margin-top: clamp(40px, 6vw, 72px); }
.twoup-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.twoup-cell .icon-xl { color: var(--ivory); }

/* ============================================================
   CENTRED STATEMENT BLOCK  (About §2 — the Upscale advantage)
   ============================================================ */
.statement { max-width: 46ch; margin: 0 auto; text-align: center; display: flex; flex-direction: column; gap: 24px; align-items: center; }
/* R4b C1 — this used to redraw the lede's own 46ch against --fs-lead (then 20-22px), landing at
   507-600px inside a 563px container: a paragraph narrower than its own box. --fs-lead is now
   flat 18px, so `none` lets .statement's own 46ch (resolving against the inherited 20px body
   size, unaffected by C1) be the single measure for the block. */
.statement .lede { max-width: none; }

/* ============================================================
   CAROUSEL  (About §4 — enterprise expertise)
   A raised dark panel. Six dots, Lime chevron arrows, keyboard-operable (arrow keys on the
   region, focusable dots), autoplay OFF. The video is a STILL: the play button is marked
   data-todo and wires no player.
   ============================================================ */
/* A RAISED plate (--panel-hi on the --panel band), which is what makes the comp's slide read as
   one composition rather than two loose blocks. position: relative so the chevrons can be lifted
   out into the gutter above 901px, where the comp draws them OUTSIDE the plate. */
.carousel { position: relative; background: var(--panel-hi); border-radius: var(--radius-sm); padding: clamp(20px, 3vw, 32px); }
.carousel-stage { display: flex; align-items: center; gap: 12px; }
/* LIME LANDS ON THE GLYPH, NOT ON THE BUTTON.
   Both of these controls carry a visually-hidden text label ("Previous", "Upscale on LinkedIn")
   for screen readers. Colouring the BUTTON Lime made that label Lime text — invisible, but
   still Lime as a text colour, which is exactly what the audit forbids and what a future
   refactor that unhides the label would ship. Scoping the Lime to [data-icon] keeps it on the
   glyph and the hairline, where it belongs, and leaves every text node Ivory. */
/* No box. The comp draws large Lime chevrons in the air beside the plate; a 44px glyph inside a
   1px square read as a disabled widget. The 44px TARGET stays — it is the button's own size. */
.carousel-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: none;
  background: transparent; border: 0;
  border-radius: var(--radius-none); cursor: pointer; color: var(--ivory);
}
.carousel-arrow [data-icon] { --icon-size: 30px; color: var(--lime-green); }
.carousel-arrow:hover [data-icon] { color: var(--ivory); }
/* NO [disabled] rule: show() wraps with a modulo, so neither arrow is ever disabled and the
   style was unreachable code. If wrapping is ever dropped, add the rule back with the change. */
/* The slides are stacked in one grid cell and swapped with display, which is the only hiding
   mechanism that also drops the inactive slides out of the accessibility tree — so a screen
   reader is offered one slide, not six. */
.carousel-panel { flex: 1 1 auto; min-width: 0; display: grid; }
.slide { grid-area: 1 / 1; display: none; }
.slide.is-active { display: grid; grid-template-columns: 1fr; gap: 20px; }
html:not(.js) .slide { display: grid; grid-template-columns: 1fr; gap: 20px; }
html:not(.js) .slide + .slide { margin-top: 32px; }
html:not(.js) .carousel-arrow, html:not(.js) .carousel-dots { display: none; }

/* THE LEFT-HAND CARD. The comp draws a bordered panel holding the client mark AND the caption
   copy, beside an equal-height video still — not a shrink-wrapped logo strip with the copy
   stacked above the video in the right column. The client marks are alpha'd out to light
   silhouettes (see the ribbon note), so the card is dark with a cream hairline rather than an
   Ivory plate, and the copy on it is the band's own Ivory. */
.slide-logo {
  background: transparent;
  border: 1px solid var(--hairline-cream);
  border-radius: var(--radius-sm);
  padding: 24px;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 24px;
}
.slide-logo img { max-height: 56px; width: auto; }
.slide-copy { display: flex; flex-direction: column; gap: 16px; }
/* img/enterprise-video.jpg is a 452x568 PORTRAIT crop — the comp draws a landscape plate, but
   the source PNG yielded only that crop and the brief forbids upscaling. With no intrinsic ratio
   on the box the portrait image set the slide's height and the plate ran two viewports tall at
   desktop width. The img is already object-fit: cover, so fixing the box crops, never stretches. */
.video-still { position: relative; border-radius: var(--radius-sm); overflow: hidden; aspect-ratio: 16 / 9; }
.video-still img { width: 100%; height: 100%; object-fit: cover; }
.play-btn {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 64px; height: 64px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--scrim); color: var(--ivory);
  border: 1px solid var(--hairline-cream-strong);
  border-radius: var(--radius-pill); cursor: pointer;
}
.play-btn [data-icon] { --icon-size: 30px; }
/* flex-wrap, and it is not decoration. Six dots at the 44px tap target plus five 4px gaps is a
   HARD 284px of min-content width, which is wider than .wrap's content box at 320px (284px) once
   .carousel's own padding is taken off — measured 40px of horizontal overflow on About at 320,
   the width this file's base rules are written for. The audit's narrowest profile is 375, so it
   never saw it. Wrapping drops the min-content width to one dot; above 375 they still fit a row. */
.carousel-dots { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin-top: 20px; padding: 0; list-style: none; }
.carousel-dot {
  /* 44x44 hit area around a 10px visual dot — grown with padding so the visual rhythm is
     untouched and the tap target still passes 2.5.8. */
  width: 44px; height: 44px; padding: 17px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; cursor: pointer;
}
.carousel-dot::before {
  content: ""; display: block; width: 10px; height: 10px;
  border-radius: var(--radius-pill);
  background: var(--light-grey);
  transition: background var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.carousel-dot[aria-current="true"]::before { background: var(--lime-green); transform: scale(1.2); }

/* ============================================================
   VALUE CARDS  (About §5) — three, Lime hairline border
   ============================================================ */
.vcards { display: grid; grid-template-columns: 1fr; gap: 20px; }
.vcard {
  border: 1px solid var(--hairline-green);
  border-radius: var(--radius-sm);
  background: var(--panel);
  /* Air above the title, as the comps draw it — the first pass ran ~55px short per card. */
  padding: clamp(40px, 5vw, 56px) 28px;
  display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center;
}
.vcard .icon-lg { color: var(--ivory); }
.vcard .sub-head { font-size: var(--fs-body); font-weight: 700; }
.vcard .body-copy { max-width: 34ch; }

/* ============================================================
   PROJECT CARDS  (Projects) — three up, then two centred
   ============================================================ */
.pcards { display: grid; grid-template-columns: 1fr; gap: 20px; }
/* The trailing two are centred rather than left-aligned in a five-column flow. Implemented with
   a second grid rather than column spans: a span trick reflows into a hole at every breakpoint
   in between, and there is nothing to put in the hole. */
.pcards--pair { max-width: 900px; margin: 20px auto 0; }
.pcard {
  border: 1px solid var(--hairline-cream);
  border-radius: var(--radius-sm);
  background: var(--panel);
  padding: clamp(40px, 5vw, 56px) 28px;
  display: flex; flex-direction: column; align-items: center; gap: 18px; text-align: center;
}
.pcard .sub-head { font-size: var(--fs-body); font-weight: 700; }
.pcard .body-copy { max-width: 36ch; }

/* ============================================================
   PARTNERSHIP PILLAR BANDS
   Odd pillars photographic with the chevron cut; even pillars flat Eerie Black with an
   oversized Lime brand glyph left and copy right.
   The comps set the pillar HEADS in Lime. Heads are WHITE here; the Lime carries in the glyph
   and the hairline. That is not a style preference — a Lime heading is one of the three hard
   rules the audit enforces.
   ============================================================ */
.pillar { position: relative; overflow: hidden; }
/* ~845px at 1440 in the comps (hero_11-hero_13); at 560px the X cut read as a thin ribbon. */
.pillar--photo { min-height: clamp(360px, 62vw, 840px); display: flex; align-items: center; }
.pillar--photo .band-photo { z-index: 0; }
.pillar--photo .wrap { position: relative; z-index: 2; width: 100%; }
.pillar-copy { max-width: 46ch; display: flex; flex-direction: column; gap: 18px; align-items: flex-start; }
.pillar-copy .sec-head { border-bottom: 1px solid var(--hairline-green); padding-bottom: 14px; }

/* The oversized brand glyph. Percentage-positioned inside an overflow:hidden band, so bleeding
   off-canvas can never widen the document. */
/* FULLY INSIDE THE BAND. At 34vw with a negative inset the mark rendered 420x420 at x=1106 and
   at x=-115 — the arrow and the % cut in half, reading as abstract green shapes rather than brand
   motifs, and a cropped Arrow breaks the rule that the mark is never altered. The comps draw the
   hero X at ~374px with a ~173px right margin and the flat-pillar arrow/% at ~245px well inside
   the left half. Positive insets, and the band still carries overflow:hidden as a backstop. */
.pillar-glyph {
  position: absolute; z-index: 1; pointer-events: none;
  width: clamp(180px, 26vw, 380px); height: auto;
  opacity: 0.9;
}
/* CENTRED WITHOUT A TRANSFORM. `top: 50%; transform: translateY(-50%)` is the usual idiom, but
   these glyphs are direct children of a .band, and under prefers-reduced-motion nothing in a
   band may carry a transform — a static centring translate is indistinguishable, to a reader
   and to the audit, from a displacement the motion layer left behind. An absolutely-positioned
   REPLACED element with both offsets pinned, an auto height and auto block margins resolves
   those margins to equal values (CSS2.1 §10.6.5), so the intrinsic ratio still sizes the glyph
   and the box centres itself with no transform at all. `transform` then belongs entirely to
   GSAP, which is what TRANSFORM OWNERSHIP below asks for. */
.pillar-glyph--right { right: clamp(40px, 10vw, 150px); top: 0; bottom: 0; margin-block: auto; }
.pillar-glyph--left  { left:  clamp(40px, 14vw, 200px); top: 0; bottom: 0; margin-block: auto; }
/* A parallaxed glyph is anchored low instead of centred — see TRANSFORM OWNERSHIP below. */
.pillar-glyph[data-parallax] { top: auto; bottom: 8%; margin-block: 0; }

/* .pillar-glyph is position:absolute, so it takes NO cell in .pillar-flat-layout's grid — with
   only .pillar-copy in flow the copy lands in the NARROW left column, underneath the glyph. An
   empty spacer holds that column open so the copy sits right, as the comps draw it. It stands
   down at a single column, where an empty flow child only adds a stray grid gap. */
.pillar-spacer { display: none; }
/* The glyph carries z-index:1 and the flat pillars' copy is not positioned, so without this the
   oversized mark paints OVER the body copy. .band--photo > .wrap does the equivalent for the
   photographic bands; the flat bands have no such rule. */
.pillar--flat .pillar-copy { position: relative; z-index: 2; }

/* ~792px tall in the comps (hero_12/hero_13); 411px flattened the whole page's rhythm. */
.pillar--flat { padding: clamp(56px, 12vw, 160px) 0; }
.pillar-flat-layout { display: grid; grid-template-columns: 1fr; gap: 32px; align-items: center; }

/* ============================================================
   ADVISORY TIMELINE
   Six rows: a white circular logo badge left, a Lime connector with a Lime terminal dot, a
   bordered card right. Rows 1 and 4 are blank badges and rows 2-4 are empty cards in the comps
   — they render as visible, LABELLED placeholders. Never an invented client name.
   ============================================================ */
/* The comp indents the whole device ~210px from the page edge, which is what gives the badge and
   its connector room to read as a timeline rather than as a badge with a dash. */
.timeline {
  display: flex; flex-direction: column; gap: 28px;
  padding: 0 0 0 clamp(0px, 9vw, 138px); list-style: none;
  /* A bare <ol> at margin:0 would let the section head and the first row touch. */
  margin: clamp(var(--space-8), 5vw, var(--space-16)) 0 0;
}
.tl-row { display: grid; grid-template-columns: var(--tl-badge, 88px) 1fr; gap: 16px; align-items: center; }
.tl-badge {
  width: var(--tl-badge, 88px); height: var(--tl-badge, 88px); flex: none;
  border-radius: var(--radius-pill);
  background: var(--ivory);
  display: flex; align-items: center; justify-content: center;
  padding: 12px; overflow: hidden;
}
/* 62% is for RECTANGULAR lockups only. NO filter here: the badge is Ivory, so it takes the
   dark-polarity marks while the ribbon takes the light silhouettes of the same set. Polarity is
   an ASSET decision, not a CSS one — a filter would have to be undone on every surface. */
.tl-badge img { max-width: 62%; max-height: 62%; width: auto; height: auto; object-fit: contain; }
/* All four filled Advisory badges are DISCS — the disc is the badge face, not a mark floating on
   one, so capping them at 62% printed a small white circle inside the Ivory circle and shrank
   the logo inside it to illegibility. URBNSURF, RSYS Sailing Academy and StudioSpace were
   recovered from the comp as opaque white circles already; row 3's crest was cut out of the wide
   ROYAL SYDNEY YACHT SQUADRON lockup, which at 62% left a ~30px crest beside unreadable type
   (the comp draws the crest alone). Discs fill the badge; rectangular lockups keep 62%. */
.tl-badge--disc { padding: 0; }
.tl-badge img.tl-mark--disc {
  max-width: 100%; max-height: 100%; width: 100%; height: 100%;
  border-radius: var(--radius-pill);
}
.tl-badge--empty { border: 1px dashed var(--hairline-strong); }
/* The connector — a Lime rule ending in a Lime dot. Hidden below 640px, where the badge and the
   card stack and a horizontal connector has nothing to connect. */
.tl-conn { display: none; }
.tl-card {
  border: 1px solid var(--hairline-green);
  border-radius: var(--radius-sm);
  background: transparent;
  padding: 20px 22px;
  min-height: 104px;
  display: flex; flex-direction: column; gap: 10px; justify-content: center;
}

/* ============================================================
   FOOTER
   Three columns: brand, Explore, Upscale — every link a section of Home. Then a Lime hairline
   and the copyright. The newsletter column was removed on 2026-09-14 (see below).
   ============================================================ */
.footer { background: var(--eerie-black); color: var(--ivory); padding: clamp(48px, 8vw, 80px) 0 28px; border-top: 1px solid var(--hairline-cream); }
.fgrid { display: grid; grid-template-columns: 1fr; gap: 40px; }
.fcol { display: flex; flex-direction: column; gap: 16px; align-items: flex-start; }
.fcol-head { font-family: var(--font-sans); font-weight: 700; font-size: var(--fs-meta); margin: 0; color: var(--ivory); }
.flinks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.flinks a { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; font-size: var(--fs-meta); font-weight: 300; color: var(--light-grey); }
.flinks a:hover { color: var(--ivory); text-decoration: underline; text-underline-offset: 5px; }
/* NO NEWSLETTER. The "Join a newsletter" column (.news-form / .news-row / .fprivacy) was
   removed on 2026-09-14 at the client's request, along with its data-todo="newsletter-backend"
   stub. The footer is now brand + two columns of Home section links; do not reinstate a
   subscribe form without a backend to connect it to. */
/* NO SOCIAL LINKS. The comps draw three (LinkedIn, Facebook, Instagram) as filled Lime circles
   in the newsletter column; they were built as Lime hairline rings and have since been removed
   at the client's request. `.socials` / `.social`, the three brand glyphs in icons.js, and the
   audit's `.social` selectors all went with them — do not reinstate one without the others. */
/* The footer logo is a fixed-height mark at every width, so its 44px tap target comes from
   padding on the link rather than from the image. Unconditional, not pointer-gated: a 30px
   target is a failure on a trackpad too. */
.fbrand a { display: inline-flex; align-items: center; min-height: 44px; }
.fbrand img { height: 26px; width: auto; margin-bottom: 4px; }
/* R4b C1 — the brand paragraph is a 33-word sentence, running copy by any test, not a label
   like .fprivacy or .fbase p beside it (both stay --text-sm). Promoted from --fs-meta to
   --fs-body so it joins the flat 18px sweep. Known cost: the footer brand column is the
   narrowest track in the grid and narrowest exactly at 901-1180px, so this grows the column's
   worst-case height by ~50px there — a pre-existing squeeze this makes worse, not a new one,
   and it is flagged to the user rather than silently widened. max-width stays a ch count: it
   now resolves to 374.5px instead of 332.9px, and the grid track is narrower than that at every
   width >=901px anyway, so the ch value is inert there. */
.fbrand p { font-size: var(--fs-body); font-weight: 300; line-height: 1.6; color: var(--light-grey); margin: 0; max-width: 34ch; }
.fbase { margin-top: 36px; padding-top: 24px; border-top: 1px solid var(--hairline-green); text-align: center; }
.fbase p { font-size: var(--text-sm); font-weight: 300; color: var(--light-grey); margin: 0; }

/* ============================================================
   REVEAL / PARALLAX BASE STATES — GATED ON html.motion
   ============================================================
   EVERY hiding rule below sits behind html.motion. site.js strips that class if GSAP,
   ScrollTrigger or Lenis failed to load, or if the page would render blank — so a library
   failure degrades to plain, fully-visible HTML instead of a white screen. That is the only
   acceptable failure mode for a marketing site, and it has shipped broken once already. */

/* A masked rise, not a fade: the block is revealed by a hard edge travelling up over it, which
   is the brand's own hard-edged vocabulary rather than a PowerPoint "appear". Forward-only —
   site.js fires these with once: true and scrolling back up never un-reveals. */
html.motion .reveal {
  opacity: 0;
  clip-path: inset(0 0 100% 0);
  transform: translateY(22px);
  transition: opacity 0.9s var(--ease-out), clip-path 1s var(--ease-out), transform 1s var(--ease-out);
}
html.motion .reveal.in { opacity: 1; clip-path: inset(0 0 -20% 0); transform: none; }
.reveal.d1 { transition-delay: .08s; }
.reveal.d2 { transition-delay: .16s; }
.reveal.d3 { transition-delay: .24s; }
.reveal.d4 { transition-delay: .32s; }
.reveal.d5 { transition-delay: .40s; }

/* A .beat is a block that arrives on its own beat after the heading — scroll-keyed, not timed. */
html.motion .beat {
  opacity: 0; transform: translateY(var(--beat-rise, 24px));
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
html.motion .beat.beat-on { opacity: 1; transform: none; }

/* FOCUS IS A REVEAL TRIGGER. The reveals are scroll-keyed (ScrollTrigger, once:true, top 86%),
   and the browser's focus-scroll does not go through Lenis — so tabbing into an unrevealed block
   left focus on an element inside an opacity:0 ancestor and the trigger never fired. Measured:
   ten consecutive keyboard stops on Home with no visible focus indicator. site.js also latches
   .in/.beat-on from a delegated focusin listener; this rule is what covers the frame before it
   runs and the case where the class is later removed. */
html.motion .reveal:focus-within {
  opacity: 1; clip-path: inset(0 0 -20% 0); transform: none;
}
html.motion .beat:focus-within { opacity: 1; transform: none; }

/* TRANSFORM OWNERSHIP — do not remove this rule.
   An element carrying data-parallax hands `transform` ENTIRELY to GSAP. A CSS transition on
   transform plus a scrubbed GSAP tween writing the same property fight, and the visible result
   is parallax lurching a frame behind the scroll. These elements reveal on opacity + clip-path
   alone, which costs nothing — the parallax is already supplying the movement.
   Keep this in step with the data-parallax handler in site.js: any new property GSAP scrubs has
   to be dropped from the transition list here. */
html.motion .reveal[data-parallax],
html.motion .beat[data-parallax] {
  transform: none;
  transition-property: opacity, clip-path;
}
/* Same reason, one level out: a parallaxed element must not inherit a centring translate from a
   layout rule either. .pillar-glyph[data-parallax] above is the worked example. */
html.motion [data-parallax] { will-change: transform; }

/* ============================================================
   FOCUS
   ONE visible focus style: a Lime ring on dark, an Eerie Black ring on cream. Applied on the
   element, not a parent, so it is never clipped by an overflow:hidden card.
   ============================================================ */
:focus-visible { outline: 2px solid var(--lime-green); outline-offset: 3px; }
.band--cream :focus-visible, .band--cream.sec :focus-visible { outline-color: var(--eerie-black); }
/* Cream-surfaced children inside a dark band (the carousel's Ivory logo plate). */
/* .slide-logo is no longer an Ivory plate (see CAROUSEL); the Advisory badge still is. */
.tl-badge :focus-visible { outline-color: var(--eerie-black); }

.skip-link {
  position: fixed; top: 12px; left: 24px; z-index: 100;
  padding: 12px 20px; background: var(--ivory); color: var(--eerie-black);
  font-size: var(--fs-meta); border-radius: var(--radius-none);
  transform: translateY(-200%);
}
.skip-link:focus { transform: none; }

/* THE SCENE RAIL ON TOUCH. The dots are already 44px (see the rail block above); on a coarse
   pointer the GAP tightens so nine 44px targets still fit a tablet's viewport height
   comfortably. Below 901px the rail is not rendered at all. */
@media (pointer: coarse) {
  .rail-list { gap: 2px; }
}

/* Touch tap targets. Gated on POINTER, not viewport width: the header logo and the footer logo
   are fixed-height marks regardless of layout width, and padding grows the hit area without
   touching the mark's on-brand size. */
@media (pointer: coarse) {
  .chrome .logo, .fbrand a { min-height: 44px; display: inline-flex; align-items: center; }
}

/* ============================================================
   RESPONSIVE — mobile-first, widening upward
   ============================================================ */

/* --- ≥560px: pairs fit --- */
@media (min-width: 560px) {
  :root { --gutter: 32px; }
  .chrome-inner { padding: 14px 32px; }
}

/* --- ≥640px: cards pair up, the timeline connector appears --- */
@media (min-width: 640px) {
  .vcards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pcards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The comp draws a ~115px badge, a ~173px Lime connector and a card about half the page wide.
     The long connector is what makes it a timeline; at 56px it disappeared. */
  :root { --tl-badge: clamp(88px, 8vw, 115px); }
  .tl-row { grid-template-columns: var(--tl-badge) clamp(56px, 12vw, 173px) minmax(0, 728px); gap: 0; }
  .tl-conn {
    display: block; position: relative; height: 2px;
    background: var(--lime-green); margin: 0 4px;
  }
  .tl-conn::after {
    content: ""; position: absolute; right: -4px; top: 50%;
    transform: translateY(-50%);
    width: 10px; height: 10px; border-radius: var(--radius-pill);
    background: var(--lime-green);
  }
  .tl-card { margin-left: 12px; }
  /* THE DIAGONAL RUNS THE OTHER WAY. Comp m_494/hero_00 draws the photo as a large triangle
     whose right and bottom edges are flush to the band and whose hypotenuse climbs from part-way
     along the BOTTOM to the top-right corner — so the photo WIDENS as it descends and the cut
     takes the TOP-LEFT away. The first pass nicked the bottom-left instead, which narrows the
     photo as it descends: the same angle, the opposite composition. */
  .cut--diagonal { clip-path: polygon(52% 0, 100% 0, 100% 100%, 0 100%); }
  .slide.is-active, html:not(.js) .slide { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.6fr); gap: 24px; align-items: stretch; }
  /* align-items: stretch, and .slide-logo does NOT stand down from it: the left column is now a
     bordered card carrying the mark AND the caption, so it is meant to match the still's height.
     AND THE STILL DROPS ITS aspect-ratio HERE — this is load-bearing. As a stretched grid item
     it gets a DEFINITE height from the row, and `aspect-ratio: 16/9` then computes its WIDTH from
     that height, ignoring the column: measured 794px inside a 760px viewport, i.e. 354px of
     horizontal overflow on About. Two columns: the card sets the row height and the still fills
     it, cropping through object-fit: cover. The ratio stays at a single column, where the width
     is the definite axis and the ratio is what stops a 452x568 portrait crop running two
     viewports tall. */
  .slide.is-active > .video-still, html:not(.js) .slide > .video-still {
    aspect-ratio: auto; align-self: stretch; min-height: 260px;
  }
  .pillar-spacer { display: block; }
  .pillar-flat-layout { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.4fr); gap: clamp(32px, 5vw, 72px); }
}

/* --- ≥901px: the header/rail boundary --- */
@media (min-width: 901px) {
  :root { --gutter: clamp(40px, 5vw, 96px); }
  .chrome-inner { padding: 14px clamp(40px, 5vw, 96px); gap: clamp(16px, 2.5vw, 40px); }
  .nav { display: block; }
  .chrome > .chrome-inner > .chrome-cta { display: inline-flex; }
  .nav-toggle { display: none; }
  .nav-sheet { display: none !important; }   /* the sheet has no role above the boundary */
  html:not(.js) .nav-sheet { display: none !important; }

  /* THE SCENE RAIL. Right margin, hero pages only, one dot per band. Its labels are real text
     (visually hidden until hover/focus), so it is a working table of contents for a screen
     reader. */
  .rail {
    display: block;
    position: fixed; right: 20px; top: 50%; transform: translateY(-50%); z-index: 70;
    transition: opacity var(--dur-slow) var(--ease-out), visibility 0s linear 0s;
  }
  .rail-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
  /* 44x44 hit area around the 8px visual dot, grown with PADDING so the visual rhythm between
     dots is untouched. The brief's accessibility floor is flat — every interactive target is
     ≥44x44px — and it does not carve out an exception for a pointer-only affordance. */
  .rail-dot {
    position: relative; display: flex; align-items: center;
    width: 44px; height: 44px; padding: 18px;
    color: var(--ivory);
  }
  .rail-dot .dot {
    width: 8px; height: 8px; flex: none;
    border-radius: var(--radius-pill);
    border: 1px solid currentColor; background: transparent;
    transition: background var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out),
                border-color var(--dur-base) var(--ease-out);
  }
  /* Active dot is Lime — an accent on a dark surface, and not colour-alone: it also fills and
     scales, so it survives a monochrome rendering. */
  .rail-dot[aria-current="true"] .dot { background: var(--lime-green); border-color: var(--lime-green); transform: scale(1.35); }
  .rail-dot .dl {
    position: absolute; right: 42px; top: 50%; transform: translateY(-50%) translateX(6px);
    padding: 8px 12px; background: var(--eerie-black);
    font-size: var(--text-sm); font-weight: 500; letter-spacing: var(--tracking-wide);
    white-space: nowrap; opacity: 0; pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
  }
  .rail-dot:hover .dl, .rail-dot:focus-visible .dl { opacity: 1; transform: translateY(-50%); }
  /* Tone follows the surface under the rail, same as the header. */
  .rail.is-light .rail-dot { color: var(--eerie-black); }
  .rail.is-light .rail-dot .dl { background: var(--cream); }
  .rail.is-light .rail-dot[aria-current="true"] .dot { background: var(--eerie-black); border-color: var(--eerie-black); }
  .rail.is-light .rail-dot:focus-visible { outline-color: var(--eerie-black); }
  /* On the Lime band the plate goes Eerie Black with Ivory text — a cream plate on Lime is
     Lime-adjacent-to-cream. site.js toggles .is-green. */
  .rail.is-light.is-green .rail-dot .dl { background: var(--eerie-black); color: var(--ivory); }
  /* RIGHT-EDGE CLEARANCE. The rail's hit column is 44px wide (.rail-dot width 44px under
     border-box, site.css:73/:1367) and sits 20-64px from the edge here, 28-72px at >=1181.
     --gutter in this range is clamp(40px, 5vw, 96px) (:1349, this same block) = 45px at 901px,
     59px at 1180px - NOT the 32px of the >=560px block (:1296), which stops applying at 901.
     So without this rule a bordered right edge runs 19px UNDER the column at 901px. Bordered
     components with a hard right edge (silo rules, headshot holds) must stop
     short of it, so on rail pages .wrap's right padding never drops below 88px.
     NOT on the two centred bands: .hero-copy and .cta-copy centre with margin-inline:auto, so an
     asymmetric gutter would shift the H1 and the contact statement (88 - gutter)/2 left of the
     header's own axis - ~21.5px at 901px, ~14.5px at 1180px - and neither block reaches the
     right edge. */
  .rail ~ main .wrap { padding-right: max(var(--gutter), 88px); }
  .rail ~ main #hero > .wrap, .rail ~ main #contact > .wrap { padding-right: var(--gutter); }

  .vcards, .pcards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .pcards--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .twoup { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cluster-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr); }
  /* Brand column leads and takes ~half the row; the two link columns (Home's sections, split
     Explore / Upscale) cluster right. Was 1.5 / 0.6 / 0.6 / 1 with the newsletter first until
     that column was removed on 2026-09-14. */
  .fgrid { grid-template-columns: minmax(0, 1.5fr) minmax(0, 0.75fr) minmax(0, 0.75fr); gap: clamp(24px, 3vw, 56px); }
  .fbase { text-align: left; }
  .cut--diagonal { clip-path: polygon(38% 0, 100% 0, 100% 100%, 0 100%); }
  /* The chevrons sit OUTSIDE the plate, as drawn — but they stay inside .carousel in the markup,
     because site.js scopes its queries to the carousel root. */
  .carousel-arrow { position: absolute; top: 50%; margin-top: -22px; }
  .carousel-arrow--prev { left: calc(-1 * clamp(44px, 4vw, 60px)); }
  .carousel-arrow--next { right: calc(-1 * clamp(44px, 4vw, 60px)); }
  .carousel-stage { gap: 0; }
  /* The flat pillars' copy lands at ~58% of the viewport in the comps, not 41%. */
  .pillar-flat-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

  /* THE OFFSET CLUSTER, at full size (comp m_350). The two OUTER cards are inset and the
     middle one runs flush, so the expanded card is visibly the widest and the group reads as one
     stepped composition rather than three tiles.

     nth-child(2)/(3)/(4), not (1)/(2)/(3): .cluster-wedge is the first child. If you reorder the
     cluster, the wedge stays first.

     Offsets live in the FLOW (margins), never in a transform — a parallax factor can then be
     layered on the cluster without the two fighting over the transform property. */
  .cluster { grid-template-columns: 1fr; gap: 18px; }
  /* SYMMETRIC. margin-left alone gave all three cards a flush RIGHT edge, so the group read as
     one right-aligned column with a nicked corner rather than a stepped cluster. The comp
     (hero_08) insets the two outer cards on BOTH sides and makes them ~29% narrower than the
     middle one. nth-child(1)/(3) now, because .cluster-wedge is no longer the cluster's first
     child — it moved up to .cluster-layout (see THE WEDGE). */
  .cluster > .ccard:nth-child(1),
  .cluster > .ccard:nth-child(3) { margin-inline: clamp(0px, 7.5vw, 108px); }
  .ccard { padding: 32px 28px; }

  .caps .cap-btn { padding: 18px 28px; min-height: 76px; }
  .cap-panel-inner { padding: 0 28px 26px; }
}

/* --- ≥1181px: the widest composition --- */
@media (min-width: 1181px) {
  :root { --sec-pad-block: clamp(96px, 8vw, 140px); }
  .rail { right: 28px; }
  .rail-list { gap: 14px; }
}

/* --- BELOW THE 901px BOUNDARY: the Partnership hero's oversized multiply glyph.
       At 34vw, centred vertically, it crosses the hero copy box from 640px to ~870px and puts
       the last third of every line on top of a Lime bar. Here it shrinks and drops to the lower
       right, clear of the copy, so the motif survives and the copy stays on the scrim. Offsets
       only — the base .pillar-glyph rule owns `transform`. --- */
@media (max-width: 900px) {
  .pillar--photo .pillar-glyph--right {
    width: clamp(150px, 26vw, 240px);
    top: auto; bottom: 6%; margin-block: 0;
  }
  /* Below the boundary the copy spans the full width, so there is no lane to keep clear and the
     eased-off right end would sit behind text. Back to the flat wash. */
  .scrim--lane { background: var(--scrim); }
}

/* --- SINGLE COLUMN: the oversized pillar glyph RETURNS TO THE FLOW rather than disappearing.
       It used to be `display: none` here, which is a real contrast fix with the wrong scope:
       with no second column an absolutely-positioned glyph lands behind the copy, and Lime at
       0.9 opacity under body text fails 4.5:1 — but deleting it left Partnership as six
       near-identical flat text blocks, the page's whole visual identity gone on phones.
       In the flow, above the copy, there is nothing behind the glyph at all, so the contrast
       conflict does not arise and the motif survives at mobile.
       .pillar--photo goes display:block so the static glyph is a block above .wrap rather than
       a flex sibling squeezing it, and takes its own padding since the flex centring it relied
       on is gone. --- */
@media (max-width: 639px) {
  .pillar-glyph {
    /* position: relative, not static: it has to stay IN THE FLOW (that is the whole point) but
       still paint above the .scrim, which is a positioned sibling at z-index 1. A static child
       sits in the normal-flow layer underneath it and the glyph came out washed dark green. */
    position: relative; z-index: 2; display: block;
    width: clamp(96px, 30vw, 140px); height: auto;
    margin: 0 0 24px; inset: auto;
  }
  .pillar--photo {
    display: block; min-height: 0;
    padding: clamp(48px, 12vw, 96px) 0;
  }
  /* The header is fixed and transparent over the hero pillar, so it takes no room in the flow —
     and the glyph is now the band's first painted thing, which would put it under the wordmark. */
  .pillar--photo[data-hero] { padding-top: calc(var(--chrome-h) + clamp(24px, 6vw, 48px)); }
  .pillar--photo .pillar-glyph { margin-left: var(--gutter); }
  .pillar-flat-layout { grid-template-columns: 1fr; }
  .pillar-spacer { display: none; }
}

/* --- The carousel stage is a flex ROW of prev / panel / next. At 360px the two 44px arrows and
       the gaps leave the panel ~135px, folding the slide copy to two or three words a line.
       Wrapping the panel onto its own row keeps the markup order that places prev before next
       (site.js's --prev/--next classes carry no CSS) while giving the copy the full width. --- */
@media (max-width: 520px) {
  .carousel-stage { flex-wrap: wrap; justify-content: center; }
  .carousel-panel { order: -1; flex-basis: 100%; }
}

/* --- very narrow phones (320-390px). Two things need it: the header has to hold a 22px
       wordmark plus a 44px toggle inside a 272px content box, and the stat figures have to stop
       forcing a wider column than the page. --- */
@media (max-width: 390px) {
  :root { --gutter: 18px; }
  .chrome-inner { padding: 12px 18px; gap: 10px; }
  .chrome .logo img { height: 18px; }
  .ccard, .vcard, .pcard { padding: 24px 18px; }
  :root { --tl-badge: 64px; }
  .tl-row { grid-template-columns: var(--tl-badge) 1fr; gap: 12px; }
  .tl-badge { padding: 8px; }
  .cap-btn { padding: 14px 16px; gap: 12px; }
  .cap-panel-inner { padding: 0 16px 18px; }
}

/* ============================================================
   REDUCED MOTION
   html.motion is never set under prefers-reduced-motion, so the whole motion layer above is
   already inert and everything is visible. This block only has to neutralise the AMBIENT CSS
   animations that live outside it — the plain-mode ribbon marquee is the main one — and the
   hover/expansion transitions, which are cheap but still movement.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .ribbon-track { animation: none !important; transform: none !important; }
  /* The ribbon stops being a marquee and becomes a wrapped row of every mark. The duplicate
     half of the track is aria-hidden in the markup, so hiding it loses nothing. */
  .ribbon-viewport { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .ribbon-track { flex-wrap: wrap; width: 100%; justify-content: center; }
  .ribbon-track > [aria-hidden="true"] { display: none !important; }
  /* Eight marks now wrap here instead of four (C9). Cap each item so a single wide wordmark
     cannot push the document sideways on a narrow phone with reduced motion on, and tighten the
     gutters so the row wraps to two lines rather than three. Real-browser correctness, not an
     audit fix: `reduced` is 1440x900 and this never fires there — it matters on a phone with
     Reduce Motion enabled, where the URBNSURF wordmark at the 40px minimum height (341px wide)
     plus 56px of padding would exceed a 360px viewport. */
  .ribbon-item { max-width: 100%; padding: 20px clamp(20px, 3vw, 36px); }
  .ribbon-item img { max-width: 100%; }
  .ccard-body, .cap-panel, .cap-btn [data-icon], .btn, .carousel-dot::before,
  .rail-dot .dot, .rail-dot .dl, .input, .chrome { transition: none !important; }
  /* The press state is movement, so it stands down too — the brightness stays, which is the
     part that carries the feedback. No transform survives under reduced motion anywhere. */
  .btn:active, .carousel-arrow:active, .nav-toggle:active,
  .play-btn:active, .carousel-dot:active { transform: none !important; transition: none !important; }
  /* Expansion still WORKS — it is an interaction, not a motion effect. It just arrives at once
     rather than sliding. */
  .entry-mark { animation: none !important; }
  html.js .entry-runway { display: none !important; }
  /* No band holds here — html.motion is never set under reduced motion, so site.js never
     creates one. Belt to the html:not(.motion) braces above. */
  .pin-spacer { padding-bottom: 0 !important; }
  .rail { transition: none !important; }
}
