/* ═══════════════════════════════════════════════════════════════════════
   Inyosi Technologies — the INNER-PAGE kit
   Layers on top of primitives.css and style.css, in that order. Loaded by
   /the-hive/, /capabilities/ and /contact/ ONLY. The homepage does not link
   it and must never need to.

   WHY A SEPARATE FILE. style.css is 160KB of homepage: eleven chapters, nine
   motion devices and a semantic token layer that the whole page is measured
   against. The inner pages need none of that and adding them to it would
   mean every future homepage change reads past three pages of unrelated
   layout. The split is also what let three pages be built at once without
   fighting over one file.

   WHAT THIS FILE MAY NOT DO. It defines no tokens. Every colour, size,
   easing and measure below comes from style.css's lattice and semantic
   layer, and any value that is not on that lattice is a defect — session 10
   found the "Send enquiry" button rendering the same size as its own
   footnote because somebody wrote .875rem by hand. If a size is needed that
   the lattice does not have, the lattice is the thing to change.

   THE HIERARCHY THESE PAGES USE, and why it is not the homepage's:
     page H1        --fs-chapter   48.00 / 26.00
     section H2     --fs-h3        30.72 / 21.60
     sub-head       --fs-sub       22.00 / 20.00
     body           --fs-lead      17.60 / 17.00
   The homepage hero keeps --fs-hero-heavy (83.84) to itself, so it stays the
   largest type on the site and an inner page can never out-shout the front
   door. Within any one inner page no two levels share a rung, which is the
   tie the lattice exists to prevent.
   ═══════════════════════════════════════════════════════════════════════ */


/* ═══ the nav, off the homepage ══════════════════════════════════════════
   navSpy() selects `.iy-nav__link[href^="#"]` and drives .is-active, which
   is what lights the small gold hexagon beside the current chapter. On an
   inner page there are no in-page chapters to spy: links 3-5 are absolute
   (`/#sectors`), so they do not match that selector at all and navSpy
   returns early having found nothing to observe. That is correct and needs
   no code — but it leaves the indicator dark on a page that IS one of the
   nav's destinations.

   So off the homepage the state is declarative: the link to the current page
   carries aria-current="page" in the markup, and these two rules give it
   exactly what .is-active gives its equivalent. The attribute is the
   accessible truth as well as the visual one, which is why it is used rather
   than a second class.

   ⚠ Written at (0,2,0) to match .iy-nav__link.is-active, not below it. A
   lower-specificity rule would lose to nothing today and lose silently to
   the first hover or theme rule added later — the same specificity fight
   that shipped a two-column layout to a 390px phone in chapter 03.
   ─────────────────────────────────────────────────────────────────────── */
.iy-nav__link[aria-current="page"] { color: var(--iy-on-dark); }
.iy-nav__link[aria-current="page"]::before { opacity: 1; transform: scale(1); }

/* And on paper, where the white foreground would be invisible. Mirrors the
   .is-light block in style.css exactly; the two must not drift. */
.iy-nav.is-light .iy-nav__link[aria-current="page"] { color: var(--iy-on-light); }
.iy-nav.is-light .iy-nav__link[aria-current="page"]::before { background: var(--iy-gold-800); }

/* The mobile drawer's equivalent. Its links are not .iy-nav__link, so they
   need their own rule or the drawer gives no sense of place at all. */
.iy-mobile__links a[aria-current="page"] { color: var(--iy-fg-accent); }


/* ═══ the page hero ══════════════════════════════════════════════════════
   The inner-page equivalent of chapter 01, and deliberately NOT a copy of
   it. The homepage hero is a full viewport of cinema with a scroll cue; an
   inner page is a document and its reader has already decided to be here, so
   this is a compact banner that gets out of the way.

   IT MUST BE DARK, and that is structural rather than aesthetic.
   navContext() in script.js only writes .is-light / .is-dark once the page
   has scrolled past 40px, so at rest on an inner page the bar carries
   neither class and renders its default — the WHITE wordmark and white
   links. A paper hero would strand a white wordmark on paper, which is the
   highest-risk failure this design has.

   navContext() also reads #hero for its .is-atop state. There is no #hero on
   these pages, so heroBottom resolves to 0, .is-atop is never set, and the
   nav arrives complete with its links and CTA visible from the first frame.
   That is the correct behaviour here and it needs no code: an inner page has
   no cinematic opening to keep clear.
   ─────────────────────────────────────────────────────────────────────── */
.iy-phero {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  /* Tall enough to be a banner, short enough that the first section is on
     screen at 900px tall. Not a vh unit: this must not become a second full
     screen of nothing on a laptop. */
  min-height: clamp(21rem, 46vh, 27rem);
}

.iy-phero__canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}

/* Two bands, not the hero's three. The hero needs a radial because its
   lattice is deliberately dense toward the upper right where the nav links
   sit; this preset (contact) is flatter and half the opacity, so it needs
   the top band to keep the links off lit cells and a bottom pull so the
   headline sits on clean ground. A radial in between would only mute a
   lattice that is already quiet. */
.iy-phero__scrim {
  position: absolute; inset: 0; pointer-events: none;
  background:
    /* 1. the top band, keeping the nav links off lit cells. */
    linear-gradient(to bottom, rgba(5,5,6,.86) 0%, rgba(5,5,6,.50) 10%, transparent 26%),
    /* 2. THE LEFT WASH, and it is the one that was missing. Every string on
       this hero — crumb, eyebrow, H1, standfirst — is set left inside
       .iy-wrap, and the first render put the breadcrumb straight over a lit
       hexagon outline. The lattice seeds RANDOMLY on every load, so "it
       looked fine" is not a property of the design, it is a property of one
       page load; the scrim has to hold for any seed. Darkening the left
       protects the type and leaves the lattice legible on the right, where
       nothing is set. Same logic as the homepage hero's radial, adapted to a
       banner that is wide rather than tall. */
    linear-gradient(to right, var(--iy-black) 0%, rgba(5,5,6,.72) 34%,
                    rgba(5,5,6,.24) 62%, transparent 84%),
    /* 3. the bottom pull. */
    linear-gradient(to top, var(--iy-black) 0%, rgba(5,5,6,.55) 30%, transparent 72%);
}

.iy-phero__inner {
  position: relative; z-index: 2; width: 100%;
  padding-top: calc(var(--nav-h) + clamp(1.5rem, 5vh, 3rem));
  padding-bottom: clamp(2.25rem, 6vh, 3.75rem);
}

.iy-phero__h1 {
  margin: .35rem 0 0;
  font-family: var(--display); font-weight: 800;
  font-size: var(--fs-chapter);
  text-transform: uppercase;
  letter-spacing: -.012em;
  line-height: 1.02;
  text-wrap: balance;
  max-width: 16em;
}

.iy-phero__sub {
  margin: 1.15rem 0 0;
  max-width: 46ch;
  font-size: var(--fs-sub);
  line-height: 1.5;
  color: var(--iy-fg-soft);
}


/* ═══ the breadcrumb ═════════════════════════════════════════════════════
   Small, tracked, and set in the eyebrow's own language so it reads as part
   of the page furniture rather than as a browser control. It sits ABOVE the
   eyebrow, which means the page opens with two tracked uppercase lines — so
   the crumb is deliberately a rung smaller (--fs-micro against --fs-eyebrow)
   and softer in colour, or the two would tie.

   Marked up as <nav aria-label="Breadcrumb"><ol>, with the current page as
   the last item carrying aria-current="page" and NO link. Structured data
   for it is a BreadcrumbList in the page's JSON-LD, not markup attributes.
   ─────────────────────────────────────────────────────────────────────── */
.iy-crumb { margin: 0 0 .9rem; }

.iy-crumb ol {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .55rem .7rem;
  font-family: var(--display); font-weight: 300;
  font-size: var(--fs-micro);
  letter-spacing: .22em; text-transform: uppercase;
  color: var(--iy-fg-soft);
}

.iy-crumb li { display: flex; align-items: center; gap: .7rem; }

/* The separator is the motif, as it is everywhere else on this site. It is a
   sibling pseudo on the item that FOLLOWS, not a trailing one on every item,
   so the list never dangles a separator after the last crumb — trap 9. */
.iy-crumb li + li::before {
  content: ""; flex: none;
  width: 7px; height: 8px;
  clip-path: var(--hex);
  background: var(--iy-mark);
}

.iy-crumb a {
  color: var(--iy-fg-soft);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  /* ≥44px tap target at 390 without moving the underline off the text: the
     padding is top-weighted because a border-bottom follows the padding box,
     and symmetric padding pushed it 11px clear of its own baseline in
     session 7. Same fix as the footer's two underlined link types.
     ⚠ MEASURED, not chosen. .8rem/.35rem rendered 49x40 at 390 — the gate
     caught it four pixels short. The crumb sets at --fs-micro, the smallest
     type on the page, so its line box is ~21.6px and the padding has to carry
     the remaining 22.4px on its own. Do not reduce these by eye. */
  padding: 1.05rem 0 .45rem;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.iy-crumb a:hover { color: var(--iy-fg-accent); border-bottom-color: var(--iy-mark); }

.iy-crumb [aria-current="page"] { color: var(--iy-fg-accent); }


/* ═══ inner-page section rhythm ══════════════════════════════════════════
   .iy-chapter's padding is clamp(4.5rem, 12vh, 9rem) — right for a chapter
   that has to fill a screen on its own, far too much for a page of six
   sections that is meant to be read straight through. This is about 60% of
   it, which keeps the page feeling composed without turning it into a
   scroll.
   ─────────────────────────────────────────────────────────────────────── */
.iy-psection {
  position: relative;
  padding-block: clamp(3rem, 7.5vh, 5.5rem);
}

/* An anchored section has to clear the fixed bar when it is jumped to.
   html{scroll-padding-top} in style.css already covers the compact bar; this
   adds the breathing room that stops a heading rendering flush under it and
   reading as clipped. Applied to the ELEMENT that carries the id. */
.iy-psection[id],
.iy-cap[id] { scroll-margin-top: calc(var(--nav-h-c) + 2.25rem); }

.iy-sec-h {
  margin: 0 0 1.1rem;
  font-family: var(--display); font-weight: 800;
  font-size: var(--fs-h3);
  letter-spacing: -.014em; line-height: 1.12;
  text-wrap: balance;
  max-width: 18em;
}

/* The long-form reading column. 68ch rather than .iy-lead's 62ch because
   this is continuous prose being read, not a standfirst being scanned. */
.iy-prose { max-width: 68ch; }
.iy-prose p {
  margin: 0 0 1.15em;
  font-size: var(--fs-lead);
  line-height: 1.72;
  color: var(--iy-fg-soft);
}
.iy-prose p:last-child { margin-bottom: 0; }

/* The opening paragraph of a prose block carries the chapter, so it is set a
   rung up and in the full-strength foreground. One per block. */
.iy-prose__lead {
  font-size: var(--fs-sub) !important;
  line-height: 1.55 !important;
  color: var(--iy-fg) !important;
  max-width: 34em;
}

/* A pulled line from their own writing. Not a testimonial and never styled
   as one — there are no customer quotes anywhere on this site and §12 of the
   verified-fact rules forbid inventing any. This is Inyosi quoting
   themselves. */
.iy-pull {
  margin: clamp(2rem, 5vh, 3rem) 0;
  padding-left: clamp(1.1rem, 3vw, 2rem);
  border-left: 2px solid var(--iy-mark);
  font-family: var(--display); font-weight: 300;
  font-size: var(--fs-sub);
  line-height: 1.45;
  color: var(--iy-fg);
  max-width: 30em;
}
.iy-pull p { margin: 0; }


/* ═══ leadership ═════════════════════════════════════════════════════════
   Two people. The portraits are hexagon-masked and treated, and BOTH of
   those decisions are doing structural work rather than decoration.

   THE PROBLEM THEY SOLVE. The two banked photographs do not match and cannot
   be made to. Kubendri's is a studio headshot on flat grey; Langalethu's is
   a candid taken in a sports hall, warm, with other people at the edges of
   frame. Placed side by side untreated they read as two pictures from two
   different companies, and the mismatch is the defect a reader notices — not
   the quality of either photograph on its own.

   THE TREATMENT. Desaturate both, then lay the brand's own gold over the
   result with mix-blend-mode: color, which maps the overlay's hue onto the
   backdrop's luminance. That is a duotone, and it is what makes the two
   frames agree: after it, the only thing left of "sports hall" versus
   "studio" is the tonal range, and the hexagon crop removes the rest.

   ⚠ This is a treatment, not a fix. Proper portraits are still an open ask
   of Inyosi and are listed as a pre-go-live item. The treatment means the
   page can ship honestly in the meantime; it does not mean the photographs
   are good.

   ⚠ The gold layer must sit UNDER the hairline frame, not over it, or the
   blend eats the frame's own colour. Hence the frame being a separate
   clipped element rather than a border — clip-path cuts a border and an
   inset box-shadow both (trap 6), so a hexagon outline is always two stacked
   clipped layers on this project.
   ─────────────────────────────────────────────────────────────────────── */
.iy-people {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.75rem, 4vw, 3.25rem);
  /* ⚠ STATED, not inherited. A grid item's default is stretch, and this row
     contains a fixed-ratio portrait: session 8's logo rendered ~1000px wide
     and distorted because a column flex container stretched it and width:auto
     then resolved against the stretched box rather than the intrinsic ratio.
     Every img in a flex or grid container on this project states its
     cross-axis behaviour explicitly. */
  align-items: start;
}

.iy-person { min-width: 0; }

/* The frame: the gold hairline, as its own clipped layer. */
.iy-person__hex {
  position: relative;
  width: 100%;
  max-width: 232px;
  aspect-ratio: 1 / 1.06;
  clip-path: var(--hex);
  background: var(--iy-frame);
  margin: 0 0 1.35rem;
}

/* The face: inset by the hairline's width, clipped to the same shape, and
   the thing that actually holds the photograph. overflow:hidden so the
   img's own transform cannot escape it. */
.iy-person__face {
  position: absolute; inset: 1.5px;
  clip-path: var(--hex);
  overflow: hidden;
  background: var(--iy-media-bg);
}

/* --px / --py / --pz are set per person on .iy-person__hex, because the two
   photographs need different framing and hard-coding one of them would crop
   the other badly.

   ⚠ object-fit: cover crops on ONE AXIS ONLY — whichever axis the source
   over-fills. Both sources here are square 400x400 in a taller-than-wide
   box, so cover crops HORIZONTALLY and the Y half of object-position is dead
   code (session 8 lost a slide to exactly this). The vertical framing is
   therefore done with a scale + origin, which works on both axes, and --py
   is deliberately absent rather than present and inert. */
.iy-person__img {
  width: 100%; height: 100%;
  display: block;
  object-fit: cover;
  object-position: var(--px, 50%) 50%;
  transform: scale(var(--pz, 1));
  transform-origin: 50% var(--pfocus, 50%);
  filter: grayscale(1) contrast(1.06) brightness(.98);
}

/* The duotone layer. `color` takes hue and saturation from this gradient and
   luminance from the photograph underneath, which is the definition of a
   duotone and is why a flat fill would not do. */
.iy-person__face::after {
  content: ""; position: absolute; inset: 0;
  background: var(--iy-metal);
  mix-blend-mode: color;
  opacity: .62;
  pointer-events: none;
}

.iy-person__name {
  margin: 0 0 .3rem;
  font-family: var(--display); font-weight: 800;
  font-size: var(--fs-card);
  letter-spacing: -.01em;
  color: var(--iy-fg);
}

.iy-person__role {
  margin: 0 0 .9rem;
  font-family: var(--display); font-weight: 300;
  font-size: var(--fs-eyebrow);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--iy-fg-accent);
}

.iy-person__bio {
  margin: 0;
  font-size: var(--fs-lead);
  line-height: 1.66;
  color: var(--iy-fg-soft);
  max-width: 34ch;
}

/* Below 620 the row becomes a column, and the portrait stops being a column
   header and becomes a lead image. It also has to be restated at matching
   specificity: a media query adds NO specificity, and a (0,2,0) rule
   elsewhere would beat a (0,1,0) reset written here — trap 3, which shipped
   a real defect in chapter 03. */
@media (max-width: 620px) {
  .iy-people { grid-template-columns: minmax(0, 1fr); gap: clamp(2.25rem, 8vw, 3rem); }
  .iy-people .iy-person__hex { max-width: 190px; }
  .iy-people .iy-person__bio { max-width: none; }
}


/* ═══ the capability blocks ══════════════════════════════════════════════
   Eight of them, each an anchor target. This page exists to retire sixteen
   404ing links (eight homepage tiles, eight footer links) by giving every
   frozen capability slug a real destination, so the ANCHOR IDS ARE THE
   CONTRACT: they are derived from the slugs frozen in
   03-information-architecture.md §2 and changing one breaks sixteen links.
   ─────────────────────────────────────────────────────────────────────── */
/* The jump list. Eight anchors is a long page, and an index whose only route
   to its eighth item is scrolling is an index that has not finished its job.
   It is a <nav> with a real <ul>, not a row of styled spans, because it IS
   navigation and a screen reader should be able to skip it as one.

   Set in the eyebrow's tracked language but a rung down, so it reads as
   furniture and does not tie with the eyebrow sitting directly above it. */
.iy-jump { margin: 0 0 clamp(2rem, 5vh, 3rem); }

.iy-jump ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap;
  gap: .55rem;
}

.iy-jump a {
  display: inline-flex; align-items: center;
  min-height: 44px;                 /* the tap-target floor, stated not hoped */
  padding: .5rem .95rem;
  border: 1px solid var(--iy-rule);
  font-size: var(--fs-micro);
  letter-spacing: .06em;
  color: var(--iy-fg-soft);
  text-decoration: none;
  transition: color .3s var(--ease), border-color .3s var(--ease), background .3s var(--ease);
}
.iy-jump a:hover {
  color: var(--iy-fg-accent);
  border-color: var(--iy-mark);
  background: rgba(90,58,5,.05);
}

.iy-caplist {
  display: grid;
  gap: clamp(1.5rem, 3.5vw, 2.5rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* ⚠ STATED, and it is fixing something. A grid item defaults to stretch, so
     every card grew to the height of the tallest in its row — which put a
     large empty white band inside the six capabilities that carry only a
     one-liner. Those six are thin because Inyosi have not yet supplied the
     detail, and that is displayed honestly rather than padded; but an empty
     box reads as a broken card, not as an honest one. With `start` the card
     ends where its content ends and the slack falls OUTSIDE it, as layout. */
  align-items: start;
}

/* ⚠ Eight items in two columns is even, so auto-placement is well defined
   here — but it is STATED anyway, because trap 8 on this project is that an
   odd child count in a multi-column grid is not "two then one" and nobody
   remembers which until it renders wrong. If a ninth capability is ever
   added, this is the line to read. */
.iy-cap {
  position: relative;
  min-width: 0;
  padding: clamp(1.4rem, 3vw, 2.1rem);
  background: var(--iy-bg-raised);
  border: 1px solid var(--iy-rule);
  border-top: 2px solid var(--iy-mark);
}

.iy-cap__n {
  display: block;
  margin: 0 0 .55rem;
  font-family: var(--display); font-weight: 300;
  font-size: var(--fs-eyebrow);
  letter-spacing: .28em;
  color: var(--iy-fg-accent);
}

.iy-cap__h {
  margin: 0 0 .6rem;
  font-family: var(--display); font-weight: 800;
  font-size: var(--fs-card);
  letter-spacing: -.008em; line-height: 1.18;
  color: var(--iy-fg);
  text-wrap: balance;
}

.iy-cap__line {
  margin: 0 0 .9rem;
  font-size: var(--fs-lead);
  line-height: 1.55;
  color: var(--iy-fg-accent);
}

.iy-cap__body { margin: 0; font-size: var(--fs-lead); line-height: 1.68; color: var(--iy-fg-soft); }

/* What the category covers. A list of nouns, never a list of claims: every
   entry has to be a thing named in the verified-fact record, whose
   prohibition list is what stops this becoming a specification sheet nobody
   can stand behind. */
.iy-cap__covers {
  list-style: none; margin: 1rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .45rem;
}
.iy-cap__covers li {
  font-size: var(--fs-micro);
  letter-spacing: .04em;
  color: var(--iy-fg-soft);
  border: 1px solid var(--iy-rule);
  padding: .38rem .7rem;
}

@media (max-width: 860px) {
  .iy-caplist { grid-template-columns: minmax(0, 1fr); }
}


/* ═══ the detail list ════════════════════════════════════════════════════
   Address / telephone / email / hours. A <dl>, because that is what it is.
   ─────────────────────────────────────────────────────────────────────── */
.iy-pdetail { margin: 0; display: grid; gap: 1.5rem; }

.iy-pdetail dt {
  font-family: var(--display); font-weight: 300;
  font-size: var(--fs-eyebrow);
  letter-spacing: .28em; text-transform: uppercase;
  color: var(--iy-fg-accent);
  margin: 0 0 .4rem;
}

.iy-pdetail dd {
  margin: 0;
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--iy-fg);
  max-width: 34ch;
}

.iy-pdetail a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--iy-rule-2);
  /* Top-weighted padding again: ≥44px tap target without the underline
     leaving its own text. */
  display: inline-block;
  padding: .7rem 0 .3rem;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.iy-pdetail a:hover { color: var(--iy-fg-accent); border-bottom-color: var(--iy-mark); }


/* ═══ the closing band ═══════════════════════════════════════════════════
   Every inner page ends by pointing somewhere. It is one CTA, not two: a
   band offering a choice is a band that gets skipped.
   ─────────────────────────────────────────────────────────────────────── */
.iy-cta-band { text-align: center; }

.iy-cta-band__h {
  margin: 0 0 1rem;
  font-family: var(--display); font-weight: 800;
  font-size: var(--fs-h3);
  letter-spacing: -.014em; line-height: 1.12;
  text-wrap: balance;
}

.iy-cta-band__p {
  margin: 0 auto 1.75rem;
  max-width: 46ch;
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--iy-fg-soft);
}

/* ── the couplet ────────────────────────────────────────────────────────
   Archivo 300 tracked over Archivo 800 tight: one family at two extremes,
   the same pairing the homepage hero uses. It exists because Inyosi's own
   writing has a couplet in it — two consecutive lines where the second
   answers the first — and this is the one place on the site where that
   rhetorical shape survives intact instead of being quarried for a headline.

   ⚠ It is set at --fs-chapter, the same rung as the page H1, and that is
   deliberate rather than a tie. Each of these pages has exactly TWO display
   moments — the claim it opens with and the claim it closes on — and they
   are meant to rhyme. A tie is two DIFFERENT levels of the hierarchy
   resolving to the same size by accident; this is one level used twice. If a
   third element ever lands on this rung, that is the tie, and it is the one
   to fix.

   text-wrap is NOT `pretty`. In Edg/151 that property parses, reports
   CSS.supports true, computes to "pretty" and changes nothing at all —
   measured on a deliberately orphaned control block, 107px of last line with
   it and 107px without. `balance` measurably works and is what is used. */
.iy-couplet { margin: 0 0 1.5rem; }

.iy-couplet__a,
.iy-couplet__b {
  display: block;
  font-family: var(--display);
  font-size: var(--fs-chapter);
  line-height: 1.06;
  text-wrap: balance;
  margin: 0;
}
.iy-couplet__a {
  font-weight: 300;
  letter-spacing: .012em;
  color: var(--iy-fg-soft);
}
.iy-couplet__b {
  font-weight: 800;
  letter-spacing: -.018em;
  color: var(--iy-fg);
}

.iy-cta-band__ctas {
  display: flex; flex-wrap: wrap; gap: .9rem;
  justify-content: center;
  margin-top: 1.9rem;
}


/* ═══ two-column page layout ═════════════════════════════════════════════
   Used by /contact/ (form beside details) and available to any page that
   needs a sidebar. Collapses at 1150 rather than a smaller number because
   that is where the homepage's own stacked-form breakpoint sits, and two
   breakpoints describing the same layout switch must be the same number —
   trap 13, which nearly drew the engage rail sideways across a vertical
   list.
   ─────────────────────────────────────────────────────────────────────── */
.iy-psplit {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

/* ── the heading split ──────────────────────────────────────────────────
   Heading in a narrow left column, the prose it introduces in a wider right
   one. This is the homepage's own `--split` head idiom, and on these pages it
   is fixing a specific defect rather than decorating: a 68ch prose column
   alone left the right HALF of a 1440 viewport empty, which reads as a page
   that has not been finished rather than as composed space. Two columns use
   the measure the page actually has.

   The prose column is the wider one on purpose. A heading has a natural
   maximum useful width and body copy does not; giving the heading the wide
   column would set it in one long line and push the reading column narrow,
   which is the wrong way round.

   ⚠ `align-items: start` is STATED. A grid item defaults to stretch, and a
   stretched heading cell would put the headline's own box at the full height
   of the prose beside it — invisible until something is vertically centred
   inside it and then baffling. */
.iy-psplit--head {
  grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  align-items: start;
}
.iy-psplit--head .iy-sec-h { max-width: 14em; }

@media (max-width: 1150px) {
  .iy-psplit { grid-template-columns: minmax(0, 1fr); gap: clamp(2.5rem, 7vw, 3.5rem); }
  /* Restated at matching specificity rather than left to the media query,
     which adds none — trap 3, and it shipped a two-column layout to a 390px
     phone once already on this project. */
  .iy-psplit.iy-psplit--head { grid-template-columns: minmax(0, 1fr); }
}


/* ═══ the static path ════════════════════════════════════════════════════
   No JS, reduced motion, or ?cap=1. The lattice canvas never mounts, so the
   hero has to be a composed dark banner on its own rather than a black box
   waiting for a script. It already is: .iy-phero's ground comes from
   .iy-ground--dark and the scrim paints regardless. Nothing to disable here
   — this block exists to record that the question was asked, because on this
   project a static fallback is a RELAYOUT and not a stopped animation
   (trap 18, the marquee that clipped seven items away with no overflow
   reported anywhere).
   ─────────────────────────────────────────────────────────────────────── */
