/* ---------- reset (replaces Tailwind preflight, no longer present) ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
ul, ol { list-style: none; padding: 0; }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
img, svg { display: block; max-width: 100%; }
table { border-collapse: collapse; }


/* ============================================================================
   MetaIT design system — light canvas, dark chrome

   The site is a LIGHT site. Dark is spent in exactly three places and nowhere
   else: the navigation chrome (top bar, header, drawer, submenus), the home
   hero, and the footer. Everything else — every inner-page banner, every card,
   the commercial offer, the closing CTA, the status pages, the whole admin body
   — is light. Dark past that point stops being an accent and becomes the theme.

   The deep tone itself is a rich indigo navy (#172147), not near-black. Same
   weight in the layout, far less heaviness on the eye.

   ---------------------------------------------------------------------------
   HOW THE TWO SURFACES WORK

   There is ONE set of component rules. Nothing is written twice for light and
   dark. Every component references *contextual* tokens — --mi-title, --mi-glass,
   --mi-line, --mi-plate and friends — and those tokens are redeclared inside
   dark regions. Drop a .mi-card into the hero and it turns into a dark card by
   inheritance, with no extra class and no override.

   To make a region dark, add `mi-dark` to it (or add its selector to the dark
   token block). Two things are then needed and only two: the token block, and a
   dark background of its own.

   Non-contextual tokens — spacing, radii, type scale, brand hues, the gold
   accent — live on :root and never change between surfaces.
   ---------------------------------------------------------------------------

   All classes are prefixed `mi-` so they can never collide with anything a
   future dependency ships.
   ========================================================================= */

:root {
  /* The page and every form control on it is light. */
  color-scheme: light;

  /* ---------- brand (non-contextual) ---------- */
  /*
    Lifted out of the MITL mark's own gradient. --mi-brand is a button fill with
    white text on it, so one value serves both surfaces. --mi-brand-light and the
    violet/cyan are for dark regions and gradients only — never body text.

    Change these and the whole site re-brands.
  */
  --mi-brand: #4356e8;
  --mi-brand-deep: #2f3fb5;
  --mi-brand-light: #8ea2ff;
  --mi-violet: #9d7bff;
  --mi-cyan: #56cffd;

  /* Reserved for "this one spends money": the Buy button and the price. */
  --mi-accent: #fec624;
  --mi-accent-dark: #f0b100;
  --mi-accent-ink: #8a6100;

  /*
    Champagne — and deliberately NOT --mi-accent above. That gold is a filled control
    meaning "this one spends money", and spending the same hue on decoration would blunt
    it. This one is desaturated and pale, used ONLY as a hairline or a small-caps label on
    the photographic page heads. Never a fill, never a button, never on a light surface
    where it would fail contrast.
  */
  --mi-metal: #d6bf95;
  --mi-metal-line: rgb(214 191 149 / 0.34);

  /* ---------- grounds ---------- */
  --mi-page: #f7f9fd;
  /*
    Three depths of indigo navy, deliberately none of them near-black. The navbar is
    NOT among them — it is white.

    --mi-ink-chrome  the hero and the admin bar. The lightest of the three, and what
                     keeps the dark reading as an accent colour rather than a theme.
    --mi-ink-deep    the thin utility strip above the header. Dark so it frames the
                     white bar below it.
    --mi-ink         the footer, which anchors the bottom of the page.
  */
  --mi-ink-chrome: #2a3866;
  --mi-ink-deep: #1e2950;
  --mi-ink: #172147;

  /*
    The hero's own ground, and deliberately deeper than all three above.

    It is NOT --mi-ink-chrome any more. That token is shared with the admin bar, and the
    hero now wants a flat, near-black navy for the copy to sit hard against — the split
    layout gets its depth from the panel edge and the hatch rather than from a gradient,
    and a lit chrome tone under that reads as haze. Kept indigo, not black, so it is still
    the same family as the footer.
  */
  --mi-ink-hero: #101b3a;
  --mi-ink-hero-panel: #16224a;

  /* ---------- status (non-contextual hues) ---------- */
  --mi-success: #129c6b;
  --mi-error: #d6455c;
  --mi-warn: #b8860b;

  /* ---------- type ---------- */
  /* Sora for display (geometric, high-end tech), Inter for reading. The old
     Exo / Fira Sans stack stays as the fallback so a blocked font request
     degrades to the previous typography rather than to a default serif. */
  --font-primary: "Sora", "Exo", system-ui, -apple-system, sans-serif;
  --font-secondary: "Inter", "Fira Sans", system-ui, -apple-system, sans-serif;

  /*
    Technical labels only — the hero eyebrow, its stat captions and the panel caption.
    A system stack, so it costs no font request: the point is the machined, evenly
    spaced look of a monospace at 11px, not any particular face.
  */
  --font-mono: ui-monospace, "SFMono-Regular", "Cascadia Mono", "Segoe UI Mono",
               Menlo, Consolas, monospace;

  --mi-text-xs: 0.75rem;
  --mi-text-sm: 0.875rem;
  --mi-text-base: 1rem;
  --mi-text-lg: 1.125rem;
  --mi-text-xl: 1.375rem;
  --mi-text-2xl: 1.75rem;
  --mi-text-3xl: 2.25rem;
  --mi-text-4xl: clamp(2.25rem, 1.5rem + 2.2vw, 3.25rem);
  --mi-text-5xl: clamp(2.75rem, 1.2rem + 4.6vw, 4.75rem);

  /* ---------- space ---------- */
  --mi-space-1: 4px;
  --mi-space-2: 8px;
  --mi-space-3: 12px;
  --mi-space-4: 16px;
  --mi-space-5: 24px;
  --mi-space-6: 32px;
  --mi-space-7: 48px;
  --mi-space-8: 64px;

  --mi-section-y: 88px;
  --mi-section-y-mobile: 52px;
  --mi-gutter: 24px;

  /* Base cap. Widened on large screens further down — at 1280 a 2062px monitor is
     left with 391px of dead margin on each side, roughly 40% of the display. */
  --mi-container: 1280px;

  /* ---------- shape ---------- */
  --mi-radius-sm: 8px;
  --mi-radius: 14px;
  --mi-radius-lg: 22px;
  --mi-radius-xl: 30px;
  --mi-radius-pill: 999px;

  --mi-blur: saturate(150%) blur(20px);
  --mi-blur-sm: saturate(140%) blur(12px);

  --mi-transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  --mi-transition-slow: 0.45s cubic-bezier(0.22, 1, 0.36, 1);

  /* ======================================================================
     CONTEXTUAL TOKENS — light surface (the default, and almost everything)
     Every one of these is redeclared in the dark block below.
     ====================================================================== */

  --mi-title: #131a3a;
  --mi-body: #4d5674;
  --mi-muted: #6d7692;
  --mi-faint: #8b94ad;
  --mi-link: #3444cc;
  --mi-link-hover: #222c8f;

  --mi-line: rgb(19 26 58 / 0.08);
  --mi-line-strong: rgb(19 26 58 / 0.14);

  /*
    Glass on a light ground is a white pane, not a grey one: a translucent BLACK
    fill over pale content just reads as dirt. The top-lit gradient is what makes
    it a pane catching light rather than a flat box.
  */
  --mi-glass: linear-gradient(160deg, rgb(255 255 255 / 0.96) 0%, rgb(255 255 255 / 0.78) 100%);
  --mi-glass-soft: linear-gradient(160deg, rgb(255 255 255 / 0.76) 0%, rgb(255 255 255 / 0.52) 100%);
  --mi-glass-strong: linear-gradient(160deg, rgb(255 255 255 / 1) 0%, rgb(255 255 255 / 0.9) 100%);
  --mi-glass-border: rgb(19 26 58 / 0.07);
  --mi-glass-border-hover: rgb(67 86 232 / 0.36);

  /* Opaque fallback where blur cannot be composited. */
  --mi-surface: #ffffff;
  --mi-surface-2: #eff2fa;

  /* Tinted band that separates two light sections. */
  --mi-tint: linear-gradient(180deg, rgb(236 240 251 / 0.9) 0%, rgb(246 249 253 / 0.7) 100%);

  /* Backing for logo wells. Every vendor mark is dark ink, so this stays light on
     BOTH surfaces — only its opacity changes. */
  --mi-plate: #f3f6fd;
  --mi-plate-border: rgb(19 26 58 / 0.06);

  --mi-field-bg: #ffffff;
  --mi-autofill: #ffffff;

  /* On a light ground depth comes from a real cast shadow. Kept shallow — heavy
     shadows on a pale page read as grime, not elevation. */
  --mi-shadow-sm: 0 1px 2px rgb(19 26 58 / 0.04), 0 2px 8px rgb(19 26 58 / 0.035);
  --mi-shadow: 0 2px 6px rgb(19 26 58 / 0.045), 0 10px 28px rgb(19 26 58 / 0.07);
  --mi-shadow-lg: 0 4px 12px rgb(19 26 58 / 0.05), 0 22px 52px rgb(19 26 58 / 0.10);
}

/* ==========================================================================
   CONTEXTUAL TOKENS — dark surface

   The full list of dark regions on the site. Chrome, hero, footer. Nothing else.
   Each also sets its own dark background further down — the tokens alone only
   recolour the contents.
   ========================================================================== */

/*
  The navigation is WHITE — header, its dropdowns and the mobile drawer are all
  light surfaces and are deliberately NOT in this list, so every control inside
  them picks up the light token set and reads in dark ink. The thin utility strip
  above the header stays dark, which frames the white bar rather than competing
  with it.
*/
/*
  The admin bar is no longer in this list. The console is light throughout,
  sidebar included — a dark bar borrowed from the site chrome made it read as a
  page of the marketing site rather than as a tool. See admin.css.
*/
.mi-dark,
.mi-topbar,
.mi-hero,
.mi-partner-band,
.mi-banner--deep,
.mi-pagehead,
.mi-footer {
  color-scheme: dark;

  --mi-title: #f5f7ff;
  --mi-body: #c2cbe4;
  --mi-muted: #a5aecb;
  --mi-faint: #8b95b4;
  --mi-link: #a9baff;
  --mi-link-hover: #ffffff;

  --mi-line: rgb(255 255 255 / 0.12);
  --mi-line-strong: rgb(255 255 255 / 0.20);

  --mi-glass: linear-gradient(160deg, rgb(255 255 255 / 0.10) 0%, rgb(255 255 255 / 0.04) 55%, rgb(255 255 255 / 0.06) 100%);
  --mi-glass-soft: linear-gradient(160deg, rgb(255 255 255 / 0.07) 0%, rgb(255 255 255 / 0.025) 100%);
  --mi-glass-strong: linear-gradient(160deg, rgb(255 255 255 / 0.15) 0%, rgb(255 255 255 / 0.06) 100%);
  --mi-glass-border: rgb(255 255 255 / 0.13);
  --mi-glass-border-hover: rgb(142 162 255 / 0.5);

  --mi-surface: #1b2650;
  --mi-surface-2: #24315f;

  --mi-tint: linear-gradient(180deg, rgb(255 255 255 / 0.05) 0%, rgb(255 255 255 / 0.02) 100%);

  /* Near-opaque here: a vendor logo needs real white behind it to stay legible
     against the dark hero. */
  --mi-plate: rgb(255 255 255 / 0.96);
  --mi-plate-border: rgb(255 255 255 / 0.6);

  --mi-field-bg: rgb(255 255 255 / 0.07);
  --mi-autofill: #26315c;

  /* A black shadow on a dark ground is invisible, so each elevation pairs the
     cast shadow with an inset hairline of light along the top edge. */
  --mi-shadow-sm: 0 2px 10px rgb(6 10 26 / 0.30), inset 0 1px 0 rgb(255 255 255 / 0.07);
  --mi-shadow: 0 14px 40px rgb(6 10 26 / 0.38), inset 0 1px 0 rgb(255 255 255 / 0.08);
  --mi-shadow-lg: 0 26px 64px rgb(6 10 26 / 0.48), inset 0 1px 0 rgb(255 255 255 / 0.11);

  color: var(--mi-body);
}

/* ============================ base ============================ */

html {
  font-family: var(--font-primary);
  background: var(--mi-page);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;

  /*
    Safety net so no single element can ever introduce a horizontal scrollbar and the
    white strip that comes with it.

    `clip`, not `hidden`: `overflow-x: hidden` makes this a scroll container, which
    silently breaks `position: sticky` on the header. `clip` does not.
  */
  overflow-x: clip;
}

body {
  margin: 0;
  color: var(--mi-body);
  font-family: var(--font-secondary);
  font-size: var(--mi-text-base);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /*
    The canvas is tinted, not flat white — faint brand washes so white cards have
    something to sit against and read as panes. Kept under 6%: any more and the
    page stops feeling clean. `fixed` so the wash stays put while content scrolls.
  */
  background-color: var(--mi-page);
  background-image:
    radial-gradient(50% 38% at 6% 0%, rgb(67 86 232 / 0.055) 0%, transparent 62%),
    radial-gradient(42% 32% at 97% 5%, rgb(157 123 255 / 0.045) 0%, transparent 64%),
    radial-gradient(44% 36% at 80% 98%, rgb(86 207 253 / 0.04) 0%, transparent 66%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--mi-space-4);
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -0.018em;
}

p { margin: 0 0 var(--mi-space-4); }
p:last-child { margin-bottom: 0; }

a {
  color: var(--mi-link);
  text-decoration: none;
  transition: color var(--mi-transition);
}
a:hover { color: var(--mi-link-hover); }

img { max-width: 100%; height: auto; }

::selection { background: rgb(67 86 232 / 0.20); color: var(--mi-title); }

:focus-visible {
  outline: 2px solid var(--mi-link);
  outline-offset: 3px;
  border-radius: var(--mi-radius-sm);
}

/* Keyboard-only skip target for the header. */
.mi-skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--mi-space-3) var(--mi-space-5);
  background: var(--mi-brand);
  color: #fff;
  border-radius: 0 0 var(--mi-radius-sm) 0;
}
.mi-skip-link:focus { left: 0; color: #fff; }

/* ============================ layout ============================ */

.mi-container {
  width: 100%;
  max-width: var(--mi-container);
  margin-inline: auto;
  padding-inline: var(--mi-gutter);
}

.mi-section {
  position: relative;
  padding-block: var(--mi-section-y);
}
.mi-section--tight { padding-block: var(--mi-space-7); }

/* Tinted band — a soft step down from the page, with hairlines so it has an edge
   instead of fading out. */
.mi-section--smoke {
  background: var(--mi-tint);
  border-block: 1px solid var(--mi-line);
}

/*
  Stacked sections each pay a full pad, so the join between two of them costs the sum of
  both — which reads as a gap rather than as rhythm. Collapse the join so the spacing
  comes from one section, not two.

  A tinted band is excluded on BOTH sides. Zeroing its own top padding would make the
  background start hard against its first line of text; zeroing the top padding of
  whatever follows it leaves that section's first card butted against the band's edge.

  Both exclusions have to live in this one selector. Written as a separate override,
  `.mi-section--smoke + .mi-section { padding-top: … }` is two class selectors against
  this rule's four and silently loses.
*/
.mi-section:not(.mi-section--smoke) + .mi-section:not(.mi-section--smoke) { padding-top: 0; }

/* ---------- backgrounds inside a band ----------

   Two ways to put something behind a section, and the difference between them is the point.

   --texture  a PHOTOGRAPH, blurred past the point of being readable as a subject. It supplies
              depth and warmth and nothing else. If you can tell what it is, it is competing
              with the copy and the blur is too low.
   --motif    an ILLUSTRATION, left sharp and simply faded. Vector lines survive being faint
              in a way photographic detail does not, so this one is not blurred at all.

   Both paint through `.mi-band`, a child span rather than a pseudo-element, because the image
   URL is admin-managed: it arrives as a custom property on the element's style attribute, and
   a pseudo-element cannot carry one from the view.

   z-index -1 with `isolation: isolate` is what layers this correctly. Painting order puts a
   negative-z child ABOVE the parent's own background and BELOW its in-flow content, so the
   band sits over the section's tint and under the text without either being moved into a
   stacking context of its own. */
.mi-section--texture,
.mi-section--motif {
  position: relative;
  isolation: isolate;
  /* Required by --texture: the blur is drawn from a layer bled past every edge, and this is
     what crops the feathered result back to a clean rectangle. */
  overflow: hidden;
}

.mi-band {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--mi-band-image);
  background-repeat: no-repeat;
  pointer-events: none;
}

/*
  Bled 72px on every side, which is slightly more than twice the blur radius. A blur samples
  beyond its own edges, so a layer that stops at the section boundary fades to transparent
  along all four sides and reads as a soft-edged rectangle pasted on. Bleeding it out and
  cropping with overflow leaves the texture running edge to edge.
*/
.mi-section--texture .mi-band {
  /* Bleed is set from the blur radius: a shade over twice 26px. */
  inset: -64px;
  background-size: cover;
  background-position: center;
  /*
    Blur 26px, opacity 0.24.

    This setting has been walked in from both directions, which is worth recording so it is
    not walked again. 30px/0.28 made faces identifiable and the photograph competed with the
    quote. 44px/0.20 removed the subject entirely but went too far the other way — soft to the
    point of being nearly absent. 26px sits between them: the shapes of a room and a group of
    people are legible as shapes, so the band has something in it, while no face resolves.

    The blur is the dial that decides whether it reads as a picture; the opacity only sets how
    much weight it carries. Raising the opacity instead of lowering the blur would have made a
    still-unreadable smear heavier, which is not the same as making the image more visible.
  */
  filter: blur(6px) saturate(112%);
  opacity: 0.24;
}

/* A veil over the texture, not under it: the blurred photograph still carries light and dark
   patches, and this is what stops the quote's descenders landing on one of the light ones.
   Lightened alongside the opacity above — between them they were doing the same job twice. */
.mi-section--texture::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgb(247 249 253 / 0.36) 0%, rgb(247 249 253 / 0.58) 100%);
  pointer-events: none;
}

/* `contain` rather than `cover`: it is a composition with a globe on one side, and cropping
   that off centre would leave an arc with no circle.

   Opacity 0.92, near-transparent though that sounds. The illustration's own alphas are baked
   between 0.16 and 0.26 at source, so the effective ink here is about 0.15-0.24 — faint by
   construction. Multiplying that by a low CSS opacity as well (0.55 first time round) took
   the globe to roughly 0.09 and it simply did not render to the eye. Two reductions of the
   same thing is one too many; the weight belongs in the file, where it can be seen while
   editing it. */
.mi-section--motif .mi-band {
  background-size: contain;
  background-position: center right;
  opacity: 0.92;
}

@media (max-width: 767px) {
  /* At phone width the motif's globe sits behind the first card rather than beside it, and a
     dot field under 14px body copy is noise. Both treatments go quieter. */
  .mi-section--motif .mi-band { opacity: 0.5; background-position: 120% 0; }
  .mi-section--texture .mi-band { opacity: 0.18; }
}

@media (prefers-reduced-transparency: reduce) {
  /*
    Softened, NOT hidden — and the difference matters more than it looks.

    `display: none` was the first attempt and it was wrong twice. Wrong in principle: the
    preference is about layers that make content harder to read, and these bands sit behind
    the content at an opacity where the copy measures 15.8:1 either way, so there is nothing
    to relieve by deleting them. And wrong in practice: this query matches in more places than
    expected — headless Chrome reports `reduce` by default — so the rule silently removed the
    background from every screenshot and would have done the same for some real visitors.

    The blur STAYS on the texture. Dropping it would leave a recognisable photograph at 28%,
    which is the one outcome the brief rules out; opacity is the right dial here, not sharpness.
  */
  .mi-section--texture .mi-band { opacity: 0.12; }
  .mi-section--motif .mi-band { opacity: 0.6; }
}

/*
  The join *into* a tinted band is the remaining case: neither side's padding can be
  zeroed, so the two full pads stack and read as a missing section. Take it out of the
  section above, which has no tint to protect.

  `:has()` is progressive — where it is unsupported the join simply keeps the older,
  more generous spacing.
*/
.mi-section:has(+ .mi-section--smoke) { padding-bottom: var(--mi-space-7); }

/* The banner and hero carry their own bottom padding, so the first section after
   them does not need a full pad on top. */
.mi-banner + .mi-section:not(.mi-section--smoke),
.mi-pagehead + .mi-section:not(.mi-section--smoke),
.mi-partner-band + .mi-section:not(.mi-section--smoke),
.mi-hero + .mi-section:not(.mi-section--smoke) { padding-top: var(--mi-space-7); }

.mi-section-head {
  max-width: 760px;
  margin-bottom: var(--mi-space-7);
}
.mi-section-head--center {
  margin-inline: auto;
  text-align: center;
}

/* Eyebrow — a chip rather than bare text, so it reads as a component. */
.mi-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--mi-space-2);
  margin-bottom: var(--mi-space-4);
  padding: 6px 14px;
  background: var(--mi-glass-soft);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-pill);
  backdrop-filter: var(--mi-blur-sm);
  -webkit-backdrop-filter: var(--mi-blur-sm);
  box-shadow: var(--mi-shadow-sm);
  color: var(--mi-link);
  font-family: var(--font-primary);
  font-size: var(--mi-text-xs);
  font-weight: 600;
  letter-spacing: 0.10em;
  line-height: 1.5;
  text-transform: uppercase;
}
.mi-eyebrow::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 3px rgb(67 86 232 / 0.14);
  flex-shrink: 0;
}
a.mi-eyebrow:hover { border-color: var(--mi-glass-border-hover); color: var(--mi-link-hover); }

.mi-title-lg { font-size: var(--mi-text-3xl); }
.mi-title-md { font-size: var(--mi-text-2xl); }
.mi-lead {
  color: var(--mi-muted);
  font-size: var(--mi-text-lg);
  line-height: 1.65;
}

/* Gradient text for the one emphasised phrase in a headline. Two ramps: on the
   dark hero it can be bright, on a light section it has to stay legible. */
.mi-grad {
  background: linear-gradient(110deg, var(--mi-brand) 0%, #6b4fd8 50%, #2f7fd0 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.mi-dark .mi-grad,
.mi-hero .mi-grad,
.mi-banner--deep .mi-grad {
  background: linear-gradient(110deg, var(--mi-brand-light) 0%, var(--mi-violet) 45%, var(--mi-cyan) 100%);
  -webkit-background-clip: text;
  background-clip: text;
}

/* ============================ glass primitives ============================ */

.mi-glass {
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow);
}

/* ============================ buttons ============================ */

.mi-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--mi-space-2);
  padding: 14px 30px;
  border: 1px solid transparent;
  border-radius: var(--mi-radius-pill);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--mi-transition), color var(--mi-transition),
              border-color var(--mi-transition), box-shadow var(--mi-transition),
              transform var(--mi-transition);
}
.mi-btn:hover { transform: translateY(-2px); }
.mi-btn:active { transform: translateY(0); }

.mi-btn--primary {
  background: linear-gradient(135deg, #5468ee 0%, var(--mi-brand-deep) 100%);
  color: #fff;
  box-shadow: 0 6px 18px rgb(47 63 181 / 0.26), inset 0 1px 0 rgb(255 255 255 / 0.22);
}
.mi-btn--primary:hover {
  background: linear-gradient(135deg, #6274f5 0%, #35459f 100%);
  color: #fff;
  box-shadow: 0 12px 30px rgb(47 63 181 / 0.34), inset 0 1px 0 rgb(255 255 255 / 0.28);
}

/* Secondary CTA. Glass rather than a solid fill, so the pair reads as
   primary/secondary without the two competing. */
.mi-btn--outline {
  background: var(--mi-glass);
  border-color: var(--mi-glass-border);
  backdrop-filter: var(--mi-blur-sm);
  -webkit-backdrop-filter: var(--mi-blur-sm);
  color: var(--mi-title);
  box-shadow: var(--mi-shadow-sm);
}
.mi-btn--outline:hover {
  background: var(--mi-glass-strong);
  border-color: var(--mi-glass-border-hover);
  color: var(--mi-title);
  box-shadow: var(--mi-shadow);
}

/* Commercial CTA. Gold is reserved for "this one spends money". */
.mi-btn--accent {
  background: linear-gradient(135deg, var(--mi-accent) 0%, var(--mi-accent-dark) 100%);
  color: #241c00;
  box-shadow: 0 8px 22px rgb(240 177 0 / 0.28), inset 0 1px 0 rgb(255 255 255 / 0.40);
}
.mi-btn--accent:hover {
  background: linear-gradient(135deg, #ffd451 0%, #f5b800 100%);
  color: #241c00;
  box-shadow: 0 14px 32px rgb(240 177 0 / 0.38), inset 0 1px 0 rgb(255 255 255 / 0.5);
}

.mi-btn--ghost {
  background: none;
  border-color: transparent;
  color: var(--mi-muted);
  box-shadow: none;
}
.mi-btn--ghost:hover { color: var(--mi-title); background: var(--mi-glass-soft); }

.mi-btn--block { width: 100%; }
/* Lives here rather than in admin.css: the training detail rail and the related-
   programs cards use it too, and admin.css is not served to the public site. */
.mi-btn--sm { padding: 10px 20px; font-size: var(--mi-text-sm); }

.mi-btn[aria-disabled="true"],
.mi-btn:disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* The chevron slides on hover — the one flourish carried from the reference. */
.mi-btn-arrow {
  display: inline-flex;
  transition: transform var(--mi-transition);
}
.mi-btn:hover .mi-btn-arrow { transform: translateX(4px); }

.mi-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mi-space-4);
  margin-top: var(--mi-space-6);
}

/* ============================ top bar (dark chrome) ============================ */

.mi-topbar {
  position: relative;
  z-index: 51;
  background: var(--mi-ink-deep);
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
}
.mi-topbar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--mi-space-5);
  min-height: 40px;
}
.mi-topbar a {
  display: inline-flex;
  align-items: center;
  gap: var(--mi-space-2);
  color: inherit;
}
.mi-topbar a:hover { color: var(--mi-accent); }

/* ============================ header (dark chrome) ============================ */

/*
  White, with a hairline so it still has an edge against the light banner on inner
  pages. Frosted rather than flat: over the dark hero the blur picks up a faint
  cool cast that ties the bar to the page instead of floating on top of it.

  Once scrolled it goes fully opaque and gains a shadow — on a white bar over
  white content, that shadow is the only thing separating the two.
*/
.mi-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgb(255 255 255 / 0.90);
  border-bottom: 1px solid var(--mi-line);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  transition: background var(--mi-transition), box-shadow var(--mi-transition),
              border-color var(--mi-transition);
}
.mi-header--stuck {
  background: #fff;
  border-bottom-color: transparent;
  box-shadow: 0 6px 24px rgb(19 26 58 / 0.10), 0 1px 0 rgb(19 26 58 / 0.06);
}

.mi-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mi-space-5);
  min-height: 76px;
}

/* ---------- brand mark ----------

  ONE logo file, used everywhere, exactly as supplied: `mitl-logo.png`. It is never
  recoloured, re-proportioned or redrawn — the aspect ratio is fixed by setting
  height only and letting width follow.

  The artwork is dark ink (navy mark, dark-grey wordmark). On the surfaces that are
  still dark — the hero panel, the footer, the admin bar — it gets a white plate
  under it rather than a modified copy of it: the logo keeps its own colours and
  simply gets the light field it was drawn for.

  The header and drawer are white, so the logo sits on them directly with no plate
  at all. A plate there would be a white box on a white bar.
*/
.mi-logo-plate,
.mi-footer-logo img,
.mi-hero-brand img,
.mi-admin-logo {
  background: #fff;
  padding: 7px 13px;
  border-radius: var(--mi-radius-sm);
  box-shadow: 0 4px 14px rgb(10 16 40 / 0.20);
}

.mi-logo {
  display: inline-flex;
  align-items: center;
  transition: transform var(--mi-transition), opacity var(--mi-transition);
}
.mi-logo img { height: 44px; width: auto; }
/* No box-shadow on hover: with no plate the mark is a transparent PNG, and a
   shadow would trace its bounding rectangle rather than its shape. */
.mi-logo:hover { transform: translateY(-1px); opacity: 0.86; }

.mi-drawer-logo { height: 36px; width: auto; }

.mi-footer-logo {
  display: inline-block;
  margin-bottom: var(--mi-space-5);
}
.mi-footer-logo img { height: 60px; width: auto; }

/* The receipt and the gateway handoff are light pages — no plate needed there. */
.mi-receipt-logo {
  height: 44px;
  width: auto;
  margin: 0 auto var(--mi-space-5);
}

.mi-nav { display: none; }
.mi-nav-list {
  display: flex;
  align-items: center;
  gap: var(--mi-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.mi-nav-item { position: relative; }

.mi-nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  border: 1px solid transparent;
  border-radius: var(--mi-radius-pill);
  background: none;
  color: var(--mi-body);
  font-family: var(--font-primary);
  font-size: var(--mi-text-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: color var(--mi-transition), background var(--mi-transition),
              border-color var(--mi-transition);
}
/* A white-on-white glass hover would be invisible, so the hover state on the white
   bar is a faint brand tint instead. */
.mi-nav-link:hover,
.mi-nav-item:focus-within > .mi-nav-link {
  background: rgb(67 86 232 / 0.07);
  border-color: rgb(67 86 232 / 0.16);
  color: var(--mi-brand-deep);
}

/* ---------- current page ----------

  Three signals at once, because any one alone is too easy to miss on a white bar:
  brand-deep ink, a heavier weight, and an accent bar beneath the pill. The bar is the
  one that reads instantly — a tint alone is hard to tell apart from the hover state,
  and the visitor's pointer is usually sitting on a different item anyway.

  500 -> 600 does widen the label very slightly, which nudges the items to its right.
  At this size in Sora it measures about a pixel and is not worth the markup a
  width-reserving ::before duplicate of every label would cost.
*/
.mi-nav-link.is-current {
  background: rgb(67 86 232 / 0.08);
  border-color: rgb(67 86 232 / 0.18);
  color: var(--mi-brand-deep);
  font-weight: 600;
}

.mi-nav-link.is-current::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -11px;
  width: 22px;
  height: 3px;
  transform: translateX(-50%);
  border-radius: 3px 3px 0 0;
  background: linear-gradient(90deg, var(--mi-brand), var(--mi-violet));
}

/* Hovering the current item must not look like leaving it. */
.mi-nav-link.is-current:hover {
  background: rgb(67 86 232 / 0.13);
  border-color: rgb(67 86 232 / 0.3);
}

/* Dropdown — hover on desktop, and kept open while focus is inside for keyboards. */
.mi-submenu {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  z-index: 20;
  min-width: 250px;
  margin: 0;
  padding: var(--mi-space-2);
  background: #fff;
  border: 1px solid var(--mi-line);
  border-radius: var(--mi-radius);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: 0 18px 44px rgb(19 26 58 / 0.14);
  list-style: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity var(--mi-transition), transform var(--mi-transition),
              visibility var(--mi-transition);
}
.mi-nav-item:hover > .mi-submenu,
.mi-nav-item:focus-within > .mi-submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/*
  The nav is right-aligned in the header, so dropdowns must open INWARD. Opening them
  rightward pushed the second level roughly 480px past the header's right edge, and
  because a closed submenu is `visibility: hidden` rather than `display: none` it stays
  in layout and still counts toward scrollable overflow. The result was a permanently
  horizontally-overflowing page: content sat left of centre and full-width backgrounds
  such as the footer stopped short of the right edge.
*/
.mi-nav-list > .mi-nav-item > .mi-submenu { left: auto; right: 0; }
.mi-submenu .mi-submenu { top: -8px; left: auto; right: calc(100% + 8px); }

.mi-submenu a,
.mi-submenu > li > span {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mi-space-3);
  padding: 10px var(--mi-space-4);
  border-radius: var(--mi-radius-sm);
  color: var(--mi-body);
  font-size: var(--mi-text-sm);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--mi-transition), color var(--mi-transition);
}
.mi-submenu a:hover,
.mi-submenu > li > span:hover {
  background: rgb(67 86 232 / 0.08);
  color: var(--mi-brand-deep);
}

/* Current page inside a dropdown. A left accent rail rather than an underline: these
   are stacked rows, and an underline on one reads as a divider between two of them. */
.mi-submenu a.is-current,
.mi-submenu > li > span.is-current {
  background: rgb(67 86 232 / 0.10);
  color: var(--mi-brand-deep);
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--mi-brand);
}

/* ---------- product marks in the navigation ----------

   The certification products carry their logo beside the label. Everything about this follows
   rule 2 of the design system: every vendor mark on disk is dark ink, so it needs a LIGHT
   plate under it, and the plate is what makes six logos from six different brands read as one
   row rather than six pasted images. */

/* Leaf rows read icon-then-label, left to right, so they pack from the start. The group rows
   keep the inherited `space-between` because they carry a trailing chevron that belongs hard
   against the right edge. */
.mi-submenu a { justify-content: flex-start; }

.mi-nav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  overflow: hidden;
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: var(--mi-radius-sm);
}

/* contain, and capped below the plate: these six files have different aspect ratios and
   nothing here may crop or stretch a registered trademark to fit a square. */
.mi-nav-icon img {
  max-width: 20px;
  max-height: 20px;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* The stand-in for a product with no artwork. Brand ink, not grey: it should read as a
   deliberate monogram rather than a missing image. */
.mi-nav-icon-initial {
  color: var(--mi-brand-deep);
  font-family: var(--font-primary);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
}

/* On the white bar a glass fill would disappear, so the toggle is drawn with a
   visible hairline and dark ink. */
.mi-menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--mi-line-strong);
  border-radius: var(--mi-radius-sm);
  background: #fff;
  color: var(--mi-title);
  font-size: 20px;
  cursor: pointer;
  transition: background var(--mi-transition), border-color var(--mi-transition),
              color var(--mi-transition);
}
.mi-menu-toggle:hover {
  background: rgb(67 86 232 / 0.07);
  border-color: rgb(67 86 232 / 0.28);
  color: var(--mi-brand-deep);
}

/* ---------- mobile drawer ---------- */

.mi-drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgb(19 26 58 / 0.5);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.mi-drawer {
  position: fixed;
  inset-block: 0;
  right: 0;
  z-index: 61;
  width: min(370px, 88vw);
  padding: var(--mi-space-5);
  /* The drawer IS the navbar on small screens, so it is white too. */
  background: #fff;
  border-left: 1px solid var(--mi-line);
  box-shadow: -24px 0 60px rgb(19 26 58 / 0.18);
  overflow-y: auto;
}

.mi-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--mi-space-5);
  padding-bottom: var(--mi-space-4);
  border-bottom: 1px solid var(--mi-line);
}

.mi-drawer-close {
  display: inline-flex;
  width: 42px;
  height: 42px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--mi-line-strong);
  border-radius: var(--mi-radius-sm);
  background: #fff;
  color: var(--mi-title);
  font-size: 20px;
  cursor: pointer;
  transition: background var(--mi-transition), border-color var(--mi-transition);
}
.mi-drawer-close:hover {
  background: rgb(67 86 232 / 0.07);
  border-color: rgb(67 86 232 / 0.28);
}

.mi-drawer-list,
.mi-drawer-sublist { margin: 0; padding: 0; list-style: none; }

.mi-drawer-list > li { border-bottom: 1px solid var(--mi-line); }

.mi-drawer-list a,
.mi-drawer-list button {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: var(--mi-space-3);
  padding: 15px 0;
  border: 0;
  background: none;
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.mi-drawer-list a:hover,
.mi-drawer-list button:hover { color: var(--mi-link); }

/* The drawer is the navbar on small screens, so it carries the same signal — a left
   accent rail, which suits a full-width stacked row better than an underline. */
.mi-drawer-list > li > a.is-current,
.mi-drawer-list > li > button.is-current {
  padding-left: var(--mi-space-3);
  border-left: 3px solid var(--mi-brand);
  color: var(--mi-brand-deep);
}

.mi-drawer-sublist a.is-current {
  color: var(--mi-brand-deep);
  font-weight: 600;
}

.mi-drawer-sublist { padding: 0 0 var(--mi-space-3) var(--mi-space-4); }
.mi-drawer-sublist a {
  padding: 9px 0;
  color: var(--mi-muted);
  font-weight: 400;
  font-size: var(--mi-text-sm);
}

/* ============================ hero (the one dark section) ============================ */

/*
  The hero is a SPLIT, not a card on a background: flat navy under the copy, a hatched
  panel under the artwork, and a hard vertical edge between them that runs the full
  height of the slide. The panel bleeds off the right-hand edge of the viewport — that
  bleed is what makes it read as a surface the page is built on rather than a box
  floating in the middle of one.

  Everything here is square. The rest of the site is drawn with pills and 22–30px radii;
  the hero is the one place that is not, and the contrast is deliberate — it is the first
  surface a visitor meets and it should read as machined rather than soft.
*/
.mi-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: 0;
  background: var(--mi-ink-hero);

  /*
    Column flex, so the split GROWS to the slide's height.

    The track is a flex row and every slide is stretched to the tallest one, but the split
    inside is a block-level grid and stops at its own content height. The panel then ends
    short of the slide's bottom edge — visibly, because its brand bar and the champagne
    hatch are supposed to meet on one line and instead sat ~30px apart on the slide the
    carousel's tallest sibling had stretched.
  */
  display: flex;
  flex-direction: column;
}
.mi-hero-split { flex: 1 1 auto; }

/*
  Rule 1 of the design system — neither ground is flat. But where the old hero spent
  three radial washes and a masked grid on that, the split now supplies the structure,
  so one wash is enough and more would only fog the edge the layout depends on.
*/
.mi-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(58% 62% at 8% 0%, rgb(89 112 255 / 0.20) 0%, transparent 70%),
    radial-gradient(40% 50% at 46% 100%, rgb(86 207 253 / 0.07) 0%, transparent 72%);
  pointer-events: none;
}

/*
  `minmax(0, 1fr)`, never a bare `1fr`.

  A bare `1fr` is `minmax(auto, 1fr)`, so the track can never shrink below its
  content's min-content width — enough for one wide child to drag the whole grid
  past the viewport. The page does not scroll sideways (html has `overflow-x: clip`)
  so it simply renders with its right-hand edge cut off.

  Every multi-track grid in this sheet that holds shrinkable content uses
  minmax(0, …) for the same reason.
*/
.mi-hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
}

/*
  The copy carries .mi-container's inset itself instead of being wrapped in one, because
  a container would also inset the panel and there would be no bleed.

  `100%` and not `100vw`: 100vw counts the scrollbar, so on Windows the whole hero would
  sit ~8–17px right of every other section on the page. This percentage resolves against
  the slide, whose width IS the client width.

  The lower bound is the ARROW's clearance, not the page gutter — the carousel arrows are
  positioned against the section, so without it they land on the first word of every line.
*/
@media (min-width: 1024px) {
  /* Two columns, so the inset belongs to the GRID: the panel is the second track and
     starts wherever the grid puts it, which is what keeps its edge vertical. */
  .mi-hero-split {
    padding-inline-start: max(
      clamp(64px, 6vw, 104px),
      calc((100% - var(--mi-container)) / 2 + var(--mi-gutter))
    );
  }
}

@media (max-width: 1023px) {
  /*
    Stacked, so the panel has to be full-bleed and only the copy is inset — the same
    padding on the grid would leave the band starting 64px in from the left with the
    slide's navy showing through beside it.

    The max() above always resolves to its first term below the container width, so it
    is written out directly rather than repeated with a percentage that cannot work
    here: on the copy, `100%` would resolve against the grid area, not the slide.
  */
  .mi-hero-copy {
    padding-inline: clamp(64px, 6vw, 104px) var(--mi-gutter);
    padding-block: clamp(44px, 6vw, 76px) clamp(36px, 5vw, 60px);
  }
}

@media (max-width: 560px) {
  /* Arrows are hidden at this width, so the gutter is all the copy needs. */
  .mi-hero-copy { padding-inline: var(--mi-gutter); }
}

.mi-hero-copy {
  align-self: center;
  max-width: 660px;
  padding-block: clamp(48px, 5.5vw, 96px) clamp(62px, 6vw, 104px);
  padding-inline-end: clamp(24px, 4vw, 72px);
}

.mi-hero h1 {
  margin-bottom: var(--mi-space-5);
  font-size: var(--mi-text-5xl);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.035em;
}

.mi-hero-lead {
  max-width: 52ch;
  color: var(--mi-body);
  font-size: clamp(1.0625rem, 0.98rem + 0.35vw, 1.1875rem);
  line-height: 1.65;
}

/*
  The eyebrow is a chip everywhere else on the site. In the hero it is bare type — a
  chip here would put a rounded pill at the top of the one square surface, and it is
  the first thing on the page, so it sets the register for everything under it.
*/
.mi-hero .mi-eyebrow {
  margin-bottom: var(--mi-space-5);
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--mi-brand-light);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.2em;
}
.mi-hero .mi-eyebrow::before {
  width: 7px;
  height: 7px;
  margin-inline-end: var(--mi-space-1);
  box-shadow: 0 0 0 3px rgb(142 162 255 / 0.16);
}

/* ---------- hero buttons ---------- */

/*
  Square, and flat. The site's default button is a pill with a gradient and a cast
  shadow, which is right on a light card and wrong against this: on the navy the
  shadow does nothing and the gradient muddies the one saturated colour in the frame.
*/
.mi-hero .mi-btn-row { margin-top: var(--mi-space-6); gap: var(--mi-space-3); }
.mi-hero .mi-btn {
  padding: 17px 32px;
  border-radius: 0;
  font-size: var(--mi-text-base);
}
.mi-hero .mi-btn--primary {
  background: var(--mi-brand);
  box-shadow: none;
}
.mi-hero .mi-btn--primary:hover {
  background: #5468ee;
  box-shadow: 0 10px 26px rgb(67 86 232 / 0.38);
}
.mi-hero .mi-btn--outline {
  background: none;
  border-color: rgb(255 255 255 / 0.26);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  color: #fff;
}
.mi-hero .mi-btn--outline:hover {
  background: rgb(255 255 255 / 0.07);
  border-color: rgb(255 255 255 / 0.55);
  box-shadow: none;
}

/* ---------- trust strip ---------- */

/*
  Real counts from the API, not decoration — and sized DOWN rather than up. They used
  to be 28px numerals in the panel, which made three incidental facts compete with the
  headline. As a machined caption row under the lead they are read after it, which is
  the order they matter in.
*/
.mi-hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(22px, 3vw, 44px);
  margin-top: var(--mi-space-6);
}

/* Marked up as a <dl>, so the value/label pair lives in the <dd>. */
.mi-hero-stat dd { display: grid; gap: 4px; margin: 0; }
.mi-hero-stat b {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.mi-hero-stat span {
  color: var(--mi-faint);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* ---------- hero panel ---------- */

/*
  The right-hand surface. Stretched by the grid so it runs the full height of the slide
  and bleeds off the right edge, with a hatch over a tone one step up from the copy's
  ground — enough to separate the two surfaces without a second colour.

  The hatch is drawn in CSS rather than shipped as an image: it is two gradients, it
  scales to any panel size, and it costs no request.
*/
.mi-hero-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--mi-space-4);
  padding: clamp(32px, 4vw, 56px) clamp(24px, 3vw, 48px) clamp(48px, 5vw, 76px);
  background:
    repeating-linear-gradient(45deg,
      rgb(255 255 255 / 0.045) 0 2px,
      transparent 2px 10px),
    linear-gradient(180deg, var(--mi-ink-hero-panel) 0%, #121c3e 100%);
  border-inline-start: 1px solid rgb(255 255 255 / 0.08);
}

/*
  Light plate under the mark, never a recoloured mark. Every logo in the project is dark
  ink; --mi-plate is light on both surfaces for exactly this reason and only its opacity
  changes. Square here, like everything else in the hero.
*/
.mi-hero-plate {
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(100%, 224px);
  min-height: clamp(60px, 5.6vw, 84px);
  padding: clamp(14px, 1.6vw, 22px) clamp(18px, 2vw, 28px);
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: 0;
  box-shadow: 0 18px 44px rgb(6 10 26 / 0.4);
}
.mi-hero-plate img {
  max-width: 100%;
  max-height: clamp(44px, 4.5vw, 62px);
  width: auto;
  object-fit: contain;
}

.mi-hero-plate-cap {
  color: var(--mi-faint);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  text-align: center;
  text-transform: uppercase;
}

/* ---------- bottom rule ---------- */

/*
  Two segments meeting on the column edge: a champagne hatch under the copy and a solid
  brand bar under the panel. It is the one place the two grounds are stated as a pair,
  which is what stops the panel edge reading as an accident of the grid.

  Each segment is drawn by the thing it belongs to — the bar by the panel, the hatch by
  the slide — so they meet on the real column edge at every width instead of on a
  percentage that has to be kept in sync with the grid.

  Champagne is the site's decorative metal: a hairline or a small-caps label on dark
  surfaces, never a fill and never on a light one. A 6px rule is within that.
*/
.mi-hero-panel::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 6px;
  background: var(--mi-brand);
}

.mi-hero-rule {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 2;
  width: min(34%, 540px);
  height: 6px;
  background: repeating-linear-gradient(-45deg,
    var(--mi-metal) 0 5px,
    rgb(255 255 255 / 0.42) 5px 10px);
  pointer-events: none;
}

.mi-hero-logos {
  display: grid;
  gap: var(--mi-space-3);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Light plate — every vendor mark on disk is dark ink and would disappear
   against the dark hero. */
.mi-hero-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 72px;
  padding: var(--mi-space-3) var(--mi-space-4);
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: var(--mi-radius);
  box-shadow: 0 6px 18px rgb(10 16 40 / 0.25);
  transition: transform var(--mi-transition), box-shadow var(--mi-transition);
}
.mi-hero-logo:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 28px rgb(10 16 40 / 0.35);
}
.mi-hero-logo img { max-height: 100%; max-width: 100%; width: auto; object-fit: contain; }

/* ==================== authorised partner band ==================== */

/*
  Full-bleed strip directly under the hero.

  Its own tone, deliberately DEEPER than the hero above it (--mi-ink-deep, the same
  value the top utility bar uses). Two dark blocks in the same shade would merge into
  one slab; a step down reads as a distinct band, which is what makes it stand out
  without adding more darkness to the page overall. Everything below it stays light.
*/
.mi-partner-band {
  position: relative;
  background: var(--mi-ink-deep);
  /* Hairlines of light top and bottom so the strip has edges rather than bleeding
     into the hero above and the light section below. */
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.10),
    inset 0 -1px 0 rgb(255 255 255 / 0.06);
}

/* Its contents are in the dark token set — see the dark block at the top of this file. */
.mi-partner-band-inner {
  display: grid;
  gap: var(--mi-space-3);
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  /* Full width by design: the marquee runs edge to edge, so this is NOT wrapped in
     .mi-container. Only the label is inset, by its own padding. */
  padding-block: clamp(14px, 1.8vw, 22px);
}

.mi-partner-band-label {
  display: flex;
  align-items: center;
  gap: var(--mi-space-2);
  margin: 0;
  padding-inline: var(--mi-gutter);
  color: var(--mi-faint);
  font-family: var(--font-primary);
  font-size: var(--mi-text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}

.mi-partner-band-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mi-brand-light);
  box-shadow: 0 0 0 3px rgb(142 162 255 / 0.18);
  flex-shrink: 0;
}

/* ---------- authorised partner marquee ---------- */

/*
  The list is rendered twice and the track slides exactly -50%, which lands the second
  copy precisely where the first started — so the loop is seamless. A single copy would
  show a gap on every cycle, and duplicating in JavaScript would leave the band empty
  until the script ran.
*/
.mi-marquee {
  position: relative;
  overflow: hidden;
  padding-block: var(--mi-space-2);
  /* Fades both ends so logos enter and leave instead of being sliced by the edge. */
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.mi-marquee-track {
  display: flex;
  width: max-content;
  animation: mi-marquee 32s linear infinite;
}
/* Pausing on hover is what makes it usable rather than decorative: these are links, and
   a moving target cannot be clicked. Focus counts too, for keyboard users tabbing in. */
.mi-marquee:hover .mi-marquee-track,
.mi-marquee:focus-within .mi-marquee-track { animation-play-state: paused; }

.mi-marquee-list {
  display: flex;
  align-items: center;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
  Separators between partners, drawn as a hairline on each item rather than as extra
  elements — so the duplicate copy needs no special handling and the rhythm stays
  perfectly even across the seam where the loop repeats.
*/
.mi-marquee-list > li {
  display: flex;
  align-items: center;
  padding-inline: clamp(18px, 2.4vw, 38px);
  border-right: 1px solid rgb(255 255 255 / 0.10);
}

@keyframes mi-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Light plate — every partner mark is dark ink and would vanish on the dark band. */
.mi-partner {
  display: flex;
  align-items: center;
  justify-content: center;
  width: clamp(112px, 12vw, 152px);
  height: clamp(52px, 5.5vw, 68px);
  padding: var(--mi-space-2) var(--mi-space-4);
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: var(--mi-radius);
  box-shadow: 0 4px 14px rgb(6 10 26 / 0.28);
  transition: transform var(--mi-transition), box-shadow var(--mi-transition);
}
.mi-partner:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 26px rgb(6 10 26 / 0.42);
}
.mi-partner img { max-width: 100%; max-height: 100%; width: auto; object-fit: contain; }

/* Fallback for a partner whose logo has not been supplied. Deliberately looks like a
   wordmark rather than a broken image, and never borrows another brand's artwork. */
.mi-partner-name {
  color: #253063;
  font-family: var(--font-primary);
  font-size: var(--mi-text-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.25;
  text-align: center;
}

/* ==================== page banner (LIGHT, all inner pages) ==================== */

/*
  Inner pages open light. The dark header already gives every page a strong top
  edge; repeating a dark band under it was most of what made the site read as a
  dark theme. This is a pale gradient with a faint brand wash instead — the same
  structure, a fraction of the weight.
*/
.mi-banner {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: var(--mi-space-7);
  background: linear-gradient(180deg, #ffffff 0%, rgb(238 242 252 / 0.55) 100%);
  border-bottom: 1px solid var(--mi-line);
}
.mi-banner::before {
  content: "";
  position: absolute;
  inset: -80% -10% auto -10%;
  height: 260%;
  z-index: -1;
  background:
    radial-gradient(30% 44% at 12% 40%, rgb(67 86 232 / 0.11) 0%, transparent 66%),
    radial-gradient(28% 40% at 88% 6%, rgb(157 123 255 / 0.09) 0%, transparent 66%);
  pointer-events: none;
}

/*
  ---------- deepened variant ----------

  Applied to the five content-rich inner banners: Director Message, Gallery & Events,
  Contact, the vendor listing (Certification Programs) and the training detail page.
  The five that use the shared _PageBanner partial — Service, Privacy, Terms, FAQ,
  Coming Soon — and the PTE voucher page keep the pale banner. Adding the class to
  those views is all it would take to match them.

  Soft indigo, deliberately a shade LIGHTER than the home hero: the hero should stay
  the most dramatic surface on the site, and a banner that matched it would flatten
  that hierarchy. Nowhere near black — this is a tinted band with depth, not a dark
  theme creeping back in.

  Because the selector is in the dark token block above, everything inside recolours
  itself: the eyebrow chip, breadcrumb, lead, fact chips and the white logo plate all
  follow without a single override here.
*/
/*
  Built in three layers, all on the modifier — no markup and no layout changes:

    base   the element's own gradient, running lighter at the top-left to deeper at
           the bottom. That fall-off IS the contrast scrim: the headline and lead sit
           over the darkest part, so no separate overlay is needed.
    ::before  the aurora — three soft colour glows, drifting very slowly.
    ::after   a fine dot matrix, masked so it fades out well before the edges, plus a
              light sheen along the top.
*/
.mi-banner--deep {
  padding-block: clamp(44px, 5.5vw, 84px) clamp(40px, 5vw, 76px);
  background: linear-gradient(168deg, #4a5ca3 0%, #3c4a86 38%, #313f6f 100%);
  /* The pale variant needs a hairline to separate from the page; a dark band already
     has an edge. A rim of light along the top gives it a glass lip instead. */
  border-bottom: 0;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.16);
}

.mi-banner--deep::before {
  /* Oversized and offset so the drift never exposes an edge. */
  inset: -45% -30% auto -30%;
  height: 230%;
  background:
    radial-gradient(28% 44% at 16% 28%, rgb(130 156 255 / 0.45) 0%, transparent 62%),
    radial-gradient(24% 38% at 84% 10%, rgb(170 132 255 / 0.38) 0%, transparent 64%),
    radial-gradient(22% 36% at 62% 88%, rgb(86 207 253 / 0.22) 0%, transparent 66%);
  animation: mi-banner-drift 34s ease-in-out infinite alternate;
  will-change: transform;
}

.mi-banner--deep::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image:
    radial-gradient(circle, rgb(255 255 255 / 0.22) 1px, transparent 1.4px),
    linear-gradient(180deg, rgb(255 255 255 / 0.07) 0%, transparent 22%);
  background-size: 24px 24px, 100% 100%;
  /* The matrix reads as texture only while it is fading out. Left at full strength
     across the whole band it stops being a background and starts being a pattern
     competing with the text. */
  mask-image: radial-gradient(72% 120% at 78% 0%, #000 0%, transparent 70%);
  -webkit-mask-image: radial-gradient(72% 120% at 78% 0%, #000 0%, transparent 70%);
  pointer-events: none;
}

/* ---------- typography & rhythm ---------- */

.mi-banner--deep .mi-breadcrumb { margin-bottom: var(--mi-space-6); }

.mi-banner--deep h1 {
  max-width: 20ch;
  font-size: clamp(2.4rem, 1.5rem + 2.9vw, 3.75rem);
  line-height: 1.06;
  letter-spacing: -0.035em;
  /* Barely-there depth. The headline sits over a gradient that shifts under it, and
     this keeps the stems crisp wherever it lands. */
  text-shadow: 0 1px 2px rgb(16 22 48 / 0.28);
}
.mi-banner--deep .mi-banner-inner > h1 { max-width: 24ch; }

.mi-banner--deep .mi-lead {
  max-width: 62ch;
  font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
  color: rgb(214 222 245 / 0.92);
}

.mi-banner--deep .mi-chips { margin-top: var(--mi-space-6); }

/* The glassmorphism, on the two elements that already exist here. Over the aurora the
   blur has something worth blurring, so these read as frosted rather than translucent. */
.mi-banner--deep .mi-eyebrow,
.mi-banner--deep .mi-chip {
  background: rgb(255 255 255 / 0.10);
  border-color: rgb(255 255 255 / 0.22);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  box-shadow: 0 6px 20px rgb(14 20 46 / 0.22), inset 0 1px 0 rgb(255 255 255 / 0.18);
}
.mi-banner--deep .mi-eyebrow { color: #dbe3ff; }

.mi-banner--deep .mi-banner-media {
  box-shadow: 0 18px 44px rgb(14 20 46 / 0.42), inset 0 1px 0 rgb(255 255 255 / 0.9);
}

/* ---------- motion ---------- */

@keyframes mi-banner-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(2.5%, -2%, 0) scale(1.08); }
}

@keyframes mi-banner-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

/*
  A short staggered entrance. Deliberately on the direct children so it needs no
  markup: breadcrumb, then the copy block, then the media plate.
*/
.mi-banner--deep > .mi-container > *,
.mi-banner--deep .mi-banner-inner > * {
  animation: mi-banner-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.mi-banner--deep .mi-banner-inner > *:nth-child(1) { animation-delay: 0.04s; }
.mi-banner--deep .mi-banner-inner > *:nth-child(2) { animation-delay: 0.10s; }
.mi-banner--deep .mi-banner-inner > *:nth-child(3) { animation-delay: 0.16s; }
.mi-banner--deep .mi-banner-inner > *:nth-child(4) { animation-delay: 0.22s; }
.mi-banner--deep .mi-banner-media { animation: mi-banner-rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.18s both; }

.mi-banner-inner { max-width: 880px; }

.mi-banner h1 {
  margin-bottom: var(--mi-space-4);
  font-size: var(--mi-text-4xl);
  letter-spacing: -0.03em;
}
.mi-banner h1:last-child { margin-bottom: 0; }

/* Two-column banner: copy left, brand plate right. */
.mi-banner-grid {
  display: grid;
  gap: var(--mi-space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
}

/* Vendor / training mark. White card so the dark-ink logo has a clean field. */
.mi-banner-media {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--mi-space-5) var(--mi-space-6);
  background: #fff;
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  box-shadow: var(--mi-shadow);
}
.mi-banner-media img { max-height: 92px; width: auto; object-fit: contain; }

/* ==================== page head (PHOTOGRAPHIC, four inner pages) ====================

   Director Message, the vendor listing, a training detail page and Contact. Everything
   here is admin-editable per page — image, crop, overlay strength, tone, and the copy —
   see PageBanner in the CMS. The CSS reads three custom properties off the element:

     --mi-pagehead-image    url() of the photograph, or unset for the plain panel
     --mi-pagehead-overlay  0-1, how dark the scrim is
     --mi-pagehead-focal    object-position, which band of the photo survives the crop

   THREE LAYERS, and no fourth. The banner these replaced stacked an animated three-colour
   aurora, a masked dot matrix and a sheen over a gradient, then set the emphasised words
   of the headline in gradient text on top of it. Each effect was defensible alone;
   together they were the reason the headline never looked crisp.

     media   the photograph, cropped, in the brand's own monotone, settling out of a slow
             scale so the surface has life without ever looping
     scrim   ONE dark layer — a left-weighted wash, a top/bottom depth pass and a corner
             vignette, composited into a single element and scaled by one opacity
     body    solid white type. No gradient text, no glow, nothing clever.

   WHERE THE "EXPENSIVE" COMES FROM, in the order of how much each contributes: the
   photograph, the empty space around the headline, the type, and only then the details —
   a champagne hairline, a fine border, a soft shadow. That order is why the metal appears
   exactly twice per banner (the eyebrow rule and the bottom edge) and is never a fill.
*/

.mi-pagehead {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  /* Generous. The single biggest lever on whether a banner reads as premium or as a
     content bar is how much room the headline is given to sit in. */
  min-height: clamp(340px, 36vw, 500px);
  padding-block: clamp(60px, 6.5vw, 104px);

  /* The plain panel, and the ground the photograph sits on while it loads. A flat brand
     navy — one colour, not a gradient. */
  background: var(--mi-ink-deep);
  /* A rim of light along the top, so the band reads as a surface catching light from the
     white navbar above it rather than as a hole cut in the page. */
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.10);

  --mi-pagehead-overlay: 0.82;
  --mi-pagehead-focal: center;

  /*
    Two contextual tokens, brightened for THIS surface only.

    The shared dark block near the top of this file sets --mi-faint to #8b95b4 and --mi-body
    to #c2cbe4. Those are tuned for flat navy, and a banner is not flat navy — it is a
    photograph the admin can replace at any time. Measured against the rendered pixels, the
    breadcrumb at #8b95b4 came out at 2.97:1 on Contact and 4.11:1 on the vendor listing, and
    dropped to 1:1 — text the exact colour of what was behind it — over a bright, mid-grey
    office photograph. It failed on banners nobody had touched.

    White at an opacity is the fix rather than a darker scrim, because the scrim is the thing
    the brief keeps asking to be lighter. Everything else in the banner already states its
    colour literally for the same reason; the breadcrumb was the one part left on tokens.

    .mi-pagehead--training redeclares both back to dark ink — it is a LIGHT banner — and wins
    on source order, so this does not reach it.
  */
  --mi-faint: rgb(255 255 255 / 0.88);
  --mi-body: rgb(255 255 255 / 0.92);
}

/* The breadcrumb is 14px over a photograph, so it also carries the shadow the headline and
   standfirst have. Not decoration — it is what holds the stems together where the crop puts
   a bright frame behind them. */
.mi-pagehead .mi-breadcrumb { text-shadow: 0 1px 3px rgb(8 12 30 / 0.55); }

/*
  ...but only on the DARK banners. All three light page heads set the breadcrumb in dark ink, and
  a 55%-black shadow under dark ink on a flat pale ground is the "reads as smudge" case the light
  variants already rule out for the headline and the standfirst. The breadcrumb was simply missed
  when the training banner was inverted, so this fixes that at the same time rather than leaving
  one banner smudged and the other clean.
*/
.mi-pagehead--training .mi-breadcrumb,
.mi-pagehead--director-message .mi-breadcrumb { text-shadow: none; }

/* ---------- the photograph ---------- */

.mi-pagehead-media,
.mi-pagehead-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.mi-pagehead-media {
  background-position: var(--mi-pagehead-focal);
  background-size: cover;
  background-repeat: no-repeat;
  /*
    Monotone by default. Four photographs from four different shoots — three agency stock,
    one of the company's own building — will never agree on white balance, and side by side
    that is what makes a site look assembled rather than designed. Stripping colour and
    letting the scrim supply the single brand tone is what makes them one family.
    `.mi-pagehead--colour` opts a campaign image out.
  */
  filter: grayscale(1) contrast(1.07) brightness(0.97);
}
.mi-pagehead--photo .mi-pagehead-media { background-image: var(--mi-pagehead-image); }
.mi-pagehead--colour .mi-pagehead-media { filter: contrast(1.03) saturate(0.9); }

/* ---------- the scrim ---------- */

/*
  Left-weighted, because the copy is left-aligned. A flat overlay dark enough for the
  headline is dark enough to erase the photograph, and one light enough to show the
  photograph leaves the headline sitting on whatever happens to be behind it. Graded
  across, both hold: ~0.96 alpha under the text, ~0.26 at the right edge where the picture
  does the work.

  Deliberately lighter overall than a "dark banner" would be. The brief is expensive, not
  dark, and past roughly 0.9 the photograph stops being a photograph and becomes a texture.

  The admin's slider scales the whole layer, so 100% is a solid brand panel and 0% is the
  bare photograph — and every value between keeps the same left-to-right weighting.
*/
.mi-pagehead-scrim {
  opacity: var(--mi-pagehead-overlay);
  background:
    /* corner vignette — depth at the edges without touching the middle */
    radial-gradient(120% 90% at 50% 45%, transparent 42%, rgb(8 12 26 / 0.42) 100%),
    /*
      Top and bottom, so the band sits under the white navbar and above the page.

      The top stop carries a second job: the breadcrumb sits in it, and the breadcrumb is the
      smallest text on the banner over the part of a photograph most likely to be sky or
      window. Measured against rendered pixels it was failing AA on banners nobody had
      touched — 2.97:1 on Contact — and a backlit office photograph took it to 1:1. Raised
      from 0.40 to 0.58 for that, which is still a graded top edge rather than a dark
      background: it is fully transparent again by a third of the way down.
    */
    linear-gradient(180deg, rgb(10 14 30 / 0.58) 0%, rgb(10 14 30 / 0) 34%, rgb(10 14 30 / 0.46) 100%),
    /* the wash the copy is read against */
    linear-gradient(97deg,
      rgb(16 22 45 / 0.96) 0%,
      rgb(18 25 51 / 0.90) 30%,
      rgb(21 29 60 / 0.60) 64%,
      rgb(25 34 70 / 0.26) 100%);
}

/*
  A champagne rule along the bottom edge that fades out well before either end. A
  full-width metal line would be a border, and a border is not a detail.
*/
.mi-pagehead::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%, var(--mi-metal-line) 22%, var(--mi-metal-line) 58%, transparent 88%);
  pointer-events: none;
}

/* ---------- layout ---------- */

.mi-pagehead > .mi-container { width: 100%; }

/* Copy left, brand plate right. One column until there is room for two. */
.mi-pagehead-grid {
  display: grid;
  gap: var(--mi-space-7);
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
}

.mi-pagehead-body { max-width: 760px; }

.mi-pagehead .mi-breadcrumb { margin-bottom: var(--mi-space-6); }

/* ---------- type ----------

   Solid colours throughout, and stated literally rather than through the contextual
   tokens: the type here sits on a photograph the admin can replace at any time, so its
   contrast must not depend on what the rest of the surface is doing. */

/*
  A flat label with a champagne rule, not the glass pill used elsewhere. Over a photograph
  a frosted chip with its own shadow is one more floating object competing with the
  headline; a rule and a word are quieter, and read as typography rather than as UI.
*/
.mi-pagehead-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--mi-space-4);
  margin-bottom: var(--mi-space-5);
  color: var(--mi-metal);
  font-family: var(--font-primary);
  font-size: 0.8125rem;
  font-weight: 500;
  /* Wide. At this size the tracking does as much work as the colour does. */
  letter-spacing: 0.24em;
  line-height: 1.5;
  text-transform: uppercase;
}
.mi-pagehead-eyebrow::before {
  content: "";
  width: 38px;
  height: 1px;
  background: currentColor;
  opacity: 0.75;
  flex-shrink: 0;
}
a.mi-pagehead-eyebrow { text-decoration: none; transition: color var(--mi-transition); }
a.mi-pagehead-eyebrow:hover { color: #fff; }

/*
  600, not 700. At this size the heavier weight reads as loud rather than as confident,
  and the tighter tracking below only holds together at the lighter one.
*/
.mi-pagehead-title {
  margin: 0;
  max-width: 20ch;
  color: #fff;
  font-size: clamp(2.5rem, 1.35rem + 3.6vw, 4.25rem);
  font-weight: 600;
  line-height: 1.03;
  letter-spacing: -0.035em;
  text-wrap: balance;
  /* Readability, not decoration: a tight shadow keeps the stems crisp wherever the crop
     happens to put a bright patch behind them. Not a glow. */
  text-shadow: 0 1px 3px rgb(8 12 30 / 0.45);
}

/*
  Near-white, not the pale blue-grey it used to be. Measured against the rendered pixels the
  old rgb(228 234 250 / 0.86) held up over a dark photograph (11.8:1 on Contact) and failed
  over a bright one (3.6:1), and "depends which photograph the admin picked" is not a
  contrast standard. The shadow is carried a little further for the same reason: it is what
  keeps the stems readable where the crop happens to put a window behind them.
*/
.mi-pagehead-lead {
  margin-top: var(--mi-space-5);
  max-width: 52ch;
  color: rgb(255 255 255 / 0.94);
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
  font-weight: 400;
  line-height: 1.75;
  text-shadow: 0 1px 4px rgb(8 12 30 / 0.5);
}

.mi-pagehead .mi-chips { margin-top: var(--mi-space-6); gap: var(--mi-space-3); }

/*
  Glass, and the one place blur is spent on this surface: these sit directly on the
  photograph, where a flat translucent fill picks up whatever is behind each chip and stops
  reading as a row. Finer than the chips elsewhere on the site — hairline border, no cast
  shadow, just an inner lip of light.
*/
.mi-pagehead .mi-chip {
  padding: 7px 16px;
  background: rgb(255 255 255 / 0.09);
  border-color: rgb(255 255 255 / 0.20);
  backdrop-filter: blur(16px) saturate(125%);
  -webkit-backdrop-filter: blur(16px) saturate(125%);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.13);
  color: rgb(238 242 255 / 0.9);
  font-weight: 400;
}
.mi-pagehead .mi-chip b { color: #fff; font-weight: 600; }

/*
  Vendor / training mark. A white card so the dark-ink logo has a clean field over the
  photograph — with a champagne hairline and a deep, soft cast shadow, because here it is
  floating on an image rather than sitting on a flat panel.
*/
.mi-pagehead-plate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--mi-space-6) var(--mi-space-7);
  background: #fff;
  border-radius: var(--mi-radius-lg);
  box-shadow:
    0 0 0 1px rgb(214 191 149 / 0.28),
    0 30px 60px -20px rgb(6 10 26 / 0.62),
    0 8px 20px -8px rgb(6 10 26 / 0.40);
}
.mi-pagehead-plate img { max-height: 92px; width: auto; object-fit: contain; }

/* ---------- the training banner: a light blue wash, not a dark scrim ----------

   Everything above builds a DARK banner: a near-opaque navy scrim over a monotone
   photograph, with white type on top. The training detail page inverts that, and it is a
   composition rather than an overlay: a flat light-blue panel holds the copy on the left, one
   soft edge hands over to the photograph, and the right third of the banner is untouched
   picture. Nothing is tinted that does not need to be.

   Three things follow from turning one dark region light, and all three are required — a
   light background with the dark type rules still in force is an unreadable banner:

   1. `.mi-pagehead` is in the dark CONTEXTUAL TOKEN block near the top of this file, so
      everything token-driven inside it — the breadcrumb, the chips, glass, lines — is drawn
      for a dark ground. The tokens are redeclared back to the light set below. This is the
      same move `@media print` makes on the hero, and the reason those components need no
      individual overrides here.
   2. The headline, standfirst and eyebrow do NOT use tokens. They are stated literally
      (`#fff`, a pale blue, champagne) precisely because they sit on an admin-replaceable
      photograph, so each one has to be restated.
   3. Both type shadows go. They exist to keep white stems crisp over a bright patch of
      photograph; under dark ink on flat colour they read as smudge.

   The opacity is fixed at 1 rather than reading --mi-pagehead-overlay, because the brief for
   this banner is exactly 100% at the left and 0% at the right. The admin's overlay slider
   therefore has no effect on the training banner — the ramp is in the gradient's own alpha
   stops instead. The other three banners are untouched and still respect it.

   The photograph here is in FULL COLOUR, unlike the other three banners. That is the CMS
   "tone" setting on the training banner row, not a rule in this file — the shared
   `.mi-pagehead--colour` class does the work. It is the one banner that opts out, because
   the monotone convention exists to stop four different shoots clashing with each other
   across the site, and this banner no longer has a dark scrim to supply a shared tone. */
.mi-pagehead--training {
  color-scheme: light;

  /*
    The ground, and it is the Director Message banner's ground rather than this one's own.

    This band used to open on #cfe8fc, a bright blue, under a saturated azure panel. It now
    carries the same near-white surface the Director Message hero uses, so the two light heroes
    read as one family instead of as two different treatments. Kept within a few units of the
    wash's flat region below, so any shortfall while the photograph loads is invisible.
  */
  background: #f7f9fd;
  /* Firmer than the 0.05 this used to carry, and for the reason the Director banner records:
     on a ground this close to the header's white the top edge has to be drawn, or the band
     bleeds into the white navbar above it. */
  box-shadow: inset 0 1px 0 rgb(19 26 58 / 0.07);

  /*
    Contextual tokens, back to the light-surface set — see note 1 above.

    These are now the Director Message values, because they were measured against a near-white
    ground and this band is now that ground. The previous set was tuned against a saturated azure
    at ~0.58 relative luminance and does not transfer: on white it was simply darker than it
    needed to be.

    Two separate sources have to be answered here — the shared dark block near the top of this
    file, AND `.mi-pagehead` itself, which sets --mi-faint and --mi-body to 88-92% WHITE for the
    dark banners. That second pair is the one that silently produces white text on a white
    background, because it is nowhere near the token block and is easy to miss.

    Re-measured against the rendered pixels after the change, on the tallest programme page
    (/eccouncil/chfi). Every one of them improved on the azure figures, because near-white is a
    lighter ground than a saturated blue:

      #131a3a title      16.4:1   (was 10.0 on azure)
      #414a68 body        8.2:1   (was  5.6)
      #545d78 faint       6.5:1   (was  4.9, the breadcrumb)
      brand-deep eyebrow  8.3:1   (was  5.0)
  */
  --mi-title: #131a3a;
  --mi-body: #414a68;
  --mi-muted: #4a5370;
  --mi-faint: #545d78;
  --mi-link: #2b39b8;
  --mi-link-hover: #1b2380;

  --mi-line: rgb(19 26 58 / 0.09);
  --mi-line-strong: rgb(19 26 58 / 0.16);

  --mi-glass: linear-gradient(160deg, rgb(255 255 255 / 0.96) 0%, rgb(255 255 255 / 0.78) 100%);
  --mi-glass-soft: linear-gradient(160deg, rgb(255 255 255 / 0.76) 0%, rgb(255 255 255 / 0.52) 100%);
  --mi-glass-strong: linear-gradient(160deg, rgb(255 255 255 / 1) 0%, rgb(255 255 255 / 0.9) 100%);
  --mi-glass-border: rgb(19 26 58 / 0.08);

  --mi-surface: #ffffff;
  --mi-surface-2: #eff2fa;
  --mi-plate: #f3f6fd;
  --mi-plate-border: rgb(19 26 58 / 0.06);
}

/*
  A PANEL, not a wash.

  This started as a five-stop gradient ramped across the full width, and the whole width was
  the problem: because some tint reached every pixel, the photograph was veiled everywhere
  and the man on the left read as a ghost behind coloured glass. Tinting an area is not the
  same as making it readable.

  So there are effectively two regions and one edge between them:

    0 -> 50%    flat colour. No ramp at all — the copy sits on one solid value, which is why
                the contrast figures above hold exactly rather than "somewhere along a ramp".
                50% is not arbitrary: .mi-pagehead-lead runs to 52ch, about 735px at desktop,
                and the flat region has to finish outside that or the ends of those lines sit
                on half-transparent photograph. Widening the FLAT part is free in gradient
                terms — it is still one edge — so readability is bought without adding ramp.
    50 -> 70%   the single soft edge, ~290px at desktop. Long enough to read as light falling
                off rather than as a box with a border.
    70 -> 100%  nothing. The photograph is completely untouched here, which is the whole
                point: it is the part a visitor actually looks at.

  One gradient, one edge, a third of the banner left alone. No vignette, no top/bottom depth
  pass, no second layer — each of those was a reason the old version read as an effect
  applied to a photograph rather than as a composition.
*/
/*
  ONE white layer, the Director Message wash rather than this banner's old azure panel.

  Same tone, same structure, same single soft edge — the only difference is where the edge sits.
  The Director banner holds a narrower copy column and can start falling off at 46%; the copy
  here is capped at 700px, which is 48.6% of a 1440 band, so the flat region runs to 50% and the
  fall-off is shifted the same few points right. Without that shift the last line of a
  three-line standfirst sits on a half-transparent wash over a bright kitchen photograph, which
  is exactly the failure a 68ch experiment on this banner already produced (2.06:1).

  Opacity is pinned to 1 with the ramp in the alpha stops, so the admin's overlay slider does not
  apply here — unchanged from before, and the same trade the Director banner makes.
*/
.mi-pagehead--training .mi-pagehead-scrim {
  opacity: 1;
  background: linear-gradient(92deg,
    rgb(255 255 255 / 0.93) 0%,
    rgb(255 255 255 / 0.91) 50%,
    rgb(252 253 255 / 0.58) 58%,
    rgb(250 252 255 / 0.18) 62%,
    rgb(248 251 255 / 0) 66%);
}

/*
  ---------- the training banner's framing ----------

  The photograph is 3:2 and the banner is nearly 3:1, so at `cover` the image scales to the
  banner's WIDTH and the height is what gets cropped. There is then no horizontal overflow at
  all, and background-position-x has nothing to move.

  106% buys ~6% of width to play with. It was arrived at by elimination: 128% was tried first
  and cost twice over, because zooming enlarges the subjects into the axis that is ALREADY the
  cropped one — her head was cut by the top edge and the standing man was reduced to a stray
  blue shape at the panel's edge. At 106% nothing is meaningfully enlarged and the headroom
  above both faces survives.

  NO background-position-x HERE, DELIBERATELY. It was pinned to 0% for a while, to push the
  pair as far right as the overflow allowed. Pinning it is what has now been removed, which is
  what moves the picture back LEFT by about 43px at 1440 — the whole 86px of overflow used to
  sit off the right-hand edge and is now split evenly. Two reasons the pin is gone rather than
  merely retuned to 50%:

    - 50% would be a NO-OP written out longhand. The base rule already sets
      `background-position: var(--mi-pagehead-focal)`, and the focal is "center 24%", so X is
      already centre. A declaration that changes nothing is a declaration that will mislead
      whoever reads it next.
    - It silently clobbered the admin. Anything other than centre in the stored focal point had
      its X thrown away here — and thrown away to exactly the value centre gives, so the
      override would have been undiagnosable from the rendering.

  So both axes now come from the CMS focal point, and the crop is entirely the admin's to move.
*/
.mi-pagehead--training .mi-pagehead-media {
  /*
    "cover", not the "106% auto" this used to carry, and the artwork is why.

    That zoom existed for a 3:2 photograph: at 1.5:1 the drawn height works out to 0.667 of the
    width, which above 1024 is always taller than the band, so the image covered and the 6%
    bought a little horizontal slack for the crop. The current file is 1520x510 — 2.98:1 — and at
    that ratio 106% of a 1440 band draws only 512px of height into a box around 631px tall. The
    shortfall would have shown as the #f7f9fd load colour in a strip above and below the
    photograph, because .mi-pagehead-media has no background-colour of its own.

    "cover" scales to whichever axis needs it and crops the other, so the band stays covered at
    whatever ratio the admin uploads next. Both axes still come from the CMS focal point.
  */
  background-size: cover;
}

/*
  The small-screen rule that used to sit here is gone, and its removal is the point rather than
  an omission. It existed to put `background-size: cover` back below 1024, because the desktop
  rule was `106% auto`. The desktop rule IS `cover` now, so the override restated the value it
  was already inheriting — a no-op that read like a fix.
*/

/* ---------- the Director Message banner: light, with the photograph kept clear ----------

   The second light page head, and it is light for a different reason than the training one. That
   banner needed a coloured panel to hold a headline over a busy photograph; this one is a bright,
   backlit meeting room shot on a near-white ground, and the brief is a white editorial hero —
   navy type on white at the left, the photograph simply left alone at the right.

   WHAT THE ARTWORK ACTUALLY CONTAINS, measured rather than assumed. The current file is
   director-team-cutout.webp, 1520x609, and the important thing about it is that it has an ALPHA
   CHANNEL: the client supplied the photograph pre-composed with its left third cut out. Sampling
   it by column gives:

     x  0-30%   fully transparent (0% opaque) — the banner's own background shows through
     x 30-35%   the alpha transition
     x 35-45%   opaque, but bright window glass — mean luminance 226-238, no subject
     x 45-95%   the five figures, peaking at 55% and again at 85%

   That changes the job of the wash completely. It no longer has to manufacture a text panel out
   of a busy photograph — the transparency already gives one — so it only has to cover the short
   overlap where the copy runs past the alpha edge onto the glass and the nearest figure. The copy
   column ends around 51% at desktop (the standfirst is 52ch), the figures start at 45%, so the
   wash carries near-solid white to 46% and is gone by 62%. Everything from 62% right — the seated
   man, the standing woman, the right-hand seated man — is unmodified photograph.

   No zoom, and no background-position-x. It is tempting to push the group about, but that is the
   mistake the training banner already paid for: zooming enlarges into the axis that is ALREADY
   cropped. `cover` stays, so proportions are natural and both axes remain the admin's through the
   focal point.

   One consequence of the alpha worth knowing: the transparent third means the image genuinely does
   NOT cover its box on the left, so `.mi-pagehead--director-message`'s own background below is not
   merely a load colour — it is the visible left-hand panel.

   Every override below answers a specific rule that would otherwise render white-on-white; the
   list came from auditing the base page head rather than from copying the training variant. The
   block sits AFTER `.mi-pagehead` in this file on purpose: `.mi-pagehead--director-message` is a
   single class and ties with it, so source order is what makes these win. */
.mi-pagehead--director-message {
  color-scheme: light;

  /* The load ground, and the plain panel if the CMS row is ever switched off. The base rule sets
     --mi-ink-deep here, which would be a navy slab under navy type. Kept within a few units of
     the wash's flat region so any shortfall is invisible. */
  background: #f7f9fd;

  /* The base rule lights the top edge with 10% white, which on a near-white band is invisible —
     the banner would bleed into the white navbar above it. On a light surface the edge has to be
     drawn, and slightly firmer than the training banner needs because this ground is much closer
     to the header's white. */
  box-shadow: inset 0 1px 0 rgb(19 26 58 / 0.07);

  /*
    Contextual tokens back to the light set. Two separate sources have to be answered here:
    the shared dark block near the top of this file, AND `.mi-pagehead` itself, which sets
    --mi-faint and --mi-body to 88-92% WHITE for the dark banners. That second pair is the one
    that silently produces white text on a white background, because it is nowhere near the token
    block and is easy to miss.

    Values are darker than the site's light defaults, measured against this near-white ground
    rather than inherited from the training block (whose figures were taken against a saturated
    azure and do not transfer).
  */
  --mi-title: #131a3a;
  --mi-body: #414a68;
  --mi-muted: #4a5370;
  --mi-faint: #545d78;
  --mi-link: #2b39b8;
  --mi-link-hover: #1b2380;

  --mi-line: rgb(19 26 58 / 0.09);
  --mi-line-strong: rgb(19 26 58 / 0.16);

  --mi-glass: linear-gradient(160deg, rgb(255 255 255 / 0.96) 0%, rgb(255 255 255 / 0.78) 100%);
  --mi-glass-soft: linear-gradient(160deg, rgb(255 255 255 / 0.76) 0%, rgb(255 255 255 / 0.52) 100%);
  --mi-glass-strong: linear-gradient(160deg, rgb(255 255 255 / 1) 0%, rgb(255 255 255 / 0.9) 100%);
  --mi-glass-border: rgb(19 26 58 / 0.08);
  --mi-glass-border-hover: rgb(67 86 232 / 0.36);

  --mi-surface: #ffffff;
  --mi-surface-2: #eff2fa;
  --mi-tint: linear-gradient(180deg, rgb(236 240 251 / 0.9) 0%, rgb(246 249 253 / 0.7) 100%);
  --mi-plate: #f3f6fd;
  --mi-plate-border: rgb(19 26 58 / 0.06);
}

/*
  ONE layer, white, and it stops well short of the people.

  The base scrim stacks three dark layers scaled by the admin's overlay slider. All of it is
  replaced: no vignette and no top/bottom depth pass, because both exist to give a dark banner
  weight and on white they only turn it grey. Opacity is pinned to 1 and the ramp lives in the
  alpha stops, so the slider no longer applies to this banner — the same trade the training
  banner makes, and worth knowing before reaching for that control.

  Near-solid white to 46%, gone by 62%. Both numbers come from the measurements above rather than
  from taste: 46% is past the alpha edge and past the end of the copy column, and 62% is clear of
  the figures that start at 45% but well before the ones from 62% on, which stay untouched.

  It is much shorter and much gentler than the version this replaces. That earlier wash had to run
  to 72% at 97.5% opacity because the photograph then had figures behind the entire headline and
  the panel had to be manufactured. With the cut-out artwork the transparency does that work, so
  the overlay is back to what the brief actually asked for — present only where the text needs it.
*/
.mi-pagehead--director-message .mi-pagehead-scrim {
  opacity: 1;
  background: linear-gradient(92deg,
    rgb(255 255 255 / 0.93) 0%,
    rgb(255 255 255 / 0.91) 46%,
    rgb(252 253 255 / 0.58) 54%,
    rgb(250 252 255 / 0.18) 58%,
    rgb(248 251 255 / 0) 62%);
}

/* Champagne on white is barely a colour. An ink hairline keeps the bottom edge as an edge. */
.mi-pagehead--director-message::after {
  background: linear-gradient(90deg,
    transparent 0%, rgb(19 26 58 / 0.13) 22%, rgb(19 26 58 / 0.13) 58%, transparent 88%);
}

/*
  The type. All three of these state their colour literally in the base rules — precisely because
  they sit over an admin-replaceable photograph — so tokens alone do not reach them.

  Both text shadows go. They exist to hold white stems together over a bright patch of
  photograph; under dark ink on flat white they read as grime. That includes the BREADCRUMB,
  which the base rule also shadows and which the training variant missed — see the note below.
*/
.mi-pagehead--director-message .mi-pagehead-title {
  color: var(--mi-title);
  text-shadow: none;
}

.mi-pagehead--director-message .mi-pagehead-lead {
  color: var(--mi-body);
  text-shadow: none;
}

/*
  The label, in gold rather than the site's champagne. --mi-metal is #d6bf95, which is a highlight
  colour for dark surfaces and lands around 1.9:1 on white — nowhere near readable for 13px
  uppercase text. #8f6a1a is the same warm family taken down until it clears AA: measured 4.95:1
  on pure white. The 38px rule before it inherits currentColor, so it turns gold with the label
  and needs no rule of its own.
*/
.mi-pagehead--director-message .mi-pagehead-eyebrow { color: #8f6a1a; }
.mi-pagehead--director-message a.mi-pagehead-eyebrow:hover { color: #6f5214; }

/* Defensive: this page sets no chips today, but the base rule dresses them as white glass with
   white bold text, which would vanish here the moment anyone adds one. */
.mi-pagehead--director-message .mi-chip {
  background: rgb(255 255 255 / 0.82);
  border-color: rgb(19 26 58 / 0.10);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.9);
  color: var(--mi-body);
}
.mi-pagehead--director-message .mi-chip b { color: var(--mi-title); }

/* ---------- the vendor listing banner: a photograph, mark top-right, name at the foot ----------

   The brief for this one is specific and the layout is the whole of it: the vendor's photograph
   fills the band, the vendor's mark sits top-right in a light glass container, and the vendor's
   name and programme count sit in the LOWER portion of the band. Everything in it is per-vendor
   already — the title is the vendor name, the standfirst is its programme count and the plate is
   its own mark — so nothing here is hard-coded and no markup changed to get this arrangement.

   This replaces a near-white, photograph-free version of the same banner. That one existed
   because a single shared photograph behind sixteen different vendor brands says nothing about
   any of them; the answer to that here is the mark, which is the one genuinely per-vendor object
   in the band and now has the strongest position in it.

   BACK ON THE DARK TOKEN SET. `.mi-pagehead` is listed in the dark contextual-token block near
   the top of this file, so removing the light overrides the previous version carried is all it
   takes for the breadcrumb, chips and glass to recolour themselves for a photograph. The one
   thing that does NOT come back on its own is the type, which states its colours literally
   precisely because it sits on an admin-replaceable image — restated at the foot of this block.

   "Not too dark" is the constraint that shapes the scrim. It is bottom-weighted rather than flat:
   the middle of the photograph is left almost untouched, and the ink is spent only along the
   bottom edge where the name and the count actually sit. A flat scrim heavy enough to carry text
   at the foot would have greyed out the whole picture. */
.mi-pagehead--vendor {
  /* Fill the band rather than centring one block in it, so the grid below can push the copy to
     the foot. The base rule centres, which is right for the other three banners. */
  align-items: stretch;

  /*
    510px exactly, from 1024 up. The band was running to 621px at 1440 because the base rule
    pays clamp(60px, 6.5vw, 104px) of padding top AND bottom — 187px of the total — on top of a
    content block that the wrapped headline had pushed to 433px.

    Both halves are addressed rather than just one: the headline is back on a single line (see
    the body width below), which takes the content to ~365px, and the padding drops to 64px a
    side. 365 + 128 = 493, which is UNDER the 510 floor, so min-height is what sets the height
    and it lands on 510 exactly — with ~17px of slack, so a vendor whose standfirst runs long
    grows the band rather than being clipped by a fixed height.
  */
  min-height: 510px;

  /* The ground under the photograph while it loads, and the panel if the CMS row is switched
     off. A deep indigo rather than the base --mi-ink-deep so there is no flash of a lighter
     navy before a night photograph paints over it. */
  background: #0d1430;
}

.mi-pagehead--vendor > .mi-container {
  display: flex;
  flex-direction: column;
}

/*
  The arrangement, in one grid and with no markup change:

    row 1, col 2   the mark, pinned top-right
    row 2, full    the name and the count, pushed to the foot by the 1fr row above them

  `.mi-pagehead-grid`'s own rule is `minmax(0,1fr) auto` with align-items:center from 768 up,
  which is copy-left / plate-right on one line. Both are restated here.
*/
.mi-pagehead--vendor .mi-pagehead-grid {
  display: block;
  flex: 1;
  grid-template-columns: minmax(0, 1fr) auto;
  /* ONE row that fills the band, not two. The two-row version pinned the mark to a top row and
     let the copy fall through a 1fr row beneath it, which put the mark in the top corner and the
     copy in the opposite corner — a diagonal with a hole in the middle. One row lets each side
     align itself against the full height instead. */
  grid-template-rows: 1fr;
  gap: var(--mi-space-6);
}

.mi-pagehead--vendor .mi-pagehead-plate {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  /* TOP of the band, not its middle. The mark is a credential stamped in the upper corner —
     it is not the right-hand half of a two-column composition, and centring it against the
     full height set it level with the headline as though the two were a pair.
     `align-self: start` reads against the same 1fr row the copy uses, so the plate lands just
     under the breadcrumb and the whole lower half of the column stays open beneath it, which
     is the air that keeps the diagonal from looking crowded. */
  align-self: start;
}

.mi-pagehead--vendor .mi-pagehead-body {
  /* Column 1 only. It spanned both columns while the mark had a row to itself; now that the mark
     occupies column 2 for the full height, spanning would run the copy underneath it. */
  grid-column: 1;
  grid-row: 1;
  /* The lower portion, and this is the line that puts it there. */
  align-self: end;
  /*
    860px, not the 62ch this started at. 62ch measures in the BODY font, so it came out around
    620px — narrower than the headline needs, and "Certification Programs" wrapped to two lines
    at 68px. Those two lines were 68px of the height on their own. The standfirst keeps its own
    54ch cap below, so widening this only affects the headline.
  */
  max-width: 860px;
}

/*
  One layer, bottom-weighted, and every stop is doing a job:

    0-34%    a light wash only. The breadcrumb and the mark sit up here and need the sky behind
             them held down a little, not covered.
    34-58%   the clearest part of the frame. On this photograph that is the skyline itself, and
             it is the reason anyone chose the picture.
    58-100%  the ramp into the foot, where the name and the count are. It arrives late and ends
             at 0.78 rather than opaque, so the buildings still read THROUGH the darkest part
             instead of the band ending in a black bar.

  Opacity is pinned to 1 with the ramp in the alpha stops, so the admin's overlay slider does not
  apply to this banner — the same trade the training and Director banners make, and worth knowing
  before reaching for that control.
*/
.mi-pagehead--vendor .mi-pagehead-scrim {
  opacity: 1;
  background:
    /*
      TWO layers, because one is provably not enough here.

      The vertical ramp alone was measured against the rendered pixels and the champagne label
      came out at 1.77:1 — a light trail in the photograph runs directly behind it. A ramp that
      only varies with HEIGHT cannot defend a copy column against a photograph that varies with
      WIDTH; raising it enough to cover the brightest thing that might land there would have
      greyed the whole band, which is the one thing the brief rules out.

      So the second layer is a pool anchored at the bottom-LEFT corner, over the copy and nothing
      else: 0.60 in the corner, half that by 44%, gone by 74%. The mark in the top-right and the
      middle of the skyline are outside it entirely.
    */
    radial-gradient(158% 152% at 2% 100%,
      rgb(8 12 30 / 0.80) 0%,
      rgb(8 12 30 / 0.60) 38%,
      rgb(8 12 30 / 0.30) 62%,
      rgb(8 12 30 / 0) 82%),
    linear-gradient(180deg,
      rgb(8 12 30 / 0.34) 0%,
      rgb(8 12 30 / 0.14) 34%,
      rgb(8 12 30 / 0.16) 58%,
      rgb(8 12 30 / 0.52) 84%,
      rgb(8 12 30 / 0.70) 100%);
}

/*
  The photograph itself. `cover` on a band this wide crops the height, so the focal point is what
  decides which slice of the sky and skyline survives — and it comes from the CMS, so the crop is
  the admin's to move rather than a number pinned here.
*/
.mi-pagehead--vendor .mi-pagehead-media {
  background-size: cover;
  /* A night photograph loses its shadow detail under any scrim at all. A touch of lift and a
     little less saturation keeps it a photograph rather than a silhouette, and is what lets the
     scrim above stay as light as it is. */
  filter: brightness(1.06) saturate(0.96);
}

/* The champagne hairline along the bottom edge is right for a photographic band, so the base
   rule stands and there is no ::after override here. */

/*
  The mark, in a light glass container. It is deliberately NOT the white slab the other
  photographic banners use: on a night skyline a solid white card is the brightest object in the
  frame and pulls the eye off the name. Frosted, hairlined and modest — and capped in width so a
  wordmark cannot grow into a billboard.
*/
.mi-pagehead--vendor .mi-pagehead-plate {
  padding: var(--mi-space-4) var(--mi-space-5);
  max-width: min(280px, 42vw);
  background: rgb(255 255 255 / 0.90);
  border-radius: var(--mi-radius);
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.9),
    0 0 0 1px rgb(255 255 255 / 0.18),
    0 18px 40px -18px rgb(6 10 26 / 0.55);
}

/* 64px, down from the 92px the shared rule allows. The mark is a credential in the corner of the
   band, not its subject — the vendor's NAME is set at up to 60px at the foot and has to stay the
   biggest thing here. */
.mi-pagehead--vendor .mi-pagehead-plate img { max-height: 64px; }

/*
  The type. All three state their colour literally in the base rules, precisely because they sit
  over an admin-replaceable photograph, so tokens alone do not reach them. The shadows stay for
  the same reason — this is white type on a picture whose crop can move under it.
*/
.mi-pagehead--vendor .mi-pagehead-title {
  color: #fff;
  text-shadow: 0 2px 10px rgb(6 10 26 / 0.55);
}

.mi-pagehead--vendor .mi-pagehead-lead {
  color: rgb(232 238 252 / 0.95);
  text-shadow: 0 1px 6px rgb(6 10 26 / 0.5);
  max-width: 54ch;
}

/* Champagne, which is what the metal token exists for: a small tracked label on a dark surface.
   The 38px rule before it inherits currentColor and turns with it. */
.mi-pagehead--vendor .mi-pagehead-eyebrow { color: var(--mi-metal); }
.mi-pagehead--vendor a.mi-pagehead-eyebrow:hover { color: #fff; }

/* The padding half of the 510px figure. Only from 1024 up: below that the base rule already pays
   60px a side and the band is shorter than 510 anyway, so forcing a desktop height onto a phone
   would only add empty space under the copy. */
@media (min-width: 1024px) {
  .mi-pagehead--vendor { padding-block: var(--mi-space-8); }
}

/*
  The mark stays top-right down to 768, where a 720px container still holds a 280px plate and a
  readable copy column beside it. It is only on a PHONE that two columns stop working, and there
  the shared grid is already one column — so all this has to do is put the plate back above the
  copy in source order and stop it being pushed to the right-hand edge on its own row.

  767 and not 1023: an earlier version of this cut over at 1023 and took the tablet with it, which
  lost the top-right placement across the whole 768-1023 range for no reason.
*/
@media (max-width: 767px) {
  .mi-pagehead--vendor .mi-pagehead-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto;
  }
  .mi-pagehead--vendor .mi-pagehead-plate {
    grid-column: 1;
    grid-row: 1;
    /* Still the RIGHT side. The grid is a single column here, so the mark drops above the copy
       rather than beside it — but it stays pinned to the right edge of that column, which is
       what keeps the arrangement recognisably the same one from phone to desktop. It was
       start-aligned, and on a phone that read as a different design rather than a narrower one. */
    justify-self: end;
  }
  .mi-pagehead--vendor .mi-pagehead-body {
    grid-column: 1;
    grid-row: 2;
  }
}

/*
  ---------- the programme banner's height ----------

  Target: 510px, the same band height the vendor listing uses, so the two steps of the journey
  do not jump.

  This one cannot get there on padding alone, and it is worth recording why. The headline here is
  the PROGRAMME NAME, which is CMS data ranging from "CCT" to the 80-character "VMware Certified
  Advanced Professional - Cloud Management and Automation Design". Measured at the old 68px in a
  760px column, that longest name ran to FIVE lines — 350px of headline on its own — and the band
  reached 808px. No amount of padding closes a 298px gap.

  So the height comes from three changes together:

    padding    93.6px a side -> 48px            saves 91px
    column     760px -> 900px                   fewer wraps on every long name
    headline   up to 68px -> up to 44px         the line that actually does the work

  44px is a real reduction and it is the deliberate cost of the fixed height: at 44px in a 900px
  column the longest name in the catalogue wraps to two lines instead of five. It is still the
  largest thing in the band and still clears AA as large text.

  The lead and the chips give back a little of their spacing for the same reason.
*/
@media (min-width: 1024px) {
  /* 43, not 44. The tallest programme page in the catalogue — a two-line name over a
     three-line standfirst — measures 423.4px of content, and 423.4 + 88 is 511.4. Two pixels
     back puts every page under the 510 floor, so min-height sets the height everywhere instead
     of the content setting it on the four longest. */
  .mi-pagehead--training { min-height: 510px; padding-block: 43px; }
}

/*
  EVERY LINE OF COPY HAS TO FINISH INSIDE THE FLAT AZURE, and that is the constraint the widths
  below are set by rather than by the height budget.

  The scrim on this banner is solid azure from 0 to 50% and fades to nothing by 70% — 720px and
  1008px at 1440. Text that runs past ~720px is sitting on a fading wash over a bright kitchen
  photograph. Measured: widening the standfirst to 68ch put its second line out at x=902 and its
  contrast fell to 2.06:1, against a 4.5 floor. So the copy column stops at 700px and the
  standfirst keeps the 52ch the scrim comment upstream was written around.
*/
.mi-pagehead--training .mi-pagehead-body { max-width: 700px; }

.mi-pagehead--training .mi-pagehead-title {
  /*
    Up to 40px, down from 68px. This is the line that buys the fixed height: the headline is a CMS
    programme name up to 80 characters, and at 68px in a 665px column the longest one wrapped to
    five lines — 350px of headline, and a 808px band. At 40px in a 700px column it is three.

    The 20ch cap the base rule applies is released for the same reason: 20ch is right for a
    hand-written page title, not for a programme name. It is replaced by a px cap rather than
    removed outright, because "no cap" would let a long name run out onto the photograph.
  */
  font-size: clamp(1.625rem, 1rem + 1.8vw, 2.5rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  max-width: 700px;
}

.mi-pagehead--training .mi-pagehead-lead {
  margin-top: var(--mi-space-3);
  /* space-1, not space-2. Four pages — the ones whose standfirst runs to four lines — landed on
     511px rather than 510: sub-pixel line heights adding up, not a design problem. Four pixels
     back here puts their content under the 510 floor so min-height sets the figure on every
     programme page rather than on all but four of them. */
  margin-bottom: var(--mi-space-1);
}

.mi-pagehead--training .mi-chips { margin-top: var(--mi-space-5); }

/* Champagne is a detail for dark surfaces; on this one it is invisible. A faint ink line
   keeps the bottom edge reading as an edge. */
.mi-pagehead--training::after {
  background: linear-gradient(90deg,
    transparent 0%, rgb(19 26 58 / 0.13) 22%, rgb(19 26 58 / 0.13) 58%, transparent 88%);
}

/* Note 2: literal colours, so each has to be restated. Note 3: the shadows go. */
.mi-pagehead--training .mi-pagehead-title {
  color: var(--mi-title);
  text-shadow: none;
}

.mi-pagehead--training .mi-pagehead-lead {
  color: var(--mi-body);
  text-shadow: none;
}

.mi-pagehead--training .mi-pagehead-eyebrow { color: var(--mi-brand-deep); }
.mi-pagehead--training a.mi-pagehead-eyebrow:hover { color: var(--mi-brand); }

/*
  The chips are white-on-white here, so the fill goes up and the border does the work — the
  reverse of the dark banner, where the fill was a faint white lift on a navy ground.
  Single-class specificity ties with `.mi-pagehead .mi-chip`; these win on source order.
*/
.mi-pagehead--training .mi-chip {
  background: rgb(255 255 255 / 0.74);
  border-color: rgb(19 26 58 / 0.10);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.9);
  color: var(--mi-body);
}
.mi-pagehead--training .mi-chip b { color: var(--mi-title); }

/* ---------- motion ----------

   Two movements, and both of them end. Nothing here loops: a banner still moving after
   the visitor has started reading is an animation, not an entrance. */

/*
  The photograph settles out of a slow zoom rather than sitting still. Long enough (20s)
  and shallow enough (1.06 -> 1) to be felt rather than seen, and it lands on scale(1) so
  the composition the admin chose in the crop slider is the one that survives.
*/
@keyframes mi-pagehead-settle {
  from { transform: scale(1.06); }
  to   { transform: scale(1); }
}
.mi-pagehead--photo .mi-pagehead-media {
  animation: mi-pagehead-settle 20s cubic-bezier(0.16, 0.6, 0.28, 1) both;
  will-change: transform;
}

@keyframes mi-pagehead-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/*
  A staggered reveal over the copy block. The steps are small — 70ms — because the point
  is that the lines arrive in reading order, not that the visitor watches them arrive.
  Targeted by element rather than by :nth-child, since which of them exist differs per
  page and a positional rule would put the delays on the wrong lines.
*/
.mi-pagehead .mi-breadcrumb,
.mi-pagehead-eyebrow,
.mi-pagehead-title,
.mi-pagehead-lead,
.mi-pagehead .mi-chips,
.mi-pagehead-plate {
  animation: mi-pagehead-rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.mi-pagehead .mi-breadcrumb { animation-delay: 0.02s; }
.mi-pagehead-eyebrow        { animation-delay: 0.09s; }
.mi-pagehead-title          { animation-delay: 0.16s; }
.mi-pagehead-lead           { animation-delay: 0.23s; }
.mi-pagehead .mi-chips      { animation-delay: 0.30s; }
.mi-pagehead-plate          { animation-delay: 0.24s; }

/* ============================ breadcrumb ============================ */

.mi-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mi-space-2);
  margin: 0 0 var(--mi-space-5);
  padding: 0;
  list-style: none;
  color: var(--mi-faint);
  font-size: var(--mi-text-sm);
}
.mi-breadcrumb a { color: var(--mi-faint); }
.mi-breadcrumb a:hover { color: var(--mi-link); }
.mi-breadcrumb [aria-current="page"] {
  color: var(--mi-body);
  font-weight: 600;
}
.mi-breadcrumb-sep { color: var(--mi-line-strong); }

/* ============================ cards ============================ */

/* `min(260px, 100%)` rather than a flat 260px: below a 260px content box a fixed
   minimum would make the single track wider than its container and overflow. */
.mi-grid {
  display: grid;
  gap: var(--mi-space-5);
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
}

.mi-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
  transition: transform var(--mi-transition-slow), box-shadow var(--mi-transition),
              border-color var(--mi-transition), background var(--mi-transition);
}
.mi-card:hover {
  transform: translateY(-6px);
  background: var(--mi-glass-strong);
  border-color: var(--mi-glass-border-hover);
  box-shadow: var(--mi-shadow-lg);
}

/* Image well — logos vary wildly in aspect ratio, so the box is fixed and the
   image is contained rather than cropped. Tinted so the mark has a frame. */
.mi-card-media {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 138px;
  padding: var(--mi-space-5);
  background: var(--mi-plate);
  border-bottom: 1px solid var(--mi-line);
}
.mi-card-media img {
  max-height: 100%;
  width: auto;
  object-fit: contain;
}

.mi-card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--mi-space-3);
  padding: var(--mi-space-5);
}

.mi-card-title {
  margin: 0;
  font-size: var(--mi-text-lg);
  line-height: 1.35;
}
.mi-card-title a { color: var(--mi-title); }
.mi-card-title a:hover { color: var(--mi-link); }

.mi-card-text {
  margin: 0;
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
  /* Keep cards in a row the same height regardless of copy length. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

.mi-card-foot { margin-top: auto; padding-top: var(--mi-space-3); }

.mi-card-count {
  color: var(--mi-faint);
  font-size: var(--mi-text-sm);
  font-weight: 500;
}

/* Vendor tile — a denser variant used on the home grid. */
.mi-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--mi-space-3);
  padding: var(--mi-space-5);
  overflow: hidden;
  text-align: center;
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
  transition: transform var(--mi-transition-slow), box-shadow var(--mi-transition),
              border-color var(--mi-transition), background var(--mi-transition);
}
.mi-tile:hover {
  transform: translateY(-6px);
  background: var(--mi-glass-strong);
  border-color: var(--mi-glass-border-hover);
  box-shadow: var(--mi-shadow-lg);
}

.mi-tile-media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 88px;
  padding: var(--mi-space-3) var(--mi-space-4);
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: var(--mi-radius);
}
.mi-tile-media img { max-height: 100%; width: auto; object-fit: contain; }

/*
  Stands in for a vendor whose brand mark the project does not have. Set on the same light
  plate and at logo weight, so the row still reads as a row of marks rather than as one
  tile with a missing image — the alternative being another brand's logo or MetaIT's own,
  and both of those say something untrue.
*/
.mi-tile-mark {
  color: #253063;
  font-family: var(--font-primary);
  font-size: var(--mi-text-lg);
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.2;
  text-align: center;
  text-transform: uppercase;
}

.mi-tile-name {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
}

.mi-badge {
  display: inline-block;
  padding: 4px 11px;
  border-radius: var(--mi-radius-pill);
  background: var(--mi-glass-soft);
  border: 1px solid var(--mi-glass-border);
  color: var(--mi-muted);
  font-size: var(--mi-text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ==================== training detail ("View details") ==================== */

/*
  Two columns: the course content, and a sticky rail carrying the actions and the
  facts. The rail is what makes this a product page rather than a wall of text —
  the enquiry CTA stays on screen for the whole scroll, however long the legacy
  copy runs.
*/
.mi-detail {
  display: grid;
  gap: var(--mi-space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

.mi-detail-main { min-width: 0; }

.mi-detail-aside {
  display: grid;
  gap: var(--mi-space-4);
  min-width: 0;
}

/* Each block of admin-authored copy gets its own card, so a long page reads as a
   sequence of sections instead of one undifferentiated column. */
.mi-doc {
  padding: clamp(24px, 3vw, 40px);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
}
.mi-doc + .mi-doc { margin-top: var(--mi-space-4); }

.mi-doc-head {
  display: flex;
  align-items: center;
  gap: var(--mi-space-3);
  margin-bottom: var(--mi-space-5);
  padding-bottom: var(--mi-space-4);
  border-bottom: 1px solid var(--mi-line);
}
.mi-doc-head h2 {
  margin: 0;
  font-size: var(--mi-text-xl);
}
/* Numbered marker — gives a long page a spine to scan down. */
.mi-doc-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: var(--mi-radius-sm);
  background: linear-gradient(150deg, rgb(67 86 232 / 0.14), rgb(157 123 255 / 0.12));
  border: 1px solid rgb(67 86 232 / 0.20);
  color: var(--mi-brand-deep);
  font-family: var(--font-primary);
  font-size: var(--mi-text-sm);
  font-weight: 700;
}

/* ---------- sticky rail ---------- */

.mi-side-card {
  padding: var(--mi-space-5);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
}

/* The one card on the page asking for the enquiry, so it gets the brand wash. */
.mi-side-card--cta {
  position: relative;
  overflow: hidden;
  background:
    linear-gradient(160deg, rgb(238 242 255 / 0.96) 0%, rgb(248 246 255 / 0.92) 100%);
  border-color: rgb(67 86 232 / 0.18);
  box-shadow: var(--mi-shadow);
}
.mi-side-card--cta::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: -55% -30% auto -30%;
  height: 150%;
  background: radial-gradient(circle at 50% 30%, rgb(67 86 232 / 0.16), transparent 62%);
  pointer-events: none;
}
.mi-side-card--cta > * { position: relative; z-index: 1; }

.mi-side-title {
  margin-bottom: var(--mi-space-3);
  font-size: var(--mi-text-base);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.mi-side-text {
  margin-bottom: var(--mi-space-4);
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
}

.mi-side-actions { display: grid; gap: var(--mi-space-3); }

/* Programme facts — price, duration, level, category — in the detail rail.
   A definition list because that is what it is: a label and its value. The grid is
   minmax(0, …) rather than a bare 1fr so a long category cannot push the column wider
   than the rail, which is the same rule every other grid here follows. */
.mi-side-facts {
  display: grid;
  gap: var(--mi-space-3);
  margin-bottom: var(--mi-space-4);
}

.mi-side-fact {
  display: grid;
  grid-template-columns: minmax(0, 7rem) minmax(0, 1fr);
  gap: var(--mi-space-3);
  align-items: baseline;
  padding-bottom: var(--mi-space-3);
  border-bottom: 1px solid var(--mi-line);
}
.mi-side-fact:last-child { padding-bottom: 0; border-bottom: 0; }

.mi-side-fact dt {
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
}

.mi-side-fact dd { font-size: var(--mi-text-sm); }
.mi-side-fact dd b { font-size: var(--mi-text-base); font-weight: 700; }

/* The price qualifier sits under the figure, not beside it — "per participant" next to
   a number reads as part of the number. */
.mi-side-fact dd .mi-hint { display: block; margin-top: 2px; }

.mi-side-subtitle {
  margin-bottom: var(--mi-space-3);
  font-size: var(--mi-text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.mi-side-list {
  display: grid;
  gap: var(--mi-space-2);
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
  list-style: none;
}
.mi-side-list li { padding-left: 1.35rem; position: relative; }
.mi-side-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--mi-brand);
}

/* Vendor identity block at the top of the rail. */
.mi-side-vendor {
  display: flex;
  align-items: center;
  gap: var(--mi-space-4);
  margin-bottom: var(--mi-space-4);
  padding-bottom: var(--mi-space-4);
  border-bottom: 1px solid var(--mi-line);
}
.mi-side-vendor-media {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 74px;
  height: 58px;
  padding: var(--mi-space-2);
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: var(--mi-radius-sm);
}
.mi-side-vendor-media img { max-height: 100%; max-width: 100%; width: auto; object-fit: contain; }
.mi-side-vendor-text { min-width: 0; }
.mi-side-vendor-text strong {
  display: block;
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
}
.mi-side-vendor-text span { color: var(--mi-faint); font-size: var(--mi-text-sm); }

/* Key/value facts. A definition list, because that is what it is. */
.mi-facts { display: grid; gap: 0; margin: 0; }
.mi-facts > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--mi-space-4);
  padding: 11px 0;
  border-bottom: 1px solid var(--mi-line);
}
.mi-facts > div:last-child { border-bottom: 0; padding-bottom: 0; }
.mi-facts dt {
  color: var(--mi-faint);
  font-size: var(--mi-text-xs);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
}
.mi-facts dd {
  margin: 0;
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-sm);
  font-weight: 600;
  text-align: right;
  overflow-wrap: anywhere;
}

/* Secondary run of facts under a block that already has its own heading. */
.mi-facts--tight {
  margin-top: var(--mi-space-5);
  padding-top: var(--mi-space-3);
  border-top: 1px solid var(--mi-line);
}
.mi-facts--tight > div { padding-block: 8px; }

/* ---------- chips ---------- */

.mi-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mi-space-2);
  margin-top: var(--mi-space-5);
}

.mi-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-pill);
  box-shadow: var(--mi-shadow-sm);
  color: var(--mi-body);
  font-size: var(--mi-text-sm);
  font-weight: 500;
}
.mi-chip b { color: var(--mi-title); font-weight: 600; }
.mi-chip::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mi-brand);
  flex-shrink: 0;
}
.mi-chip--ok::before { background: var(--mi-success); }

/* ============================ prose ============================ */

/*
  Wraps admin-authored HTML from long_description / detail1..4. That markup was
  written in a WYSIWYG editor and carries arbitrary tags and inline styles, so
  everything it can emit is normalised here.
*/
.mi-prose {
  color: var(--mi-body);
  font-size: var(--mi-text-base);
  line-height: 1.85;
  overflow-wrap: break-word;
}
.mi-prose > :first-child { margin-top: 0; }
.mi-prose > :last-child { margin-bottom: 0; }

.mi-prose h1, .mi-prose h2, .mi-prose h3,
.mi-prose h4, .mi-prose h5, .mi-prose h6 {
  margin: var(--mi-space-6) 0 var(--mi-space-3);
}
.mi-prose h2 { font-size: var(--mi-text-xl); }
.mi-prose h3 { font-size: var(--mi-text-lg); }
.mi-prose h4 { font-size: var(--mi-text-base); }

/*
  The legacy editor wrote inline `color:` on much of this copy, picked for a white
  page — which is what it sits on again, so it is left alone. Inside a dark region
  that same near-black would be unreadable, so there and only there it is
  neutralised and the cascade supplies the colour.
*/
.mi-dark .mi-prose [style*="color"],
.mi-dark .mi-prose font[color] { color: inherit !important; }

/*
  The same editor also wrote absolute font sizes, several of them under 11px on
  sub-headings like "Core subjects:". Those are not a design choice worth keeping,
  and they are the one inline style that actively hurts the page.
*/
.mi-prose [style*="font-size"] { font-size: inherit !important; }

.mi-prose ul, .mi-prose ol {
  margin: 0 0 var(--mi-space-4);
  padding-left: var(--mi-space-5);
}
.mi-prose li { margin-bottom: var(--mi-space-2); }
.mi-prose ul { list-style: disc; }
.mi-prose ol { list-style: decimal; }
.mi-prose li::marker { color: var(--mi-brand); }

.mi-prose a { color: var(--mi-link); text-decoration: underline; }
.mi-prose a:hover { color: var(--mi-link-hover); }

/*
  The legacy editor wrapped list items in `<a href="#">` — 17 of them on the CCNA
  page alone. They are not links, they go nowhere, and styling them as links
  invites clicks that do nothing. Present them as the text they always were.
*/
.mi-prose a[href="#"],
.mi-prose a:not([href]) {
  color: inherit;
  text-decoration: none;
  cursor: text;
  pointer-events: none;
}

.mi-prose strong, .mi-prose b { color: var(--mi-title); font-weight: 600; }

.mi-prose img {
  height: auto;
  border-radius: var(--mi-radius);
}

.mi-prose blockquote {
  margin: var(--mi-space-5) 0;
  padding: var(--mi-space-4) var(--mi-space-5);
  background: var(--mi-glass-soft);
  border-left: 3px solid var(--mi-brand);
  border-radius: 0 var(--mi-radius) var(--mi-radius) 0;
}

.mi-prose code {
  padding: 2px 6px;
  background: var(--mi-surface-2);
  border-radius: 5px;
  font-size: 0.9em;
}

/* Legacy content contains fixed-width tables; contain them instead of letting
   them force the page to scroll sideways. */
.mi-prose table {
  width: 100%;
  margin-bottom: var(--mi-space-4);
  border-collapse: collapse;
  font-size: var(--mi-text-sm);
}
.mi-prose th, .mi-prose td {
  padding: 11px var(--mi-space-4);
  border: 1px solid var(--mi-line);
  text-align: left;
}
.mi-prose th {
  background: var(--mi-surface-2);
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-weight: 600;
}

.mi-prose-scroll {
  overflow-x: auto;
  border-radius: var(--mi-radius);
}

.mi-content-block + .mi-content-block {
  margin-top: var(--mi-space-7);
  padding-top: var(--mi-space-7);
  border-top: 1px solid var(--mi-line);
}

/* Long-form pages read better inside a panel than floating on the canvas. */
.mi-panel {
  padding: clamp(24px, 3.5vw, 48px);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
}

/* ============================ forms ============================ */

.mi-form { display: grid; gap: var(--mi-space-4); }

.mi-field { display: grid; gap: 7px; }

.mi-label {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-sm);
  font-weight: 600;
}

.mi-input,
.mi-textarea,
select.mi-input {
  width: 100%;
  padding: 13px var(--mi-space-4);
  border: 1px solid var(--mi-line-strong);
  border-radius: var(--mi-radius-sm);
  background: var(--mi-field-bg);
  color: var(--mi-title);
  font-family: var(--font-secondary);
  font-size: var(--mi-text-base);
  box-shadow: var(--mi-shadow-sm);
  transition: border-color var(--mi-transition), box-shadow var(--mi-transition),
              background var(--mi-transition);
}
.mi-input::placeholder,
.mi-textarea::placeholder { color: var(--mi-faint); }

.mi-input:hover,
.mi-textarea:hover { border-color: var(--mi-glass-border-hover); }

.mi-input:focus,
.mi-textarea:focus {
  outline: none;
  border-color: var(--mi-brand);
  box-shadow: 0 0 0 4px rgb(67 86 232 / 0.14);
}
.mi-textarea { min-height: 150px; resize: vertical; }

/* Chrome paints autofilled inputs with its own ground that ignores `background`;
   an inset shadow the size of the field is the only way to override it. */
.mi-input:-webkit-autofill,
.mi-textarea:-webkit-autofill {
  -webkit-text-fill-color: var(--mi-title);
  box-shadow: 0 0 0 1000px var(--mi-autofill) inset;
  caret-color: var(--mi-title);
}

/* The native checkbox is drawn by the platform; `accent-color` is the only hook
   that tints it without replacing the control and losing its semantics. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--mi-brand); }

.mi-field-error { color: var(--mi-error); font-size: var(--mi-text-sm); }

.mi-alert {
  padding: var(--mi-space-4) var(--mi-space-5);
  border: 1px solid transparent;
  border-radius: var(--mi-radius);
  font-size: var(--mi-text-sm);
}
.mi-alert--success {
  border-color: rgb(18 156 107 / 0.30);
  background: rgb(18 156 107 / 0.09);
  color: #0b6647;
}
.mi-alert--error {
  border-color: rgb(214 69 92 / 0.32);
  background: rgb(214 69 92 / 0.08);
  color: #96253a;
}
.mi-alert--warn {
  border-color: rgb(184 134 11 / 0.35);
  background: rgb(240 177 0 / 0.10);
  color: #7a5800;
}
/*
  The neutral variant. It exists because "your QR order is not paid yet" is a STATE, not a
  problem: --warn reads as something going wrong and --success would be an outright lie
  about whether money has arrived. Brand blue says "here is where you stand".
*/
.mi-alert--info {
  border-color: rgb(67 86 232 / 0.28);
  background: rgb(67 86 232 / 0.07);
  color: #2b3a9e;
}
.mi-alert strong { color: inherit; font-weight: 700; }
.mi-alert a { color: inherit; text-decoration: underline; }


/* ==================== paying for a PTE voucher ====================

   Two blocks, both used only by the PTE voucher checkout: the pay-method chooser on the
   form, and the QR page it can lead to. The voucher is the only thing the site sells, so
   scoping the QR "to PTE only" is a matter of these living here and being referenced by
   those two views alone.
*/

/*
  A statement, not a chooser. There were two radio cards here — gateway or QR — until the
  gateway option was withdrawn for want of a live eSewa integration. One method left means
  there is nothing to choose, so this states what will happen rather than asking a question
  with a single possible answer.

  It keeps the card shape the radios had, so the form's rhythm is unchanged and putting the
  chooser back is a swap rather than a redesign.
*/
.mi-pay-single {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--mi-space-4);
  border: 1px solid var(--mi-line);
  border-radius: var(--mi-radius);
  background: var(--mi-surface);
}
.mi-pay-single-title {
  margin: var(--mi-space-1) 0 0;
  font-weight: 650;
  color: var(--mi-title);
}

/* ---------- the QR page ---------- */

.mi-qr-pay { max-width: 940px; margin-inline: auto; }
.mi-qr-pay-head { text-align: center; margin-bottom: var(--mi-space-6); }
.mi-qr-pay-head .mi-lead { margin-inline: auto; max-width: 56ch; }

.mi-qr-pay-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--mi-space-6);
  align-items: start;
}

/*
  The QR is capped at 340px and centred rather than stretched. A merchant card blown up to
  fill a desktop column looks like a mistake, and past a point extra pixels do not make a
  code any easier to scan — the phone is holding a camera to the screen either way.
*/
.mi-qr-pay-code {
  justify-self: center;
  width: 100%;
  max-width: 340px;
  padding: var(--mi-space-4);
  border: 1px solid var(--mi-line);
  border-radius: var(--mi-radius-lg);
  /* White regardless of surface: a QR needs its quiet zone to stay white to scan. */
  background: #fff;
  box-shadow: var(--mi-shadow-sm);
}
.mi-qr-pay-code img { width: 100%; height: auto; }

.mi-qr-pay-side { display: flex; flex-direction: column; gap: var(--mi-space-5); min-width: 0; }

.mi-qr-pay-facts {
  display: grid;
  gap: var(--mi-space-4);
  padding: var(--mi-space-5);
  border: 1px solid var(--mi-line);
  border-radius: var(--mi-radius);
  background: var(--mi-surface-2);
}
.mi-qr-pay-facts dt {
  font-size: var(--mi-text-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--mi-muted);
}
.mi-qr-pay-facts dd { margin: var(--mi-space-1) 0 0; }

.mi-qr-pay-amount {
  font-size: var(--mi-text-2xl);
  font-weight: 800;
  color: var(--mi-title);
  font-variant-numeric: tabular-nums;
}

/* Monospace and selectable: this is a code the buyer has to copy into a remarks field. */
.mi-qr-pay-ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--mi-text-lg);
  font-weight: 700;
  color: var(--mi-title);
  user-select: all;
  word-break: break-all;
}

.mi-qr-pay-steps {
  display: flex;
  flex-direction: column;
  gap: var(--mi-space-3);
  counter-reset: qrstep;
  color: var(--mi-body);
  font-size: var(--mi-text-sm);
}
/*
  Two columns: the number, then ONE span holding the whole step. The view wraps each step's
  content deliberately — grid promotes every element child to its own grid item and only
  merges bare adjacent text, so a step containing a <strong> or a link laid itself out as
  three items across two columns and came out as a staircase.
*/
.mi-qr-pay-steps li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--mi-space-3);
  align-items: start;
}
/* Numbered by counter because list-style was stripped by the reset at the top of this file. */
.mi-qr-pay-steps li::before {
  counter-increment: qrstep;
  content: counter(qrstep);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--mi-brand);
  color: #fff;
  font-size: var(--mi-text-xs);
  font-weight: 700;
}

.mi-qr-pay-actions { display: flex; flex-wrap: wrap; gap: var(--mi-space-3); }

/* ============================ footer (dark chrome) ============================ */

.mi-footer {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-top: var(--mi-space-7);
  margin-top: var(--mi-space-8);
  background: var(--mi-ink);
  font-size: var(--mi-text-sm);
}
.mi-footer::before {
  content: "";
  position: absolute;
  inset: -60% -10% auto -10%;
  height: 200%;
  z-index: -1;
  background:
    radial-gradient(32% 44% at 14% 18%, rgb(99 122 255 / 0.22) 0%, transparent 68%),
    radial-gradient(28% 40% at 88% 4%, rgb(157 123 255 / 0.16) 0%, transparent 68%);
  pointer-events: none;
}
/* Light seam along the very top edge. */
.mi-footer::after {
  content: "";
  position: absolute;
  inset-inline: 15%;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgb(160 178 255 / 0.6), transparent);
  pointer-events: none;
}

.mi-footer-grid {
  display: grid;
  gap: var(--mi-space-7);
  grid-template-columns: minmax(0, 1fr);
  padding-bottom: var(--mi-space-6);
}
/* The address and email lines are long unbreakable strings; without this they set
   a min-content width that the column cannot shrink below. */
.mi-footer-grid > * { min-width: 0; }
.mi-footer a { overflow-wrap: anywhere; }

.mi-footer h3 {
  margin-bottom: var(--mi-space-4);
  color: var(--mi-title);
  font-size: var(--mi-text-xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.mi-footer ul { margin: 0; padding: 0; list-style: none; }
.mi-footer li { margin-bottom: 11px; }
.mi-footer a { color: var(--mi-muted); }
.mi-footer a:hover { color: var(--mi-link); }

.mi-footer-hours {
  display: flex;
  justify-content: space-between;
  gap: var(--mi-space-3);
}
.mi-footer-hours span:last-child { color: var(--mi-body); }

/*
  The head-office address, where the opening hours used to sit in the Get In Touch column.
  A column of lines rather than a run of text: each line is its own span so the address
  breaks where the business writes it and never mid-line in a narrow column.

  `font-style: normal` is doing real work — <address> is italic by default and the reset at
  the top of this file does not touch it, so without this the footer address leans while
  everything around it stands upright.

  .mi-footer-hours above is NOT dead: the hours still render on the contact page and on
  every training page, which is why the CMS rows were left in place.
*/
.mi-footer-address {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-style: normal;
}

.mi-footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--mi-space-4);
  padding-block: var(--mi-space-5);
  border-top: 1px solid var(--mi-line);
  color: var(--mi-faint);
}
.mi-footer-bottom ul { display: flex; flex-wrap: wrap; gap: var(--mi-space-5); }
.mi-footer-bottom li { margin: 0; }
.mi-footer-copy { margin: 0; }
.mi-footer-copy a { color: var(--mi-body); }

/* ============================ misc ============================ */

.mi-empty {
  padding: var(--mi-space-8) var(--mi-space-5);
  background: var(--mi-glass-soft);
  border: 1px dashed var(--mi-line-strong);
  border-radius: var(--mi-radius-lg);
  color: var(--mi-muted);
  text-align: center;
}

.mi-notfound {
  padding-block: var(--mi-space-8);
  text-align: center;
}

/* ---------- status pages (404 / error) ---------- */

.mi-status {
  position: relative;
  overflow: hidden;
  max-width: 640px;
  margin-inline: auto;
  padding: clamp(32px, 4vw, 56px) clamp(24px, 3vw, 48px);
  background: var(--mi-glass-strong);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-xl);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-lg);
  text-align: center;
}
.mi-status::before {
  content: "";
  position: absolute;
  inset: -60% -20% auto -20%;
  height: 200%;
  z-index: 0;
  background: radial-gradient(38% 46% at 50% 24%, rgb(67 86 232 / 0.13) 0%, transparent 66%);
  pointer-events: none;
}
.mi-status > * { position: relative; z-index: 1; }
.mi-status .mi-btn-row { justify-content: center; }

/* The code as a watermark rather than a headline — it labels the situation without
   being the first thing read, which is the sentence underneath it. */
.mi-status-code {
  display: block;
  margin-bottom: var(--mi-space-3);
  background: linear-gradient(180deg, rgb(67 86 232 / 0.42), rgb(67 86 232 / 0.06));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-family: var(--font-primary);
  font-size: clamp(4.5rem, 12vw, 7rem);
  font-weight: 800;
  line-height: 0.95;
  letter-spacing: -0.05em;
}

.mi-status-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--mi-space-2) var(--mi-space-5);
  margin: var(--mi-space-7) 0 0;
  padding-top: var(--mi-space-5);
  border-top: 1px solid var(--mi-line);
  font-size: var(--mi-text-sm);
}
.mi-status-links a { color: var(--mi-muted); }
.mi-status-links a:hover { color: var(--mi-link); }

/* ============================ content pages ============================ */

.mi-title-sm {
  margin-bottom: var(--mi-space-3);
  font-size: var(--mi-text-lg);
}

/*
  Long-form legal/policy copy reads badly at full container width. The cap is on
  the panel, so the text column inside lands near 76ch once the padding is taken
  off — the same measure this had before.
*/
.mi-prose--narrow {
  max-width: 92ch;
  padding: clamp(24px, 3.5vw, 44px);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
}
.mi-prose--narrow + .mi-prose--narrow { margin-top: var(--mi-space-5); }

/*
  ---------- the institutional welcome, set to the container ----------

  `.mi-prose--narrow` alone caps at 92ch, which at this type size lands around 930px inside a
  1280px container. Measured on the page, that left the block finishing ~290px short of the
  right-hand gutter while its own 44px padding pushed the first character 44px inside the
  section heading above it. Two misalignments that compound: the copy looked indented AND the
  section looked unfinished on the right.

  Fixed by letting the card fill the container and setting the copy in TWO columns above
  1024px rather than by stretching one 150-character line across it. Long measures are the
  usual cost of "use the full width", and they are harder to read, not more generous; a pair
  of columns uses the same space and keeps each line around 60 characters.

  The horizontal padding comes down as well — 44px to 32px at the top end — so the first
  character sits closer to the heading's own left edge without the text touching the border.

  A modifier rather than a change to `.mi-prose--narrow` itself: that class also sets the
  service, privacy-policy and terms pages, which are single long documents where one column
  is right and two would be wrong.
*/
.mi-prose--spread {
  max-width: none;
  padding: clamp(24px, 3.5vw, 40px) clamp(20px, 2.4vw, 32px);
}

@media (min-width: 1024px) {
  .mi-prose--spread {
    columns: 2;
    column-gap: clamp(32px, 4vw, 60px);
  }
  /* A paragraph split across the column break costs more in readability than an uneven
     column foot does in tidiness. */
  .mi-prose--spread > p { break-inside: avoid; }
  .mi-prose--spread > p:first-child { margin-top: 0; }
}

.mi-split {
  display: grid;
  gap: var(--mi-space-8);
  grid-template-columns: minmax(0, 1fr);
}

.mi-grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }

.mi-note {
  padding: var(--mi-space-5);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-left: 3px solid var(--mi-brand);
  border-radius: var(--mi-radius);
  backdrop-filter: var(--mi-blur-sm);
  -webkit-backdrop-filter: var(--mi-blur-sm);
  box-shadow: var(--mi-shadow-sm);
}
.mi-note p { color: var(--mi-muted); font-size: var(--mi-text-sm); }

.mi-quote {
  position: relative;
  margin: 0;
  padding: var(--mi-space-6) var(--mi-space-5) var(--mi-space-5);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
  color: var(--mi-body);
  transition: transform var(--mi-transition-slow), border-color var(--mi-transition),
              box-shadow var(--mi-transition);
}
.mi-quote:hover {
  transform: translateY(-4px);
  border-color: var(--mi-glass-border-hover);
  box-shadow: var(--mi-shadow);
}
/* Oversized opening mark as a watermark, not as punctuation in the text flow. */
.mi-quote::before {
  content: "\201C";
  position: absolute;
  top: 4px;
  left: var(--mi-space-5);
  color: rgb(67 86 232 / 0.24);
  font-family: var(--font-primary);
  font-size: 60px;
  line-height: 1;
  pointer-events: none;
}
.mi-quote p { position: relative; }

/*
  By-line under a quote. Renders only when a name was supplied — the quotes ported from the
  legacy site were unattributed and none was invented, so an unnamed quote stands alone
  rather than under an empty rule.
*/
.mi-quote-by {
  display: grid;
  gap: 2px;
  margin-top: var(--mi-space-4);
  padding-top: var(--mi-space-3);
  border-top: 1px solid var(--mi-line);
  font-style: normal;
}
.mi-quote-by strong {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-sm);
  font-weight: 650;
}
.mi-quote-by span {
  color: var(--mi-faint);
  font-size: var(--mi-text-xs);
}

/* ---------- closing CTA band ---------- */

.mi-cta {
  position: relative;
  display: grid;
  gap: var(--mi-space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  overflow: hidden;
  padding: clamp(28px, 4vw, 52px);
  background:
    linear-gradient(135deg, rgb(237 241 255 / 0.96) 0%, rgb(255 255 255 / 0.92) 48%, rgb(244 239 255 / 0.96) 100%);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-xl);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-lg);
}
/* Sits inside `overflow: hidden`, so the blur is clipped to the band and does not
   bleed onto the sections above and below. */
.mi-cta::before {
  content: "";
  position: absolute;
  inset: -60% -10% auto 22%;
  height: 220%;
  z-index: 0;
  background: radial-gradient(circle, rgb(67 86 232 / 0.16), transparent 64%);
  filter: blur(50px);
  pointer-events: none;
}
.mi-cta > * { position: relative; z-index: 1; }

.mi-cta-orb {
  position: absolute;
  z-index: 0;
  inset: auto -6% -60% auto;
  width: 380px;
  height: 380px;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(157 123 255 / 0.20), transparent 66%);
  filter: blur(60px);
  pointer-events: none;
}

.mi-cta-body { max-width: 62ch; }
.mi-cta-body h2 { margin-bottom: var(--mi-space-3); }
.mi-cta-body p { margin: 0; }

.mi-cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mi-space-4);
}

/* ---------- payment safety notice ---------- */

/*
  Amber, not red. This is a standing caution that sits on the page permanently — red is
  for something that has just gone wrong, and a permanent red panel is either alarming
  or, worse, tuned out. Amber reads as "pay attention" without shouting, and keeps the
  page premium.
*/
.mi-scam {
  position: relative;
  display: grid;
  gap: var(--mi-space-4);
  grid-template-columns: minmax(0, 1fr);
  overflow: hidden;
  padding: clamp(22px, 3vw, 34px);
  background:
    linear-gradient(135deg, rgb(255 250 236 / 0.96) 0%, rgb(255 255 255 / 0.94) 60%, rgb(255 248 232 / 0.96) 100%);
  border: 1px solid rgb(214 152 0 / 0.30);
  border-radius: var(--mi-radius-xl);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow);
}
.mi-scam::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--mi-accent-dark), transparent);
  pointer-events: none;
}

.mi-scam-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 52px;
  height: 52px;
  border-radius: var(--mi-radius);
  background: linear-gradient(140deg, rgb(254 198 36 / 0.26), rgb(240 177 0 / 0.14));
  border: 1px solid rgb(214 152 0 / 0.28);
  color: var(--mi-accent-ink);
}

.mi-scam-body { min-width: 0; }

.mi-scam-title {
  margin: 0 0 var(--mi-space-2);
  font-size: var(--mi-text-xl);
  letter-spacing: -0.015em;
}

.mi-scam-text {
  margin: 0;
  color: var(--mi-body);
  max-width: 78ch;
}

.mi-scam-list {
  display: grid;
  gap: 8px;
  margin: var(--mi-space-4) 0 0;
  padding: 0;
  list-style: none;
}
.mi-scam-list li {
  position: relative;
  padding-left: 28px;
  color: var(--mi-body);
  font-size: var(--mi-text-sm);
  line-height: 1.55;
}
/* A cross, not a tick: every one of these is something NOT to do, and a tick beside
   "never send money to a personal account" reads as instruction rather than warning. */
.mi-scam-list li::before {
  content: "\00D7";
  position: absolute;
  left: 0;
  top: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgb(214 69 92 / 0.12);
  color: var(--mi-error);
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
}

.mi-scam-foot {
  margin: var(--mi-space-4) 0 0;
  padding-top: var(--mi-space-4);
  border-top: 1px solid rgb(214 152 0 / 0.22);
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
}
.mi-scam-foot a { font-weight: 600; white-space: nowrap; }

/* ---------- contact ---------- */

.mi-contact-list { margin-bottom: var(--mi-space-6); }
.mi-contact-list li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mi-space-3);
  justify-content: space-between;
  padding: 12px 0;
  border-bottom: 1px solid var(--mi-line);
}
.mi-contact-list strong {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-weight: 600;
}

.mi-address {
  margin-bottom: var(--mi-space-6);
  color: var(--mi-muted);
  font-style: normal;
  line-height: 1.85;
}

.mi-field-row {
  display: grid;
  gap: var(--mi-space-4);
  grid-template-columns: minmax(0, 1fr);
}

.mi-form-note { color: var(--mi-faint); font-size: var(--mi-text-sm); }

/* Honeypot: off-screen rather than display:none, which some bots detect. */
.mi-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The Google Maps embed is a light third-party iframe. On a light page it sits in
   naturally — it just needs a frame. */
.mi-map {
  overflow: hidden;
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  box-shadow: var(--mi-shadow);
  line-height: 0;
}
.mi-map iframe { width: 100%; height: 440px; border: 0; }

/* ---------- FAQ ---------- */

/* <details>/<summary>: an accessible accordion with no JavaScript. */
.mi-faq {
  display: grid;
  gap: var(--mi-space-3);
  max-width: 88ch;
}

.mi-faq-item {
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius);
  backdrop-filter: var(--mi-blur-sm);
  -webkit-backdrop-filter: var(--mi-blur-sm);
  box-shadow: var(--mi-shadow-sm);
  transition: border-color var(--mi-transition), background var(--mi-transition),
              box-shadow var(--mi-transition);
}
.mi-faq-item:hover { border-color: var(--mi-glass-border-hover); }
.mi-faq-item[open] {
  background: var(--mi-glass-strong);
  border-color: var(--mi-glass-border-hover);
  box-shadow: var(--mi-shadow);
}

.mi-faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mi-space-4);
  padding: var(--mi-space-4) var(--mi-space-5);
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.mi-faq-q::-webkit-details-marker { display: none; }
.mi-faq-q::after {
  content: "+";
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--mi-line-strong);
  border-radius: 50%;
  color: var(--mi-brand);
  font-size: var(--mi-text-base);
  line-height: 1;
  transition: background var(--mi-transition), color var(--mi-transition);
}
.mi-faq-item[open] .mi-faq-q::after {
  content: "\2212";
  background: var(--mi-brand);
  border-color: var(--mi-brand);
  color: #fff;
}

.mi-faq-a {
  padding: 0 var(--mi-space-5) var(--mi-space-5);
  color: var(--mi-body);
}

/* ---------- featured offer (Buy card) ---------- */

/*
  The one commercial element on the home page. It earns its emphasis from a warm
  gold wash and the accent edge rather than from being a dark block — a dark card
  here was doing the same job twice and costing the page most of its lightness.
*/
.mi-offer {
  position: relative;
  display: grid;
  gap: var(--mi-space-5);
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  overflow: hidden;
  padding: clamp(24px, 3vw, 40px);
  background:
    linear-gradient(135deg, rgb(255 250 236 / 0.97) 0%, rgb(255 255 255 / 0.94) 46%, rgb(247 249 254 / 0.97) 100%);
  border: 1px solid rgb(240 177 0 / 0.22);
  border-radius: var(--mi-radius-xl);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-lg);
}
/* Accent edge marks this as commercial. */
.mi-offer::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  z-index: 2;
  background: linear-gradient(90deg, transparent, var(--mi-accent), transparent);
  pointer-events: none;
}
/* Warm glow behind the price column, so the Buy side reads first. */
.mi-offer::after {
  content: "";
  position: absolute;
  z-index: 0;
  inset: -30% -8% -30% auto;
  width: 520px;
  background: radial-gradient(circle at 70% 50%, rgb(254 198 36 / 0.20), transparent 62%);
  filter: blur(40px);
  pointer-events: none;
}
.mi-offer > * { position: relative; z-index: 1; }

.mi-offer-media {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 130px;
  padding: var(--mi-space-5);
  background: #fff;
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  box-shadow: var(--mi-shadow-sm);
}
.mi-offer-media img { max-height: 92px; width: auto; object-fit: contain; }

.mi-offer-body { min-width: 0; }

.mi-offer-title {
  margin-bottom: var(--mi-space-3);
  font-size: var(--mi-text-2xl);
}

.mi-offer-text { color: var(--mi-muted); }

.mi-offer-list {
  display: grid;
  gap: 8px;
  margin-top: var(--mi-space-4);
}
.mi-offer-list li {
  position: relative;
  padding-left: 26px;
  color: var(--mi-body);
  font-size: var(--mi-text-sm);
}
.mi-offer-list li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  top: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: rgb(18 156 107 / 0.14);
  color: var(--mi-success);
  font-size: 10px;
  font-weight: 700;
}

.mi-offer-buy {
  display: grid;
  gap: var(--mi-space-3);
  justify-items: start;
  padding: var(--mi-space-5);
  background: linear-gradient(160deg, rgb(255 248 228 / 0.95) 0%, rgb(255 253 246 / 0.9) 100%);
  border: 1px solid rgb(240 177 0 / 0.30);
  border-radius: var(--mi-radius-lg);
  box-shadow: var(--mi-shadow-sm);
}

.mi-offer-price { display: flex; align-items: baseline; gap: var(--mi-space-3); }

.mi-offer-was {
  color: var(--mi-faint);
  font-size: var(--mi-text-base);
  text-decoration: line-through;
}

.mi-offer-now {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-3xl);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
}
.mi-offer-currency {
  margin-right: 5px;
  color: var(--mi-accent-ink);
  font-size: var(--mi-text-base);
  font-weight: 600;
}

.mi-offer-cta { width: 100%; justify-content: center; }

.mi-offer-note {
  margin: 0;
  color: var(--mi-faint);
  font-size: var(--mi-text-xs);
  line-height: 1.65;
}
.mi-offer-note strong { color: var(--mi-body); }

.mi-offer-details { font-size: var(--mi-text-sm); text-decoration: underline; }

/* Visually hidden but announced by screen readers. */
.mi-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- checkout ---------- */

.mi-price-table {
  width: 100%;
  max-width: 460px;
  margin-bottom: var(--mi-space-4);
  font-size: var(--mi-text-sm);
}
.mi-price-table td {
  padding: 11px 0;
  border-bottom: 1px solid var(--mi-line);
  color: var(--mi-body);
}
.mi-price-table td:last-child { text-align: right; white-space: nowrap; }
.mi-price-total td {
  border-bottom: 0;
  padding-top: var(--mi-space-4);
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-lg);
  font-weight: 700;
}
.mi-price-total td:last-child { color: var(--mi-brand-deep); }

.mi-checkbox {
  display: flex;
  gap: var(--mi-space-3);
  align-items: flex-start;
  color: var(--mi-body);
  font-size: var(--mi-text-sm);
  cursor: pointer;
}
.mi-checkbox input { margin-top: 4px; flex-shrink: 0; width: 16px; height: 16px; }

/* The buyer's receipt — a document, so it stays white. */
.mi-receipt {
  max-width: 640px;
  margin-inline: auto;
  padding: var(--mi-space-8) var(--mi-space-6);
  background: var(--mi-glass-strong);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-xl);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-lg);
  text-align: center;
}
.mi-receipt .mi-price-table { margin-inline: auto; text-align: left; }
.mi-receipt .mi-btn-row { justify-content: center; }

/* ==================== director message ==================== */

/*
  Two columns: the profile rail and the letter. The rail is sticky on desktop so the
  Director stays present for the whole read, and moves above the letter below the
  two-column breakpoint — the person should be established before their words.
*/
.mi-director {
  display: grid;
  gap: var(--mi-space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
.mi-director-main { min-width: 0; }
.mi-director-aside { min-width: 0; }

/* ---------- profile card ---------- */

/*
  The Director card carries a gradient BORDER rather than a gradient fill.

  Two backgrounds in one declaration: the glass sits in the padding box, the gradient sits in
  the border box, and a transparent 1px border lets the second show through only as the edge.
  That is the whole trick — no pseudo-element, no mask, and the card keeps its backdrop-filter
  because the padding-box layer is still the same --mi-glass gradient it always was.

  Brand indigo leads at 0.55 in the top-left corner, thins to almost nothing across the
  middle, and champagne returns at 0.34 in the bottom-right. Two accents on opposite corners
  read as a bevel catching light; a full-perimeter gradient reads as a highlighter, which is
  what "not flashy" rules out. These alphas started at 0.30/0.22 and were raised because at
  1px there is no area for a low alpha to accumulate over — a border that subtle simply is not
  a border. It is still one pixel: more visible, not thicker.

  THREE shadow layers, each doing a different job, and none of them a glow:
    - a 1px indigo ring outside the border, which reads as a second hairline and is most of
      what makes the edge legible against a white page;
    - a close, tight shadow, which is what makes a card look like it is resting on the page
      rather than floating above it;
    - the shared wide shadow for actual depth.
*/
.mi-profile {
  overflow: hidden;
  border: 1px solid transparent;
  border-radius: var(--mi-radius-xl);
  background:
    var(--mi-glass) padding-box,
    linear-gradient(145deg,
      rgb(67 86 232 / 0.55) 0%,
      rgb(67 86 232 / 0.18) 30%,
      rgb(67 86 232 / 0.06) 56%,
      rgb(214 191 149 / 0.34) 100%) border-box;
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow:
    0 0 0 1px rgb(67 86 232 / 0.09),
    0 2px 6px -2px rgb(19 26 58 / 0.12),
    var(--mi-shadow);
}

.mi-profile-portrait {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  background:
    radial-gradient(80% 70% at 50% 0%, rgb(67 86 232 / 0.13) 0%, transparent 70%),
    linear-gradient(160deg, #eef2ff 0%, #f7f9fd 100%);
  border-bottom: 1px solid var(--mi-line);
}
.mi-profile-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Faces sit high in a portrait; centre-cropping cuts foreheads. */
  object-position: 50% 22%;
}

/* Placeholder shown while no portrait has been supplied — the brand mark, floated on
   the tinted well. Never a stock face. */
.mi-profile-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 136px;
  height: 136px;
  padding: var(--mi-space-5);
  background: #fff;
  border: 1px solid var(--mi-glass-border);
  border-radius: 50%;
  box-shadow: var(--mi-shadow);
}
.mi-profile-mark img { width: 100%; height: auto; }

.mi-profile-body { padding: var(--mi-space-5); }

.mi-profile-name {
  margin: 0 0 2px;
  font-size: var(--mi-text-xl);
  letter-spacing: -0.02em;
}
.mi-profile-role {
  margin: 0;
  color: var(--mi-brand-deep);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
}
.mi-profile-org {
  margin: 2px 0 0;
  color: var(--mi-muted);
  font-size: var(--mi-text-sm);
}
.mi-profile-body .mi-btn { margin-top: var(--mi-space-5); }

/* ---------- the letter ---------- */

.mi-letter {
  position: relative;
  overflow: hidden;
  padding: clamp(26px, 3.6vw, 52px);
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-xl);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow);
}
/* Brand seam along the top edge, the same detail the admin login card carries. */
.mi-letter::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--mi-brand), var(--mi-violet), transparent);
  pointer-events: none;
}

/*
  The first paragraph IS the lead-in — set larger and darker, with a rule under it.
  Doing it in CSS rather than as a separate element means there is no second copy of
  the opening sentence to drift out of sync with the message.
*/
.mi-letter .mi-prose > p:first-child {
  margin-bottom: var(--mi-space-5);
  padding-bottom: var(--mi-space-5);
  border-bottom: 1px solid var(--mi-line);
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: clamp(1.0625rem, 0.95rem + 0.5vw, 1.3rem);
  font-weight: 500;
  line-height: 1.55;
  letter-spacing: -0.012em;
}

/* Slightly more open than the site default: this is one continuous read, not a
   reference page being skimmed. */
.mi-letter .mi-prose { line-height: 1.9; }
.mi-letter .mi-prose p + p { margin-top: var(--mi-space-4); }

.mi-letter-sign {
  margin-top: var(--mi-space-6);
  padding-top: var(--mi-space-5);
  border-top: 1px solid var(--mi-line);
}
.mi-letter-sign-rule {
  display: block;
  width: 56px;
  height: 3px;
  margin-bottom: var(--mi-space-3);
  border-radius: 3px;
  background: linear-gradient(90deg, var(--mi-brand), var(--mi-violet));
}
.mi-letter-byline {
  margin: 0;
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
}

/* ---------- milestone callout ---------- */

.mi-milestone {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mi-space-4);
  margin-top: var(--mi-space-4);
  padding: var(--mi-space-5);
  background: linear-gradient(135deg, rgb(255 250 236 / 0.95) 0%, rgb(255 255 255 / 0.9) 100%);
  border: 1px solid rgb(240 177 0 / 0.28);
  border-radius: var(--mi-radius-lg);
  box-shadow: var(--mi-shadow-sm);
}
.mi-milestone p {
  margin: 0;
  min-width: 0;
  flex: 1;
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 500;
  line-height: 1.5;
}
.mi-milestone-badge {
  flex-shrink: 0;
  padding: 5px 13px;
  border-radius: var(--mi-radius-pill);
  background: linear-gradient(135deg, var(--mi-accent), var(--mi-accent-dark));
  color: #241c00;
  font-family: var(--font-primary);
  font-size: var(--mi-text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---------- pull quote ---------- */

.mi-pullquote {
  position: relative;
  max-width: 62ch;
  margin: 0 auto;
  padding: clamp(28px, 4vw, 56px) clamp(24px, 3vw, 48px);
  text-align: center;
}
/* Oversized opening mark as a watermark behind the text, not punctuation in it. */
.mi-pullquote::before {
  content: "\201C";
  position: absolute;
  top: -0.15em;
  left: 50%;
  transform: translateX(-50%);
  color: rgb(67 86 232 / 0.14);
  font-family: var(--font-primary);
  font-size: clamp(7rem, 16vw, 11rem);
  line-height: 1;
  pointer-events: none;
}
.mi-pullquote blockquote {
  position: relative;
  margin: 0;
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: clamp(1.35rem, 1.05rem + 1.3vw, 2rem);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
/*
  A literal colour, and darker than any of the body tokens, because this caption has been
  measured three times against three different backgrounds behind it:

    --mi-muted  on the bare tint          ~4.00:1   fail
    --mi-muted  over the blurred band      4.24:1   fail
    --mi-body   over the band at 26px blur 4.22:1   fail

  Each time the band got more visible — which is what was asked for — this 14px tracked-out
  uppercase line lost contrast. #333c58 clears AA at 5.5:1 against the lightest patch the
  photograph puts behind it, and is still clearly a caption rather than body copy.
*/
.mi-pullquote figcaption {
  position: relative;
  margin-top: var(--mi-space-5);
  color: #333c58;
  font-size: var(--mi-text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.mi-pullquote figcaption::before {
  content: "";
  display: block;
  width: 40px;
  height: 2px;
  margin: 0 auto var(--mi-space-4);
  border-radius: 2px;
  background: linear-gradient(90deg, var(--mi-brand), var(--mi-violet));
}

/* ==================== scroll reveal ==================== */

/*
  Gated behind `.mi-js`, which site.js puts on <html> from an inline snippet in the
  document head. That ordering matters: if the hidden state came from CSS alone, a
  visitor with JavaScript blocked would get a permanently blank page, and adding the
  class from the deferred end-of-body script would flash the content first.

  Transform and opacity only — both composited, so this cannot cause layout jank.
*/
.mi-js [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
}
.mi-js [data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

/* ============================ gallery ============================ */

/* ---------- banner polaroid ---------- */

/*
  One photograph in the banner, in a polaroid frame, replacing the three-plate mosaic.

  A real polaroid's border is NOT even: the two sides and the top are the same narrow margin and
  the bottom is roughly three times deeper. That single proportion is what makes the shape read
  as a photograph rather than as a white-bordered image, so it is set explicitly here instead of
  being left to a uniform padding — and the caption lives in the deep edge, which is the whole
  reason the edge is deep.

  The frame is WHITE and stays white in both themes. It sits on .mi-banner--deep, which is dark
  in every variant, and a polaroid that took a dark surface token would stop being a polaroid.
*/
.mi-polaroid {
  width: 100%;
  margin: 0;
  padding: 13px 13px 0;
  background: #fbfbf8;
  border-radius: 3px;
  box-shadow:
    0 26px 50px -18px rgb(6 10 26 / 0.62),
    0 2px 6px rgb(6 10 26 / 0.30);
  /* Laid down by hand rather than placed. Straightens on hover — the tilt is the resting
     state, so returning to 0deg reads as picking the photograph up. */
  transform: rotate(-2.4deg);
  transition: transform var(--mi-transition-slow), box-shadow var(--mi-transition);
}

.mi-polaroid:hover {
  transform: rotate(0deg) translateY(-4px);
  box-shadow:
    0 34px 60px -18px rgb(6 10 26 / 0.68),
    0 2px 8px rgb(6 10 26 / 0.34);
}

/*
  4:5, and the ratio is on the WINDOW rather than on the image, so the frame keeps its shape
  whatever is dropped into it from the admin. object-fit then decides what of a wider or taller
  original survives — never the frame's geometry, which is the part that has to stay a polaroid.
*/
.mi-polaroid-photo {
  display: block;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: #1a2340;
}

.mi-polaroid-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The handwritten note in the deep edge. Right-aligned and italic as specified; the colour is
   an ink grey rather than pure black, which is what pen on a polaroid actually looks like. */
.mi-polaroid-caption {
  padding: 13px 4px 17px;
  color: #3f4250;
  font-family: var(--font-primary);
  font-size: var(--mi-text-sm);
  font-style: italic;
  letter-spacing: 0.01em;
  text-align: right;
}

/* ---------- editorial grid ---------- */

/*
  Deterministic masonry. Rather than letting a column layout decide, each photo
  carries a span picked from its aspect ratio and how well it carries at size (see
  GalleryContent.Photo.Span), so the arrangement is designed rather than emergent
  and is identical on every render.

  `dense` packing lets a single-cell photo backfill a hole left by a big one.
*/
.mi-gal {
  display: grid;
  gap: var(--mi-space-3);
  grid-auto-flow: dense;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: 150px;
}

.mi-gal-item {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--mi-radius);
  background: var(--mi-surface-2);
  box-shadow: var(--mi-shadow-sm);
  transition: transform var(--mi-transition-slow), box-shadow var(--mi-transition);
}
.mi-gal-item--big { grid-column: span 2; grid-row: span 2; }
.mi-gal-item--wide { grid-column: span 2; }
.mi-gal-item--tall { grid-row: span 2; }

/*
  A stacked pair: two photographs pinned to ONE column, one above the other.

  This has to be a wrapper rather than two spans, because nothing about a span can
  express "and put the next one directly beneath me" — the grid flows row-major, so
  two consecutive single cells land side by side, and `dense` backfilling makes that
  less predictable rather than more. The wrapper occupies one column and two rows,
  the same footprint as `tall`, and divides it between its children.

  Rows are `1fr 1fr` off a definite two-row height, so both halves resolve to a real
  box and the images inside them have something to size against; `minmax(0, 1fr)`
  because a bare 1fr floors at the content's height and the images would push the
  box taller than its two rows.
*/
.mi-gal-stack {
  grid-row: span 2;
  display: grid;
  grid-template-rows: repeat(2, minmax(0, 1fr));
  gap: var(--mi-space-3);
  min-height: 0;
}

/* Inside the wrapper the children are laid out by IT, not by the album grid, so any
   span class they might carry has to stop applying. */
.mi-gal-stack .mi-gal-item {
  grid-column: auto;
  grid-row: auto;
  min-height: 0;
}

/* A short album: two photographs side by side at a size worth looking at, rather
   than two cells adrift in a four-column grid. */
.mi-gal--pair {
  grid-template-columns: minmax(0, 1fr);
  grid-auto-rows: 300px;
}
.mi-gal--pair .mi-gal-item { grid-column: auto; grid-row: auto; }

.mi-gal-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--mi-transition-slow);
}
.mi-gal-item:hover { transform: translateY(-3px); box-shadow: var(--mi-shadow-lg); }
.mi-gal-item:hover img { transform: scale(1.05); }

/* Caption scrim. Dark by necessity — it sits on a photograph, and only over the
   bottom of the image, so it does not darken the page. */
.mi-gal-cap {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--mi-space-3);
  padding: var(--mi-space-5) var(--mi-space-4) var(--mi-space-3);
  background: linear-gradient(to top, rgb(13 18 42 / 0.88) 0%, rgb(13 18 42 / 0.45) 55%, transparent 100%);
  color: #fff;
  font-size: var(--mi-text-sm);
  line-height: 1.4;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity var(--mi-transition), transform var(--mi-transition);
}
.mi-gal-item:hover .mi-gal-cap,
.mi-gal-item:focus-visible .mi-gal-cap { opacity: 1; transform: none; }

.mi-gal-cap-text { text-shadow: 0 1px 3px rgb(0 0 0 / 0.4); }

.mi-gal-zoom {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border: 1px solid rgb(255 255 255 / 0.35);
  border-radius: 50%;
  background: rgb(255 255 255 / 0.14);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

/*
  There is no hover on a touch screen, so a hover-only caption would be a caption
  nobody on a phone ever sees. Show it permanently there instead.
*/
@media (hover: none) {
  .mi-gal-cap { opacity: 1; transform: none; }
}


/* ==================== gallery collections ====================

   The Gallery page draws one grid PER COLLECTION, each under its own head, rather than
   flattening every photograph into a single grid. That was the right call while every
   photograph was of one building; it stopped being right when the collections became four
   countries and two partner events, because nothing on a tile tells you a Colombo office
   from a Chitwan one.

   Everything here is scoped under .mi-bento or .mi-gal-collection, both of which exist only
   on that page, so no other page's grid is touched.
*/

/*
  ANCHOR OFFSET. The rail below jumps to these, and the header is sticky — without this the
  collection title lands underneath it and the visitor sees a grid with no heading, which
  reads as a broken jump rather than a covered one.
*/
.mi-gal-collection { scroll-margin-top: 96px; }

.mi-gal-collection + .mi-gal-collection { margin-top: var(--mi-space-8); }

/*
  The head: a large index numeral, the editorial block, and the metadata rail.

  The numeral is a WATERMARK, not a label — it is aria-hidden in the markup and drawn in a
  pale brand tint rather than a readable ink, because its job is rhythm down the left edge.
  It is the one place on this light page where a big flat number can carry weight without
  competing with the photographs beside it.
*/
.mi-gal-collection-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--mi-space-3) var(--mi-space-4);
  margin-bottom: var(--mi-space-5);
  padding-bottom: var(--mi-space-4);
  border-bottom: 1px solid var(--mi-line);
}

.mi-gal-collection-n {
  font-size: var(--mi-text-3xl);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: -0.03em;
  color: rgb(67 86 232 / 0.18);
  font-variant-numeric: tabular-nums;
}

.mi-gal-collection-body { min-width: 0; }

.mi-gal-collection-title {
  margin: var(--mi-space-1) 0 0;
  font-size: var(--mi-text-2xl);
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--mi-title);
}

.mi-gal-collection-desc {
  margin: var(--mi-space-2) 0 0;
  max-width: 62ch;
  color: var(--mi-body);
}

/*
  Metadata. Drops to its own row under the body at narrow widths (the default here) and is
  pulled up beside it at desktop — see the breakpoint block further down. `Held` is often
  absent, so this must look finished with a single pair in it.
*/
.mi-gal-collection-meta {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: var(--mi-space-2) var(--mi-space-5);
  margin: var(--mi-space-3) 0 0;
}

.mi-gal-collection-meta > div { min-width: 0; }

.mi-gal-collection-meta dt {
  font-size: var(--mi-text-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mi-muted);
}

.mi-gal-collection-meta dd {
  margin: 2px 0 0;
  font-size: var(--mi-text-sm);
  font-weight: 650;
  color: var(--mi-title);
  font-variant-numeric: tabular-nums;
}

/*
  THE JUMP RAIL — plain in-page anchors, no JavaScript. It is a navigation aid, not a filter:
  every collection is on the page and stays on the page, so a visitor who ignores the rail
  loses nothing and one with JavaScript blocked gets exactly the same behaviour.

  Champagne is NOT used here. This sits on the light --smoke band, where --mi-metal fails
  contrast; it is reserved for the dark banner above and the caption scrims below.
*/
.mi-gal-rail { margin-bottom: var(--mi-space-7); }

.mi-gal-rail ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mi-space-2);
}

.mi-gal-rail a {
  display: inline-flex;
  align-items: center;
  gap: var(--mi-space-2);
  padding: var(--mi-space-2) var(--mi-space-4);
  border: 1px solid var(--mi-line);
  border-radius: var(--mi-radius-pill);
  background: var(--mi-surface);
  box-shadow: var(--mi-shadow-sm);
  color: var(--mi-title);
  font-size: var(--mi-text-sm);
  font-weight: 600;
  text-decoration: none;
  transition: border-color var(--mi-transition), transform var(--mi-transition),
              box-shadow var(--mi-transition);
}

.mi-gal-rail a:hover {
  border-color: var(--mi-line-strong);
  transform: translateY(-2px);
  box-shadow: var(--mi-shadow-lg);
}

.mi-gal-rail-n {
  font-size: var(--mi-text-xs);
  font-weight: 800;
  color: var(--mi-brand);
  font-variant-numeric: tabular-nums;
}

.mi-gal-rail-t { min-width: 0; }

/* The count. A plain tint rather than a status dot — nothing here has a state. */
.mi-gal-rail-c {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: var(--mi-radius-pill);
  background: var(--mi-surface-2);
  color: var(--mi-muted);
  font-size: var(--mi-text-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/*
  IMAGE ANCHORING — load-bearing, not a nicety.

  Nine of the supplied photographs carry a printed location band across the TOP of the frame
  (SRI LANKA, BANGLADESH, POKHARA, CHITWAN and so on). The cells are fixed shapes and
  `object-fit: cover` throws away the overflow, and centred cover takes it off BOTH edges —
  which cut the band off every one of them. Anchoring to the top spends the whole crop on the
  bottom of the frame instead, where these photographs have floor and feet rather than the
  label and the faces.

  Spans are chosen per photograph from its aspect ratio so that the crop stays small in the
  first place (see CmsSeeder.SeedGalleryAsync); this is the second half of that pair.
*/
.mi-bento .mi-gal-item img { object-position: center top; }

/*
  The caption block. The scrim and its hover behaviour are unchanged; this only stacks a
  place above the caption where one is known, so a tile answers "where" as well as "what".
*/
.mi-gal-cap-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

/*
  Champagne, and here it is correct: this sits on the dark caption scrim, which is the
  surface --mi-metal exists for. Small caps at a wide track so it reads as a label rather
  than as a second line of caption.
*/
.mi-gal-place {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--mi-metal);
  font-size: var(--mi-text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.55);
}

.mi-gal-place svg { flex-shrink: 0; }
.mi-gal-place > span { min-width: 0; }


/* ==================== certification vendor cards ====================

   The home page's vendor grid. It replaces a plain `.mi-tile` link per catalogue vendor with a
   card that can carry the partner's own programmes beneath it — ETS is the only one with any
   today (TOEFL and GRE), and the product list is drawn only when there are some, so a partner
   that sells one thing gets a plain card rather than an empty container.

   Each card is one anchor covering the mark, the name and the CTA, so the whole tile is the
   click target and there is exactly one stop in the tab order per brand. The card used to also
   list the partner's own products beneath it; those moved to the partner's own page, which is
   what the CTA opens. */

/*
  Explicit column counts, not auto-fill. There are six vendors, and auto-fill at a 240px floor
  gave FOUR columns on a 1216px container — so the grid ran 4 + 2 and the second row sat half
  empty. Six divides by three and by two, so a fixed 3 / 2 / 1 ladder tiles exactly at every
  width. It still degrades sensibly if a seventh vendor is added; it simply stops being exact,
  which is the normal grid behaviour rather than a designed-in gap.
*/
.mi-grid--vendors { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 640px) {
  .mi-grid--vendors { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .mi-grid--vendors { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.mi-vtile {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--mi-glass);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius-lg);
  backdrop-filter: var(--mi-blur);
  -webkit-backdrop-filter: var(--mi-blur);
  box-shadow: var(--mi-shadow-sm);
  transition:
    transform var(--mi-transition-slow),
    box-shadow var(--mi-transition),
    border-color var(--mi-transition);
}

.mi-vtile:hover {
  transform: translateY(-3px);
  border-color: var(--mi-glass-border-hover);
  box-shadow: var(--mi-shadow);
}

/*
  flex: 1 so the card's contents absorb any height difference inside a row. Cards stretch to
  match their tallest neighbour (grid default); with every card now carrying the same three
  elements they are usually equal, but a brand name long enough to wrap still makes one taller,
  and without this the shorter cards would leave their plate floating with dead space beneath
  instead of centring in the space they were given.
*/
.mi-vtile-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--mi-space-3);
  padding: var(--mi-space-5);
  text-align: center;
  text-decoration: none;
}

/*
  THE LOGO PLATE, and the rule that matters here is that nothing is allowed to distort.

  These six marks are wildly different shapes — Oracle is a wide wordmark at roughly 5:1, ACT is
  nearly square, the Pearson lockup is a stacked block. A fixed width/height pair on the <img>
  would impose one ratio on all of them, so the markup deliberately sets neither and the rules
  below cap BOTH axes and let object-fit letterbox inside the cap. A mark can therefore only ever
  be smaller than its box, never stretched to fill it.
*/
.mi-vtile-media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 96px;
  padding: var(--mi-space-3) var(--mi-space-4);
  background: var(--mi-plate);
  border: 1px solid var(--mi-plate-border);
  border-radius: var(--mi-radius);
}

.mi-vtile-media img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* No artwork supplied for this brand yet. The name set as a mark — not MetaIT's own logo, and
   not another vendor's, which is the legacy defect this pattern replaced. */
.mi-vtile-mark {
  color: #253063;
  font-family: var(--font-primary);
  font-size: var(--mi-text-lg);
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.2;
  text-align: center;
  text-transform: uppercase;
}

.mi-vtile-name {
  color: var(--mi-title);
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/*
  THE CALL TO ACTION.

  A span, not a link — see the markup. It borrows .mi-btn--outline so it is the same pill the
  rest of the site uses for a secondary action, and .mi-btn--sm because a card is not a hero.

  The hover state is driven from the CARD, not from the button, for two reasons. The pointer is
  usually somewhere else on the tile when the visitor decides to click, and the button should
  already look live by then; and .mi-btn's own :hover lifts it 2px, which on a card that is
  itself lifting 3px reads as the button sliding loose. So the lift is cancelled here and only
  the fill, the border and the arrow respond.
*/
.mi-vtile-cta {
  margin-top: var(--mi-space-2);
}

.mi-vtile-cta,
.mi-vtile-cta:hover {
  transform: none;
}

.mi-vtile:hover .mi-vtile-cta {
  background: var(--mi-glass-strong);
  border-color: var(--mi-glass-border-hover);
  box-shadow: var(--mi-shadow);
}

.mi-vtile:hover .mi-vtile-cta .mi-btn-arrow {
  transform: translateX(4px);
}

/* ==================== gallery, refined ====================

   A presentation pass over the gallery page: frames, depth, rhythm, labels and hover. One rule
   governs all of it and it is a hard constraint, not a preference:

     NOTHING HERE MAY CHANGE AN IMAGE'S RENDERED BOX.

   Every photograph on this page is `width/height: 100%` of its card, so a 1px `border` would
   take the content box down by 2px and every image would shrink — 292x195 becomes 290x193. The
   frames below are drawn with `outline`, which paints on the OUTSIDE of the border box and costs
   no layout at all, and with box-shadow. The same constraint rules out padding on the cards and
   any change to the grids' columns, rows or gap, so every spacing improvement is made OUTSIDE
   the image containers.

   The measured boxes are identical before and after this block:
     1440  cards 292x195 (one 600x195)   mosaic 204x292 + 204x140 + 204x140
     1024  cards 228x195 (one 472x195)   mosaic 204x292 + 204x140 + 204x140
      768  cards 232x175                 mosaic 204x260 + 204x124 + 204x124
      390  cards 165x150 (one 342x150)   mosaic 165x228 + 165x108 + 165x108

   And this block sits BEFORE the prefers-reduced-motion and print blocks at the end of the
   file DELIBERATELY. Both of them reset gallery classes; anything appended after them would win
   on source order and silently undo the reset. New gallery rules belong here, not at the end.

   What this pass does NOT touch: the closing invitation. `.mi-cta--invite` and the section that
   wraps it are left exactly as they were, including the space above them — see the note on the
   tint parity below, which is what keeps that panel's own padding from being re-timed. */

/* ---------- image cards ---------- */

/*
  The frame is an OUTLINE, and that is the point of this rule rather than an implementation
  detail. It was an inset ring: a 10%-navy hairline drawn INSIDE the photograph, which over a
  mid-tone image was invisible, and which the corner radius clipped thinnest exactly where a
  frame has to read. An outline sits outside the box, follows the radius all the way round at
  full strength, and — like the inset ring and unlike a border — costs nothing in layout.
*/
.mi-gal-item {
  /*
    16px, and the mosaic plates in the banner now match it. It was 22px here and 14px there
    while a comment claimed the two were the same frame. 22px is a panel radius; on a 195px-tall
    photograph it reads as a rounded tile, where 16px reads as a plate with a machined corner.
  */
  border-radius: 16px;
  outline: 1px solid rgb(19 26 58 / 0.10);
  /*
    Three stops, close to far. The previous stack went from a 2px blur straight to a 26px one,
    so there was a gap in the middle of the ramp and the penumbra stepped instead of falling off.
  */
  box-shadow:
    0 1px 2px rgb(19 26 58 / 0.05),
    0 4px 10px -4px rgb(19 26 58 / 0.07),
    0 14px 30px -14px rgb(19 26 58 / 0.16);
  transition:
    transform var(--mi-transition-slow),
    box-shadow var(--mi-transition-slow),
    outline-color var(--mi-transition-slow);
}

/*
  Hover: 3px of lift, a deeper cast, and the hairline darkens to the page's own deep navy —
  NOT to brand indigo, which is the link and button colour and made this read as a SaaS tile
  hover applied to a photograph. The lift is deliberately small: on a grid of seven cards an
  obvious jump reads as a glitch rather than as a response.
*/
.mi-gal-item:hover,
.mi-gal-item:focus-visible {
  transform: translateY(-3px);
  box-shadow:
    0 2px 4px rgb(19 26 58 / 0.06),
    0 8px 18px -8px rgb(19 26 58 / 0.10),
    0 26px 50px -20px rgb(19 26 58 / 0.26);
}

/* The frame warms to the page's own deep navy on HOVER only. */
.mi-gal-item:hover { outline-color: rgb(19 26 58 / 0.28); }

/*
  Keyboard focus gets the real focus ring, and this is a defect fix rather than a refinement.
  The frame on these cards is drawn with an outline — which is also what the global
  :focus-visible rule near the top of this file uses — so the card's 1px frame was overriding
  the 2px focus ring, and a keyboard user's only indicator was a navy hairline at about 1.8:1.
  The site convention is restored explicitly here, offset so it reads outside the photograph.
*/
.mi-gal-item:focus-visible {
  outline: 2px solid var(--mi-link);
  outline-offset: 3px;
}

/* 1.03, down from 1.05. The brief asks for a minimal zoom, and across a 195px card the
   difference between the two is a photograph settling rather than a photograph moving. */
.mi-gal-item:hover img,
.mi-gal-item:focus-visible img { transform: scale(1.03); }

/* ---------- the banner mosaic ---------- */

/* One champagne corner bracket, offset off the top-right of the frame — the site's one metal
   accent, used the one way the token allows: a hairline on a dark ground, never a fill and never
   on a light surface. It is anchored to the UPRIGHT box, so it holds its place on the banner
   while the photograph tilts inside it and straightens on hover. */
.mi-polaroid-frame {
  position: relative;
  display: block;
  width: 100%;
  /* The cap is HERE and not on .mi-polaroid. The bracket is positioned against this box, and an
     inline-block wrapper around a width:100% figure resolves to the whole column — which at the
     one-column breakpoint left the bracket floating a long way to the right of the photograph
     it is meant to frame. Capping the wrapper keeps the two locked together at every width. */
  max-width: 292px;
}
.mi-polaroid-frame::before {
  content: "";
  position: absolute;
  inset: -16px -16px auto auto;
  width: 84px;
  height: 84px;
  border-top: 1px solid var(--mi-metal-line);
  border-right: 1px solid var(--mi-metal-line);
  border-top-right-radius: 18px;
  pointer-events: none;
}

/* ---------- the bento grid ---------- */

/*
  The page is one grid now, so there are no album heads and nothing above the grid inside its
  section. `.mi-gal`'s top margin went with them — it existed to put air between a head and the
  photographs it introduced, and with no head it was a gap at the top of the band.

  The rhythm below is the SAME padding the two album sections paid, so the vertical shape of the
  page either side of the grid is unchanged. Three classes, which ties the specificity of the
  collapse rules near the top of this file (.mi-banner + .mi-section:not(--smoke) and
  .mi-section:has(+ .mi-section--smoke)) and wins on source order.
*/
.mi-gal { margin-top: 0; }

.mi-section.mi-bento {
  padding-block: var(--mi-section-y);
  position: relative;
  isolation: isolate;
}

/*
  Texture on the tinted band — a fine dot matrix in the page's own navy, at an alpha where it is
  felt rather than seen, masked so it is gone before it reaches the photographs.

  z-index -1 under `isolation: isolate` is the layering the `.mi-band` note earlier in this file
  describes: a negative-z child paints above its parent's own background and below its in-flow
  content, so this sits over the tint and under the grid without moving either.
*/
.mi-bento::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: radial-gradient(rgb(19 26 58 / 0.055) 1px, transparent 1px);
  background-size: 22px 22px;
  mask-image: linear-gradient(158deg, #000 0%, transparent 48%);
  -webkit-mask-image: linear-gradient(158deg, #000 0%, transparent 48%);
  pointer-events: none;
}

/* ---------- section rhythm ---------- */


/* ---------- the gallery banner ---------- */

/*
  There is no `--lux` modifier any more either, and this is why.

  It set a second ::before and a second ::after on the same element as `.mi-banner--deep`, and
  lost both fights it started. Its ::before replaced the aurora's three radial glows with a 1px
  champagne line while INHERITING the aurora's `animation: mi-banner-drift 34s infinite` — so the
  gallery banner had no aurora, and its hairline was being translated 2.5% and scaled 1.08 on a
  34-second loop. Its ::after replaced the 24px/0.22 dot matrix AND the top sheen with a fainter
  26px matrix, which left the page's showcase banner less lit than the plain deepened one.

  Both pseudo-elements are back to doing the job they were written for. The champagne edge is now
  its own element in the view, which cannot collide with either.
*/
.mi-banner-edge {
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  z-index: 1;
  background: linear-gradient(90deg,
    transparent 0%, var(--mi-metal-line) 24%, var(--mi-metal-line) 60%, transparent 88%);
  pointer-events: none;
}

/*
  `.mi-banner--deep`'s own padding clamp was dead above 1024px: the `.mi-banner` rule in the
  1024 media query further down this file has equal specificity and comes later, so the deepened
  banner rendered 64/48 instead of the 84/76 it asks for — making the page's entrance its
  tightest block. Two classes restores the intent regardless of source order.
*/
.mi-banner.mi-banner--deep {
  padding-block: clamp(44px, 5.5vw, 84px) clamp(40px, 5vw, 76px);
}

/*
  Hierarchy in the copy stack. Every gap in it was a single 16px step, under a headline that
  reaches 60px — so the label, the headline and the lead read as one undifferentiated block. The
  steps now grow with the type they follow.
*/
.mi-banner--deep .mi-eyebrow { margin-bottom: var(--mi-space-5); }

/*
  The breadcrumb, brightened — and this one is a contrast fix, not a preference.

  It inherits --mi-faint for the link and --mi-body for the current page, which on a dark surface
  are #8b95b4 and #c2cbe4. The aurora's first and brightest glow is centred at 16% 28%, which is
  exactly where the breadcrumb sits, so those two measured 2.31:1 and 4.31:1 — both already under
  AA before this pass, with --lux flattening the background. Restoring the aurora put the light
  back under them and took them to 1.78:1 and 3.52:1, so the fix is owed either way.

  Near-white for the link and white for the current page keeps a step of hierarchy between them
  while both clear 4.5:1. The text-shadow is the same detail .mi-pagehead uses at 1879 and for the
  same reason: this type sits over a gradient that moves, and the shadow keeps the stems crisp
  wherever it lands. It is not counted toward the ratios above — WCAG does not credit it.

  The separator is aria-hidden="true" and is a decorative glyph, so it is left at --mi-line-strong.
*/
.mi-banner--deep .mi-breadcrumb { text-shadow: 0 1px 2px rgb(8 12 30 / 0.45); }
/* rgb(233 238 252) also passed, at 4.56 — six hundredths of headroom over the floor, which is
   close enough to the line that antialiasing on another GPU could take it under. */
.mi-banner--deep .mi-breadcrumb a { color: rgb(243 246 255); }
.mi-banner--deep .mi-breadcrumb a:hover { color: #fff; }
.mi-banner--deep .mi-breadcrumb [aria-current="page"] { color: #fff; }

/*
  The headline. `.mi-banner h1` further up sets font-size and letter-spacing at the SAME
  specificity as `.mi-banner--deep h1` and about sixty lines later, so neither of the deepened
  banner's own values has ever rendered: it asks for -0.035em and a clamp topping out at 60px,
  and it has been getting -0.03em at 52px.

  The tracking is restored unconditionally — it costs no layout and tighter tracking is most of
  what makes display type read as set rather than as typed.

  The SIZE is not, and that is measured rather than cautious. At the intended clamp the headline
  runs 1 line at 1440 and 1280, 2 at 1100 and 1024, and 3 at 768 — against 1/1/1/2/2 today. So
  the rule that has been overriding it is, by accident, the better responsive curve, and only the
  top end is worth taking back. 60px at 1280 was checked against the 764px copy column: one line.

  600 rather than 700 is the call this sheet already records for .mi-pagehead-title — at display
  size the heavier weight reads as loud rather than as confident.
*/
.mi-banner.mi-banner--deep h1 {
  margin-bottom: var(--mi-space-5);
  font-weight: 600;
  letter-spacing: -0.035em;
}

@media (min-width: 1280px) {
  .mi-banner.mi-banner--deep h1 { font-size: 3.75rem; }
}

/*
  The three facts, as a rail. They were `.mi-chip` capsules, and `.mi-chip::before` is a dot with
  a coloured halo — the site's status-indicator idiom — with the `--ok` variant spending a
  success green on the word "Kathmandu". A place is not a status. Numerals in the display face at
  a size worth reading, labels in the 11px mono, separated by hairlines of light.
*/
/*
  A GRID by default and a divided rail only where the rail measurably fits, because a wrapped
  flex item keeps its own border-left — which leaves a vertical hairline at the start of a line
  with nothing to its left. Measured: the three facts want 524px as one row, and the banner's
  copy column gives 268px at 768, 400px at 900 and 508px at 1024 (the mosaic takes the rest from
  768 up), reaching 584px at 1100. So 1100 is where the rail is switched on, and `nowrap` there
  makes the orphaned divider unreachable rather than unlikely.
*/
.mi-gal-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--mi-space-5) var(--mi-space-4);
  margin: var(--mi-space-6) 0 0;
}

.mi-gal-stat { min-width: 0; }

/* The place name is the long one; on two columns it takes a line of its own. */
.mi-gal-stat:last-child { grid-column: 1 / -1; }

@media (min-width: 1100px) {
  .mi-gal-stats {
    display: flex;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 0;
    margin-top: var(--mi-space-7);
  }
  .mi-gal-stat:last-child { grid-column: auto; }
  .mi-gal-stat + .mi-gal-stat {
    margin-left: var(--mi-space-6);
    padding-left: var(--mi-space-6);
    border-left: 1px solid rgb(255 255 255 / 0.18);
  }
}

.mi-gal-stat dt {
  margin-bottom: var(--mi-space-2);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* 0.82, not the lead's 0.92: this is 11px and tracked out, so it is measured against AA for
     small text rather than for body copy. */
  color: rgb(221 229 250 / 0.82);
}

.mi-gal-stat dd {
  margin: 0;
  font-family: var(--font-primary);
  font-size: var(--mi-text-xl);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: #fff;
}


/* ---------- the closing invitation ---------- */

/*
  The shared CTA paints a three-stop diagonal gradient across itself. Here that is replaced by
  one near-white surface, so what gives the panel its presence is the edge and the shadow
  rather than colour — which is the difference between an expensive-looking card and a tinted
  one. The blurred indigo orb behind it stays; it is the single soft accent and it earns its
  place once the gradient underneath is gone.
*/
.mi-cta--invite {
  padding: clamp(32px, 4.5vw, 60px);
  background: linear-gradient(180deg, rgb(255 255 255 / 0.96) 0%, rgb(250 251 254 / 0.94) 100%);
  border-color: rgb(19 26 58 / 0.10);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.9),
    0 1px 2px rgb(19 26 58 / 0.04),
    0 26px 60px -28px rgb(19 26 58 / 0.26);
}

/* The address line, darkened. Taking the tinted gradient out from under this panel made the
   surface lighter, and --mi-muted measured 3.8:1 against it — a contrast cost of the visual
   change, so it is paid here rather than left. */
.mi-cta--invite .mi-lead { color: var(--mi-body); }

/* A short navy rule above the heading. Two words of hierarchy: it marks this as a closing
   invitation rather than another content card, without adding a label to read. */
.mi-cta--invite .mi-cta-body::before {
  content: "";
  display: block;
  width: 44px;
  height: 2px;
  margin-bottom: var(--mi-space-4);
  border-radius: 2px;
  background: var(--mi-ink);
}

/* ---------- lightbox ---------- */

/*
  Built by site.js and injected once; the markup is not in the view because without
  JavaScript the gallery items are plain links to the full-size image, which already
  works. Dark by necessity — a viewer is meant to disappear around the photograph —
  and it is an overlay, so it does not affect the page's own balance.
*/
.mi-lb {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  /*
    minmax(0, 1fr), not 1fr. A bare `1fr` is minmax(auto, 1fr), so the stage row
    grows to its content — a 1600px photograph — and pushes the caption and the
    prev/next controls off the bottom of the screen. Flooring the minimum at 0 lets
    the row be smaller than the image, which is what makes `max-height: 100%` on
    the image bite.
  */
  grid-template-rows: minmax(0, 1fr) auto;
  gap: var(--mi-space-4);
  padding: clamp(16px, 4vw, 48px);
  background: rgb(13 18 42 / 0.90);
  backdrop-filter: blur(14px) saturate(120%);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
  animation: mi-lb-in 0.22s ease both;
}
.mi-lb[hidden] { display: none; }

@keyframes mi-lb-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes mi-lb-zoom {
  from { opacity: 0; transform: scale(0.97); }
  to { opacity: 1; transform: none; }
}

/*
  THE STAGE IS `position: relative` AND THE PHOTOGRAPH IS ABSOLUTE INSIDE IT. That is the
  whole fix for a viewer that used to crop, and it is not interchangeable with centring the
  image as a flow child.

  What was here before was `display: grid; place-items: center` with `max-height: 100%` on
  the image, which looks correct and does not work. A percentage max-height only resolves
  against a containing block with a DEFINITE height, and this stage's height comes from the
  `minmax(0, 1fr)` row on .mi-lb — indefinite at the time the image is laid out. So the
  max-height resolved against `auto`, constrained nothing, and the image rendered at close to
  its natural size and was then cut off by the `overflow: hidden` below.

  Measured in Chrome before the change, with the 720x1280 ribbon-cutting photograph on a
  1262x764 viewport: stage 1166x585, image 698x1242 — more than twice the height of the box
  containing it, so more than half the photograph was simply not visible. Every photograph
  was affected; portraits worst, because they overflow furthest.

  An absolutely positioned element resolves percentages against its containing block's used
  padding box, which IS definite here, so `max-width`/`max-height` finally bite. `inset: 0`
  plus `margin: auto` is what centres it on both axes once it is out of flow.

  Keep `width`/`height: auto` beside the caps: they are what makes the browser scale the
  other axis to preserve the ratio, and they are why this needs no object-fit. The element
  box IS the photograph, so the radius and the shadow still hug the image itself rather than
  framing empty letterbox — which is what `object-fit: contain` on a stage-filling box would
  have given, and is why that easier fix was not used.

  Nothing here upscales: with only maxima set, a photograph smaller than the stage stays at
  its own size rather than being blown up soft.
*/
.mi-lb-stage {
  position: relative;
  min-height: 0;
  overflow: hidden;
}
.mi-lb-img {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: var(--mi-radius);
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.5);
  animation: mi-lb-zoom 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.mi-lb-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--mi-space-4);
  color: #fff;
}
.mi-lb-cap {
  min-width: 0;
  font-family: var(--font-primary);
  font-size: var(--mi-text-base);
  font-weight: 500;
}
.mi-lb-count {
  display: block;
  margin-top: 3px;
  color: rgb(255 255 255 / 0.6);
  font-family: var(--font-secondary);
  font-size: var(--mi-text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.mi-lb-nav { display: flex; gap: var(--mi-space-3); flex-shrink: 0; }

.mi-lb-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid rgb(255 255 255 / 0.25);
  border-radius: 50%;
  background: rgb(255 255 255 / 0.10);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--mi-transition), border-color var(--mi-transition),
              transform var(--mi-transition);
}
.mi-lb-btn:hover {
  background: rgb(255 255 255 / 0.20);
  border-color: rgb(255 255 255 / 0.45);
  transform: translateY(-2px);
}
.mi-lb-btn:disabled { opacity: 0.35; pointer-events: none; }

/* Close sits top-right over the stage rather than in the bar, where it would
   compete with the caption.

   THE z-index IS LOAD-BEARING. This button and the stage are both positioned and both
   children of .mi-lb, and the stage comes LATER in the DOM — so with everything at
   `z-index: auto` the stage and the photograph inside it paint on top of this button and
   swallow the tap. Verified with elementFromPoint at the button's own centre: without this
   line the hit at 390x844 returned .mi-lb-img and at 1280x860 returned .mi-lb-stage, never
   the button. On desktop that damage was hidden — the stage is the backdrop and closing is
   what a backdrop click does anyway — but on a phone the photograph covers the corner and
   the close button simply stopped working.

   The stage only became a positioned element when the viewer was fixed to stop cropping
   (see .mi-lb-stage above), so this pairs with that change and cannot be dropped
   independently of it. */
.mi-lb-close {
  position: absolute;
  z-index: 2;
  top: clamp(12px, 3vw, 28px);
  right: clamp(12px, 3vw, 28px);
}

body.mi-lb-open { overflow: hidden; }

/* ============================ responsive ============================ */

@media (min-width: 640px) {
  .mi-hero-logos { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 560px) {
  /* Icon beside the copy once there is room for both on one line. */
  .mi-scam { grid-template-columns: auto minmax(0, 1fr); gap: var(--mi-space-5); }
}

@media (min-width: 820px) {
  /* Label pinned left, marquee taking the rest of the width. Below this it stacks,
     because a fixed label plus a scrolling track on a phone leaves the track too
     narrow to show more than one logo at a time. */
  .mi-partner-band-inner { grid-template-columns: auto minmax(0, 1fr); gap: var(--mi-space-5); }
  .mi-partner-band-label { padding-right: 0; }
}

@media (min-width: 640px) {
  .mi-gal { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 175px; }
  .mi-gal--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 340px; }
}

@media (min-width: 768px) {
  .mi-footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mi-field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mi-banner-grid { grid-template-columns: minmax(0, 1fr) auto; }
  .mi-pagehead-grid { grid-template-columns: minmax(0, 1fr) auto; }
  .mi-polaroid-frame { max-width: 300px; }
}

@media (min-width: 900px) {
  .mi-split { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
  .mi-offer { grid-template-columns: 220px minmax(0, 1fr) 280px; gap: var(--mi-space-6); }
  .mi-cta { grid-template-columns: minmax(0, 1fr) auto; }
}

@media (min-width: 1024px) {
  :root {
    --mi-section-y: 104px;
    --mi-gutter: 32px;
  }

  .mi-nav { display: block; }
  .mi-menu-toggle { display: none; }

  /*
    The split becomes a split. Below this the panel is a band under the copy, because a
    ~36% column at tablet width is too narrow to hold a logo plate at a readable size.

    The panel track is a percentage rather than an fr so its width does not move when the
    headline wraps to a different number of lines: an fr track shares whatever the copy
    leaves, and the copy is CMS-authored, so the panel edge would land somewhere new on
    every slide of the same carousel.
  */
  .mi-hero-split { grid-template-columns: minmax(0, 1fr) minmax(300px, 36%); }
  .mi-banner { padding-block: var(--mi-space-8) var(--mi-space-7); }

  .mi-detail { grid-template-columns: minmax(0, 1fr) 350px; gap: var(--mi-space-6); }
  /* Sticks under the sticky header, which is ~76px plus a little air. */
  .mi-detail-aside { position: sticky; top: 100px; }

  .mi-gal { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 195px; gap: var(--mi-space-4); }
  .mi-gal--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 440px; }
  .mi-polaroid-frame { max-width: 328px; }

  /*
    There is room for a third column at this width, so the collection metadata comes up out
    of its own row and sits beside the title, right-aligned against the grid below it. Below
    this width it stays stacked — squeezing "Held / 15 April 2026" beside a wrapping title
    made both unreadable.
  */
  /* The QR beside its instructions rather than above them. Below this width the column is
     too narrow for both, and a 340px code next to a 200px list reads as cramped. */
  .mi-qr-pay-grid { grid-template-columns: 340px minmax(0, 1fr); gap: var(--mi-space-7); }
  .mi-qr-pay-code { justify-self: start; }

  .mi-gal-collection-head { grid-template-columns: auto minmax(0, 1fr) auto; }
  .mi-gal-collection-meta {
    grid-column: 3;
    grid-row: 1;
    align-self: end;
    justify-content: flex-end;
    text-align: right;
    margin: 0;
  }
  .mi-gal-collection-n { font-size: var(--mi-text-4xl); }

  .mi-director { grid-template-columns: 340px minmax(0, 1fr); gap: var(--mi-space-6); }
  /* Sticks under the sticky header, which is ~76px plus a little air. */
  .mi-director-aside { position: sticky; top: 100px; }

  .mi-footer-grid { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr); }
}

/* Let wide monitors actually use their width. Text still sits inside narrower blocks
   (.mi-prose--narrow, .mi-section-head) so line length stays readable — it is the grids
   and cards that benefit from the extra room. */
@media (min-width: 1500px) {
  :root { --mi-container: 1400px; }
  .mi-detail { grid-template-columns: minmax(0, 1fr) 380px; }
}

@media (min-width: 1700px) {
  :root { --mi-container: 1560px; }
}

@media (min-width: 1900px) {
  :root {
    --mi-container: 1720px;
    --mi-gutter: 40px;
  }
}

/* Ultrawide. Still capped rather than full-bleed — past roughly 1900px the eye has to
   track too far across a row for the content to stay comfortable. */
@media (min-width: 2200px) {
  :root { --mi-container: 1900px; }
}

@media (max-width: 1023px) {
  :root { --mi-section-y: var(--mi-section-y-mobile); }

  .mi-banner h1 { font-size: var(--mi-text-3xl); }
  .mi-title-lg { font-size: var(--mi-text-2xl); }

  /*
    On a phone the copy fills the width, so the scrim's left-to-right grading has nothing
    left to protect — the headline can land anywhere across the image. Flatten it to an
    even wash of the same colour, which is what the grading averages out to.
  */
  /*
    Tablet and phone. The artwork's own ratio governs here rather than a minimum height:
    a banner designed at 16:9 becomes very tall in a narrow column, and forcing a floor on
    top of that pushes the rest of the page below the fold.
  */
  .mi-hero--banner {
    min-height: 0;
    max-height: 82vh;
  }

  .mi-pagehead { min-height: 0; }
  .mi-pagehead-scrim {
    background:
      radial-gradient(130% 100% at 50% 45%, transparent 46%, rgb(8 12 26 / 0.38) 100%),
      linear-gradient(180deg, rgb(10 14 30 / 0.34) 0%, rgb(10 14 30 / 0.04) 42%, rgb(10 14 30 / 0.42) 100%),
      linear-gradient(96deg, rgb(16 22 45 / 0.94) 0%, rgb(19 26 54 / 0.86) 100%);
  }

  /*
    The light training wash flattens here for exactly the reason the dark one above does:
    below this width the copy fills the container, so there is no clear right-hand side left
    to leave uncovered — the standfirst runs straight across it. Graded, the last two lines
    of that paragraph land on bare photograph and dark ink on a mid-grey photo is not
    readable, which is the mirror of the problem the grading solves on desktop.

    So the ramp goes even and near-opaque. The photograph is effectively given up on a
    phone; the desktop split is where it does its work. Needed because the --training rule
    earlier in this file outranks the plain .mi-pagehead-scrim rule above and would
    otherwise keep its 100%-to-0% ramp at every width.
  */
  .mi-pagehead--training .mi-pagehead-scrim {
    background: linear-gradient(96deg, rgb(255 255 255 / 0.96) 0%, rgb(250 252 255 / 0.93) 100%);
  }

  /*
    The Director banner flattens here for the same reason, and it is REQUIRED rather than tidy.
    The variant scrim above is (0,2,0) and beats the plain `.mi-pagehead-scrim` rule in this
    block regardless of the media query, so without this entry the desktop white-to-transparent
    ramp survives onto a phone — where the copy spans the full container and the last lines of
    the standfirst would land on bare photograph in dark ink.

    Near-solid white rather than the desktop ramp: below this width there is no clear right-hand
    side to compose against, so the photograph is given up and the copy gets one flat value.
  */
  .mi-pagehead--director-message .mi-pagehead-scrim {
    background: linear-gradient(96deg, rgb(255 255 255 / 0.96) 0%, rgb(250 252 255 / 0.93) 100%);
  }

  /*
    The vendor banner keeps its bottom-weighted shape here rather than flattening like the two
    above, and that is the point of it: the copy sits at the FOOT of this band, so a ramp that
    runs top-to-bottom still lands its dark end exactly where the type is, at any width. What
    changes is only how early it starts — the band is shorter and the name wraps to more lines
    on a phone, so the ramp begins at 30% instead of 58% and the clear window in the middle of
    the photograph closes up.
  */
  .mi-pagehead--vendor .mi-pagehead-scrim {
    background: linear-gradient(180deg,
      rgb(8 12 30 / 0.40) 0%,
      rgb(8 12 30 / 0.26) 30%,
      rgb(8 12 30 / 0.66) 72%,
      rgb(8 12 30 / 0.82) 100%);
  }


  /* The plate is full-width here, so the champagne ring reads as a border round a slab
     rather than as an edge on a floating card. */
  .mi-pagehead-plate { box-shadow: 0 18px 40px -18px rgb(6 10 26 / 0.55); }
  /*
    Stacked. The panel becomes a short band under the copy — the headline leads on a
    small screen rather than being pushed below the fold by artwork.

    It keeps its own bottom bar, so the champagne hatch is dropped: stacked, the two
    segments no longer meet on a column edge, they overlap on the same 6px line.
  */
  .mi-hero-panel {
    min-height: clamp(150px, 26vw, 220px);
    border-inline-start: 0;
    border-block-start: 1px solid rgb(255 255 255 / 0.08);
  }
  .mi-hero-rule { display: none; }
  /* Below the two-column breakpoint the rail carries the actions, so it goes
     ABOVE the copy — a buyer should not have to scroll a 3000-word legacy
     description to reach the enquiry button. */
  .mi-detail-aside { order: -1; }

  /* Same reasoning on the director page: establish who is speaking before the
     message, and cap the portrait well so it does not become a full-width slab. */
  .mi-profile { max-width: 460px; margin-inline: auto; }
  .mi-profile-portrait { aspect-ratio: 16 / 9; }
}

@media (max-width: 480px) {
  .mi-grid { grid-template-columns: minmax(0, 1fr); }
  .mi-btn { width: 100%; }
  .mi-hero-logos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mi-hero-logo { height: 64px; }
  .mi-hero-stats { gap: var(--mi-space-5); }
  .mi-quote::before { font-size: 46px; }
  .mi-banner-media { width: 100%; }
}

/*
  Blur is the most expensive property in this sheet. Where the platform reports it
  cannot composite it cheaply, drop to an opaque surface — the layout, contrast and
  hierarchy are all unchanged, only the see-through is lost. Both surfaces are
  covered because --mi-surface is itself contextual.
*/
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .mi-header { background: #fff; }
  .mi-card, .mi-tile, .mi-quote, .mi-receipt, .mi-panel,
  .mi-submenu, .mi-drawer, .mi-prose--narrow, .mi-faq-item, .mi-note,
  .mi-eyebrow, .mi-btn--outline, .mi-menu-toggle, .mi-doc, .mi-side-card,
  .mi-chip, .mi-status, .mi-cta, .mi-offer {
    background: var(--mi-surface);
  }
  .mi-offer-buy, .mi-side-card--cta { background: var(--mi-surface-2); }

  /* More specific than the rule above, so it needs saying again — and here the
     translucency was doing real work, holding the row together over a photograph. */
  .mi-pagehead .mi-chip { background: rgb(27 38 80 / 0.88); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  /*
    The blanket rule above is not enough for a looping animation, and is actively
    harmful to one. Collapsing an `infinite alternate` to 0.01ms does not stop it —
    it makes it flip between its endpoints thousands of times a second, which is a
    strobe served to precisely the people who asked for less motion. Looping
    animations have to be switched off outright.
  */
  /* Same reasoning as the banner aurora: an infinite animation collapsed to 0.01ms
     does not stop, it strobes. The partner band is switched off and simply sits
     still, which is a complete list rather than a degraded one. */
  .mi-marquee-track { animation: none !important; }
  .mi-marquee { mask-image: none; -webkit-mask-image: none; overflow-x: auto; }

  .mi-banner--deep::before { animation: none !important; }

  /* Entrance animations use `both`, so cancelling them without restoring the end
     state would leave the content stuck at opacity 0. */
  .mi-banner--deep > .mi-container > *,
  .mi-banner--deep .mi-banner-inner > *,
  .mi-banner--deep .mi-banner-media {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .mi-btn:hover, .mi-card:hover, .mi-tile:hover,
  /*
    `both` again — cancelling these without restoring the end state would leave the whole
    banner at opacity 0, and the photograph frozen mid-zoom at scale(1.06), which is a
    crop the admin never chose.
  */
  .mi-pagehead-media,
  .mi-pagehead .mi-breadcrumb,
  .mi-pagehead-eyebrow,
  .mi-pagehead-title,
  .mi-pagehead-lead,
  .mi-pagehead .mi-chips,
  .mi-pagehead-plate {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  /* :focus-visible as well as :hover. The gallery card lifts on both, so cancelling only the
     hover left the same 3px translate and the same 1.03 zoom reachable from the keyboard. */
  .mi-quote:hover, .mi-gal-item:hover, .mi-lb-btn:hover { transform: none; }
  .mi-gal-item:focus-visible { transform: none; }
  .mi-gal-item:hover img,
  .mi-gal-item:focus-visible img { transform: none; }
  .mi-polaroid, .mi-polaroid:hover { transform: none; }

  /* Reveal-on-scroll is motion by definition. Show everything immediately instead of
     animating it — including elements the observer has not reached yet, which is why
     this targets [data-reveal] and not just .is-in. */
  .mi-js [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/*
  Print — training pages get printed as course outlines. The page is already light,
  so only the dark chrome needs flattening.
*/
@media print {
  .mi-topbar, .mi-header, .mi-footer, .mi-btn-row, .mi-breadcrumb,
  .mi-hero-panel, .mi-hero-rule, .mi-map, .mi-drawer, .mi-drawer-backdrop, .mi-cta,
  .mi-detail-aside, .mi-lb, .mi-polaroid { display: none; }

  /* Captions are hover-revealed on screen; on paper there is no hover, so print them. */
  .mi-gal-cap { position: static; opacity: 1; transform: none; background: none; color: #000; padding: 6px 0; }
  .mi-gal-zoom { display: none; }

  /* The place must survive onto paper — it is the only thing naming where a photograph was
     taken. Champagne is a screen colour on a dark scrim; on white it is nearly invisible, so
     it prints as ink like the caption beside it. */
  .mi-gal-place { color: #000; text-shadow: none; }
  /* A rail of in-page jump links is meaningless on paper — every collection is already on it. */
  .mi-gal-rail { display: none; }
  /* Keep a collection's head with the grid it introduces rather than orphaned on a page end. */
  .mi-gal-collection { break-inside: auto; }
  .mi-gal-collection-head { break-after: avoid; page-break-after: avoid; }
  .mi-gal-collection-n { color: #666; }

  /* Decoration that would print as ink on paper: the champagne edge under the banner and the
     dot matrix on the tinted band are both surface, and paper has no surface. */
  .mi-banner-edge { display: none !important; }
  /* Indented back inside the print block: at column 0 this read as a global override. */
  .mi-bento::after { display: none !important; }

  /* One continuous grid replaced two short ones, so the page has no natural break left in it.
     Without this a card can be sliced across the fold. */
  .mi-gal-item { break-inside: avoid; page-break-inside: avoid; }

  /*
    The banner stat rail states its colours literally — #fff on the numerals — precisely so a
    change to the dark token set cannot wash them out on screen. Which means paper has to be
    handled on its own, or the three facts print white on white. Same trap the pagehead title
    below already documents.
  */
  .mi-gal-stat dt,
  .mi-gal-stat dd { color: #000 !important; }
  .mi-gal-stat + .mi-gal-stat { border-left-color: #ccc !important; }

  html, body { background: #fff !important; color: #000 !important; }

  .mi-hero, .mi-banner--deep, .mi-pagehead {
    background: none !important;
    min-height: 0 !important;
    padding-block: 0 !important;
    box-shadow: none !important;
  }
  .mi-hero::before, .mi-hero::after,
  .mi-banner::before, .mi-status::before, .mi-cta::before { display: none !important; }

  /* The hero's padding and its container inset now live on the split, and the inset is
     sized to clear the carousel arrows — which are not printed. On paper it is just a
     margin the rest of the outline does not have. */
  .mi-hero-split { padding-inline: 0 !important; }
  .mi-hero-copy { padding: 0 !important; max-width: none !important; }

  /* The photograph, its scrim and the champagne edge are the surface, and on paper there
     is no surface — printing them would put a solid dark slab at the top of a course
     outline, with a metal rule under it. */
  .mi-pagehead-media, .mi-pagehead-scrim, .mi-pagehead::after { display: none !important; }

  /* The hero and the deepened banners are dark regions, so their contextual tokens
     are the dark set — which on white paper is near-invisible. Force the light
     values back. */
  .mi-hero, .mi-banner--deep, .mi-pagehead {
    --mi-title: #000;
    --mi-body: #222;
    --mi-muted: #444;
    --mi-faint: #555;
  }

  /* These three state their colour literally rather than through the tokens above,
     precisely so a replaceable photograph cannot change them — which means paper has
     to be handled on its own. */
  .mi-pagehead-title, .mi-pagehead-lead, .mi-pagehead-eyebrow {
    color: #000 !important;
    text-shadow: none !important;
  }

  /* Same reason, and this one prints on the page most likely to be printed: the fact
     chips on a training detail page are near-white text on a translucent fill, which on
     paper is white on white. */
  .mi-pagehead .mi-chip {
    background: #fff !important;
    border-color: #ccc !important;
    color: #222 !important;
    backdrop-filter: none !important;
  }
  .mi-pagehead .mi-chip b { color: #000 !important; }

  .mi-pagehead-plate { box-shadow: none !important; }

  h1, h2, h3, h4, h5, h6, .mi-prose strong { color: #000 !important; }

  .mi-detail { grid-template-columns: minmax(0, 1fr) !important; }

  .mi-card, .mi-tile, .mi-quote, .mi-panel, .mi-receipt, .mi-prose--narrow,
  .mi-doc, .mi-offer, .mi-status {
    background: #fff !important;
    border: 1px solid #ccc !important;
    box-shadow: none !important;
    backdrop-filter: none !important;
  }
}

/* ---------- optional VAT details ---------- */

/* The .mi-gst* class names are deliberately unchanged: the buyer-facing wording moved
   from GST to VAT, but renaming selectors would be churn across the stylesheet and the
   view for no visible gain. */

/*
  Disclosure driven by :has() on the checkbox state, so it needs no JavaScript — the
  fields work with scripting blocked, and there is no flash of an open panel before a
  script closes it.

  The panel is display:none until wanted rather than merely visually hidden: hidden
  inputs still post, and a buyer who typed a VAT number then unticked the box must not have
  it silently submitted. (The client also nulls those fields on an unticked box, so this
  is belt and braces.)
*/
.mi-gst { display: grid; gap: 0; }

.mi-gst-panel { display: none; }

.mi-gst:has(.mi-gst-toggle:checked) .mi-gst-panel {
  display: grid;
  gap: var(--mi-space-4);
  margin-top: var(--mi-space-4);
  padding: var(--mi-space-5);
  background: var(--mi-glass-soft);
  border: 1px solid var(--mi-glass-border);
  border-radius: var(--mi-radius);
  animation: mi-gst-open 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes mi-gst-open {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/*
  Fallback for browsers without :has(). Showing the panel unconditionally is the right
  failure: the fields are optional and clearly labelled, so a visitor can ignore them.
  Hiding them with no way to open would lose the feature outright.
*/
@supports not selector(:has(*)) {
  .mi-gst-panel {
    display: grid;
    gap: var(--mi-space-4);
    margin-top: var(--mi-space-4);
  }
}

/* ==================== hero carousel ==================== */

/*
  Scroll-snap, not transforms.

  The browser then owns the hard parts: touch and trackpad swipe, momentum, and
  snapping — all native, all correct on every platform. With JavaScript blocked the
  track is still a horizontal scroller containing every slide, so the content is never
  lost. site.js only adds arrows, dots and autoplay on top.
*/
.mi-hero-carousel {
  position: relative;
  isolation: isolate;
}

.mi-hero-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* The scrollbar is chrome for a control that has arrows and dots of its own. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Stops a vertical drag on a phone being stolen by the horizontal scroller. */
  touch-action: pan-y pinch-zoom;
  overscroll-behavior-x: contain;
}
.mi-hero-track::-webkit-scrollbar { display: none; }

/* Each slide is exactly one viewport wide and carries its own background, so the
   carousel reads as a sequence of complete banners rather than a sliding filmstrip. */
.mi-hero-carousel .mi-hero {
  flex: 0 0 100%;
  width: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* ---------- full-bleed banner slides ----------

   A slide whose whole content is one finished piece of artwork — the certification
   banners, which carry their own headline in the pixels. No copy is rendered over them.

   THE HARD PART IS THE SHAPE. The artwork is a fixed rectangle (roughly 16:9); the hero
   is whatever shape the viewport makes it, which on a phone is nearly square and on a
   wide monitor is a letterbox. `cover` never distorts, but it crops — and cropping a
   banner whose headline is burnt into the left third can cut the headline in half.

   So the height is driven by the artwork's own ratio rather than by the copy slides:

     aspect-ratio   gives the slide the artwork's shape, so at most widths there is
                    nothing to crop at all
     min/max-height keep it from becoming a letterbox strip on an ultrawide monitor or
                    a full-screen slab on a tall phone
     object-position lets the admin choose which band survives when there IS a crop

   The track is a flex row, so all slides share the tallest height. Mixing banner slides
   with copy slides therefore crops the banners by whatever the copy needs — fine while
   the copy slide leads, and it disappears entirely once every slide is a banner, which
   is the state this is built for.
*/
.mi-hero--banner {
  display: block;
  /* The artwork is the surface; the hero's own gradient would only show through as a
     seam at the edges of the crop. */
  background: var(--mi-ink-deep);

  /*
    NOT `aspect-ratio`. That gives the element a definite height, which stops it
    stretching, and a slide that will not stretch leaves a bare strip of navy under it
    whenever another slide in the track is taller — which is exactly what a copy slide is.

    `min-height` expressed in vw is the same 16:9 shape (9/16 = 56.25vw) but as a FLOOR,
    so the slide still stretches to match the tallest sibling. An all-banner carousel
    therefore sits at a true 16:9, and a mixed one fills the hero completely.
  */
  min-height: clamp(300px, min(56.25vw, 74vh), 720px);
}

/*
  The carousel reserves space under every slide for the dots. On a banner that reservation
  is a bare navy band below finished artwork, so here the dots float over the image instead
  and the artwork runs to the bottom edge.

  Doubled class deliberately: the rule this overrides is `.mi-hero-carousel .mi-hero`,
  which has the SAME specificity and is declared later in this file, so a single-class
  override loses on source order.
*/
.mi-hero-carousel .mi-hero.mi-hero--banner { padding: 0; }

/* The aurora and the technical grid belong to the copy slides. Over a photograph they
   are two more things competing with artwork that is already finished. */
.mi-hero--banner::before,
.mi-hero--banner::after { display: none; }

.mi-hero-banner-img {
  display: block;
  width: 100%;
  height: 100%;
  /* cover, never contain: contain would letterbox the artwork inside the hero with bars
     down both sides, which reads as a mistake rather than as a banner. */
  object-fit: cover;
  object-position: center;
}

/* ---------- an all-banner carousel takes the artwork's own shape ----------

   The 16:9 floor above exists to stop a banner leaving a strip of bare navy under it when
   a TALLER copy slide is setting the track's height. With every slide a banner there is no
   such sibling, so the floor stops being a safeguard and becomes the only thing cropping
   the picture: artwork wider than 16:9 gets `cover`-trimmed at the sides, and a burnt-in
   headline sitting in the left third is exactly what gets cut.

   So here the image is laid out at its natural ratio — full width, `height: auto`, nothing
   to crop against — and the slide takes its height from the picture. This is NOT `contain`:
   there is no box left over to letterbox inside, so no bars appear at any width. It also
   stays fluid on its own, which is what makes it responsive without a single breakpoint.

   The one thing it cannot fix: artwork this wide is SHORT on a phone. A 2.95:1 banner at
   390px wide is ~132px tall, and small print burnt into the pixels will not be legible at
   that size. The fix for that is a separate portrait crop of the artwork, not a CSS rule —
   cropping here would trade the whole headline away to enlarge part of it. */
.mi-hero-carousel--banner-only .mi-hero--banner {
  min-height: 0;
}
.mi-hero-carousel--banner-only .mi-hero-banner-img {
  height: auto;
}

.mi-hero-banner-link {
  display: block;
  width: 100%;
  height: 100%;
}
/* The banner is the link's entire content, so the focus ring has to sit inside it or it
   is drawn off-slide by the carousel's overflow. */
.mi-hero-banner-link:focus-visible {
  outline: 3px solid #fff;
  outline-offset: -5px;
}

/* Slides after the first hold their own height rather than collapsing: the track is a
   flex row, so every slide is already as tall as the tallest one. */
.mi-hero-title {
  margin-bottom: var(--mi-space-5);
  font-size: var(--mi-text-5xl);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.035em;
}

/* ---------- arrows ---------- */

.mi-hero-arrow {
  position: absolute;
  top: 50%;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(40px, 4vw, 52px);
  height: clamp(40px, 4vw, 52px);
  transform: translateY(-50%);
  border: 1px solid rgb(255 255 255 / 0.28);
  border-radius: 50%;
  background: rgb(255 255 255 / 0.12);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--mi-transition), border-color var(--mi-transition),
              transform var(--mi-transition), opacity var(--mi-transition);
}
.mi-hero-arrow:hover {
  background: rgb(255 255 255 / 0.22);
  border-color: rgb(255 255 255 / 0.5);
}
.mi-hero-arrow--prev { left: clamp(8px, 1.6vw, 24px); }
.mi-hero-arrow--next { right: clamp(8px, 1.6vw, 24px); }

/* At the ends the arrow dims but stays operable — it wraps round. Disabling it would
   strand a keyboard user who tabbed to it. */
.mi-hero-arrow[data-edge] { opacity: 0.45; }

/*
  ---------- arrows over finished artwork ----------

  The glass treatment above is built for the copy slides, which are dark navy: a translucent
  white disc with a white chevron reads immediately there. Over a BANNER it can disappear
  completely — the supplied artwork is mostly pale, and white-on-near-white leaves the
  chevron with essentially no contrast, so the control is present, focusable and clickable
  but invisible.

  Fixed the same way the dots already solve it: change the CONTROL, never the picture. The
  disc becomes an opaque white plate with a dark chevron and carries its own ring and
  shadow, so it separates from light and dark artwork alike without scrimming a band of the
  image — which would be retouching work the client supplied finished.

  Scoped to --banners so a carousel of copy slides keeps the glass arrows unchanged.
*/
.mi-hero-carousel--banners .mi-hero-arrow {
  background: rgb(255 255 255 / 0.94);
  border-color: rgb(10 15 40 / 0.10);
  color: var(--mi-ink);
  box-shadow: 0 0 0 1px rgb(8 12 28 / 0.06), 0 6px 20px rgb(8 12 28 / 0.22);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.mi-hero-carousel--banners .mi-hero-arrow:hover {
  background: #fff;
  border-color: rgb(10 15 40 / 0.18);
  box-shadow: 0 0 0 1px rgb(8 12 28 / 0.10), 0 10px 26px rgb(8 12 28 / 0.28);
}

/* ---------- dots ---------- */

.mi-hero-dots {
  position: absolute;
  left: 50%;
  bottom: clamp(12px, 1.8vw, 22px);
  z-index: 3;
  display: flex;
  gap: 9px;
  transform: translateX(-50%);
}

.mi-hero-dot {
  width: 9px;
  height: 9px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.32);
  /*
    A ring rather than a scrim. On a banner slide these sit directly on the artwork, which
    can be light anywhere, and a translucent white dot on a pale sky is invisible. Darkening
    a band of the image to fix that would be retouching artwork the client supplied
    finished; a shadow on the dot itself leaves the picture alone.
  */
  box-shadow: 0 0 0 1px rgb(8 12 28 / 0.28), 0 1px 3px rgb(8 12 28 / 0.45);
  cursor: pointer;
  transition: background var(--mi-transition), width var(--mi-transition);
}
.mi-hero-dot:hover { background: rgb(255 255 255 / 0.55); }

/* The active dot stretches into a pill rather than only changing colour — shape reads
   at a glance where a 9px colour shift does not. */
.mi-hero-dot[aria-selected="true"] {
  width: 26px;
  border-radius: var(--mi-radius-pill);
  background: #fff;
}

/*
  The dots sit on the slide, so something has to leave room under the last line of copy.
  On a copy slide that is the copy column's own bottom padding — the slide itself has no
  padding any more, because the panel has to reach the bottom edge of the hero for the
  split to read as two grounds rather than as a card.

  Doubled class: this overrides `.mi-hero-carousel .mi-hero`, same specificity, declared
  earlier — a single-class rule would lose on source order.
*/
.mi-hero-carousel .mi-hero.mi-hero { padding-bottom: 0; }

@media (max-width: 560px) {
  /* Arrows on top of a phone-width slide cover the copy. The band still swipes. */
  .mi-hero-arrow { display: none; }

  /*
    Except on a banner carousel, where there is no copy underneath them to cover — the
    slide is one finished picture — and stepping between whole banners is the point of the
    control. Tucked in tighter and a size down so they sit on the artwork rather than
    across it.
  */
  .mi-hero-carousel--banners .mi-hero-arrow {
    display: inline-flex;
    width: 38px;
    height: 38px;
    font-size: 22px;
  }
  .mi-hero-carousel--banners .mi-hero-arrow--prev { left: 8px; }
  .mi-hero-carousel--banners .mi-hero-arrow--next { right: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  /* Smooth scrolling IS the animation here; jumping straight to the slide is the
     reduced-motion equivalent, not a degraded one. Autoplay is disabled in site.js. */
  .mi-hero-track { scroll-behavior: auto; }
}

/*
  Carousel headlines are set smaller than the old single hero was.

  That hero hard-coded a <br> to control where "IT Training & Certification Programs"
  broke. Titles now come from the database and cannot carry layout, so the type has to
  be sized to wrap gracefully on its own — at the old 4.75rem the default title ran to
  four lines and pushed the buttons off the fold.
*/
.mi-hero-carousel .mi-hero h1,
.mi-hero-carousel .mi-hero-title {
  font-size: clamp(2.05rem, 1.15rem + 2.9vw, 3.75rem);
  line-height: 1.03;
  letter-spacing: -0.032em;
  text-wrap: balance;
}

/*
  Sized in px, not ch.

  `ch` resolves against the ELEMENT's font size, and this block is set at body size —
  so a "34ch" cap measured about 300px and forced the headline to stack one or two
  words per line. Only the headline is large; the box it lives in is not.

  Wider than it was: the panel is now a fixed 36% rather than an fr track sharing the
  row, so the copy column has real room and the headline can hold three lines at the
  size the layout is drawn for.
*/
.mi-hero-carousel .mi-hero-copy { max-width: 700px; }

/*
  The training page's sidebar, and a grid-item floor rather than anything to do with touch.

  `.mi-detail-aside` is a grid item of `.mi-detail` and itself a grid holding the side cards. A
  grid item's default `min-width: auto` refuses to shrink below its content's min-content size, so
  on the one training page whose sidebar carries the enquiry CTA the inner column resolved to
  354px inside a 272px track — and pushed the whole document 58px sideways at 320, 3px at 375. The
  other training pages were unaffected only because their sidebars hold narrower cards.

  `min-width: 0` is the standard release valve for exactly this. Measured before applying: with it
  the document overflow goes 58 -> 0, while `max-width: 100%` on the card leaves it at 58 — which
  is what identifies the cause as the auto floor rather than a width declaration.

  Not width-scoped: at desktop the track is wide enough that this is a no-op, and leaving it
  unconditional means the same class of bug cannot reappear when a longer card is added.
*/
.mi-detail-aside,
.mi-side-card { min-width: 0; }

/* ==================== touch ergonomics ====================

   Scoped to `max-width: 1023px` OR `(hover: none)`. 1023 is not an arbitrary number: it is the
   exact width at which this site already swaps the desktop nav for the hamburger, so everything
   below applies precisely when the mobile chrome is showing and never when the desktop layout is.
   The `(hover: none)` half then catches a touchscreen wider than that — a tablet in landscape,
   where the pointer is still a fingertip.

   A desktop window dragged narrower than 1024px gets these rules too, and that is correct rather
   than a leak: at that width it is already showing the mobile layout, drawer and all.

   The measured problem was never layout. A sweep of every page at 320, 375, 390, 412, 425, 768,
   1024 and 1440 found zero horizontal document overflow and zero elements past the viewport on the
   public site. What it did find was targets too small for a fingertip: 44px is the figure WCAG
   2.5.5 and both platform guidelines use, and several controls sat at 18-40px.

   THE TECHNIQUE, and why it is used instead of simply making things bigger: a transparent ::after
   box extends the hit area past the visible control without moving or resizing anything. The
   control keeps the size it was drawn at — which matters here because several of these sizes are
   deliberate, documented decisions (the banner carousel's 38px arrows, for one) — and the finger
   still gets 44px. Where a control is a plain button with no such constraint, it is simply given
   a 44px minimum instead, which is the honest fix. */
@media (max-width: 1023px), (hover: none) {

  /*
    The top bar's email and phone. 166x24 and 116x24 — the height is the line box, since the bar
    itself is only 40px tall and padding the links would grow it. The hit area is expanded instead,
    so the bar keeps its height and the two links become 44px targets that overlap nothing: they
    sit at the end of a flex row with space-5 between them.
  */
  .mi-topbar a { position: relative; }
  .mi-topbar a::after {
    content: "";
    position: absolute;
    /* Vertical only. A pseudo-element that sticks out sideways extends the scrollable width of
       every ancestor with visible overflow — which is the exact defect this pass exists to remove.
       The link is already 166px wide; it is the 24px height that fails, and -12px fixes that. */
    inset: -12px 0;
  }

  /*
    Carousel arrows. 38x38 on a banner slide and 40x40 elsewhere, and the 38 is deliberate — see
    the note at the 560px breakpoint, where copy-slide arrows are hidden entirely and banner ones
    are tucked in a size smaller so they sit on the artwork rather than across it. Expanding the
    hit area keeps that decision and still clears 44.
  */
  .mi-hero-arrow::after {
    content: "";
    position: absolute;
    /* The arrows sit inset from the slide edge, so a few pixels of horizontal reach cannot touch
       the viewport edge — unlike the links above, which sit hard against the container. */
    inset: -4px;
  }

  /*
    Carousel dots, and this is the one control that cannot fully reach 44.

    The dots are 9px wide, 9px apart. Widening the hit area horizontally past the 18px pitch would
    make adjacent targets overlap, so a tap meant for dot 2 would select dot 3 — worse than a small
    target, because it acts on the wrong one. Widening the GAP instead was tried and reverted: at
    22px the dot row spread far enough across a phone-width banner to sit over the artwork's own
    caption strip, which is a visible cost to the design for a control that already has two
    alternatives on touch (the band swipes, and the arrows step).

    So: the full 44px vertically, where a thumb is least accurate, and the pitch horizontally, where
    more would be actively harmful. 18x44 rather than 9x9. The dots keep their exact appearance.
  */
  .mi-hero-dot { position: relative; }
  .mi-hero-dot::after {
    content: "";
    position: absolute;
    inset: -18px -4px;
  }

  /*
    The small button, used by the training rail and the programme cards. 36px tall at
    padding 10px/20px. A plain button with nothing riding on its exact height, so it gets the
    minimum outright rather than an invisible box around it — a real 44px target beats a 36px one
    that merely responds to a wider tap.
  */
  .mi-btn--sm {
    min-height: 44px;
    padding-block: 12px;
  }

  /* The banner eyebrow is a LINK on a programme page — the route back to its vendor — and 20px
     tall. Only the linked form needs this; the plain <span> version is not a target. */
  a.mi-pagehead-eyebrow { position: relative; }
  a.mi-pagehead-eyebrow::after {
    content: "";
    position: absolute;
    inset: -13px 0;
  }

  /* 42px, two short. It is a lone control in the drawer head with room around it. */
  .mi-drawer-close {
    width: 44px;
    height: 44px;
  }

  /*
    .mi-btn is white-space: nowrap, which is right for a button sitting in a row of them — a
    wrapped label there looks like a mistake. On a FULL-WIDTH button it is the opposite: the
    training sidebar's "+977-01-5911984" button could not shrink below its text plus 40px of
    padding, so at 320px it measured 304px inside a 222px column and pushed the document 33px
    sideways. A block button already owns its line, so wrapping costs nothing.
  */
  .mi-btn--block {
    white-space: normal;
  }

  /*
    The contact page's email and phone, 27px tall as block links in a list. Hit area rather than
    padding, because the list's own rhythm is set by its gap and padding the anchors would double
    the spacing between rows.
  */
  .mi-contact-list a { position: relative; }
  .mi-contact-list a::after {
    content: "";
    position: absolute;
    inset: -9px 0;
  }

  /*
    Card titles — the programme name on a vendor listing, the exam name on a partner page. 22px
    tall, and a real target: it is the first thing a thumb goes for on a card even though the card
    also carries a "View details" button.

    inline-block with vertical padding rather than a ::after box, because these titles wrap. An
    absolutely positioned pseudo-element anchors to the first line box only, so on a two-line title
    it would cover the top line and miss the bottom one. inline-block grows the whole box, however
    many lines it runs to, and the card body is a flex column with a gap — so the card gets taller
    and nothing collides.
  */
  .mi-card-title a {
    display: inline-block;
    padding-block: 11px;
  }
  .mi-card-title { margin-block: -11px; }
}

/*
  The gallery banner's stat labels, at 11px. Tracked out and uppercase, which reads at a desktop
  viewing distance but not on a phone held at arm's length, and 12px is the floor worth holding to.
  Width-scoped rather than touch-scoped, and at the same 1023px boundary as the block above: this
  is about how small the type is on a narrow screen, which a tablet and a narrow desktop window
  share with a phone.
*/
@media (max-width: 1023px) {
  .mi-gal-stat dt { font-size: 0.75rem; }
}

/* ---------- the banner's <picture> wrapper ----------

   <picture> is display: inline by default, which would put the banner on a text baseline and
   leave a few pixels of the slide's background showing beneath it. It carries no styling of its
   own beyond matching the box the <img> used to occupy directly. */
.mi-hero-banner-picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* In banner-only mode the image is laid out at its natural ratio and the slide takes ITS height,
   so the wrapper must not impose one — otherwise the portrait crop below 400px would be measured
   against a box sized for the 2.95:1 desktop artwork. */
.mi-hero-carousel--banner-only .mi-hero-banner-picture {
  height: auto;
}
