/* =============================================================================
   institutional.css  ·  the /institutional page              (2026-08-14)
   =============================================================================
   The public "Institutional" page: the other half of the landing's
   `#user-choice` audience fork, in the client's six beats:

     Institutional Solutions · Tailored Solutions · Deployment Solutions ·
     Accelerate Your Research · Contact · Enterprise Solutions

   WHY A PAGE-PRIVATE STYLESHEET, in the `inst-` namespace:
     Same reasoning individual.css documents, and the same discipline. Everything
     here is one page's furniture. This file reads ONLY the shared token contract
     (static/css/site-tokens.css): no new colour, curve, duration, type size,
     radius or z-band is invented. The namespace is what guarantees this page and
     /individual can never collide: `inst-` vs `ind-` on every class, and
     `#instrail` vs `#irail` on the rail.

   LOAD ORDER: public_base.html links fonts.css, site-tokens.css, site-base.css
   and site-chrome.css (all in blocks above `additional_head`), then this file.
   So every var() below is defined by the time it is read, and this file wins
   ties against the chrome.

   MOTION / NO-JS CONTRACT:
     Every rule below paints the page FULLY VISIBLE by default. The reveal
     choreography is opt-in and double-gated: it needs BOTH `html.inst-js` (set
     by an inline script in the template's <head>, so scripting-off never arms
     it) AND `prefers-reduced-motion: no-preference`. There is therefore no state
     in which content can be stranded at opacity 0, and institutional.js adds a
     `load` safety net on top of that. Same reason the rail is hidden without JS:
     its dots scroll, and a control that cannot do its job should not take six
     tab stops in front of the content.

   COLOUR · THE LEVEL LADDER (added 2026-08-14)
   -----------------------------------------------------------------------------
   This file used to name a `--sem-*` token three times in 870 lines, which is
   why the page read as flat navy while /guides/* read as vibrant off the same
   palette. It now runs the guides' `entry -> build -> master` ladder end to
   end. See the `THE LEVEL LADDER` block below for the token set and the
   per-section assignment; the short version is that NOTHING in this file names
   a `--sem-*` except that block and the two places where a run is explicitly
   the whole ladder (the step connectors, the three ladder bands' mesh). Every
   other rule reads `--inst-lvl-*`, so re-pointing a level is a six-line change.

   RESTRAINT, so this does not become a rainbow:
     · ONE LEVEL PER SECTION. Three sections carry more, and in all three the
       content is genuinely a ladder: the three steps, the four matrix rows, the
       four tier cards.
     · ACCENT IS EMPHASIS, NEVER BODY COPY. No paragraph, lede, list item, label
       or card body on this page takes a hue. `.inst-grad` is scoped to
       `.inst-title` only and is therefore unavailable below display size.
     · ONE GRADIENT-FILLED ELEMENT PER SECTION plus the CTA. The step band's
       three number tiles are that section's one gradient element, and they are
       a set rather than three separate decisions.
     · THE CTA LAYER STAYS ON ENTRY, site-wide. `--sem-entry` is the contract's
       own definition of "the primary action" (site-tokens.css:437), so the
       buttons deliberately do NOT follow their card's rung: a violet and a mint
       "Contact" button in the same row would be reading the level as if it were
       a button variant, which it is not.

   COLOUR · THE ONE RULE THIS PAGE FAMILY KEEPS BREAKING
   -----------------------------------------------------------------------------
   CYAN IS NEVER THE SOLE DIFFERENTIATOR. --accent (#00f2fe) measures 1.004:1
   against white body ink. It is a perfectly good colour against the page black
   (~14:1) and it is used freely for icons, marks and glows, but the moment it
   is asked to say "this text is a link" or "this button is different from that
   one", it is saying nothing at all to a reader who cannot separate the hues.
   So every state on this page carries a second, non-hue channel:

     · links in prose      cyan + a real underline
     · primary button      accent FILL carrying dark ink (a light block)
     · ghost button        outline carrying light ink (a dark block)
     · pending button      DASHED outline + the words "(request access)"
     · active rail dot     a larger disc + a ring + aria-current
     · focus               a 2px accent ring with an offset, on every control

   Ink levels, measured on this page's own ground (#070a1c under the backdrop's
   radials, which shift surface luminance by under .01):
     --ink-1  21:1   headings
     --ink-2  ~12.4:1 body
     --ink-3  ~7.2:1  ledes and secondary
   --ink-4 is NOT used for body-sized text anywhere here.
   ============================================================================= */

/* ---------------------------------------------------------------------------
   PAGE SHELL
   --------------------------------------------------------------------------- */
body.inst-page {
  background: var(--bg-page);
  font-family: var(--font-ui);
  color: var(--ink-2);
}

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

/* ---------------------------------------------------------------------------
   THE LEVEL LADDER  ·  entry -> build -> master
   ---------------------------------------------------------------------------
   The same system guide-system.css:81-190 runs on /guides/*, rebuilt under this
   page's namespace because guide-system.css is NOT loaded on public_base.html
   (only fonts / site-tokens / site-base / site-chrome are). NO TOKEN VALUE IS
   FORKED: every declaration below points at a `--sem-*` or `--grad-*` already
   defined in site-tokens.css:439-508. Re-pointing a level is a six-line change
   here and nothing else in this file names a `--sem-*` directly.

   The defaults live on :root, NOT on the components, and that is load-bearing
   for the same reason guide-system.css documents it: custom properties inherit,
   so a default declared here is beaten by an `.inst-lv--*` on any ancestor
   (which is how a whole section takes a level) AND by one on the element itself
   (which is how a single card inside that section can still be a different
   rung). Declaring the defaults on `.inst-card` instead would make every card
   immune to its own section's level, because a direct declaration always beats
   an inherited one however far up it sits.

   PER-SECTION ASSIGNMENT, from 2026-08-14-marketing-page-accent-system.md, and
   it is systematic rather than decorative so a reader can infer the rule:

     inst-hero                        entry
     inst-solutions   (4 personas)    entry, one level for the whole band
     inst-accelerate  (3 steps)       entry / build / master   <- a real ladder
     inst-deployment  (matrix rows)   SaaS entry, API entry, MCP build,
                                      White-label master
     inst-contact                     build (the level its `--amb-build` class
                                      already declared before this pass)
     inst-enterprise  (4 tier cards)  the SAME four products as the matrix, on
                                      the SAME four rungs. The two sections have
                                      to agree product for product or the ladder
                                      is noise.
   --------------------------------------------------------------------------- */
:root {
  --inst-lvl:      var(--sem-entry);
  --inst-lvl-2:    var(--sem-entry-2);
  --inst-lvl-wash: var(--sem-entry-wash);
  --inst-lvl-line: var(--sem-entry-line);
  --inst-lvl-edge: var(--sem-entry-edge);
  --inst-lvl-glow: var(--sem-entry-glow);
  --inst-lvl-grad: var(--grad-entry);
}

.inst-lv--entry {
  --inst-lvl:      var(--sem-entry);
  --inst-lvl-2:    var(--sem-entry-2);
  --inst-lvl-wash: var(--sem-entry-wash);
  --inst-lvl-line: var(--sem-entry-line);
  --inst-lvl-edge: var(--sem-entry-edge);
  --inst-lvl-glow: var(--sem-entry-glow);
  --inst-lvl-grad: var(--grad-entry);
}

.inst-lv--build {
  --inst-lvl:      var(--sem-build);
  --inst-lvl-2:    var(--sem-build-2);
  --inst-lvl-wash: var(--sem-build-wash);
  --inst-lvl-line: var(--sem-build-line);
  --inst-lvl-edge: var(--sem-build-edge);
  --inst-lvl-glow: var(--sem-build-glow);
  --inst-lvl-grad: var(--grad-build);
}

.inst-lv--master {
  --inst-lvl:      var(--sem-master);
  --inst-lvl-2:    var(--sem-master-2);
  --inst-lvl-wash: var(--sem-master-wash);
  --inst-lvl-line: var(--sem-master-line);
  --inst-lvl-edge: var(--sem-master-edge);
  --inst-lvl-glow: var(--sem-master-glow);
  --inst-lvl-grad: var(--grad-master);
}

/* The two ambience classes the template already carried are now ALIASES for the
   ladder, so a section states its level once, in the markup, and the hairline,
   the mesh, the card edges and the headline gradient all follow from it. */
.inst-section--amb-build  { --inst-lvl:      var(--sem-build);
                            --inst-lvl-2:    var(--sem-build-2);
                            --inst-lvl-wash: var(--sem-build-wash);
                            --inst-lvl-line: var(--sem-build-line);
                            --inst-lvl-edge: var(--sem-build-edge);
                            --inst-lvl-glow: var(--sem-build-glow);
                            --inst-lvl-grad: var(--grad-build); }
.inst-section--amb-master { --inst-lvl:      var(--sem-master);
                            --inst-lvl-2:    var(--sem-master-2);
                            --inst-lvl-wash: var(--sem-master-wash);
                            --inst-lvl-line: var(--sem-master-line);
                            --inst-lvl-edge: var(--sem-master-edge);
                            --inst-lvl-glow: var(--sem-master-glow);
                            --inst-lvl-grad: var(--grad-master); }

/* ONE static backdrop. No animation, no JS, no canvas. Continuous background
   motion behind reading text is a WCAG 2.3.3 problem, which is why
   public_base.html defaults the animated orb OFF and this page opts in to a
   still field instead. Purely AMBIENT: it encodes nothing, and the alphas are
   chosen so the surface luminance under body text is unchanged to two decimal
   places (the --ink-2 measurement in the docblock still holds). */
.inst-backdrop {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 72% 52% at 50% -12%, rgba(0, 242, 254, .07), transparent 70%),
    radial-gradient(ellipse 55% 40% at 3% 46%, rgba(179, 136, 255, .04), transparent 70%),
    radial-gradient(ellipse 58% 42% at 97% 82%, rgba(105, 240, 174, .032), transparent 70%),
    radial-gradient(ellipse 60% 45% at 88% 106%, rgba(88, 185, 255, .045), transparent 72%);
}

.inst-main { position: relative; z-index: var(--z-content); }
.inst-main:focus { outline: none; }  /* it receives programmatic focus from the skip link */

/* ---------------------------------------------------------------------------
   SECTION + CONTENT COLUMN
   1200 / clamp(20,4vw,40) is the measure the landing's `.act9-wrap` and
   individual.css both use, so this page's text column starts and ends on the
   same verticals as the shared footer directly beneath it.

   `scroll-margin-top` is load-bearing, not polish: the header is `position:
   fixed`, so a hash jump to `#inst-contact` would otherwise land the section's
   first line underneath it. --sh-anchor is declared once, on :root in
   site-chrome.css, as the header's real height per breakpoint.
   --------------------------------------------------------------------------- */
.inst-section {
  position: relative;
  padding: var(--section-pad-y) 0;
  scroll-margin-top: var(--sh-anchor, 62px);
}
/* The rail moves focus to the section it scrolls to (js/institutional.js), which
   needs tabindex="-1" in the template. Programmatic focus must not draw a ring
   around a whole band: the ring belongs on the control the user activated. */
.inst-section:focus { outline: none; }
.inst-section + .inst-section { padding-top: 0; }

/* Positioned and lifted so the ambient mesh below (`.inst-section::after`, a
   z-index:0 positioned layer) paints BEHIND the content column. Without the
   z-index the wrap is an unpositioned block, which paints under every
   positioned sibling however low its z-index, and the orbs would wash over the
   text. */
.inst-wrap {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 40px);
}

/* ---------------------------------------------------------------------------
   AMBIENT MESH  ·  where most of the guides' perceived vibrancy actually lives
   ---------------------------------------------------------------------------
   `.gs-mesh__orb--1/2/3` (guide-system.css:368-386) is three 400-700px discs at
   `--sem-*-glow`, blurred to 100px, drifting on a 14-20s loop. This is the same
   idea with two deliberate differences:

     · SOFT RADIAL STOPS, NOT `filter: blur()`. A blurred 700px disc has to
       overhang the box to look like a bloom, which means either `overflow:
       hidden` on every band or a horizontal scrollbar. A radial-gradient can be
       CENTRED outside its own box and is clipped to it for free, so the same
       bloom cannot overflow anything by construction.
     · NOTHING ANIMATES. public_base.html defaults the animated background off
       on this page for WCAG 2.3.3 (continuous motion behind reading text), and
       the file's own docblock commits to a still field. A drifting orb would
       reintroduce exactly what that decision removed, and it would then need a
       reduced-motion escape hatch. Static keeps the colour and owes nothing.

   The colour is the SECTION's own level, so the page walks cyan -> violet ->
   mint down the scroll, matching the hairline between the bands. The three
   sections whose CONTENT is a ladder override this below to walk the ladder
   inside the band as well; that is informational, not a rainbow.

   ALPHA BUDGET: `--sem-*-glow` is .30-.32, this layer is at .52, so the peak is
   ~.16 and it sits in the OUTER margins, not under the text column. Composited,
   the surface under body copy moves from #070a1c to about rgb(6,41,59) at the
   very worst point, where --ink-2 still measures 9.6:1. The file's headline
   contrast claims therefore still hold.
   --------------------------------------------------------------------------- */
.inst-section::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: .52;
  background:
    radial-gradient(circle 560px at -4% 6%,   var(--inst-lvl-glow), transparent 64%),
    radial-gradient(circle 460px at 104% 92%, var(--inst-lvl-glow), transparent 62%);
}
/* Alternate bands mirror, so six stacked sections do not read as one repeated
   wallpaper tile. Cosmetic only: if a section is ever inserted the orbs swap
   sides and nothing else changes. */
.inst-section:nth-of-type(even)::after {
  background:
    radial-gradient(circle 560px at 104% 6%, var(--inst-lvl-glow), transparent 64%),
    radial-gradient(circle 460px at -4% 92%, var(--inst-lvl-glow), transparent 62%);
}

/* THE THREE LADDER BANDS. Their content walks entry -> build -> master, so
   their atmosphere does too, in the same reading order as the items:
   left-to-right for the three steps and the four tier cards, top-to-bottom for
   the matrix rows. */
#inst-accelerate::after {
  background:
    radial-gradient(circle 420px at 6% 22%,  var(--sem-entry-glow),  transparent 66%),
    radial-gradient(circle 420px at 50% 96%, var(--sem-build-glow),  transparent 66%),
    radial-gradient(circle 420px at 96% 18%, var(--sem-master-glow), transparent 66%);
}
#inst-deployment::after {
  background:
    radial-gradient(circle 480px at -4% 4%,   var(--sem-entry-glow),  transparent 64%),
    radial-gradient(circle 420px at 104% 52%, var(--sem-build-glow),  transparent 64%),
    radial-gradient(circle 460px at -2% 98%,  var(--sem-master-glow), transparent 64%);
}
#inst-enterprise::after {
  background:
    radial-gradient(circle 420px at 8% 10%,  var(--sem-entry-glow),  transparent 66%),
    radial-gradient(circle 420px at 58% 98%, var(--sem-build-glow),  transparent 66%),
    radial-gradient(circle 440px at 98% 14%, var(--sem-master-glow), transparent 66%);
}

/* A hairline between beats so six stacked bands read as a sequence, walking
   cyan -> violet -> mint down the page. AMBIENT, NOT INFORMATIONAL: a 1px
   hairline that fades to transparent at both ends is under every contrast floor
   by construction, which is exactly why it is safe to vary by hue and exactly
   why nothing may ever be hung off it. Its only job is to stop the page reading
   as one long navy scroll. */
.inst-section::before {
  content: "";
  position: absolute; left: 50%; top: 0;
  width: min(1200px, 100% - 2 * clamp(20px, 4vw, 40px));
  transform: translateX(-50%);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--inst-lvl-line), transparent);
}
.inst-section:first-of-type::before { display: none; }

/* ---------------------------------------------------------------------------
   SECTION HEADERS
   No mono `// LABEL` eyebrow: the client had those removed site-wide on
   2026-08-05 as a duplicate of the rail label sitting beside the same section.
   Each section's first `.inst-rise` child is therefore its <h1>/<h2>.
   --------------------------------------------------------------------------- */
.inst-head { max-width: 860px; }
.inst-head--center { margin-inline: auto; text-align: center; }

/* Section 6 puts a button opposite the heading. Wrapping (not shrinking) is
   deliberate: at narrow widths the button drops under the title at full width
   rather than squeezing the heading into a column. */
.inst-head--split {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-5);
  max-width: none;
}
.inst-head--split .inst-title { flex: 1 1 380px; }

.inst-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--type-d3);
  font-weight: 600;
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  color: var(--ink-1);
}
/* The hero H1 is a full sentence (100 characters), not a two-word display line.
   It deliberately does NOT take --type-d2 the way /individual's four-word hero
   does: at d2 this heading is five lines at 1440 and eight on a phone, which
   reads as a paragraph set in display type. d3 keeps it to three lines at 1440
   and it is still the largest thing on the page. */
.inst-title--hero { font-size: var(--type-d3); }

/* ACCENT-SPLIT HEADING · the guides' `.gs-hero__title span` / `.gs-gradient`
   (guide-system.css:478), under this page's namespace and reading the SECTION's
   level rather than a fixed entry->build ramp.

   Split at a natural phrase boundary, never mid-clause: the gradient has to
   read as emphasis on a phrase, not as a colour accident halfway through a
   noun. See the per-heading note in templates/institutional.html.

   CONTRAST: this only ever wraps DISPLAY text (--type-d3, 30 to 50px), so the
   3:1 large-text floor applies, and every stop clears it on #070a1c with room
   to spare: cyan 14.0:1, --accent-2 #58b9ff 8.0:1, violet #b388ff 7.4:1,
   --sem-build-2 #8b6dff 5.4:1, mint #69f0ae 13.3:1, --sem-master-2 #2fe0d0
   10.8:1. It is deliberately NOT available to anything smaller, which is why
   there is no `.inst-grad` on a body or card-title selector anywhere below.

   `-webkit-text-fill-color: transparent` is what actually paints the gradient
   through the glyphs; `color` stays inherited underneath so a browser without
   background-clip support still renders solid --ink-1 text rather than nothing. */
.inst-title .inst-grad {
  background: var(--inst-lvl-grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.inst-sub {
  margin: var(--space-5) 0 0;
  font-size: var(--type-u3);
  line-height: var(--lh-body);
  color: var(--ink-3);
}

/* ---------------------------------------------------------------------------
   BUTTONS
   Three variants, and the difference between them is never only hue:
     primary  FILL   accent gradient carrying dark ink  -> a light block
     ghost    OUTLINE solid hairline carrying light ink -> a dark block
     pending  OUTLINE DASHED + a visible "(request access)" suffix
   In greyscale that is light-block / dark-block-solid-edge / dark-block-dashed-
   edge, which is three distinguishable things with the hue removed entirely.

   All are >=48px tall (>=40px for --sm), i.e. clear of the 44px tap-target floor
   at every breakpoint where they are the primary control.
   --------------------------------------------------------------------------- */
.inst-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-height: 48px;
  padding: 0 var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-ui);
  font-size: var(--type-u5);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform    var(--dur-2) var(--ease-out),
    box-shadow   var(--dur-2) var(--ease-out),
    background   var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    color        var(--dur-2) var(--ease-out);
}
.inst-btn--sm { min-height: 40px; padding: 0 var(--space-4); font-size: var(--type-u6); }
.inst-btn__arrow { flex: 0 0 auto; }

.inst-btn--primary {
  background: var(--grad-accent);
  color: #04121a;
  box-shadow: 0 6px 18px rgba(0, 242, 254, .18);
}
.inst-btn--primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 26px var(--accent-glow);
  color: #04121a;
}

.inst-btn--ghost {
  background: var(--accent-wash);
  border-color: var(--accent-line-hi);
  color: var(--ink-1);
}
.inst-btn--ghost:hover {
  border-color: var(--accent);
  background: rgba(0, 242, 254, .1);
  color: var(--ink-1);
}

/* PENDING · a CTA whose destination the client has not supplied.
   See the TODO(client) block in templates/institutional.html section 6. It is a
   real link to the consult form, and it says so: the dashed edge marks it as
   provisional for a sighted reader and the "(request access)" suffix says the
   same thing in words for everyone else. The moment the two docs URLs exist,
   this variant and the suffix both come out. */
.inst-btn--pending {
  background: transparent;
  border-style: dashed;
  border-color: var(--line-2);
  color: var(--ink-2);
}
.inst-btn--pending:hover {
  border-color: var(--accent-line-hi);
  background: var(--sf-1);
  color: var(--ink-1);
}
.inst-btn__hint { font-weight: 500; color: var(--ink-3); }

.inst-ctas {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4);
  margin-top: var(--space-6);
}
.inst-ctas--center { justify-content: center; }

.inst-note {
  margin: var(--space-4) 0 0;
  font-family: var(--font-mono);
  font-size: var(--type-m2);
  letter-spacing: var(--track-data);
  color: var(--ink-3);
}

/* ---------------------------------------------------------------------------
   FOCUS
   ONE visible ring for every interactive element. The site accent, not the UA
   default: Chromium draws its default ring in rgb(16,16,16), which on this navy
   is an invisible focus indicator.
   --------------------------------------------------------------------------- */
.inst-page a:focus-visible,
.inst-page button:focus-visible,
.inst-page input:focus-visible,
.inst-page textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
.inst-btn:focus-visible { border-radius: var(--r-pill); }

/* ---------------------------------------------------------------------------
   MEDIA PANEL  ·  and the missing-image contract
   Every photo on this page sits in one of these. The panel carries the aspect
   box, the radius and the hairline, so the layout is identical whether the
   image arrives or not: explicit width/height on the <img> plus `aspect-ratio`
   here means the box is reserved before a byte is fetched, and there is no CLS
   either way.

   MISSING IMAGES DEGRADE TO A BRANDED PANEL, NOT A BROKEN-IMAGE ICON.
   js/institutional.js adds `is-missing` when an <img> errors (and also catches
   images that already failed before the deferred script ran, via
   `complete && naturalWidth === 0`).

   `opacity: 0` and NOT `display: none` on the failed image, deliberately: the
   browser's broken-image glyph is part of the replaced element's rendering, so
   opacity hides it, while an opacity-0 <img> is still in the accessibility
   tree, so its alt text survives. `display: none` would take the alt with it,
   which on a page whose photos carry the only description of each persona is a
   real loss. The ring below is a pure `::after` box with no text content, so it
   adds nothing to the a11y tree and cannot duplicate the alt.
   --------------------------------------------------------------------------- */
.inst-media {
  position: relative;
  /* 3:2, matching the masters' real 1600x1067 / 800x533 geometry. Not 4:3: with
     `object-fit: cover` a 4:3 box crops about 11% off the top and bottom of
     every frame, which on the boardroom shot takes the laptops out of it. */
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--r-lg);
  /* Reads the level, so the empty-panel state and the frame around a loaded
     photo both belong to the band they sit in. */
  border: 1px solid var(--inst-lvl-line);
  background:
    linear-gradient(160deg, var(--inst-lvl-wash), transparent 70%),
    var(--bg-raise);
}
.inst-media__img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity var(--dur-2) var(--ease-out);
}
.inst-media.is-missing .inst-media__img { opacity: 0; }
.inst-media.is-missing::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  /* Above .inst-persona .inst-media::before, which is a positioned z-index:1
     pseudo on the same element and would otherwise paint over this. */
  z-index: 2;
  width: clamp(38px, 14%, 64px); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: 2px solid var(--inst-lvl-edge);
  border-radius: var(--r-full);
  box-shadow: 0 0 0 6px var(--inst-lvl-wash);
}

.inst-media--card { border-radius: 0; border: 0; }

/* ---------------------------------------------------------------------------
   (1) HERO
   --------------------------------------------------------------------------- */
.inst-hero { padding-top: clamp(56px, 8vw, 104px); }
.inst-hero .inst-sub { color: var(--ink-2); }

.inst-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 460px);
  align-items: center;
  gap: clamp(32px, 5vw, 64px);
}
@media (max-width: 900px) {
  .inst-hero__grid { grid-template-columns: minmax(0, 1fr); }
  /* The photo leads on narrow screens: it is the fastest statement of who this
     page is for, and a 100-character H1 above the fold is a wall. */
  .inst-media--hero { order: -1; max-width: 460px; width: 100%; margin-inline: auto; }
}

/* ---------------------------------------------------------------------------
   GRIDS + CARDS
   Every track is minmax(0, 1fr), never bare 1fr: a bare 1fr floors at
   min-content, which is the usual cause of a grid overflowing its container and
   minting a horizontal scrollbar on the whole page.
   --------------------------------------------------------------------------- */
.inst-grid {
  display: grid;
  gap: var(--space-5);
  margin-top: var(--space-7);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.inst-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.inst-card {
  position: relative;
  display: flex; flex-direction: column;
  padding: var(--space-6) var(--space-5);
  /* The resting edge carries the card's level at --*-line (a hairline, well
     under any contrast floor and carrying no information on its own), and hover
     steps it to the full hue. The card's MEANING never rests on that edge: the
     level is always also said in a shape somewhere on the card (the numbered
     tile on a step, the top rule on a tier card, the left rule on a matrix row). */
  border: 1px solid var(--inst-lvl-line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .012));
  transition: border-color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.inst-card:hover { border-color: var(--inst-lvl-edge); }

.inst-card__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--type-u2);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  color: var(--ink-1);
}
.inst-card__body {
  margin: 0;
  font-size: var(--type-u5);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* ---- (2) TAILORED SOLUTIONS · the four persona cards ---- */
.inst-persona { padding: 0; overflow: hidden; }
.inst-persona .inst-media { flex: 0 0 auto; }
.inst-persona__body { padding: var(--space-5); }
/* A cool wash keeps four separately-shot photos reading as one set and lifts the
   card's top edge off the brightest frames. */
.inst-persona .inst-media::before {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, transparent 40%, var(--inst-lvl-wash) 100%),
    linear-gradient(180deg, rgba(7, 10, 28, .08) 0%, rgba(7, 10, 28, .62) 100%);
}

/* ---------------------------------------------------------------------------
   (3) THE DEPLOYMENT MATRIX
   A real <table> with real <th scope>. See the long note in the template for
   why, and for why the explicit ARIA roles are mandatory rather than decorative.

   DESKTOP (>=1000px): an ordinary table. `border-collapse: separate` with
   `border-spacing: 0` plus per-cell top/left hairlines, because collapse mode
   cannot round the outer corners.
   --------------------------------------------------------------------------- */
.inst-matrix {
  margin-top: var(--space-7);
  border: 1px solid var(--line-1);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, rgba(255, 255, 255, .035), rgba(255, 255, 255, .01));
  /* Clips the header row's fill to the rounded corner. It does NOT create a
     scroll container: nothing inside is ever wider than this box. */
  overflow: hidden;
}
.inst-matrix__table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  text-align: left;
}
.inst-matrix__table th,
.inst-matrix__table td {
  padding: var(--space-4) var(--space-5);
  vertical-align: top;
  border-top: 1px solid var(--line-1);
}
.inst-matrix__table thead th {
  border-top: 0;
  background: rgba(255, 255, 255, .04);
  font-family: var(--font-ui);
  font-size: var(--type-u6);
  font-weight: 600;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-1);
}
/* The row header is the answer to "which deployment is this", so it takes the
   display face and full ink, and a left accent rule that runs the height of the
   row. Shape, not hue: the rule is present on row headers and absent on cells. */
/* The row header is the answer to "which deployment is this", so it also carries
   that product's RUNG. `--sem-rule-ladder` (3px) is the ladder weight from the
   contract; `--sem-rule-interrupt` (5px) is reserved for caution/halt and never
   appears on this page. The wash behind it is the second, area-sized channel:
   the four rows are distinguishable as four different fills before any hue is
   resolved, and the rule itself is present on row headers and absent on cells,
   which is the shape half of the same statement. */
.inst-matrix__table tbody th[scope="row"] {
  width: 15%;
  min-width: 128px;
  font-family: var(--font-display);
  font-size: var(--type-u4);
  font-weight: 600;
  color: var(--ink-1);
  box-shadow: inset var(--sem-rule-ladder) 0 0 0 var(--inst-lvl-edge);
}
/* The wash is on the ROW, not on the row header cell, and it fades out across
   the first data column. On the header cell alone it was a 128px chip in a
   1120px band, which is not enough area to carry a four-way distinction; run
   across the row it is the thing that actually makes the four products legible
   as four products from across the room. It stops well before the third column,
   so the bulk of the body copy still sits on the plain surface the file's
   contrast figures were measured against. */
.inst-matrix__table tbody tr {
  background: linear-gradient(90deg, var(--inst-lvl-wash) 0%, transparent 58%);
}
.inst-matrix__table td + td,
.inst-matrix__table th + td,
.inst-matrix__table th + th { border-left: 1px solid var(--line-1); }

.inst-matrix__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  font-size: var(--type-u6);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
/* A hollow ring marker rather than a disc bullet, matching the pip idiom the
   rest of the site uses for "one item in a set". Decorative: it is a pseudo, so
   it never reaches the accessibility tree, and the <li> is already a list item
   there. */
.inst-matrix__list li {
  position: relative;
  padding-left: calc(var(--sem-pip) + var(--space-3));
}
.inst-matrix__list li::before {
  content: "";
  position: absolute; left: 0; top: .55em;
  width: var(--sem-pip); height: var(--sem-pip);
  border-radius: var(--r-full);
  border: 1.5px solid var(--inst-lvl);
}

/* The per-cell column label. Hidden on desktop (the <thead> is doing that job),
   shown on mobile where the thead is visually gone. aria-hidden in the markup
   in both states, because the table's own column association is what a screen
   reader uses and this would only ever be a duplicate. */
.inst-matrix__key { display: none; }

/* ---- MATRIX · MOBILE (<1000px) -------------------------------------------
   Four columns of three-item bullet lists cannot be read at 390px in a
   horizontal scroller: the row header scrolls out of view and every cell loses
   what it is a cell OF. So each ROW becomes a stacked card. The deployment name
   is the card heading; every cell carries its own visible column label.

   The <thead> is visually hidden with the standard clip technique, NOT
   `display: none`: the column headers must stay in the accessibility tree,
   because `role="columnheader"` is what still associates each cell with its
   column when the display switch below strips the browser's implicit table
   semantics.
   ------------------------------------------------------------------------- */
@media (max-width: 999px) {
  .inst-matrix {
    border: 0;
    border-radius: 0;
    background: none;
  }
  .inst-matrix__table,
  .inst-matrix__table tbody,
  .inst-matrix__table tr,
  .inst-matrix__table th,
  .inst-matrix__table td { display: block; width: auto; }

  .inst-matrix__table thead {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* The stacked row keeps its rung: the 3px rule rotates from the row header's
     left edge to the top of the whole card, which is the same statement in the
     shape this layout has room for. */
  .inst-matrix__table tr {
    margin-bottom: var(--space-5);
    border: 1px solid var(--inst-lvl-line);
    border-top: var(--sem-rule-ladder) solid var(--inst-lvl-edge);
    border-radius: var(--r-lg);
    background: linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .012));
    overflow: hidden;
  }
  .inst-matrix__table tr:last-child { margin-bottom: 0; }

  .inst-matrix__table tbody th[scope="row"] {
    width: auto;
    min-width: 0;
    padding: var(--space-4) var(--space-5);
    border-top: 0;
    border-bottom: 1px solid var(--line-1);
    background: var(--inst-lvl-wash);
    box-shadow: none;
    font-size: var(--type-u3);
  }
  .inst-matrix__table td,
  .inst-matrix__table th + td { border-left: 0; }
  .inst-matrix__table td:first-of-type { border-top: 0; }

  .inst-matrix__key {
    display: block;
    margin-bottom: var(--space-3);
    font-size: var(--type-u6);
    font-weight: 600;
    letter-spacing: var(--track-label);
    text-transform: uppercase;
    color: var(--ink-3);
  }
}

/* ---------------------------------------------------------------------------
   (4) ACCELERATE YOUR RESEARCH  ·  the three step cards
   ---------------------------------------------------------------------------
   THE PROBLEM THIS SHAPE SOLVES. `#inst-solutions` directly above is four
   photo-top persona cards. The client asked for photography here too, and three
   MORE cards of the same construction would give the page two interchangeable
   photo grids and flatten the difference between "which of these is you"
   (four parallel options) and "how you get started" (one sequence with a
   direction). So: photos, in a visibly different component. Four axes of
   difference, all of them structural rather than a restyle:

     1 THE ORDINAL IS A TYPOGRAPHIC ELEMENT, not text inside the <h3>. A
       gradient-filled 48px tile carrying the numeral, exactly the guides'
       `.gs-hero__icon` idiom (guide-system.css:445) with a numeral instead of a
       glyph. It is REAL TEXT in the DOM, not a CSS counter: this file's earlier
       note about not faking the ordinal still holds, the number just moved out
       of the heading. It is aria-hidden because the <ol> already announces
       "item 1 of 3" and the tile would otherwise say it twice.
     2 THE LADDER. Step 1 entry, step 2 build, step 3 master. Tile fill, card
       edge, connector and the band's own mesh all move with it, so the three
       cards have a direction and a colour progression rather than being three
       equivalent choices.
     3 A DIFFERENT FRAME. 16:9 at ~310px against the persona cards' 3:2 at
       262px, inset inside the card's padding with its own smaller radius rather
       than bleeding to the card edge. Landscape band versus portrait-ish photo
       block, and it also crops ~25% more off the top and bottom of the same
       source geometry, so the same photography reads tighter here.
     4 A CONNECTIVE DEVICE. A 2px rule crossing each gap at the exact vertical
       centre of the number tiles, gradient-filled from this step's hue to the
       next one's. That is what turns three cards into "1 then 2 then 3". It
       becomes vertical when the grid collapses to one column.

   The connector's geometry is EXACT rather than eyeballed, and depends on two
   things staying true: the card's padding-top is --space-6 (40px) and the tile
   is 48px tall, so the tile's centre is at 64px from the card's top edge on
   every card at every width. Change either and --inst-step-axis changes with
   it; nothing else needs touching.
   --------------------------------------------------------------------------- */
.inst-steps {
  --inst-step-axis: calc(var(--space-6) + 24px);   /* padding-top + half tile */
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: var(--space-7) 0 0;
  padding: 0;
  list-style: none;
}
.inst-step {
  position: relative;
  display: flex; flex-direction: column;
  padding: var(--space-6) var(--space-5);
  border: 1px solid var(--inst-lvl-line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .012));
  transition: border-color var(--dur-2) var(--ease-out);
}
.inst-step:hover { border-color: var(--inst-lvl-edge); }

/* THE NUMBER TILE. Dark ink on a light gradient block, the one construction on
   this page that inverts the page's own polarity, which is why it reads as a
   marker rather than as another piece of furniture. --sem-ink-on-fill (#04121a)
   is the contract's token for exactly this: it measures 13.7:1 on cyan, 7.1:1
   on violet and 13.3:1 on mint, so the numeral is legible on all three rungs. */
.inst-step__num {
  display: grid; place-items: center;
  flex: 0 0 auto;
  width: 48px; height: 48px;
  margin-bottom: var(--space-5);
  border-radius: var(--r-md);
  background: var(--inst-lvl-grad);
  box-shadow: 0 6px 20px var(--inst-lvl-glow);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1;
  color: var(--sem-ink-on-fill);
}

/* THE CONNECTOR. Drawn in the GAP between cards, never under one, so it needs
   no z-index games against the cards' translucent fills. Two stubs, and each is
   explicitly the ramp from its own rung to the next: cyan -> violet after step
   1, violet -> mint after step 2. The last step has none, which is what says
   "the sequence ends here". */
.inst-step::after {
  content: "";
  position: absolute;
  top: calc(var(--inst-step-axis) - var(--sem-rule-ladder) / 2);
  left: 100%;
  width: var(--space-5);
  height: var(--sem-rule-ladder);
  border-radius: var(--sem-rule-ladder);
}
.inst-steps > .inst-step:nth-child(1)::after {
  background: linear-gradient(90deg, var(--sem-entry), var(--sem-build));
}
.inst-steps > .inst-step:nth-child(2)::after {
  background: linear-gradient(90deg, var(--sem-build), var(--sem-master));
}
.inst-steps > .inst-step:last-child::after { content: none; }

/* The step photo. `.inst-media` / `.inst-media__img` are kept verbatim because
   js/institutional.js's missing-image contract keys on exactly those two class
   names; only the box geometry differs from the persona cards. */
.inst-media--step {
  aspect-ratio: 16 / 9;
  border-radius: var(--r-md);
  margin-bottom: var(--space-5);
}
/* A level-tinted veil, so a photo shot for step 2 sits inside the violet rung
   rather than beside it. Weak on purpose: it tints, it does not colourise, and
   nothing on the card depends on being able to read it.

   IT ALSO DOES NOT DEPEND ON THE PHOTO'S EXPOSURE, in either direction, and the
   three frames must all sit in the same DARK luminance band. The first
   `step-accelerate` delivery measured mean luminance 129.2 against 43.5 for the
   approved `step-customize` and was rejected: a high-key daylight frame reads as
   a lit rectangle punched through a navy page, and one card being brighter than
   the other two breaks the ladder, which is supposed to be the only thing
   separating them. A darkening veil cannot rescue that, so do not reach for one
   here when the next bright frame arrives; reject the frame. */
.inst-media--step::before {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, transparent 45%, var(--inst-lvl-wash) 100%),
    linear-gradient(180deg, rgba(7, 10, 28, .05) 0%, rgba(7, 10, 28, .42) 100%);
}

.inst-step__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--type-u2);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  color: var(--ink-1);
}
.inst-step__body {
  margin: 0;
  font-size: var(--type-u5);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* ---------------------------------------------------------------------------
   (5) CONTACT · the consult form
   --------------------------------------------------------------------------- */
.inst-panel {
  padding: clamp(28px, 4vw, 56px);
  border: 1px solid var(--inst-lvl-line);
  border-radius: var(--r-lg);
  background:
    radial-gradient(ellipse 80% 120% at 50% 0%, var(--inst-lvl-wash), transparent 70%),
    linear-gradient(180deg, rgba(255, 255, 255, .045), rgba(255, 255, 255, .012));
}
.inst-panel .inst-head { max-width: 62ch; }

.inst-form { margin-top: var(--space-6); }
/* Two per row, then full width, her layout exactly. auto-fit collapses to one
   column below the 260px track minimum with no second media query. */
.inst-form__row {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
.inst-field { margin-bottom: var(--space-5); }

.inst-label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--type-u5);
  font-weight: 500;
  line-height: var(--lh-snug);
  color: var(--ink-2);
}
/* The asterisk is aria-hidden in the markup and the input carries `required`,
   so the requirement reaches assistive tech from the control rather than from a
   glyph. --sem-halt, not cyan: "you must fill this in" is the one meaning that
   hue is reserved for, and it is legible at 7.79:1 on this surface. */
.inst-req { color: var(--sem-halt); }

.inst-input {
  width: 100%;
  padding: 13px 16px;
  background: var(--sf-1);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  color: var(--ink-1);
  font-family: inherit;
  font-size: var(--type-u4);
  line-height: var(--lh-snug);
  transition: border-color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out);
}
.inst-input::placeholder { color: var(--ink-4); }
.inst-input:focus { border-color: var(--accent); background: var(--sf-2); }
.inst-input--area { min-height: 118px; resize: vertical; }

.inst-form__submit { width: 100%; margin-top: var(--space-2); }

/* LEFT-RAIL CALLOUT · the REGIONAL TIP block on /guides/getting-started, at the
   one place on this page where a note actually exists to carry it. It was grey
   mono micro-copy centred under the submit button, which reads as a disclaimer;
   the accent rule and wash make it read as a commitment, which is what the
   sentence is. The TEXT stays --ink-3: the rule is coloured, the copy is not. */
.inst-form .inst-note {
  margin-top: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: var(--sem-rule-ladder) solid var(--inst-lvl);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--inst-lvl-wash);
  text-align: left;
}

/* ---------------------------------------------------------------------------
   (6) ENTERPRISE SOLUTIONS · the four tier cards
   ---------------------------------------------------------------------------
   THESE ARE THE SAME FOUR PRODUCTS AS THE DEPLOYMENT MATRIX in section 3, and
   before this pass the two sections agreed on nothing: the matrix rows all
   carried the same cyan left rule and these four cards all carried flat cyan
   bullet dots, so a reader had no way to tell that "MCP" up there and "MCP
   Deployment" down here were the same thing. They now share a rung each,
   SaaS entry / API entry / MCP build / White-label master, and they say it with
   the same 3px `--sem-rule-ladder` weight and the same `--*-wash` fill: rotated
   from the row header's LEFT edge to the card's TOP edge, because that is where
   a card has room for it. Same device, same weight, same hue, one quarter turn.
   --------------------------------------------------------------------------- */
.inst-tier { padding-top: var(--space-5); }
.inst-tier::before {
  content: "";
  position: absolute; left: -1px; right: -1px; top: -1px;
  height: var(--sem-rule-ladder);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--inst-lvl);
}
/* A short wash under the rule, so the four cards differ by an area and not only
   by a 3px line. Fades out well above the bullet list, which stays on the plain
   card fill so the body copy's measured contrast is unchanged. */
.inst-tier::after {
  content: "";
  position: absolute; left: 0; right: 0; top: 0;
  height: 96px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: linear-gradient(180deg, var(--inst-lvl-wash), transparent);
  pointer-events: none;
}
.inst-tier > * { position: relative; }

.inst-tier__sub {
  margin: calc(var(--space-3) * -1) 0 var(--space-4);
  font-size: var(--type-u5);
  line-height: var(--lh-body);
  color: var(--ink-3);
}
.inst-tier__list {
  display: grid;
  gap: var(--space-3);
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
  font-size: var(--type-u5);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.inst-tier__list li {
  position: relative;
  padding-left: calc(var(--sem-pip) + var(--space-3));
}
/* A FILLED disc here against the matrix's hollow ring: "included" versus "one
   capability among several". Shape, and it survives greyscale. The FILL is the
   card's rung, which is what makes the four cards' bullet columns read as four
   different products instead of one repeated cyan list. */
.inst-tier__list li::before {
  content: "";
  position: absolute; left: 0; top: .55em;
  width: var(--sem-pip); height: var(--sem-pip);
  border-radius: var(--r-full);
  background: var(--inst-lvl);
}
.inst-tier__ctas {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  margin-top: auto;   /* the CTA row bottoms out, so four cards of unequal
                         bullet counts still align their buttons */
}

/* =============================================================================
   SECTION RAIL  ·  #instrail
   -----------------------------------------------------------------------------
   The same pattern individual.css documents, rebuilt self-contained under this
   page's own namespace so the two rails can never share a selector. Kept from
   that implementation, deliberately:
     · real <button>s, so the rail is keyboard reachable and announced as a
       navigation list rather than a row of decorative dots;
     · a transparent ::before that grows the 20x20 dot's HIT area to the rail's
       full 44 x 44 pitch, without changing a pixel of what is painted;
     · an explicit :focus-visible ring in the accent, because the UA default is
       near-black on a near-black page;
     · `aria-current="true"` moving with `.is-active` (set in js/institutional.js);
     · labels off below 860px.
   ============================================================================= */
.instrail {
  position: fixed; top: 50%; right: clamp(14px, 2vw, 26px);
  transform: translateY(-50%);
  z-index: var(--z-rail);
  /* 20px dot + 24px gap = a 44px pitch exactly, which is what lets the hit area
     below be a full 44 x 44 without two dots' zones overlapping. */
  display: flex; flex-direction: column; align-items: flex-end; gap: 24px;
}
.instrail::before {
  content: "";
  position: absolute; right: 9px; top: -8px; bottom: -8px; width: 1px;
  background: linear-gradient(180deg, transparent, rgba(0, 242, 254, .3), transparent);
}
.instrail__dot {
  position: relative;
  appearance: none; -webkit-appearance: none;
  border: 0; background: transparent; cursor: pointer;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  padding: 0;
}
.instrail__dot::after {
  content: "";
  width: 8px; height: 8px; border-radius: var(--r-full);
  background: rgba(130, 205, 255, .32);
  box-shadow: 0 0 0 1px rgba(0, 242, 254, .25);
  transition: transform var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
/* Hit area, not paint: the pseudo grows the 20x20 button to a full 44 x 44
   target. The pseudo paints nothing. */
.instrail__dot::before { content: ""; position: absolute; inset: -12px; }
.instrail__dot:hover::after { background: rgba(0, 242, 254, .7); transform: scale(1.2); }
/* ACTIVE is a SIZE step plus a ring, not just a colour step: scale(1.35) and a
   4px halo are both visible with the hue removed. */
.instrail__dot.is-active::after {
  background: var(--accent); transform: scale(1.35);
  box-shadow: 0 0 0 4px rgba(0, 242, 254, .18), 0 0 10px rgba(0, 242, 254, .55);
}
.instrail__dot:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm);
}
.instrail__label {
  position: absolute; right: 26px; top: 50%;
  transform: translateY(-50%) translateX(6px);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--type-m3);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: rgba(255, 255, 255, .9);
  background: rgba(9, 14, 32, .82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 4px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--accent-line);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.instrail__dot:hover .instrail__label { opacity: 1; transform: translateY(-50%) translateX(0); }
/* The ACTIVE label is persistent, so unlike the hover label it must never sit on
   top of the content column. This page's longest label ("Accelerate Your
   Research") is wider than /individual's, so the always-on label is gated one
   step higher, at 1700px:
     1700 -> content ends at 1450, label starts at ~1500. Clear.
     1600 -> content ends at 1400, label would start at ~1400. Touching, so off.
   Below that the dots still work and still label themselves on hover, which is
   transient and sits on a blurred pill. Only the always-on label is withheld. */
@media (min-width: 1700px) {
  .instrail__dot.is-active .instrail__label { opacity: 1; transform: translateY(-50%) translateX(0); }
}
@media (max-width: 860px) {
  .instrail { right: 8px; gap: 16px; }
  .instrail__label { display: none; }
}
/* Without JS the dots cannot scroll anywhere, so they must not take six tab
   stops on the way to the content. */
html:not(.inst-js) .instrail { display: none; }

/* =============================================================================
   REVEAL CHOREOGRAPHY  ·  double-gated, opt-in
   -----------------------------------------------------------------------------
   Default (this file's normal state): nothing is hidden. The transform below
   only exists inside BOTH gates, so:
     · JS off / script threw  -> no `html.inst-js`  -> the page renders as a
                                 plain, fully readable document;
     · reduced motion         -> media query excludes -> everything static and
                                 at full opacity.
   js/institutional.js adds a third guarantee on `load`.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  html.inst-js .inst-reveal {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity var(--dur-6) var(--ease-out), transform var(--dur-6) var(--ease-out);
  }
  html.inst-js .inst-reveal.is-in {
    opacity: 1;
    transform: none;
  }
  /* Children stagger off their parent's reveal via an inline --i index. */
  html.inst-js .inst-rise {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur-5) var(--ease-out), transform var(--dur-5) var(--ease-out);
    transition-delay: calc(var(--stagger-lead) + var(--i, 0) * var(--stagger-unit));
  }
  html.inst-js .inst-reveal.is-in .inst-rise {
    opacity: 1;
    transform: none;
  }
}

/* =============================================================================
   RESPONSIVE
   Verified at 1920 / 1440 / 1024 / 768 / 390. Nothing here may introduce a
   horizontal scrollbar: every grid track is minmax(0, 1fr) and every image is
   width:100% inside an aspect-boxed panel.
   ============================================================================= */
@media (max-width: 1200px) {
  .inst-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 1023px) {
  .inst-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .inst-steps { grid-template-columns: minmax(0, 1fr); }
  /* One column, so the connector rotates: it now drops from the bottom of each
     card to the top of the next, on the vertical axis of the number tiles
     (card padding-left + half the tile). Same rule, same two-hue ramp, same
     "the last step has none". */
  .inst-step::after {
    top: 100%;
    left: calc(var(--space-5) + 24px - var(--sem-rule-ladder) / 2);
    width: var(--sem-rule-ladder);
    height: var(--space-5);
  }
  .inst-steps > .inst-step:nth-child(1)::after {
    background: linear-gradient(180deg, var(--sem-entry), var(--sem-build));
  }
  .inst-steps > .inst-step:nth-child(2)::after {
    background: linear-gradient(180deg, var(--sem-build), var(--sem-master));
  }
  /* One column means the photo is suddenly card-width, and a strict 16:9 at
     658px is 370px of photo per step: the three-step band becomes three screens
     tall on a tablet and stops reading as one sequence you can take in. The cap
     lets `object-fit: cover` crop to a tighter band instead. It only bites
     between about 500 and 1023px; below that 16:9 is already shorter than the
     cap, so a phone is untouched. */
  .inst-media--step { max-height: 220px; }
  /* An orphaned third card in a 2-up grid reads as a mistake; centring it under
     the pair reads as deliberate. Scoped to the 3-up grid: the 4-up sets are
     even and never orphan. */
  .inst-grid:not(.inst-grid--4) > :last-child:nth-child(odd) {
    grid-column: 1 / -1; max-width: 460px; margin-inline: auto;
  }
}

@media (max-width: 767px) {
  .inst-grid,
  .inst-grid--4 { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
  .inst-grid:not(.inst-grid--4) > :last-child:nth-child(odd) { max-width: none; }
  .inst-head { max-width: none; }
  .inst-ctas { gap: var(--space-3); }
  /* Full-width buttons on a phone, EXCEPT the small in-card pair: two 40px
     pills side by side is the readable shape there, and stacking them would
     make a four-card section four screens tall. */
  .inst-btn { width: 100%; }
  .inst-btn--sm { width: auto; flex: 1 1 auto; }
  .inst-panel { padding: var(--space-5) var(--space-4); }
}
