/* ═══════════════════════════════════════════════════════════════════════
   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.

   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. 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.
   ─────────────────────────────────────────────────────────────────────── */
.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. Every string on this hero — crumb, eyebrow, H1,
       standfirst — is set left inside .iy-wrap. The lattice seeds RANDOMLY
       on every load, so the scrim has to protect the type for any seed, not
       just one that happens to render clear. 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. */
.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 pushes it clear of its own baseline. Same fix as
     the footer's two underlined link types.
     ⚠ MEASURED, not chosen. 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 Inyosi's own writing. Not a testimonial and never
   styled as one — there are no customer quotes anywhere on this site. */
.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. Portraits taken at different times, against
   different backgrounds and in different light rarely match. Placed side by
   side untreated they read as pictures from different places, and that
   mismatch is what a reader notices.

   THE TREATMENT. Desaturate every portrait, 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 frames agree: after it, only the tonal range differs, and the
   hexagon crop removes the rest.

   ⚠ 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, 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: a stretched image's width:auto resolves
     against the stretched box rather than the intrinsic ratio, distorting
     it. 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. 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. */
@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
   capability slug a real destination, so the ANCHOR IDS ARE THE CONTRACT:
   changing one breaks sixteen links elsewhere on the site.
   ─────────────────────────────────────────────────────────────────────── */
/* 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 any capability card with less content than
     its neighbours. An empty box reads as a broken card, not 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: an odd child count in a multi-column grid
   does not auto-place as "two then one", and it renders wrong until this is
   set explicitly. 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, so this
   never reads as 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.
   ─────────────────────────────────────────────────────────────────────── */
.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. */
  .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.
   ─────────────────────────────────────────────────────────────────────── */
