/* ==========================================================================
   Mirai Minds — Global Stylesheet
   Pure CSS · Mobile-first · Premium
   Brand: #BC5A1B (Terracotta) — official Mirai Minds palette
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand — official Mirai Minds guidelines (We Design Global, May 2024)
     Primary: #BC5A1B terracotta · #EB940E marigold · #A5C3C2 sage
     Secondary: #EFDC95 soft gold · #EFEEE4 cream */
  --brand:        #BC5A1B;  /* Terracotta — primary, every primary CTA */
  --brand-dark:   #A34D16;
  --brand-darker: #843E11;
  --brand-tint:   #F7E7DA;
  --brand-tint-2: #FCF4EC;

  /* Accents — one job each */
  --accent-sun:   #EB940E;  /* Marigold — stars, badges, small highlights only */
  --accent-sage:  #A5C3C2;  /* Sage — calm surfaces, secondary chips */
  --sage-deep:    #4E7C7A;  /* Sage, dark enough for text on cream (AA) */
  --sage-tint:    #E4EDEC;
  --accent-yellow:#EFDC95;  /* Soft gold — warm section tint */
  --gold-tint:    #F9F3DC;

  /* Neutrals — warm, on the brand cream */
  --ink:      #241A12;   /* near-black, warm */
  --ink-soft: #3D3630;
  --muted:    #6E655B;   /* neutral warm gray, 4.9:1 on cream */
  --line:     #E6E0D2;
  --surface:  #FFFFFF;
  --canvas:   #EFEEE4;   /* brand cream — tinted sections */
  --canvas-2: #F7F5EC;   /* lighter cream — page ground, placeholders */

  /* Typography — official Mirai Minds guidelines
     Headings: Nunito · Body: Open Sans (self-hosted, assets/css/fonts.css) */
  --font-head: 'Nunito', 'Noto Sans JP', system-ui, -apple-system, sans-serif;
  --font-body: 'Open Sans', 'Noto Sans JP', system-ui, -apple-system, sans-serif;
  --measure: 62ch;

  /* Radii — one system: inputs and chips 12, cards 20, panels 28, buttons pill */
  --r-sm: 12px;
  /* The vertical rhythm of a band, and of the taller "photo" bands. Every
     section's padding reads one of these two, so the whole site's density is
     these two lines rather than a number repeated down the file. The old
     values (104px and 120px) put 208px of nothing at every seam and ran the
     home page to ten screens on a desktop and fifteen on a phone. */
  --band:    clamp(44px, 5.4vw, 72px);
  --band-lg: clamp(52px, 6.4vw, 88px);
  --r-md: 16px;
  --r-lg: 20px;
  --r-xl: 28px;
  --r-pill: 999px;

  /* Shadows — warm-tinted, never blue-black */
  --shadow-xs: 0 1px 2px rgba(60, 30, 10, .05);
  --shadow-sm: 0 4px 14px rgba(60, 30, 10, .07);
  --shadow-md: 0 12px 32px rgba(60, 30, 10, .10);
  --shadow-lg: 0 26px 60px rgba(60, 30, 10, .14);
  --shadow-brand: 0 12px 28px rgba(188, 90, 27, .28);

  /* Motion */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --fast: .18s;
  --base: .32s;

  /* Layout */
  --wrap: 1200px;
  --gutter: clamp(20px, 5vw, 48px);
  --header-h: 72px;
}

/* --------------------------------------------------------------------------
   2. Reset & Base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

/* The horizontal clamp lives here, on <html>, and must not be moved to <body>.

   overflow-x: hidden on body alone is the trap: with overflow-y left unset it
   computes to `hidden auto`, and a box whose two axes differ stops being
   "visible" — body turns into its own scroll container. Two things then go
   wrong. It takes scrolling away from the viewport (window.scrollTo silently
   does nothing), and, wherever the browser draws classic scrollbars rather
   than overlay ones, body reserves a vertical scrollbar gutter inside the
   page. That reserved strip is the empty band down the right-hand side, and it
   appears from the first section tall enough to need scrolling — which on this
   page is the Classes row, making it look like a fault in the card grid.

   On <html> the same declaration clamps the page without creating a nested
   scroller, so the viewport keeps the scroll and no gutter is reserved. */
html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--header-h) + 20px);
  overflow-x: hidden;
}

/* Freezes the page behind the open mobile drawer. Set on <html> by
   components/Header.jsx, and it has to be <html> — on <body> it does nothing
   here, for the same propagation reason the note above describes.

   The root element's overflow is the one handed to the viewport. Because the
   rule above leaves html at `hidden auto` rather than `visible`, html has
   already claimed that propagation, and body's overflow is no longer forwarded
   anywhere. Setting `overflow: hidden` on body therefore fails twice over: the
   viewport keeps its own overflow-y and the page scrolls on regardless, and
   body — now a box whose axes differ — becomes its own scroll container, which
   moves .site-header's nearest scrolling ancestor from the viewport to body.
   A sticky element resolves against that ancestor's scrollport, and body's is
   never scrolled, so the header would leave the screen the instant the drawer
   opened: measured at top: -1500px on a page scrolled to 1500.

   On <html> the declaration reaches the viewport, so scrolling actually stops
   and the header goes on sticking to the viewport as it always did. Both axes
   go hidden, which keeps the horizontal clamp above intact. */
html.is-nav-open { overflow: hidden; }

body {
  font-family: var(--font-body);
  color: var(--ink-soft);
  /* Cream, not white. The brand palette carries a page ground (--canvas-2)
     and a card ground (--surface); shipping white here spent neither and made
     the page read as software rather than as a school. Section 18 inverts the
     pair everywhere else to match: cards and the sections that used to be
     "tinted" are the white ones now. */
  background: var(--canvas-2);
  line-height: 1.6;
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-width: 100%; }

a { color: inherit; text-decoration: none; }

button { font: inherit; cursor: pointer; border: none; background: none; }

ul { list-style: none; padding: 0; }

h1, h2, h3, h4 {
  font-family: var(--font-head);
  color: var(--ink);
  line-height: 1.12;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
p { text-wrap: pretty; }

:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection { background: var(--brand); color: #fff; }

/* --------------------------------------------------------------------------
   3. Layout Helpers
   -------------------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--band); }

.section--tint { background: var(--canvas); }

.section-head {
  max-width: 640px;
  /* 52px under a heading, on top of the band's own padding, was a second gap
     doing the same job as the first. */
  margin-bottom: clamp(22px, 3vw, 36px);
}

.section-head--center {
  margin-inline: auto;
  text-align: center;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .8rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--brand-dark);
  background: var(--brand-tint);
  padding: 7px 14px;
  border-radius: var(--r-pill);
  margin-bottom: 18px;
}

.section-title {
  font-size: clamp(1.8rem, 3.6vw, 2.6rem);
  margin-bottom: 12px;
}

.section-sub {
  font-size: clamp(1rem, 1.4vw, 1.1rem);
  color: var(--muted);
  max-width: var(--measure);
  margin-inline: auto;
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--brand);
  --btn-fg: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 48px;
  padding: 13px 24px;
  border-radius: var(--r-pill);
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .98rem;
  letter-spacing: -0.01em;
  background: var(--btn-bg);
  color: var(--btn-fg);
  box-shadow: var(--shadow-xs);
  transition: transform var(--fast) var(--ease),
              box-shadow var(--base) var(--ease),
              background var(--base) var(--ease);
  white-space: nowrap;
}

.btn svg { width: 19px; height: 19px; }

.btn--primary {
  --btn-bg: var(--brand);        /* Terracotta — the one CTA colour */
  --btn-fg: #fff;
  box-shadow: var(--shadow-brand);
}
.btn--primary:hover {
  background: var(--brand-dark);
  transform: translateY(-2px);
  box-shadow: 0 16px 34px rgba(188, 90, 27, .36);
}
.btn:active { transform: translateY(1px) scale(.98); box-shadow: var(--shadow-xs); }

.btn--secondary {
  --btn-bg: #fff;
  --btn-fg: var(--brand-dark);
  border: 1.5px solid var(--brand-tint);
}
.btn--secondary:hover {
  border-color: var(--brand);
  color: var(--brand-dark);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}

.btn--line {
  --btn-bg: var(--accent-sage);   /* Sage — the quiet secondary, never a CTA */
  --btn-fg: var(--ink);
  box-shadow: 0 10px 24px rgba(165, 195, 194, .30);
}
.btn--line:hover {
  --btn-bg: #8FB0AF;
  transform: translateY(-2px);
  box-shadow: 0 14px 30px rgba(165, 195, 194, .38);
}

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  padding-inline: 6px;
  min-height: 44px;
}
.btn--ghost:hover { color: var(--brand-dark); }

.btn--lg { min-height: 54px; padding: 16px 30px; font-size: 1.05rem; }
.btn--block { width: 100%; }

.btn .arrow { transition: transform var(--base) var(--ease); }
.btn:hover .arrow { transform: translateX(4px); }

/* --------------------------------------------------------------------------
   5. Header
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(255, 255, 255, .92);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--base), box-shadow var(--base), background var(--base);
}
/* site-effects.js adds .is-scrolled once the page moves; the bar firms up. */
.site-header.is-scrolled {
  background: rgba(255, 255, 255, .96);
  border-bottom-color: var(--line);
  box-shadow: var(--shadow-sm);
}

/* Logged in, WordPress's own admin bar (32px, 46px under 782px) sits above
   the page; without these offsets the sticky header slides underneath it on
   scroll. Visitors never load these selectors — body.admin-bar only exists
   for logged-in users. */
body.admin-bar .site-header { top: 32px; }
@media (max-width: 782px) {
  body.admin-bar .site-header { top: 46px; }
}

.nav {
  display: flex;
  align-items: center;
  /* Logo, pill and a 46px menu button have to fit a 360px phone. */
  gap: clamp(12px, 4vw, 20px);
  height: var(--header-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--ink);
  letter-spacing: -0.02em;
  margin-right: auto;
  /* The logo image is 38px; the link around it is still a 44px tap target. */
  min-height: 44px;
  min-width: 0;
}
.brand__mark {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border-radius: 12px;
  background: #fff;
  border: 1px solid var(--line);
  box-shadow: var(--shadow-xs);
}
.brand__mark svg { width: 30px; height: 30px; }
.brand small {
  display: block;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: .64rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--brand-dark);
}

/* Real logo image — navbar */
.brand__logo {
  height: 44px;
  width: auto;
  display: block;
  object-fit: contain;
}

/* Real logo image — footer (slightly larger) */
.brand__logo--footer {
  height: 52px;
  filter: none;
}


.nav__links {
  display: none;
  align-items: center;
  gap: 4px;
}
.nav__links a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 13px;
  border-radius: var(--r-pill);
  font-weight: 500;
  font-size: .96rem;
  line-height: 1;
  white-space: nowrap;
  color: var(--ink-soft);
  transition: color var(--fast), background var(--fast);
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 15px; right: 15px; bottom: 6px;
  height: 2px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--base) var(--ease);
  border-radius: 2px;
}
.nav__links a:hover { color: var(--brand-dark); }
.nav__links a:hover::after,
.nav__links a[aria-current="page"]::after { transform: scaleX(1); }
.nav__links a[aria-current="page"] { color: var(--brand-dark); }

.nav__cta {
  display: none;
  align-items: center;
  gap: 10px;
}

.nav__toggle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 46px; height: 46px;
  /* A flex item shrinks by default: on a 360px phone the logo and the
     "Find a program" pill squeezed the button to 36px wide. */
  flex-shrink: 0;
  border-radius: 12px;
  border: 1.5px solid var(--line);
  padding: 0 11px;
  margin-left: auto;
}
.nav__toggle span {
  height: 2px; width: 100%;
  background: var(--ink);
  border-radius: 2px;
  transition: transform var(--base) var(--ease), opacity var(--fast);
}
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Mobile drawer: full height, cream, the CTA pinned at the bottom. */
.mobile-menu {
  position: fixed;
  inset: var(--header-h) 0 0 0;
  background: var(--canvas-2);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  padding: 18px var(--gutter) max(28px, env(safe-area-inset-bottom));
  display: grid;
  align-content: start;
  gap: 6px;
  overflow-y: auto;
  transform: translateY(-12px);
  opacity: 0;
  visibility: hidden;
  transition: transform var(--base) var(--ease), opacity var(--base), visibility var(--base);
  z-index: 99;
}
.mobile-menu.is-open { transform: translateY(0); opacity: 1; visibility: visible; }
.mobile-menu a {
  padding: 14px 12px;
  border-radius: var(--r-md);
  font-family: var(--font-head);
  font-weight: 500;
  font-size: 1.1rem;
  color: var(--ink);
  min-height: 44px;
  display: flex;
  align-items: center;
}
.mobile-menu a:hover { background: var(--brand-tint); color: var(--brand-dark); }
.mobile-menu__cta { display: grid; gap: 10px; margin-top: auto; padding-top: 18px; }

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  padding-top: clamp(36px, 5vw, 64px);
  padding-bottom: clamp(48px, 7vw, 88px);
  background:
    radial-gradient(70% 120% at 85% -10%, var(--brand-tint) 0%, transparent 55%),
    radial-gradient(50% 80% at 5% 10%, var(--gold-tint) 0%, transparent 50%),
    var(--canvas-2);
  overflow: hidden;
}
/* The crane pattern, faint, behind the copy column only. 1.5.10: opacity
   lowered from .35 so the mark reads as a watermark; size and fade as before. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0 55% 0 0;
  background: url(../images/pattern-crane.svg) 0 0 / 320px 320px repeat;
  opacity: .2;
  -webkit-mask-image: radial-gradient(80% 90% at 20% 30%, #000 20%, transparent 75%);
  mask-image: radial-gradient(80% 90% at 20% 30%, #000 20%, transparent 75%);
  pointer-events: none;
}
.hero .wrap { position: relative; }

.hero__grid {
  display: grid;
  gap: clamp(40px, 5vw, 64px);
  align-items: center;
}

.hero__badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 16px 8px 8px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-sm);
  font-size: .85rem;
  font-weight: 500;
  color: var(--ink-soft);
  margin-bottom: 24px;
}
.hero__badge b { color: var(--brand-dark); }
.hero__badge .pill {
  display: inline-grid; place-items: center;
  padding: 4px 10px;
  font-size: .75rem; font-weight: 700;
  letter-spacing: .04em;
  color: #fff;
  background: var(--accent-sun);
  border-radius: var(--r-pill);
}

.hero__title {
  font-size: clamp(2.2rem, 4.6vw, 3.5rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  margin-bottom: 18px;
}
/* Flipped: the sentence carries the brand gradient, the accent words sit in
   ink so the promise ("confident global citizens") is the darkest thing on
   the screen. Gradient is painted per line via the inline spans. */
.hero__title {
  background: linear-gradient(120deg, var(--brand), var(--accent-sun));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero__title .grad {
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  -webkit-text-fill-color: var(--ink);
  color: var(--ink);
}

.hero__sub {
  font-size: clamp(1.05rem, 1.5vw, 1.2rem);
  color: var(--ink-soft);
  max-width: 30em;
  margin-bottom: 28px;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 22px;
  margin-bottom: 12px;
}
/* The quiet second option: a text link, not a second button. */
.hero__alt {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-head);
  font-weight: 600;
  color: var(--brand-dark);
  text-decoration: underline;
  text-decoration-color: var(--brand-tint);
  text-underline-offset: 5px;
  text-decoration-thickness: 2px;
  transition: text-decoration-color var(--fast);
}
.hero__alt:hover { text-decoration-color: var(--brand); }
/* The three facts that decide the click, directly under the button. A list of
   short chips rather than a sentence: at hero size these are read at a glance,
   and a prose line at the same spot was skipped entirely. It carries the gap
   down to the rating strip that .hero__actions used to hold. */
.hero__proof {
  display: flex; flex-wrap: wrap;
  gap: 8px 20px;
  margin-bottom: 34px;
}
.hero__proof li {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink-soft);
  font-size: .92rem;
}
.hero__proof svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--brand); }

.hero__meta,
.trust__families {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}
.trust__families small { color: var(--muted); font-size: .9rem; display: block; }
.trust__families strong { color: var(--ink); }
.trust__families .stars { font-size: .9rem; line-height: 1; }
.hero__avatars { display: flex; }
.hero__avatars span {
  width: 42px; height: 42px;
  border-radius: 50%;
  border: 3px solid #fff;
  margin-left: -12px;
  background-size: cover;
  background-position: center;
  box-shadow: var(--shadow-xs);
}
.hero__avatars span:first-child { margin-left: 0; }
.hero__meta small { color: var(--muted); font-size: .9rem; }
.hero__meta strong { color: var(--ink); }
.stars { color: var(--accent-sun); letter-spacing: 2px; }
.hero__avatars span { width: 38px; height: 38px; margin-left: -10px; }

/* Hero visual */
.hero__visual { position: relative; }
.hero__photo {
  position: relative;
  aspect-ratio: 4 / 4.4;
  border-radius: var(--r-xl);
  background:
    url(../images/pattern-crane.svg) center / 240px 240px repeat,
    linear-gradient(160deg, rgba(188,90,27,.10), rgba(165,195,194,.22)),
    var(--canvas-2);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  display: grid;
  place-items: center;
  color: var(--brand-dark);
}
.hero__photo .ph-icon { position: absolute; top: 40%; opacity: .35; width: 64px; height: 64px; }

/* Laid over the "Image Coming Soon" box exactly as .class-card__img is: z-index
   1 covers the placeholder at 0 and stays under the floating cards at 3. The
   source is 3:2 and the box is 4:4.4, so object-fit crops to the tall frame. */
.hero__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__card {
  position: absolute;
  background: #fff;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  padding: 14px 18px;
  display: flex;
  align-items: center;
  gap: 12px;
  animation: float 5s var(--ease) infinite;
}
.hero__card .ico {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border-radius: 12px;
  color: #fff;
}
.hero__card b { display: block; color: var(--ink); font-family: var(--font-head); font-size: 1.05rem; }
.hero__card small { color: var(--muted); font-size: .78rem; }

.hero__card--tl { top: 26px; left: -14px; }
.hero__card--tl .ico { background: var(--brand); }
.hero__card--br { bottom: 30px; right: -14px; animation-delay: 1.4s; }
.hero__card--br .ico { background: var(--accent-sun); }

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-12px); }
}

/* --------------------------------------------------------------------------
   7. Trust Bar
   -------------------------------------------------------------------------- */
.trust {
  border-block: 1px solid var(--line);
  background: var(--surface);
}
.trust__inner {
  display: grid;
  gap: 22px;
  padding-block: 26px;
  align-items: center;
}
.trust__lead {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 34px;
  align-items: center;
}
.trust__stat { display: flex; align-items: baseline; gap: 10px; }
.trust__stat b {
  font-family: var(--font-head);
  font-size: 2rem;
  font-weight: 800;
  line-height: 1;
  color: var(--brand);
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; /* stops the width jumping while it counts up */
}
/* The label used to be .86rem of --muted at default weight, which read thin
   and washed out beside a number twice its size. It now sits in the heading
   face at semibold in full --ink, so the two parts look like one designed
   unit rather than a figure with a caption trailing after it.

   Baseline alignment on the parent, not center: these are two runs of text on
   the same line, and centring left the label floating slightly high against
   the digits. */
.trust__stat small {
  font-family: var(--font-head);
  color: var(--ink);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .005em;
}
.trust__stat .divider { width: 1px; height: 34px; background: var(--line); }

/* Logo rail — slides sideways in a seamless loop */
.trust__rail {
  position: relative;
  min-width: 0;          /* lets the rail shrink inside its grid track */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.trust__track {
  display: flex;
  width: max-content;
  animation: rail-scroll 30s linear infinite;
}
.trust__rail:hover .trust__track,
.trust__rail:focus-within .trust__track { animation-play-state: paused; }

/* The track holds 4 identical groups and travels exactly 2 of them,
   so the loop restarts on a matching frame with no visible gap. */
@keyframes rail-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Partners rail (home "Our Trusted Education Partners"): the same seamless
   loop as .trust__rail, full-bleed so the marks enter and leave at the screen
   edge, with the name under each mark as the static row had. Slower than the
   schools rail because each item is taller and carries a caption. */
.partners { padding: 40px 0 80px; }
.partners__rail {
  position: relative;
  min-width: 0;
  overflow: hidden;
  margin-top: 8px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.partners__track {
  display: flex;
  width: max-content;
  animation: rail-scroll 40s linear infinite;
}
.partners__rail:hover .partners__track,
.partners__rail:focus-within .partners__track { animation-play-state: paused; }
.partners__row {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  gap: 80px;
  padding-right: 80px;
  flex: 0 0 auto;
}
.partners__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  flex: 0 0 auto;
}
.partners__mark {
  width: 170px;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.partners__mark img {
  max-height: 60px;
  max-width: 160px;
  width: auto;
  height: auto;
  object-fit: contain;
}
.partners__name {
  font-size: 1.1rem;
  font-weight: 700;
  margin: 0;
  color: var(--brand);
  white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  .partners__track { animation: none; flex-wrap: wrap; width: auto; justify-content: center; }
  .partners__row[aria-hidden="true"] { display: none; }
  .partners__row { flex-wrap: wrap; justify-content: center; padding-right: 0; }
}

.logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px 34px;
  opacity: .78;
}
.logos span {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--muted);
  letter-spacing: -0.01em;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.logos span svg { width: 20px; height: 20px; color: var(--brand); }

/* Inside the rail the logos stay on one line and repeat at an even spacing */
.trust__track .logos {
  flex-wrap: nowrap;
  justify-content: flex-start;
  padding-right: 34px;
}
.trust__track .logos span { white-space: nowrap; }

/* School and partner marks in the rail: one fixed height so the row reads as
   a line, width free so a wide wordmark and a square crest both fit. */
.trust__track .logos .trust__logo {
  display: inline-flex;
  align-items: center;
  height: 44px;
}
.trust__track .logos .trust__logo img {
  height: 100%;
  width: auto;
  max-width: 160px;
  object-fit: contain;
  /* A mark supplied on a white card blends into the cream strip instead of
     sitting in a white box; a transparent mark is unaffected. */
  mix-blend-mode: multiply;
}

/* --------------------------------------------------------------------------
   Banner pop-up — a camp, a discount, an event. A published Banners record
   (inc/promo.php); shows once per visitor per banner, again after an edit.
   -------------------------------------------------------------------------- */
.promo {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgba(30, 20, 14, .55);
  backdrop-filter: blur(2px);
}
.promo[hidden] { display: none; }
.promo__card {
  position: relative;
  width: min(520px, 100%);
  background: var(--canvas, #fff);
  border-radius: 20px;
  padding: 36px 32px 28px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .25);
  text-align: center;
}
.promo__card img {
  display: block;
  width: 100%;
  max-height: 220px;
  object-fit: cover;
  border-radius: 12px;
  margin-bottom: 20px;
}
.promo__eyebrow {
  display: inline-block;
  font-size: .8rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--brand);
  margin-bottom: 10px;
}
.promo__card h2 {
  font-family: var(--font-head);
  font-size: clamp(1.4rem, 3vw, 1.9rem);
  line-height: 1.15;
  margin: 0 0 12px;
}
.promo__card p { margin: 0 0 22px; color: var(--ink-soft, var(--ink)); }
.promo__close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink);
}
.promo__close:hover { background: rgba(0, 0, 0, .06); }
/* Bars: pinned to one edge, no backdrop, the card becomes a strip. */
.promo--top, .promo--bottom {
  inset: auto 0;
  display: block;
  padding: 0;
  background: none;
  backdrop-filter: none;
}
.promo--top { top: 0; }
.promo--bottom { bottom: 0; }
.promo--top .promo__card, .promo--bottom .promo__card {
  width: 100%;
  border-radius: 0;
  padding: 14px 56px 14px 20px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
  text-align: center;
}
.promo--top .promo__card img, .promo--bottom .promo__card img { display: none; }
.promo--top .promo__custom, .promo--bottom .promo__custom {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.promo--top .promo__custom > *, .promo--bottom .promo__custom > * { margin: 0; }
.promo--top .promo__custom h2, .promo--bottom .promo__custom h2 { font-size: 1.1rem; }
.promo--top .promo__custom .wp-block-buttons, .promo--bottom .promo__custom .wp-block-buttons { margin-top: 0; }
.promo--top .promo__close, .promo--bottom .promo__close { top: 50%; transform: translateY(-50%); }

/* Colours */
.promo--brand .promo__card { background: var(--brand); color: #fff; }
.promo--brand .promo__card h2, .promo--brand .promo__card p, .promo--brand .promo__close, .promo--brand .promo__eyebrow { color: #fff; }
.promo--brand .promo__custom .wp-block-button__link { background: #fff; color: var(--brand); }
.promo--dark .promo__card { background: #1e1611; color: #fff; }
.promo--dark .promo__card h2, .promo--dark .promo__card p, .promo--dark .promo__close { color: #fff; }

.promo__custom { text-align: left; }
.promo__custom .wp-block-buttons { margin-top: 18px; }
.promo__custom .wp-block-button__link { background: var(--brand); color: #fff; border-radius: 999px; padding: 12px 22px; text-decoration: none; display: inline-block; }
.promo__custom img { max-width: 100%; height: auto; border-radius: 12px; }
.promo__custom > :first-child { margin-top: 0; }
.promo__custom > :last-child { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   8. Feature Cards (Why Mirai Minds)
   -------------------------------------------------------------------------- */
.grid-3 {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
}
/* Five program cards on the three-column grid left a hole where the sixth
   would be. A wrapping row with the same column counts centres a short last
   row instead. The four-up guide keeps its grid; phones keep the rail. */
@media (min-width: 640px) {
  .grid-3:not(.grid-3--four) { display: flex; flex-wrap: wrap; justify-content: center; }
  .grid-3:not(.grid-3--four) > * { flex: 0 0 calc((100% - 24px) / 2); min-width: 0; }
}
@media (min-width: 760px) {
  .grid-3:not(.grid-3--four) > * { flex-basis: calc((100% - 32px) / 3); }
}
@media (min-width: 900px) {
  .grid-3:not(.grid-3--four) > * { flex-basis: calc((100% - 48px) / 3); }
}

.feature-card {
  position: relative;
  background: linear-gradient(135deg, var(--brand), var(--accent-sun));
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  padding: 34px 30px;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease), border-color var(--base);
  overflow: hidden;
}
.feature-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--base) var(--ease);
}
.feature-card:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-md);
  border-color: transparent;
}
.feature-card:hover::before { transform: scaleX(1); }

.feature-card__ico {
  width: 62px; height: 62px;
  display: grid; place-items: center;
  border-radius: 18px;
  background: rgba(255, 255, 255, .18);
  color: #fff;
  margin-bottom: 22px;
}
.feature-card__ico svg { width: 30px; height: 30px; }

.feature-card h3 { font-size: 1.3rem; margin-bottom: 10px; color: #fff; }
.feature-card p { color: rgba(255, 255, 255, .88); font-size: .98rem; }

/* --------------------------------------------------------------------------
   9. Class Cards
   -------------------------------------------------------------------------- */
.class-grid {
  display: grid;
  gap: 26px;
  grid-template-columns: 1fr;
}

.class-card {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}

.class-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }

.class-card__media {
  position: relative;
  aspect-ratio: 16 / 10;
  background: linear-gradient(150deg, var(--brand-tint), var(--canvas-2));
  display: grid;
  place-items: center;
  color: var(--brand-dark);
  overflow: hidden;
}
.class-card__media svg { width: 54px; height: 54px; opacity: .45; }

/* The real photo, when a class record carries one. Absolute + inset:0 rather
   than a plain in-flow img for two reasons: it has to cover the "Image Coming
   Soon" placeholder that ::after paints over this same box, and it has to fill
   the 16/10 frame regardless of the file's own ratio (the supplied files are
   4:3). Because the box is forced to a size here, object-fit: cover does real
   work — it crops the overflow instead of letting the image stretch.
   z-index 1 is the whole point: above the placeholder at 0, below the tag and
   flag at 2. */
.class-card__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--base) var(--ease);
}
/* The one piece of movement the cards keep. It happens inside the media frame,
   which is overflow:hidden, so nothing shifts in the page layout and no card
   moves against its neighbors — the reason the lift was removed. */
.class-card:hover .class-card__img { transform: scale(1.04); }

@media (prefers-reduced-motion: reduce) {
  .class-card__img,
  .class-card:hover .class-card__img { transition: none; transform: none; }
}

/* Same job on the class detail hero, minus the hover — nothing there is a
   link, so a zoom on hover would suggest one. */
.detail-hero__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.class-card:nth-child(2) .class-card__media { background: linear-gradient(150deg, #FBEED4, #F7E7DA); color: #C2760A; }
.class-card:nth-child(3) .class-card__media { background: linear-gradient(150deg, #E6EFEE, #EFEEE4); color: #4E7C7A; }

.class-card__body { padding: 26px 26px 28px; display: flex; flex-direction: column; flex: 1; }
.class-card__meta {
  display: flex; flex-wrap: wrap; gap: 8px 14px;
  color: var(--muted); font-size: .84rem; margin-bottom: 12px;
}
.class-card__meta span { display: inline-flex; align-items: center; gap: 6px; }
.class-card__meta svg { width: 15px; height: 15px; color: var(--brand); }
.class-card h3 { font-size: 1.32rem; margin-bottom: 10px; }
.class-card__body p { color: var(--muted); font-size: .95rem; margin-bottom: 20px; }
.class-card__foot {
  margin-top: auto;
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 18px; border-top: 1px solid var(--line);
}
.class-card__price { font-family: var(--font-head); font-weight: 700; color: var(--ink); }
/* "/ session" sits beside the fee rather than under it: on its own line it
   made a two-line block out of four characters. */
.class-card__price small { font-weight: 400; color: var(--muted); font-size: .75rem; }
.class-card__link {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-head); font-weight: 600; font-size: .92rem;
  color: var(--brand-dark);
  min-height: 44px;
}
.class-card__link svg { width: 16px; height: 16px; transition: transform var(--base) var(--ease); }
.class-card__link:hover svg { transform: translateX(4px); }

/* Booking footer (Book a Trial listing) — the same bordered footer, stacked
   instead of split, so the button gets the card's full width and reads as the
   one thing to do. */
.class-card__foot--book {
  display: block;
  text-align: center;
}
.class-card__next {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-bottom: 14px;
  color: var(--ink-soft); font-size: .89rem;
}
.class-card__next b { font-family: var(--font-head); font-weight: 700; color: var(--ink); }
.class-card__next svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--brand); }

/* The quiet second option. Deliberately plain text under the button: the card
   already has one action, and a second of equal weight is exactly the choice
   this listing exists to remove. */
.class-card__aside {
  display: inline-block;
  margin-top: 12px; padding: 6px 4px;
  color: var(--muted); font-size: .88rem;
}
.class-card__aside:hover { color: var(--brand-dark); text-decoration: underline; text-underline-offset: 3px; }

/* --------------------------------------------------------------------------
   10. Testimonials
   -------------------------------------------------------------------------- */
/* The three cards share one set of row heights, so the quote mark, the quote
   and the author block each start on the same line across all three.

   grid-template-rows defines those rows once: mark, quote, author. Each card
   inherits them via `grid-row: span 3` + `subgrid`, so a short quote in card
   three no longer pulls its author block up higher than the others.

   All three rows are auto, not `auto 1fr auto`. With 1fr the quote row grew
   to fill whatever height the tallest card had spare, which opened a band of
   empty space between every quote and the author beneath it. auto sizes the
   row to the longest quote and no further, so the cards stay level and the
   spacing is the margin below the blockquote, nothing more. */
.testi-grid {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
  grid-template-rows: auto auto auto;
  align-items: stretch;
}
.testi-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 32px 30px;
  box-shadow: var(--shadow-xs);
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
/* The glyph's own box is much taller than the mark looks, so its visual gap to
   the quote reads larger than the 8px suggests. height caps the box to what is
   actually visible. */
.testi-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
.testi-card .quote-mark {
  font-family: var(--font-head); font-size: 3.4rem; line-height: .6;
  color: var(--brand); opacity: .22;
  height: 26px; margin-bottom: 0;
}
/* The middle subgrid row is the <blockquote>, not the <p> inside it. Styling
   only the <p> left the blockquote as an unsized box that did not fill its
   row, which is what let the author blocks drift to different heights. The
   margin belongs on the blockquote for the same reason. */
.testi-card blockquote { margin: 0 0 24px; }
.testi-card p { color: var(--ink-soft); font-size: 1.02rem; margin: 0; }
/* align-items:start pins the avatar to the top of the author row. Centring it
   re-introduces an offset the moment one card's meta wraps to two lines and
   another's does not. */
.testi-card__author { display: flex; align-items: flex-start; gap: 13px; }
.testi-card__avatar {
  width: 50px; height: 50px; border-radius: 50%;
  background: linear-gradient(140deg, var(--brand), var(--accent-sun));
  display: grid; place-items: center; color: #fff;
  font-family: var(--font-head); font-weight: 700; font-size: 1.1rem;
  flex-shrink: 0;
}
.testi-card__author b { display: block; color: var(--ink); font-family: var(--font-head); }
.testi-card__author small { color: var(--muted); font-size: .84rem; }

/* --------------------------------------------------------------------------
   11. Seasonal Banner
   -------------------------------------------------------------------------- */
/* Graphic, not atmospheric — the counterpart to .cta-strip__box's soft
   treatment. A clean gradient face and one hard-edged brand mark, so the two
   dark terracotta blocks on this page read as different objects.

   Padding, radius and shadow are unchanged from the original: the box occupies
   exactly the space it did before. */
.banner {
  position: relative;
  isolation: isolate;
  border-radius: var(--r-xl);
  overflow: hidden;
  background:
    linear-gradient(115deg, var(--brand-darker) 0%, var(--brand) 62%, var(--accent-sun) 140%);
  color: #fff;
  padding: clamp(38px, 6vw, 68px);
  box-shadow: var(--shadow-lg);
}
/* The brand lockup in the top-right corner.

   Sized by WIDTH, with height following the file's own ratio. It used to be
   forced into a square box: the art is a wide horizontal lockup (bird plus
   wordmark), so fitting it to the square's width left most of the height empty
   and rendered the wordmark too small to read. Width-first uses the full box.

   No drop-shadow: at this size the lockup carries itself against the
   terracotta, and the shadow only read as a dark smudge under the wordmark. */
.banner__logo {
  position: absolute;
  top: clamp(14px, 1.8vw, 26px);
  right: clamp(16px, 2vw, 30px);
  width: clamp(150px, 17vw, 230px);
  height: auto;
  object-fit: contain;
  pointer-events: none;
  z-index: 3;
}

.banner__inner {
  position: relative;
  z-index: 2;
  display: grid;
  gap: 26px;
  align-items: center;
}
.banner .eyebrow { background: rgba(255,255,255,.18); color: #fff; }
.banner h2 { color: #fff; font-size: clamp(1.8rem, 4vw, 2.8rem); margin-bottom: 12px; }
.banner p { color: rgba(255,255,255,.9); max-width: 540px; }
.banner__cta { display: flex; flex-wrap: wrap; gap: 14px; }
.banner .btn--primary { --btn-bg: #fff; --btn-fg: var(--brand-darker); box-shadow: var(--shadow-md); }
.banner .btn--primary:hover { --btn-bg: #fff; background: #fff; }
.banner .btn--secondary { --btn-fg: #fff; background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.4); }
.banner .btn--secondary:hover { background: rgba(255,255,255,.2); border-color: #fff; }
/* Narrow screens: the panel is one column and the corner mark would sit on the
   eyebrow, so it shrinks and the eyebrow row is kept clear for it. Placed after
   the base .banner .eyebrow rule above — same specificity, so source order is
   what decides it. */
@media (max-width: 639.98px) {
  .banner__logo { width: 120px; height: auto; }
  .banner .eyebrow { max-width: calc(100% - 70px); }
}

/* --------------------------------------------------------------------------
   12a. Featured post + Latest rail (blog.html)
   The rail fills the same height as the featured post and scrolls on its own.
   -------------------------------------------------------------------------- */
.featured-grid {
  display: grid;
  gap: 26px;
  grid-template-columns: 1fr;
}

.feature-post {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
.feature-post:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
.feature-post__media {
  aspect-ratio: 16 / 10;
  background: linear-gradient(150deg, var(--canvas-2), var(--brand-tint));
}
/* Above the placeholder at 0, below the gradient overlay at 2 that the
   headline sits on — otherwise the title loses its contrast. */
.feature-post__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.feature-post__cat {
  position: absolute;
  top: 16px; left: 16px;
  z-index: 3;
  padding: 7px 14px;
  background: rgba(255,255,255,.92);
  backdrop-filter: blur(6px);
  border-radius: var(--r-pill);
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .78rem;
  color: var(--ink);
  box-shadow: var(--shadow-xs);
}
/* Dark scrim keeps the title readable whatever photo sits behind it */
.feature-post__overlay {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  padding: clamp(20px, 3.5vw, 32px);
  background: linear-gradient(to top, rgba(36,26,18,.94) 0%, rgba(36,26,18,.74) 55%, rgba(36,26,18,0) 100%);
  color: #fff;
}
.feature-post__overlay h2 {
  font-size: clamp(1.2rem, 2.5vw, 1.85rem);
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: #fff;
  margin-bottom: 14px;
}
.feature-post__link { color: inherit; }
.feature-post__link::after { content: ""; position: absolute; inset: 0; z-index: 4; }
.feature-post__meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 18px;
  font-size: .85rem;
  color: rgba(255,255,255,.84);
}
.feature-post__meta span { display: inline-flex; align-items: center; gap: 8px; }
.feature-post__meta svg { width: 15px; height: 15px; opacity: .8; }

.latest {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}
.latest__inner {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
}
.latest__title {
  flex-shrink: 0;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--brand-dark);
}
.latest__list { display: grid; gap: 8px; align-content: start; }

.latest-item {
  position: relative;
  display: grid;
  grid-template-columns: 74px 1fr;
  gap: 12px;
  padding: 8px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.latest-item:hover { background: var(--brand-tint-2); border-color: var(--line); }
.latest-item__media {
  aspect-ratio: 1 / 1;
  border-radius: var(--r-sm);
  background: var(--canvas-2);
  border: 1px dashed #D8CFC0;
}
/* The export shipped this as an empty dashed placeholder because its posts had
   no images. Real posts do, so the box holds the thumbnail when there is one
   and keeps the placeholder when there is not. */
.latest-item__media img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: inherit;
}
.latest-item__media:has(img) { border-style: solid; border-color: var(--line); }

.latest-item h3 {
  font-size: .9rem;
  line-height: 1.35;
  margin-bottom: 5px;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.latest-item small { color: var(--muted); font-size: .76rem; }

/* --------------------------------------------------------------------------
   12. Blog Cards — deliberately its own look, not the .class-card treatment:
   the photo is mounted inside the card, the read time rides the image, the
   category is a dot + label, and the footer is a byline + round arrow button.
   -------------------------------------------------------------------------- */
.blog-grid {
  display: grid;
  gap: 26px;
  grid-template-columns: 1fr;
}
.blog-card {
  position: relative;
  display: flex; flex-direction: column;
  padding: 12px;                 /* photo sits inset, rather than bleeding to the edge */
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-xl);    /* softer corner than the class cards */
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease), border-color var(--base);
}
.blog-card:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-md);
  border-color: var(--brand-tint);
}
.blog-card__media {
  position: relative;
  aspect-ratio: 16 / 11;
  border-radius: var(--r-md);
  overflow: hidden;
  background: linear-gradient(150deg, var(--canvas-2), var(--brand-tint));
}

/* Read time rides the bottom-right of the photo */
.blog-card__time {
  position: absolute;
  right: 10px; bottom: 10px;
  z-index: 2;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  background: rgba(36, 26, 18, .74);
  backdrop-filter: blur(6px);
  color: #fff;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .75rem;
}

/* The cover, laid over the placeholder exactly as .class-card__img is: z-index
   1 puts it above the "Image Coming Soon" box at 0 and below the read-time
   pill at 2. object-fit crops the 16:9 source to the card's 16:11 box. */
.blog-card__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--base) var(--ease);
}
/* Contained by the media frame's overflow:hidden, same as .class-card__img —
   the card itself stays put. */
.blog-card:hover .blog-card__img { transform: scale(1.04); }

@media (prefers-reduced-motion: reduce) {
  .blog-card__img,
  .blog-card:hover .blog-card__img { transition: none; transform: none; }
}

.blog-card__body { padding: 18px 12px 8px; display: flex; flex-direction: column; flex: 1; }

/* Category reads as a label with a dot, not a pill */
.blog-card__cat {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-head); font-weight: 700;
  font-size: .75rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--brand-dark);
  margin-bottom: 10px;
}
.blog-card__cat::before {
  content: "";
  width: 7px; height: 7px; flex-shrink: 0;
  border-radius: 50%;
  background: var(--brand);
}

.blog-card h3 { font-size: 1.2rem; margin-bottom: 9px; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.blog-card p { color: var(--muted); font-size: .93rem; margin-bottom: 20px;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* Just the date and the arrow — no divider rule, no byline */
.blog-card__foot {
  margin-top: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.blog-card__date { color: var(--muted); font-size: .82rem; }

.blog-card__go {
  width: 42px; height: 42px; flex-shrink: 0;
  display: grid; place-items: center;
  border: 1.5px solid var(--line);
  border-radius: 50%;
  color: var(--brand-dark);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.blog-card__go svg { width: 17px; height: 17px; transition: transform var(--base) var(--ease); }
.blog-card:hover .blog-card__go { background: var(--brand); border-color: var(--brand); color: #fff; }
.blog-card:hover .blog-card__go svg { transform: translateX(2px); }
/* Stretches the one link over the whole card, so the card is clickable
   but screen readers and keyboards still see a single clean link. */
.blog-card__go::after { content: ""; position: absolute; inset: 0; z-index: 3; }

/* --------------------------------------------------------------------------
   12b. Blog Article (blog-detail.html)
   Split reading view: the title, cover and share links stay pinned on the
   left while the article text scrolls past them on the right.
   -------------------------------------------------------------------------- */
/* One reading column, one sidebar. The text is the page; the rail beside it
   is for what to read next. Single column until there is room for both. */
.article-layout {
  display: grid;
  gap: 40px;
  /* minmax(0, …), not 1fr: a bare 1fr track never shrinks below its widest
     child, so one 1024px image or 640px embed in a post widened the reading
     column to 720px on a 360px phone and the page scrolled sideways. */
  grid-template-columns: minmax(0, 1fr);
  align-items: start;   /* required — a stretched item can never stick */
}

/* A title comes from a CMS, so its length is unknown: it wraps, and a single
   very long word breaks rather than pushing the column wide. */
.article-head h1 {
  font-size: clamp(1.9rem, 4vw, 2.7rem);
  letter-spacing: -0.03em;
  line-height: 1.2;
  margin: 12px 0 16px;
  overflow-wrap: break-word;
}
.article-head__meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 18px;
  color: var(--muted);
  font-size: .86rem;
}
.article-head__meta span { display: inline-flex; align-items: center; gap: 7px; }
.article-head__meta svg { width: 15px; height: 15px; color: var(--brand); }

/* Pictures inside the text. Drop a real <img> into .article__figure-media and
   the "Image Coming Soon" placeholder is covered automatically. */
.article__figure { margin-block: clamp(26px, 4vw, 38px); }
.article__figure-media {
  aspect-ratio: 16 / 9;
  border-radius: var(--r-lg);
}
.article__figure-media img {
  position: relative;
  z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover;
}
.article__figure figcaption {
  margin-top: 10px;
  font-size: .84rem;
  color: var(--muted);
  text-align: center;
}

/* Sidebar stack */
.article-side__inner { display: grid; gap: 20px; }

.article__body { max-width: 720px; overflow-wrap: break-word; }
.article__body > * + * { margin-top: 22px; }
.article__body p { color: var(--ink-soft); font-size: 1.05rem; line-height: 1.8; }
.article__body h2 {
  font-size: clamp(1.4rem, 2.4vw, 1.75rem);
  letter-spacing: -0.02em;
  margin-top: clamp(40px, 5vw, 56px);
}
.article__body h3 { font-size: 1.14rem; margin-top: 32px; }
.article__intro {
  font-size: 1.16rem;
  color: var(--ink-soft);
  line-height: 1.75;
}
.article__body ul { list-style: none; padding: 0; display: grid; gap: 12px; }
.article__body li {
  position: relative;
  padding-left: 30px;
  color: var(--ink-soft);
  font-size: 1.02rem;
  line-height: 1.7;
}
.article__body li::before {
  content: "";
  position: absolute;
  left: 6px; top: .65em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--muted);
}
/* Reading column stays uncoloured on purpose — a quote is set apart by space
   and a plain rule, not by the brand palette. Nothing here should compete
   with the text. */
.article__body blockquote {
  margin-block: clamp(30px, 4vw, 42px);
  padding-left: 26px;
  border-left: 3px solid var(--line);
  font-size: 1.05rem;
  line-height: 1.8;
  color: var(--ink-soft);
}

/* Media an editor drops into a post. The block library's stylesheet is
   dequeued (inc/enqueue.php), so nothing else sizes these: every picture,
   embed, table and code block has to stay inside the reading column at any
   width. height: auto is what keeps a photo's proportions once max-width has
   narrowed it, since the width and height attributes WordPress prints would
   otherwise hold it at its full height and squash it. Wide and full alignments
   are held to the column too; the sidebar sits beside it on desktop. */
.article__body figure { margin-inline: 0; max-width: 100%; }
.article__body img { height: auto; }
.article__body .alignwide,
.article__body .alignfull { width: auto; max-width: 100%; margin-inline: 0; }
.article__body .aligncenter { margin-inline: auto; }
.article__body figcaption,
.article__body .wp-element-caption {
  margin-top: 10px;
  font-size: .84rem;
  color: var(--muted);
  text-align: center;
}
.article__body iframe,
.article__body embed,
.article__body object,
.article__body video { display: block; max-width: 100%; }
.article__body .wp-has-aspect-ratio iframe { width: 100%; height: auto; aspect-ratio: 16 / 9; }
.article__body .wp-embed-aspect-4-3 iframe { aspect-ratio: 4 / 3; }
.article__body .wp-embed-aspect-1-1 iframe { aspect-ratio: 1; }
.article__body .wp-embed-aspect-9-16 iframe { aspect-ratio: 9 / 16; }
.article__body .wp-block-table,
.article__body pre { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.article__body table { width: 100%; border-collapse: collapse; font-size: .95rem; }
.article__body th,
.article__body td { padding: 10px 12px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.article__body pre {
  padding: 16px 18px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  font-size: .9rem;
  overflow-wrap: normal;
}
.article__body .wp-block-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap: 12px;
}
.article__body .wp-block-gallery img { width: 100%; }
.article__body ol { padding-left: 1.4em; display: grid; gap: 12px; }
.article__body ol li { padding-left: 4px; }
.article__body ol li::before { content: none; }

/* Side-by-side "Scenario A / Scenario B" boxes */
.article__share {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  padding-block: 16px;
  margin-top: 22px;
  /* The article's first line sat about 10px under the rule. */
  margin-bottom: clamp(24px, 3vw, 32px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.article__share span {
  font-family: var(--font-head); font-weight: 600;
  font-size: .92rem; color: var(--ink);
  margin-right: 4px;
}
.article__share a {
  width: 44px; height: 44px; /* the touch-target minimum */
  display: grid; place-items: center;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--muted);
  transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.article__share a:hover { color: var(--brand-dark); border-color: var(--brand); transform: translateY(-2px); }
.article__share svg { width: 18px; height: 18px; }

/* --------------------------------------------------------------------------
   12c. Plain pages and the 404
   A page written as text in the editor (Privacy policy, Image credits) has no
   section block around it. page.php gives it a title band and this column: the
   article's own measure and rhythm, centred because there is no sidebar.
   -------------------------------------------------------------------------- */
.page-prose__body { margin-inline: auto; }

/* The 404's one action sat in a <p>, flush under the sentence above it, and
   .page-hero a (section 28) repainted its label in ink on terracotta. A row of
   its own gives it the space and hands the colour back to the button. */
.page-hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: clamp(22px, 3vw, 32px);
}
.page-hero .page-hero__actions .btn--primary,
.page-hero .page-hero__actions .btn--primary:hover { color: var(--btn-fg); }

/* --------------------------------------------------------------------------
   13. CTA Strip
   -------------------------------------------------------------------------- */
.cta-strip { text-align: center; }

/* The closing ask. Deep terracotta rather than the old cream: it is the last
   block on the page, it should read as a full stop, and the marigold button
   only looks like the obvious next move against something dark.

   isolation + overflow keep the decorative layer inside the rounded corners
   and stop its blend modes reaching the page behind. */
.cta-strip__box {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    radial-gradient(120% 140% at 12% 0%, rgba(235, 148, 14, .40), transparent 58%),
    radial-gradient(110% 130% at 92% 100%, rgba(165, 195, 194, .30), transparent 55%),
    linear-gradient(135deg, var(--brand-darker), var(--brand) 55%, var(--brand-dark));
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--r-xl);
  padding: clamp(48px, 7vw, 88px) var(--gutter);
  box-shadow: var(--shadow-lg);
}
/* A hairline of light along the top edge — the detail that keeps a dark panel
   from looking like a flat swatch. */
.cta-strip__box::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  pointer-events: none;
}

.cta-strip__inner { position: relative; z-index: 2; }

.cta-strip__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 18px;
  padding: 7px 16px 7px 13px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .22);
  color: #fff;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .86rem;
  letter-spacing: .01em;
  backdrop-filter: blur(6px);
}
.cta-strip__eyebrow svg { width: 15px; height: 15px; color: var(--accent-yellow); }

.cta-strip h2 {
  font-size: clamp(2rem, 4.6vw, 3.1rem);
  line-height: 1.12;
  letter-spacing: -.015em;
  margin-bottom: 16px;
  color: #fff;
}
.cta-strip p {
  color: rgba(255, 255, 255, .86);
  max-width: 560px;
  margin: 0 auto 32px;
  font-size: 1.03rem;
}

.cta-strip__actions { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; }
/* Phone: two buttons that do not fit side by side stack as one column of
   equal width, not two pills of different lengths centred one over the
   other. */
@media (max-width: 639.98px) {
  .cta-strip__actions { flex-direction: column; align-items: stretch; max-width: 320px; margin-inline: auto; }
}
/* On terracotta the marigold button needs a warm halo rather than the standard
   dark shadow, which would disappear into the panel. */
.cta-strip__actions .btn--primary { box-shadow: 0 14px 34px rgba(132, 62, 17, .45); }
.cta-strip__actions .btn--primary:hover { box-shadow: 0 18px 42px rgba(132, 62, 17, .55); }
/* The other four pages that use this panel (about, blog, classes/[slug],
   schools) pass .btn--secondary, which is white-on-white-border and reads as
   heavy as the primary against terracotta. Same glass treatment the dark
   .banner already uses, so the primary stays the obvious action. */
.cta-strip__actions .btn--secondary {
  --btn-bg: rgba(255, 255, 255, .12);
  --btn-fg: #fff;
  border-color: rgba(255, 255, 255, .4);
}
.cta-strip__actions .btn--secondary:hover {
  --btn-bg: rgba(255, 255, 255, .2);
  color: #fff;
  border-color: #fff;
}
/* .btn--ghost is ink-on-transparent by default and would be unreadable here. */
.cta-strip__actions .btn--ghost {
  --btn-fg: #fff;
  padding-inline: 24px;
  border: 1px solid rgba(255, 255, 255, .30);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .06);
  transition: background var(--base) var(--ease), border-color var(--base) var(--ease);
}
.cta-strip__actions .btn--ghost:hover {
  color: #fff;
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .5);
}

.cta-strip__points {
  list-style: none;
  padding: 0;
  margin: 30px auto 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 26px;
}
.cta-strip__points li {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: rgba(255, 255, 255, .82);
  font-size: .93rem;
}
.cta-strip__points svg { width: 13px; height: 13px; color: var(--accent-yellow); flex: none; }
/* A point that wraps (a phone) keeps its tick in front of the first word.
   As a flex row the tick sat at the far left while the two centred lines of
   text floated away from it. */
@media (max-width: 639.98px) {
  .cta-strip__points li { display: block; text-align: center; }
  .cta-strip__points svg { display: inline-block; vertical-align: -1px; margin-right: 8px; }
}

/* ---- Decorative layer ----
   Sits behind the copy (z-index 1 against .cta-strip__inner's 2) and is
   aria-hidden in the markup. Nothing here carries meaning. */
.cta-decor {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
  border-radius: inherit;
}

/* Two soft color blooms. Marigold top-left, sage bottom-right, so the panel
   has depth from opposite corners instead of one flat wash. */
.cta-decor__bloom {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: .5;
}
.cta-decor__bloom--a {
  width: 460px; height: 460px;
  top: -190px; left: -130px;
  background: radial-gradient(circle, rgba(239, 220, 149, .85), transparent 70%);
}
.cta-decor__bloom--b {
  width: 400px; height: 400px;
  right: -140px; bottom: -170px;
  background: radial-gradient(circle, rgba(165, 195, 194, .75), transparent 70%);
}

/* Faint blueprint grid — the texture that reads as "considered" rather than
   "empty". Masked to fade out before it reaches the text. */
.cta-decor__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, .07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .07) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(120% 100% at 50% 0%, #000 15%, transparent 72%);
  mask-image: radial-gradient(120% 100% at 50% 0%, #000 15%, transparent 72%);
}

/* Line-art marks around the edges. Hidden on small screens, where there is no
   margin to place them in without crowding the copy. */
.cta-decor__shape {
  position: absolute;
  color: rgba(255, 255, 255, .34);
  display: none;
}
.cta-decor__shape--bubble { width: 42px; height: 42px; top: 15%; left: 7%; animation: cta-float 9s var(--ease) infinite; }
.cta-decor__shape--node   { width: 38px; height: 38px; bottom: 18%; left: 12%; animation: cta-float 11s var(--ease) infinite .8s; }
.cta-decor__shape--star   { width: 26px; height: 26px; top: 22%; right: 11%; color: rgba(239, 220, 149, .70); animation: cta-float 8s var(--ease) infinite .4s; }
.cta-decor__shape--pencil { width: 40px; height: 40px; bottom: 16%; right: 8%; animation: cta-float 10s var(--ease) infinite 1.2s; }
.cta-decor__shape--ring   { width: 120px; height: 120px; top: -34px; right: 24%; color: rgba(255, 255, 255, .14); }

@media (min-width: 900px) {
  .cta-decor__shape { display: block; }
}

@keyframes cta-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-14px) rotate(5deg); }
}

/* The global reduce-motion rule already kills the animation; this also settles
   the shapes at their resting position. */
@media (prefers-reduced-motion: reduce) {
  .cta-decor__shape { transform: none; }
}

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  position: relative;
  isolation: isolate;
  background: var(--brand);          /* Terracotta — official brand treatment (white logo on terracotta) */
  color: rgba(255,255,255,.86);
  padding-block: clamp(36px, 4vw, 56px) 18px;
  overflow: hidden;
}
/* The crane pattern in white over the terracotta, faint, fading toward the
   text so it never competes with the columns. */
.site-footer::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url(../images/pattern-crane.svg) 0 0 / 320px 320px repeat;
  opacity: .45;
  filter: brightness(0) invert(1);
  -webkit-mask-image: linear-gradient(90deg, transparent 35%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 35%, #000 100%);
  pointer-events: none;
}
.footer__grid {
  display: grid;
  gap: 26px;
  grid-template-columns: 1fr;
  margin-bottom: 20px;
}
.footer__brand .brand { margin-bottom: 12px; display: inline-block; }
.footer__brand p { color: rgba(255,255,255,.78); font-size: .94rem; max-width: 300px; margin-bottom: 16px; }
.footer__socials { display: flex; gap: 12px; }
.footer__socials a {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: 12px;
  background: rgba(255,255,255,.14);
  color: #fff;
  transition: background var(--fast), transform var(--fast), color var(--fast);
}
.footer__socials a svg { width: 20px; height: 20px; }
.footer__socials a:hover { background: #fff; color: var(--brand); transform: translateY(-3px); }

.footer__col h4 {
  color: #fff; font-size: .82rem; letter-spacing: .1em;
  text-transform: uppercase; margin-bottom: 13px;
  font-family: var(--font-head);
}
/* Row gap is trimmed rather than the links' min-height — that stays at 30px
   so each one is still a usable tap target. */
.footer__col ul { display: grid; gap: 7px; }
.footer__col a, .footer__col li { color: rgba(255,255,255,.8); font-size: .95rem; transition: color var(--fast); }
.footer__col a { display: inline-flex; align-items: center; gap: 9px; min-height: 30px; }
.footer__col a:hover { color: var(--accent-yellow); }
.footer__col a svg { width: 17px; height: 17px; color: var(--accent-yellow); flex-shrink: 0; }

.footer__bottom {
  display: flex; flex-wrap: wrap; gap: 14px;
  justify-content: space-between; align-items: center;
  padding-top: 15px;
  border-top: 1px solid rgba(255,255,255,.2);
  font-size: .88rem; color: rgba(255,255,255,.72);
}
.footer__bottom a:hover { color: #fff; }
.footer__legal { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.footer__legal a { min-height: 30px; display: inline-flex; align-items: center; color: rgba(255,255,255,.8); }

/* Brand logo image (navbar + footer) */
.brand__logo { height: 38px; width: auto; display: block; }
.footer__brand .brand__logo { height: 46px; }

/* --------------------------------------------------------------------------
   14b. Page Hero (inner pages)
   -------------------------------------------------------------------------- */
.page-hero {
  position: relative;
  /* Text-only hero (Programs, Trial, Schools): no image to balance, so it
     stays short and the filters and grid enter the first screen. */
  padding-block: clamp(36px, 5vw, 56px);
  text-align: center;
  background:
    radial-gradient(60% 120% at 80% -20%, var(--brand-tint) 0%, transparent 55%),
    radial-gradient(46% 90% at 8% 0%, var(--gold-tint) 0%, transparent 52%),
    var(--canvas-2);
  overflow: hidden;
}
/* The crane pattern at the edges, faded out of the copy. */
.page-hero::before {
  content: "";
  position: absolute; inset: 0;
  background: url(../images/pattern-crane.svg) 0 0 / 320px 320px repeat;
  opacity: .2; /* 1.5.10: from .4; size and fade unchanged */
  -webkit-mask-image: linear-gradient(90deg, #000 0%, transparent 28%, transparent 72%, #000 100%);
  mask-image: linear-gradient(90deg, #000 0%, transparent 28%, transparent 72%, #000 100%);
  pointer-events: none;
}
.page-hero > .wrap { position: relative; }
.page-hero__inner { max-width: 720px; margin-inline: auto; }
.page-hero h1 {
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  letter-spacing: -0.03em;
  margin-bottom: 16px;
}
.page-hero p {
  font-size: clamp(1.02rem, 1.5vw, 1.2rem);
  color: var(--muted);
  max-width: 560px;
  margin-inline: auto;
}
/* The breadcrumb always takes a row of its own. It used to be inline-flex,
   which let the .eyebrow pill after it ride up onto the same line and read as
   "Home > Classes OUR CLASSES". Being a flex row makes that impossible, so no
   page has to opt in to correct spacing. */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  font-size: .84rem;
  color: var(--muted);
  margin-bottom: 18px;
}
/* Centered inside the centered hero; the detail hero below overrides it back to
   the left, where its own copy starts. */
.page-hero .breadcrumb { justify-content: center; }
.breadcrumb a {
  color: var(--muted);
  font-weight: 500;
  border-radius: var(--r-pill);
  transition: color .18s ease;
}
.breadcrumb a:hover { color: var(--brand-dark); text-decoration: none; }
.breadcrumb a:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
/* The last crumb is the page you are on: no link, but weighted so the trail
   resolves somewhere instead of trailing off. */
.breadcrumb [data-crumb] {
  color: var(--ink);
  font-weight: 600;
}
.breadcrumb svg { width: 13px; height: 13px; opacity: .45; flex: none; }

/* --------------------------------------------------------------------------
   14c. Filter Bar (Enrollment)
   -------------------------------------------------------------------------- */
.filter-bar {
  position: sticky;
  top: var(--header-h);
  z-index: 40;
  /* Sticky under the header — same rule as .site-header: stays solid so the
     class cards scrolling past can never be read through it. */
  background: var(--surface);
  border-block: 1px solid var(--line);
}

/* Not sticky on a phone. The dropdowns go full-width at 560px and stack, which
   turns the bar from one row into three: measured at 274px, and with the 77px
   header above it that pins 351px — 46% of a 375×760 viewport, and 53% on a
   667px-tall phone. The reader is then browsing a list of tall cards through a
   half-height window, which costs more than the filters are worth once set.

   Static, not a compacted sticky row. Filtering here is a thing you do once on
   arrival, not while reading: there are three programs, the bar sits directly
   under the page heading, and getting back to it is one flick. Keeping it
   pinned would mean shrinking the controls to earn the space, and a 44px tap
   target is not what should pay for a bar nobody is using at that moment.

   639.98px, so "phone" keeps meaning one thing across this file — the same
   breakpoint as the card rails, the reveal and the hover rules. */
@media (max-width: 639.98px) {
  .filter-bar { position: static; }
}
.filter-bar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 18px 28px;
  padding-block: 18px;
}
.filter-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
}
.filter-group__label {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .78rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 2px;
}
.filter-group__label svg { width: 16px; height: 16px; color: var(--brand); }
.chip {
  min-height: 44px;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 17px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--line);
  background: #fff;
  color: var(--ink-soft);
  font-family: var(--font-head);
  font-weight: 500;
  font-size: .92rem;
  transition: all var(--fast) var(--ease);
}
.chip:hover { border-color: var(--brand); color: var(--brand-dark); transform: translateY(-1px); }
.chip[aria-pressed="true"] {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  box-shadow: var(--shadow-brand);
}
.filter-reset {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 44px;
  padding: 8px 14px;
  border-radius: var(--r-pill);
  font-family: var(--font-head);
  font-weight: 500;
  font-size: .9rem;
  color: var(--muted);
  transition: color var(--fast), background var(--fast);
}
.filter-reset:hover { color: var(--brand-dark); background: var(--brand-tint); }
.filter-reset svg { width: 16px; height: 16px; }

/* Themed filter dropdowns (Age / Schools / Subject) */
.filter-drop { position: relative; }
.filter-drop__btn {
  min-height: 46px;
  display: inline-flex; align-items: center; gap: 9px;
  padding: 8px 15px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--line);
  background: #fff;
  color: var(--ink);
  font-family: var(--font-head);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.filter-drop__btn:hover { border-color: var(--brand); }
.filter-drop.is-open .filter-drop__btn { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-tint); }
.filter-drop__ico { width: 17px; height: 17px; color: var(--brand); flex-shrink: 0; }
.filter-drop__label {
  font-size: .75rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.filter-drop__value { font-size: .92rem; font-weight: 600; color: var(--ink-soft); }
/* Only show the value once a real selection is made — no "All ages/schools/subjects" placeholder */
.filter-drop:not(.has-selection) .filter-drop__value { display: none; }
.filter-drop.has-selection .filter-drop__value { color: var(--brand-dark); font-weight: 700; }
.filter-drop__caret {
  width: 15px; height: 15px; color: var(--muted); margin-left: 2px;
  transition: transform var(--fast) var(--ease);
}
.filter-drop.is-open .filter-drop__caret { transform: rotate(180deg); }

.filter-drop__menu {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
  min-width: 230px;
  /* The bar is sticky, so a menu taller than the viewport can never be
     scrolled into view by scrolling the page: it moves with the bar. Cap it
     and scroll inside instead. 60vh leaves the bar and a row of cards
     visible; 420px stops it running away on a tall desktop. */
  max-height: min(60vh, 420px);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: none; flex-direction: column; gap: 2px;
  padding: 8px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}
/* School names run to three words plus a campus; 230px wraps every one. */
.filter-drop[data-filter="venue"] .filter-drop__menu { min-width: 320px; }
.filter-drop.is-open .filter-drop__menu {
  display: flex;
  animation: dropIn var(--fast) var(--ease) both;
}
@keyframes dropIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.filter-drop__opt {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left;
  min-height: 42px; padding: 8px 11px;
  border-radius: var(--r-sm);
  background: none; border: 0; cursor: pointer;
  font-family: var(--font-head); font-weight: 600; font-size: .94rem;
  color: var(--ink-soft);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.filter-drop__opt:hover { background: var(--brand-tint-2); color: var(--brand-dark); }
.filter-drop__opt[aria-selected="true"] { color: var(--brand-dark); }
/* Faceted: how many programs an option would leave, given the other two
   dropdowns. Zero and not chosen means it is taken out of the list, so
   picking an age leaves only the programs and schools that age can book. */
.filter-drop__count {
  margin-left: auto; padding-left: 12px;
  font-size: 12px; font-variant-numeric: tabular-nums;
  color: var(--muted, #8a8078);
}
.filter-drop__opt.is-unavailable { display: none; }
.filter-drop__check {
  width: 20px; height: 20px; flex-shrink: 0;
  display: grid; place-items: center;
  border: 1.6px solid var(--line); border-radius: 7px;
  color: #fff;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.filter-drop__check svg { width: 13px; height: 13px; opacity: 0; transition: opacity var(--fast) var(--ease); }
.filter-drop__opt[aria-selected="true"] .filter-drop__check {
  background: var(--brand); border-color: var(--brand);
}
.filter-drop__opt[aria-selected="true"] .filter-drop__check svg { opacity: 1; }

@media (max-width: 639.98px) {
  .filter-drop, .filter-drop__btn { width: 100%; }
  .filter-drop__btn { justify-content: flex-start; }
  .filter-drop__value { margin-left: auto; }
  .filter-drop__menu { width: 100%; min-width: 0; }
  .filter-drop[data-filter="venue"] .filter-drop__menu { min-width: 0; }
}

.results-meta {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 12px;
  margin-bottom: 28px;
}
.results-meta__count { color: var(--muted); font-size: .96rem; }
.results-meta__count b { color: var(--ink); font-family: var(--font-head); }

/* Enrollment class card = extends .class-card with richer body */
.enroll-grid {
  display: grid;
  gap: 26px;
  grid-template-columns: 1fr;
}
.class-card.is-hidden { display: none; }
/* One short line beside a tick, so the tick is centred on it (1.5.24). */
.class-card__outcome {
  display: flex; align-items: center; gap: 9px;
  padding: 12px 14px;
  background: var(--brand-tint-2);
  border-radius: var(--r-sm);
  color: var(--ink-soft);
  font-size: .88rem;
  margin-bottom: 18px;
}
.class-card__outcome svg { width: 17px; height: 17px; color: var(--brand); flex-shrink: 0; margin-top: 2px; }

.no-results {
  grid-column: 1 / -1;
  text-align: center;
  padding: 60px 20px;
  color: var(--muted);
}
.no-results svg { width: 52px; height: 52px; color: var(--line); margin: 0 auto 18px; }
.no-results h3 { color: var(--ink); margin-bottom: 8px; }

/* --------------------------------------------------------------------------
   14d. Class Detail
   -------------------------------------------------------------------------- */
.detail-hero {
  position: relative;
  isolation: isolate;
  padding-block: clamp(40px, 6vw, 72px);
  background:
    radial-gradient(60% 120% at 88% -10%, var(--brand-tint) 0%, transparent 55%),
    var(--canvas-2);
  overflow: hidden;
}
/* With a class photo: the photo fills the band under a terracotta tint and
   the copy turns white. The right-hand media box is then redundant and
   hidden; without a photo the old two-column layout stays. */
.detail-hero--photo { color: #fff; padding-block: clamp(56px, 8vw, 110px); }
.detail-hero__bg { position: absolute; inset: 0; z-index: -2; }
.detail-hero__bg img { width: 100%; height: 100%; object-fit: cover; object-position: center 35%; }
.detail-hero--photo::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(90deg, rgba(60, 28, 10, .86) 0%, rgba(60, 28, 10, .72) 45%, rgba(132, 62, 17, .35) 100%),
    linear-gradient(180deg, rgba(60, 28, 10, .2), rgba(60, 28, 10, .5));
}
.detail-hero--photo .detail-hero__grid { grid-template-columns: minmax(0, 1fr); max-width: 760px; }
.detail-hero--photo .detail-hero__media { display: none; }
.detail-hero--photo h1,
.detail-hero--photo .detail-hero__lead,
.detail-hero--photo .breadcrumb a,
.detail-hero--photo .breadcrumb [data-crumb] { color: #fff; }
.detail-hero--photo .detail-hero__lead { color: rgba(255, 255, 255, .88); }
.detail-hero--photo .breadcrumb { color: rgba(255, 255, 255, .7); }
.detail-hero--photo .breadcrumb svg { opacity: .7; }
.detail-hero--photo .tag-pill {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .3);
  color: #fff;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.detail-hero--photo .tag-pill svg { color: var(--accent-yellow); }
.detail-hero--photo .partner-note { color: rgba(255, 255, 255, .85); }
.detail-hero--photo .btn--secondary {
  --btn-bg: rgba(255, 255, 255, .12);
  --btn-fg: #fff;
  border-color: rgba(255, 255, 255, .4);
}

/* Phone: the one action stays reachable while the parent reads. */
.sticky-cta {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 90;
  display: none;
  padding: 10px var(--gutter) max(10px, env(safe-area-inset-bottom));
  background: rgba(255, 255, 255, .94);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px rgba(60, 30, 10, .08);
}
.sticky-cta .btn { width: 100%; }
@media (max-width: 899.98px) {
  .sticky-cta { display: block; }
  body.has-sticky-cta { padding-bottom: 76px; }
}
.detail-hero__grid {
  display: grid;
  gap: clamp(32px, 5vw, 56px);
  align-items: center;
}
.detail-hero__tags { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; }
.tag-pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px;
  border-radius: var(--r-pill);
  background: #fff;
  border: 1px solid var(--line);
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .82rem;
  color: var(--ink-soft);
  box-shadow: var(--shadow-xs);
}
.tag-pill svg { width: 15px; height: 15px; color: var(--brand); }
.tag-pill--brand { background: var(--brand); border-color: var(--brand); color: #fff; }
.tag-pill--brand svg { color: #fff; }

.detail-hero h1 {
  font-size: clamp(2.1rem, 5vw, 3.3rem);
  letter-spacing: -0.03em;
  margin-bottom: 16px;
}
.detail-hero__lead { font-size: clamp(1.02rem, 1.5vw, 1.18rem); color: var(--muted); max-width: 520px; margin-bottom: 28px; }
.detail-hero__actions { display: flex; flex-wrap: wrap; gap: 13px; margin-bottom: 26px; }
.detail-hero__facts { display: flex; flex-wrap: wrap; gap: 22px 32px; }
.detail-hero__fact small { display: block; color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; margin-bottom: 3px; }
.detail-hero__fact b { font-family: var(--font-head); font-size: 1.1rem; color: var(--ink); }

.detail-hero__media {
  position: relative;
  aspect-ratio: 4 / 3.4;
  border-radius: var(--r-xl);
  background: linear-gradient(150deg, rgba(188,90,27,.14), rgba(165,195,194,.20)), var(--canvas-2);
  box-shadow: var(--shadow-lg);
  display: grid; place-items: center;
  color: var(--brand-dark);
  overflow: hidden;
}
.detail-hero__media svg { width: 64px; height: 64px; opacity: .4; }
.detail-hero__media::after {
  content: "Class photo";
  position: absolute; bottom: 22px;
  font-family: var(--font-head); font-weight: 500; opacity: .5; font-size: .9rem;
}

/* Outcomes */
.outcome-grid {
  display: grid;
  gap: 18px;
  grid-template-columns: 1fr;
}
.outcome-item {
  display: flex; gap: 15px; align-items: flex-start;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 22px 24px;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
.outcome-item:hover { transform: translateY(-4px); box-shadow: var(--shadow-sm); }
.outcome-item__ico {
  width: 44px; height: 44px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 12px;
  background: var(--brand-tint);
  color: var(--brand-dark);
}
.outcome-item__ico svg { width: 22px; height: 22px; }
.outcome-item h3 { font-size: 1.1rem; margin-bottom: 5px; }
.outcome-item p { color: var(--muted); font-size: .93rem; }

/* Structure stats */
.structure-grid {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.stat-tile {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 28px 26px;
  box-shadow: var(--shadow-xs);
}
.stat-tile__ico {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: 14px;
  background: var(--brand-tint);
  color: var(--brand-dark);
  margin-bottom: 18px;
}
.stat-tile__ico svg { width: 26px; height: 26px; }
.stat-tile b { display: block; font-family: var(--font-head); font-size: 1.6rem; color: var(--ink); line-height: 1.1; margin-bottom: 6px; }
.stat-tile span { color: var(--muted); font-size: .92rem; }

/* Teacher profile card */
.teacher-card {
  display: grid;
  gap: 28px;
  align-items: center;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: clamp(26px, 4vw, 40px);
  box-shadow: var(--shadow-sm);
}
.teacher-card__photo {
  aspect-ratio: 1;
  max-width: 220px;
  width: 100%;
  border-radius: var(--r-lg);
  background: linear-gradient(150deg, var(--brand), var(--accent-sage));
  display: grid; place-items: center;
  color: #fff;
  font-family: var(--font-head); font-weight: 700; font-size: 2.6rem;
  box-shadow: var(--shadow-md);
}
.teacher-card__role {
  display: inline-block;
  padding: 5px 13px; border-radius: var(--r-pill);
  background: var(--brand-tint); color: var(--brand-dark);
  font-family: var(--font-head); font-weight: 600; font-size: .78rem;
  letter-spacing: .04em; margin-bottom: 12px;
}
.teacher-card h3 { font-size: 1.5rem; margin-bottom: 12px; }
.teacher-card p { color: var(--muted); margin-bottom: 18px; }
.teacher-card__certs { display: flex; flex-wrap: wrap; gap: 9px; }
.teacher-card__certs span {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: var(--r-pill);
  border: 1px solid var(--line);
  font-size: .82rem; color: var(--ink-soft);
}
.teacher-card__certs svg { width: 15px; height: 15px; color: var(--brand); }

/* FAQ accordion */
.faq-list {
  max-width: 780px;
  margin-inline: auto;
  display: grid;
  gap: 14px;
}
.faq-item {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: box-shadow var(--base), border-color var(--base);
}
.faq-item.is-open { box-shadow: var(--shadow-sm); border-color: var(--brand-tint); }
.faq-item__q {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  text-align: left;
  padding: 22px 24px;
  min-height: 44px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--ink);
}
.faq-item__q .icon {
  flex-shrink: 0;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--brand-tint);
  color: var(--brand-dark);
  transition: transform var(--base) var(--ease), background var(--base);
}
.faq-item__q .icon svg { width: 17px; height: 17px; }
.faq-item.is-open .faq-item__q .icon { transform: rotate(45deg); background: var(--brand); color: #fff; }
.faq-item__a {
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--base) var(--ease);
}
.faq-item__a p { padding: 0 24px 24px; color: var(--muted); font-size: .97rem; }

/* --------------------------------------------------------------------------
   14e. About page
   -------------------------------------------------------------------------- */
.story {
  display: grid;
  gap: clamp(32px, 5vw, 60px);
  align-items: center;
}
.story__media {
  position: relative;
  aspect-ratio: 4 / 4.4;
  border-radius: var(--r-xl);
  background: linear-gradient(155deg, rgba(188,90,27,.16), rgba(165,195,194,.20)), var(--canvas-2);
  box-shadow: var(--shadow-lg);
  display: grid; place-items: center;
  color: var(--brand-dark);
  overflow: hidden;
}
.story__media svg { width: 60px; height: 60px; opacity: .4; }
/* Tablet, stacked: at the full 690px column the 4:4.4 portrait came to 760px
   tall, a screen of one photograph blown up past its resolution. */
@media (min-width: 640px) and (max-width: 899.98px) {
  .story__media { width: 100%; max-width: 460px; }
}
.story__media::after {
  content: "Founder photo";
  position: absolute; bottom: 22px;
  font-family: var(--font-head); font-weight: 500; opacity: .5; font-size: .9rem;
}
/* Covers the "Image Coming Soon" box at z-index 0 and stays under the 2022
   badge at 3. The source is roughly 4:3 and the box is 4:4.4, so object-fit
   crops the sides rather than letterboxing a portrait frame. */
.story__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Inset, not hung off the edge: .story__media clips its overflow for the
   photo's rounded corners, so at -14px the badge lost its left edge and its
   own corners and sat flush against the frame. */
.story__badge {
  position: absolute;
  left: clamp(14px, 2.4vw, 22px); bottom: clamp(14px, 2.4vw, 22px);
  background: #fff;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  padding: 16px 20px;
}
.story__badge b { display: block; font-family: var(--font-head); font-size: 1.7rem; color: var(--ink); line-height: 1; }
.story__badge small { color: var(--muted); font-size: .82rem; }
.story__copy p { color: var(--ink-soft); margin-bottom: 18px; font-size: 1.04rem; }
.story__copy p:last-child { margin-bottom: 0; }
.story__sign {
  margin-top: 26px;
  display: flex; align-items: center; gap: 14px;
}
.story__sign-avatar {
  width: 52px; height: 52px; border-radius: 50%;
  background: linear-gradient(140deg, var(--brand), var(--accent-sun));
  display: grid; place-items: center; color: #fff;
  font-family: var(--font-head); font-weight: 700; flex-shrink: 0;
}
.story__sign b { font-family: var(--font-head); color: var(--ink); display: block; }
.story__sign small { color: var(--muted); font-size: .86rem; }

/* Values grid */
.values-grid {
  display: grid;
  gap: 22px;
  grid-template-columns: 1fr;
}
.value-card {
  position: relative;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 30px 28px;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
.value-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
.value-card__num {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: 1rem;
  color: var(--brand);
  opacity: .5;
  margin-bottom: 14px;
}
.value-card__ico {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: 16px;
  background: var(--brand-tint);
  color: var(--brand-dark);
  margin-bottom: 20px;
}
.value-card__ico svg { width: 27px; height: 27px; }
.value-card:nth-child(2) .value-card__ico { background: #FBEED4; color: #C2760A; }
.value-card:nth-child(3) .value-card__ico { background: #E6EFEE; color: #4E7C7A; }
.value-card:nth-child(4) .value-card__ico { background: #F8EFD3; color: #B07A0C; }
.value-card h3 { font-size: 1.22rem; margin-bottom: 9px; }
.value-card p { color: var(--muted); font-size: .95rem; }

/* Teacher grid */
.team-grid {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
}
.team-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
.team-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
.team-card__photo {
  aspect-ratio: 1 / 1;
  display: grid; place-items: center;
  color: #fff;
  font-family: var(--font-head); font-weight: 700; font-size: 2.4rem;
  background: linear-gradient(150deg, var(--brand), var(--brand-darker));
}
/* Laid over the "Image Coming Soon" box exactly as .class-card__img is: z-index
   1 covers the placeholder at 0. The photos are square and so is the box, so
   object-fit only guards against a non-square file being dropped in later. */
.team-card__img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.team-card:nth-child(2) .team-card__photo { background: linear-gradient(150deg, var(--accent-sun), #C2760A); }
.team-card:nth-child(3) .team-card__photo { background: linear-gradient(150deg, var(--accent-sage), #7FA3A2); }
.team-card:nth-child(4) .team-card__photo { background: linear-gradient(150deg, var(--accent-yellow), #D9C066); }
.team-card__body { padding: 22px 24px 26px; }
.team-card__role {
  display: inline-block;
  padding: 4px 11px; border-radius: var(--r-pill);
  background: var(--brand-tint); color: var(--brand-dark);
  font-family: var(--font-head); font-weight: 600; font-size: .75rem;
  letter-spacing: .03em; margin-bottom: 12px;
}
.team-card h3 { font-size: 1.22rem; margin-bottom: 12px; }
.team-card__certs { display: flex; flex-wrap: wrap; gap: 7px; }
.team-card__certs span {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: var(--r-pill);
  border: 1px solid var(--line);
  font-size: .78rem; color: var(--ink-soft);
}
.team-card__certs svg { width: 13px; height: 13px; color: var(--brand); }

/* Team section group headings (Our team / Our teachers) */
.team-section { margin-bottom: 40px; }
.team-section:last-child { margin-bottom: 0; }
.team-section__heading {
  font-family: var(--font-head);
  font-size: 1rem;
  font-weight: 700;
  margin-bottom: 20px;
}
.team-section__pill {
  display: inline-block;
  padding: 6px 18px;
  border-radius: var(--r-pill);
  background: var(--brand-tint);
  color: var(--brand-dark);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.team-card h4 { font-size: 1.22rem; margin-bottom: 12px; }

/* Safety policy */
.safety {
  display: grid;
  gap: clamp(30px, 4vw, 50px);
  align-items: center;
}
.safety__panel {
  background:
    radial-gradient(120% 130% at 100% 0%, rgba(255,255,255,.1), transparent 50%),
    linear-gradient(150deg, var(--brand-darker), var(--brand));
  border-radius: var(--r-xl);
  padding: clamp(30px, 4vw, 46px);
  color: #fff;
  box-shadow: var(--shadow-lg);
}
.safety__panel .eyebrow { background: rgba(255,255,255,.18); color: #fff; }
.safety__panel h2 { color: #fff; font-size: clamp(1.7rem, 3.5vw, 2.4rem); margin-bottom: 14px; }
.safety__panel > p { color: rgba(255,255,255,.9); }
.safety__list { display: grid; gap: 16px; }
.safety__row {
  display: flex; gap: 14px; align-items: flex-start;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 20px 22px;
  box-shadow: var(--shadow-xs);
}
.safety__row .ico {
  width: 42px; height: 42px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 12px;
  background: var(--brand-tint); color: var(--brand-dark);
}
.safety__row .ico svg { width: 21px; height: 21px; }
.safety__row h3 { font-size: 1.08rem; margin-bottom: 4px; }
.safety__row p { color: var(--muted); font-size: .92rem; }

/* --------------------------------------------------------------------------
   14f. Contact page

   The form used to sit in a two-column grid with an info aside beside it. The
   aside was a third of the form's height, so it left a large block of dead
   space, and its "Reply time" card was a solid brand-color panel competing
   with the submit button. Both are gone: the three facts now run as one quiet
   row above the form, and the form uses .form-narrow like every other form
   page on the site.
   -------------------------------------------------------------------------- */
/* No width cap: at 720px the three facts measured a few pixels too wide and
   the reply time dropped to a second line on a 1440px screen. On a phone
   they stack as one left-aligned column, centred as a group, so the icons
   line up instead of each row centring on its own length. */
.contact-row {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 12px clamp(20px, 4vw, 40px);
  margin: 0 auto clamp(24px, 4vw, 36px);
}
@media (max-width: 639.98px) {
  .contact-row { flex-direction: column; align-items: flex-start; width: fit-content; max-width: 100%; gap: 4px; }
}
.contact-row li {
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--ink-soft);
  font-size: .95rem;
}
.contact-row svg { width: 17px; height: 17px; flex-shrink: 0; color: var(--brand); }
.contact-row a { font-family: var(--font-head); font-weight: 600; color: var(--ink); }
.contact-row a:hover { color: var(--brand-dark); }

/* Form card */
.form-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: clamp(26px, 4vw, 42px);
  box-shadow: var(--shadow-md);
}
.form-card h2 { font-size: clamp(1.5rem, 3vw, 2rem); margin-bottom: 8px; }
.form-card > p { color: var(--muted); margin-bottom: 26px; }

.field { margin-bottom: 20px; }
.field label {
  display: block;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .9rem;
  color: var(--ink);
  margin-bottom: 8px;
}
.field label .req { color: var(--accent-sun); }
/* The schools catalog input is a lone field outside a .field wrapper, so it is
   named here rather than restyled — one input can't be allowed to drift from
   every other input on the site. */
.field input,
.field textarea,
.field select,
.catalog__form input {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--ink);
  background: var(--canvas);
  border: 1.5px solid var(--line);
  border-radius: var(--r-md);
  padding: 14px 16px;
  min-height: 48px;
  transition: border-color var(--fast), background var(--fast), box-shadow var(--fast);
}
.field textarea { resize: vertical; min-height: 140px; line-height: 1.6; }
.field input::placeholder,
.field textarea::placeholder,
.catalog__form input::placeholder { color: #A99C8B; }
.field select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364726F' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  padding-right: 44px;
}
.field input:focus,
.field textarea:focus,
.field select:focus,
.catalog__form input:focus {
  outline: none;
  border-color: var(--brand);
  background-color: #fff;
  box-shadow: 0 0 0 4px rgba(188,90,27,.14);
}

.form-success {
  display: none;
  align-items: center;
  gap: 12px;
  padding: 16px 18px;
  border-radius: var(--r-md);
  background: var(--brand-tint);
  color: var(--brand-darker);
  font-weight: 500;
  margin-bottom: 22px;
}
.form-success.is-visible { display: flex; }
.form-success svg { width: 22px; height: 22px; flex-shrink: 0; color: var(--brand); }

.form-error {
  padding: 16px 18px;
  border-radius: var(--r-md);
  background: #FBEAE9;
  color: #8C271F;
  font-weight: 500;
  margin-bottom: 22px;
}

/* Map grid */
.map-grid {
  display: grid;
  gap: 22px;
  grid-template-columns: 1fr;
}
/* Five venues on a three-column grid left the second row two cards and an
   empty slot. Centred as a wrapping row instead, the short row sits in the
   middle. Only from 640px: below that the rail rules own this grid. Two
   across on a tablet, where a third of the column broke "Tatsuminomori"
   mid-word. */
@media (min-width: 640px) {
  .map-grid { display: flex; flex-wrap: wrap; justify-content: center; }
  .map-grid > .map-box { flex: 0 0 calc((100% - 22px) / 2); min-width: 0; }
}
@media (min-width: 900px) {
  .map-grid > .map-box { flex-basis: calc((100% - 44px) / 3); }
}
.map-box {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: #fff;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
.map-box:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
.map-box__frame {
  aspect-ratio: 16 / 11;
  display: grid; place-items: center;
  background:
    repeating-linear-gradient(45deg, var(--canvas) 0 12px, var(--canvas-2) 12px 24px);
  color: var(--brand-dark);
  position: relative;
}
.map-box__frame svg { width: 44px; height: 44px; opacity: .5; }
.map-box__frame span {
  position: absolute; bottom: 16px;
  font-family: var(--font-head); font-weight: 500; font-size: .84rem; opacity: .55;
}
.map-box__foot {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 18px 20px 20px;
}
.map-box__foot .ico {
  width: 40px; height: 40px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 11px;
  background: var(--brand-tint); color: var(--brand-dark);
}
.map-box__foot .ico svg { width: 20px; height: 20px; }
/* The text keeps a readable measure: in three tablet columns the icon left
   it about 120px and "Tatsuminomori" broke mid-word, so below 9rem the icon
   moves above the text instead of beside it. */
.map-box__foot { flex-wrap: wrap; }
.map-box__foot > div { flex: 1 1 9rem; min-width: 0; }
.map-box__foot h3 { font-size: 1.05rem; margin-bottom: 3px; }
.map-box__foot p { color: var(--muted); font-size: .88rem; }

/* --------------------------------------------------------------------------
   14g. Image placeholders — "Image Coming Soon" boxes
   Simple light-gray rectangles, no external images. Drop in real photos later.
   -------------------------------------------------------------------------- */
.hero__photo,
.detail-hero__media,
.story__media,
.class-card__media,
.blog-card__media,
.feature-post__media,
.article__figure-media,
.team-card__photo,
.teacher-card__photo {
  position: relative;
  overflow: hidden;
  background: var(--canvas-2) !important;
}
.hero__photo::after,
.detail-hero__media::after,
.story__media::after,
.class-card__media::after,
.blog-card__media::after,
.feature-post__media::after,
.article__figure-media::after,
.team-card__photo::after,
.teacher-card__photo::after {
  /* No photo yet: the brand pattern on cream, never a text placeholder on
     a public page. */
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    url(../images/pattern-crane.svg) center / 200px 200px repeat,
    linear-gradient(150deg, var(--sage-tint), var(--canvas-2));
  border-radius: inherit;
  opacity: 1;
}
/* Compact label for small avatar boxes */
.team-card__photo::after,
.teacher-card__photo::after { font-size: .78rem; }

/* Keep overlays (tags, badges) above the placeholder text */
.blog-card__time { z-index: 2; }
.feature-post__cat { z-index: 3; }
.feature-post__overlay { z-index: 2; }
.story__badge { z-index: 3; }
.hero__card { z-index: 3; }

/* --------------------------------------------------------------------------
   15. Reveal-on-scroll
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .5s var(--ease), transform .5s var(--ease);
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Phones get no reveal at all: content is painted in its final position from
   the first frame.

   On a small screen almost everything is in or near the viewport at load, so
   the effect stopped reading as a reveal and read as the page arriving late.
   It also fails in one direction only — if the observer in SiteEffects.jsx
   never fires (JS blocked, an aborted navigation), opacity:0 leaves the
   section permanently invisible rather than merely un-animated. Overriding
   both properties here removes that risk on the devices where the animation
   was worth the least.

   639.98px matches the rail breakpoint below, so "phone" means one thing
   throughout this file for rails, reveal, hover and the filter bar. The
   899.98px family (sticky class CTA, 44px targets, hero stacking, header
   pill) is deliberately wider: those rules want tablets too. */
@media (max-width: 639.98px) {
  .reveal,
  .reveal.is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   16. Responsive
   -------------------------------------------------------------------------- */
@media (min-width: 640px) {
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .class-grid { grid-template-columns: repeat(2, 1fr); }
  .testi-grid { grid-template-columns: repeat(2, 1fr); }
  .blog-grid { grid-template-columns: repeat(2, 1fr); }
  .footer__grid { grid-template-columns: 1.4fr 1fr 1fr; }
  .banner__inner { grid-template-columns: 1.6fr auto; }
  .banner__cta { justify-content: flex-end; }
  .outcome-grid { grid-template-columns: repeat(2, 1fr); }
  .structure-grid { grid-template-columns: repeat(4, 1fr); }
  .teacher-card { grid-template-columns: 220px 1fr; }
  .values-grid { grid-template-columns: repeat(2, 1fr); }
  .team-grid { grid-template-columns: repeat(2, 1fr); }
  .map-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Three-up rows keep three columns from tablet portrait (810px iPad) up:
   at two columns the third program, testimonial or post sat alone on a
   second row. A tighter gap pays for the third column until 900px. */
@media (min-width: 760px) {
  .grid-3 { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .class-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .testi-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .blog-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .testi-card { padding: 24px 22px; }
}

@media (min-width: 900px) {
  .hero__grid { grid-template-columns: 1.05fr .95fr; }
  .grid-3 { grid-template-columns: repeat(3, 1fr); gap: 24px; }
  .class-grid { grid-template-columns: repeat(3, 1fr); gap: 24px; }
  .testi-grid { grid-template-columns: repeat(3, 1fr); gap: 24px; }
  .blog-grid { grid-template-columns: repeat(3, 1fr); gap: 24px; }
  .testi-card { padding: 32px 30px; }

  /* Text leads, the rail follows it down the page.
     Sticky sits on the grid item, not on .article-side__inner: a sticky box
     can only travel inside its containing block, and __inner's parent is only
     as tall as __inner itself — so it would have nowhere to go and would just
     scroll away. The grid area spans the whole row, which is the room it needs
     (that is also why .article-layout must keep align-items: start).
     The max-height is the guard: however many posts a backend returns, the
     rail can never outgrow the screen and strand its own bottom out of reach. */
  .article-layout { grid-template-columns: minmax(0, 1fr) 340px; gap: 48px; }
  .article-side {
    position: sticky;
    top: calc(var(--header-h) + 24px);
  }
  .article-side__inner {
    max-height: calc(100vh - var(--header-h) - 48px);
    max-height: calc(100dvh - var(--header-h) - 48px);
  }
  .article-side .latest { display: flex; max-height: inherit; }
  .article-side .latest__inner { flex: 1; min-height: 0; }
  .article-side .latest__list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-right: 8px;
    scrollbar-width: thin;
    scrollbar-color: #DFD7CA transparent;
  }
  .article-side .latest__list::-webkit-scrollbar { width: 7px; }
  .article-side .latest__list::-webkit-scrollbar-track { background: transparent; }
  .article-side .latest__list::-webkit-scrollbar-thumb { background: #DFD7CA; border-radius: var(--r-pill); }

  /* Featured post sets the row height; the rail is pinned inside it and
     scrolls, so a long list never stretches the layout. Scoped to the blog
     listing — the same .latest block is reused as a plain sidebar on an
     article page, where it must size to its own content instead. */
  .featured-grid { grid-template-columns: minmax(0, 1fr) 340px; }
  .featured-grid .latest { position: relative; }
  .featured-grid .latest__inner { position: absolute; inset: 0; }
  .featured-grid .latest__list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-right: 8px;
    scrollbar-width: thin;
    scrollbar-color: #DFD7CA transparent;
  }
  .featured-grid .latest__list::-webkit-scrollbar { width: 7px; }
  .featured-grid .latest__list::-webkit-scrollbar-track { background: transparent; }
  .featured-grid .latest__list::-webkit-scrollbar-thumb { background: #DFD7CA; border-radius: var(--r-pill); }
  .featured-grid .latest__list::-webkit-scrollbar-thumb:hover { background: #C9BFAE; }
  .trust__inner { grid-template-columns: auto 1fr; }
  .logos { justify-content: flex-end; }
  .footer__grid { grid-template-columns: 1.6fr 1fr 1fr 1.2fr; }
  .detail-hero__grid { grid-template-columns: 1.05fr .95fr; }
  .story { grid-template-columns: .9fr 1.1fr; }
  .values-grid { grid-template-columns: repeat(2, 1fr); }
  /* Four columns for the four teachers on the About page. A fifth person
     starts a second row with three gaps, so add people in fours — or drop
     this to repeat(3, 1fr) if the team goes back down to three. */
  .team-grid { grid-template-columns: repeat(4, 1fr); }
  .safety { grid-template-columns: 1fr 1fr; }
}

/* Desktop nav from 900px: a laptop window at 1000-1100px must not fall
   back to a hamburger. */
@media (min-width: 900px) {
  .nav__links { display: flex; }
  .nav__cta { display: flex; }
  .nav__toggle { display: none; }
  .mobile-menu { display: none; }
}

/* --------------------------------------------------------------------------
   Schools page — Catalog, case studies
   -------------------------------------------------------------------------- */
.catalog {
  display: flex; flex-wrap: wrap; align-items: center; gap: clamp(18px, 3vw, 30px);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  padding: clamp(22px, 4vw, 38px);
}
.catalog__ico {
  flex: none; display: grid; place-items: center;
  width: 60px; height: 60px; border-radius: var(--r-md);
  background: var(--brand-tint); color: var(--brand-dark);
}
.catalog__ico svg { width: 30px; height: 30px; }
.catalog__copy { flex: 1 1 280px; }
.catalog__copy h2 { font-size: clamp(1.4rem, 3vw, 2rem); margin-bottom: 8px; }
.catalog__copy p { color: var(--muted); }

/* Layout only — the input's own styling comes from the shared field rules, so
   it can never look like a different site's input. One row on desktop; the
   button drops under the field rather than squeezing it on narrow screens. */
.catalog__form {
  display: flex; flex-wrap: wrap; gap: 12px;
  margin-top: 18px;
}
.catalog__form input { flex: 1 1 240px; min-width: 0; }
.catalog__form .btn { flex: 0 0 auto; }
.catalog__copy .form-success { margin-top: 18px; }
.catalog__copy .field__help { margin-top: 10px; }

/* The catalog follows the formats grid on the same ground, so the two
   sections' paddings stacked into a 208px hole between the grid and the
   card. The card sits one section-gap under the grid instead. */
.section--tint + .section--tint:has(> .wrap > .catalog) { padding-top: 0; }

/* "See the full program catalog" is wider than a phone's note box, and a
   button does not wrap, so it ran out past both edges of the box. */
@media (max-width: 639.98px) {
  .mm-page-schools .section-foot--note .btn { max-width: 100%; white-space: normal; text-align: center; }
}

/* --------------------------------------------------------------------------
   Steps — a plain ordered process (For Schools onboarding)

   Kept deliberately dumb so it survives the move to WordPress. Every step is
   styled entirely on its own: no connector line, no ::before/::after, no
   :nth-child tints, no positioning measured against a sibling. A page builder
   wraps each repeater item in its own div, which silently breaks all four of
   those — so none of them are used. What is left is icon, title, text, and a
   grid, which any builder reproduces one-to-one.

   It is an <ol> because the order is real and a screen reader should announce
   it. The global reset only zeroes <ul>, so the list padding is cleared here.
   -------------------------------------------------------------------------- */
.steps {
  display: grid;
  gap: 30px 24px;
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
  margin: 0;
}
.step { text-align: center; }
.step__ico {
  width: 56px; height: 56px;
  margin: 0 auto 16px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--brand-tint);
  color: var(--brand-dark);
}
.step__ico svg { width: 26px; height: 26px; }
.step h3 { font-size: 1.1rem; margin-bottom: 8px; }
.step p { color: var(--muted); font-size: .93rem; }

/* The column counts live HERE, not up in section 16 with the other responsive
   grids, and that is the whole point.

   They were in section 16, which is earlier in the file than the .steps base
   rule above. Same specificity, so source order decided it and the base
   `grid-template-columns: 1fr` won at every width — the four steps rendered as
   one tall column on desktop for as long as the rule has existed. Keeping the
   overrides directly after the thing they override makes that unfixable by
   accident: anything that beats them now has to be written below this line. */
/* ---- The connector ----
   Drawn on .steps itself, never on the individual steps, and that is what keeps
   the note above true. The warning there is about a page builder wrapping each
   repeater ITEM in its own div, which breaks anything measured against a
   sibling — a ::before on .step, a :nth-child tint, a counter. The container is
   the one element that survives that wrapping untouched, so a line drawn on it
   is safe: the items can be nested as deep as a builder likes and the line
   still runs behind them.

   The step's own icon punches the gap in the line with a ring of page color
   rather than the line being broken into segments. One rule, any number of
   steps, nothing positional.

   --surface is correct only while this section sits on white. If it ever moves
   into a .section--tint, the ring has to become var(--canvas) or the line will
   appear to pass through a white halo. */
.steps { position: relative; }

.steps::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: 27px;              /* center of the 56px icon, less half the 2px line */
  top: 28px;               /* center of the first icon */
  bottom: 0;
  width: 2px;
  /* Fades out below the last node instead of stopping at a measured point. A
     hard stop would need :last-child or a height that assumes four equal
     steps — the first breaks under a page builder, the second breaks the day
     a fifth step is added or one runs to three lines.

     88%, and the number is measured rather than guessed: the line's box runs
     from the first node's center to the container's foot, and within that box
     the fourth node sits at 86.8%. Starting the fade any earlier puts the last
     node inside the fade, which reads as that step being disconnected from the
     process — the one thing the line exists to prevent. */
  background: linear-gradient(var(--brand-tint) 0%, var(--brand-tint) 88%, transparent 100%);
}

/* Vertical timeline below 900px: icon on the rail, text beside it. This
   replaces the old centered stack, which spent a full icon's height on empty
   space above every heading. There is no two-column state any more — two
   columns cannot carry a connector in either direction, and the vertical
   timeline reads better on a tablet than a 2x2 grid of centered blocks. */
.step {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 56px 1fr;
  column-gap: 18px;
  text-align: left;
}
.step__ico {
  grid-column: 1;
  grid-row: 1 / span 2;
  margin: 0;
  box-shadow: 0 0 0 6px var(--surface);
}
.step h3 { grid-column: 2; align-self: center; }
.step p  { grid-column: 2; }

/* Horizontal stepper on desktop. The inset is half a COLUMN, so the line
   starts and ends on the centers of the outer two nodes rather than running to
   the edges of the section.

   Not 12.5%. A column is not an eighth of the container once the 24px gaps are
   counted: with four columns there are three gaps, so a column is
   (100% - 3 x 24px) / 4 and half of one is that over 8. At 941px wide, 12.5%
   put the line at 117.6px while the node actually sits at 108.6px — a 9px
   overhang at each end, which reads as a misaligned line rather than a
   deliberate one. Tied to both the column count and the gap: change either and
   this has to change with it. */
@media (min-width: 900px) {
  .steps { grid-template-columns: repeat(4, 1fr); }

  .steps::before {
    left: calc((100% - 3 * 24px) / 8);
    right: calc((100% - 3 * 24px) / 8);
    top: 28px;
    bottom: auto;
    width: auto;
    height: 2px;
    background: var(--brand-tint);
  }

  .step {
    grid-template-columns: 1fr;
    text-align: center;
  }
  .step__ico {
    grid-row: auto;
    margin: 0 auto 16px;
    box-shadow: 0 0 0 8px var(--surface);
  }
  .step h3, .step p { grid-column: auto; }
}

.case-grid { display: grid; gap: 24px; grid-template-columns: 1fr; }
.case-card {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 14px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  padding: clamp(26px, 3.4vw, 36px);
  transition: transform var(--fast) var(--ease), box-shadow var(--base) var(--ease);
}
.case-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
/* A landscape well, not the 64px square it used to be. The square suited the
   two- and three-letter placeholders that were here before; real school marks
   are mostly wordmarks, and these three range from 1:1 (the Shinagawa crest) to
   4.72:1 (the Ryozan wordmark). Squeezed into 64px square, the widest would
   render 14px tall and unreadable.

   Fixing the HEIGHT and letting width find its own size is what makes a row of
   mismatched logos sit level — the shared baseline is the box, not the artwork.

   No tinted tile any more. It was there to give the initials a background, and
   a filled tile fights a transparent wordmark while showing a hard white square
   behind the Shinagawa JPEG, which carries its own white ground. */
.case-card__logo {
  --logo-w: 160px;
  --logo-h: 64px;
  display: grid; place-items: center;
  width: var(--logo-w); height: var(--logo-h);
  color: var(--brand-dark);
  font-family: var(--font-head); font-weight: 800; letter-spacing: .5px;
}
/* max-* rather than width/height: the image is scaled down to fit the well and
   never stretched up past its own pixels, so a small mark stays crisp instead
   of being blown out to fill the box.

   The caps are the tokens above, NOT 100%. A percentage max-height has to
   resolve against the grid area, and here it silently did not — the square
   Shinagawa crest came out 160x160, four times the height of its 64px well,
   pushing the card open while the two wordmarks behaved. Only max-width was
   doing any work. Naming the sizes removes the resolution step entirely, and
   keeps the well and its cap tied to one pair of values. */
.case-card__logo img {
  max-width: var(--logo-w);
  max-height: var(--logo-h);
  width: auto;
  height: auto;
  object-fit: contain;
}
.case-card h3 { font-size: 1.16rem; }
.case-card__tag {
  display: inline-block;
  padding: 6px 15px; border-radius: var(--r-pill);
  background: var(--canvas); color: var(--muted);
  font-size: .82rem; font-weight: 600;
}
.testi-grid--single { grid-template-columns: 1fr; max-width: 640px; margin-inline: auto; }

/* Home — newsletter / resource guide link below blog teasers */
.newsletter-link {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 8px 14px; margin-top: 36px; text-align: center;
}
.newsletter-link p { color: var(--muted); margin: 0; }
.newsletter-link a {
  font-family: var(--font-head); font-weight: 700; color: var(--brand-dark);
  display: inline-flex; align-items: center; gap: 6px;
}
.newsletter-link a svg { width: 18px; height: 18px; }

@media (min-width: 760px) {
  .case-grid { grid-template-columns: repeat(3, 1fr); }
}

/* --------------------------------------------------------------------------
   Backend-safe text
   Class names, school names and schedules come from the class registry (and
   later, an API). Their length is not ours to control, so anywhere one lands
   is told to wrap instead of pushing the layout sideways. Grid and flex
   children default to min-width:auto, which makes a long unbroken string
   stretch its track — min-width:0 is what actually lets the wrap happen.
   -------------------------------------------------------------------------- */
h1, h2, h3, h4, p, li, b, span, small, label, legend { overflow-wrap: anywhere; }

.class-card__body,
.class-card__foot,
.class-card__meta span,
.class-card__outcome,
.detail-hero__copy,
.detail-hero__fact,
.partner-note p,
.field,
.check span { min-width: 0; }

/* Card titles and teasers clamp to two lines, so a long line from the backend
   can't leave one card taller than its neighbors. The full text is on the
   detail page. The teaser stays a flex row (tick + text), so the clamp goes on
   the inner span — putting -webkit-box on the row itself would break the row. */
.class-card h3 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.class-card__outcome > span {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Long prices/labels wrap under each other rather than colliding. */
.class-card__foot { flex-wrap: wrap; gap: 10px 14px; }
.class-card__link { flex-shrink: 0; }

.u-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;
}

/* WordPress's own name for the same thing. The skip link in header.php and any
   core or plugin markup we don't control use this class, not .u-sr-only, and
   core's copy of the rule ships inside wp-block-library — which mm_dequeue_core()
   drops. Without it here the skip link renders as visible text above the header. */
.screen-reader-text {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Tab brings it back — that is the whole point of a skip link, so it has to
   leave the clip rect, not merely gain a background. z-index clears the
   sticky header's 100. */
.skip-link:focus {
  position: fixed; top: 12px; left: 12px; z-index: 200;
  width: auto; height: auto; margin: 0; overflow: visible;
  clip: auto; clip-path: none;
  padding: 12px 20px;
  background: var(--surface); color: var(--brand);
  font-family: var(--font-head); font-weight: 700;
  border-radius: var(--r-sm); box-shadow: var(--shadow-md);
}

/* Keeps a phrase on one line so a heading breaks where it reads best, without
   hard-coding WHERE the break falls.

   A <br> would do it at one width and be wrong at every other; this only says
   "these words travel together", and the browser still picks the line count to
   suit the space. Wrap the tail of the heading, never the whole thing — a
   nowrap heading that outgrows its column pushes the page sideways.

   Check the phrase still fits at 320px before using it: it cannot break, so
   anything longer than the narrowest column will overflow rather than wrap. */
.u-nowrap { white-space: nowrap; }

/* --------------------------------------------------------------------------
   Partner (tie-up) note — class detail hero + enrollment form
   -------------------------------------------------------------------------- */
.partner-note {
  display: flex; gap: 13px; align-items: flex-start;
  background: var(--brand-tint-2);
  border: 1px solid var(--brand-tint);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-md);
  padding: 16px 18px;
  margin-bottom: 26px;
}
.partner-note[hidden] { display: none; }
.partner-note__ico {
  flex-shrink: 0;
  display: grid; place-items: center;
  width: 32px; height: 32px;
  border-radius: 9px;
  background: var(--brand-tint);
  color: var(--brand-dark);
}
.partner-note__ico svg { width: 17px; height: 17px; }
.partner-note p { margin: 0; color: var(--ink-soft); font-size: .92rem; line-height: 1.6; }
.partner-note b { font-family: var(--font-head); color: var(--ink); }

.cta-strip__alt { margin-top: 20px; font-size: .93rem; }
.cta-strip__alt a {
  font-family: var(--font-head); font-weight: 600;
  color: var(--brand-dark);
  border-bottom: 1.5px solid var(--brand-tint);
  padding-bottom: 2px;
}
.cta-strip__alt a:hover { border-bottom-color: var(--brand); }

/* --------------------------------------------------------------------------
   Enrollment form
   Deliberately plain: one column of labeled fields, same .field styling as
   the contact form. No numbered sections — the form is short enough not to
   need them.
   -------------------------------------------------------------------------- */
.field[hidden] { display: none; }

.form-note {
  color: var(--muted); font-size: .9rem;
  margin: 14px 0 0;
  text-align: center;
}
.form-note[hidden] { display: none; }
.form-note a { color: var(--brand-dark); font-weight: 600; border-bottom: 1.5px solid var(--brand-tint); }
.form-note a:hover { border-bottom-color: var(--brand); }

.form-foot { color: var(--muted); font-size: .86rem; text-align: center; margin-top: 14px; }

/* Checkbox rows — the whole row is the label, so the tap target is large. */
.check {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 0;
  cursor: pointer;
}
.check input[type="checkbox"] {
  flex-shrink: 0;
  appearance: none; -webkit-appearance: none;
  width: 22px; height: 22px; margin: 0;
  border: 1.5px solid var(--line);
  border-radius: 7px;
  background: var(--canvas);
  cursor: pointer;
  transition: background var(--fast), border-color var(--fast);
}
.check input[type="checkbox"]:checked {
  background: var(--brand) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center no-repeat;
  border-color: var(--brand);
}
.check input[type="checkbox"]:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; }
.check span { color: var(--ink-soft); font-size: .92rem; line-height: 1.55; }
/* Consent sits directly above the submit — give the button room to breathe. */
.check + .btn { margin-top: 12px; }

/* Fields flagged by validation */
.field input.is-invalid,
.field select.is-invalid,
.field textarea.is-invalid,
.check input.is-invalid,
.catalog__form input.is-invalid {
  border-color: var(--accent-sun);
  background-color: #FFF9EF;
}

/* --------------------------------------------------------------------------
   Simple form pages (trial + enrollment)
   One centered column — no sidebar, nothing to collide with.
   -------------------------------------------------------------------------- */
.form-narrow { max-width: 720px; margin-inline: auto; }

/* The chosen class's school / schedule / ages / tuition, on one line under the
   Class select. Replaces the old summary panel: same facts, no extra furniture. */
.class-hint {
  display: flex; flex-wrap: wrap; gap: 8px 16px;
  margin: -8px 0 22px;
  color: var(--muted); font-size: .88rem;
}
.class-hint:empty { display: none; }
.class-hint[hidden] { display: none; }
.class-hint span { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.class-hint svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--brand); }

/* Read-only rows — a value the visitor is shown, not asked for (the class they
   clicked, the schedule that comes with it). Styled to read as information
   rather than as a disabled input someone might try to fix. */
.field--static { margin-bottom: 20px; }
.field__label {
  display: block;
  font-family: var(--font-head); font-weight: 600; font-size: .9rem;
  color: var(--ink); margin-bottom: 8px;
}
.field__static {
  margin: 0;
  font-family: var(--font-head); font-weight: 700;
  font-size: 1.15rem; color: var(--ink);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.field__static--sm { font-size: 1rem; font-weight: 600; }

.field__help { margin: 7px 0 0; color: var(--muted); font-size: .84rem; }
.field__error { margin: 7px 0 0; color: #8C271F; font-size: .84rem; }
.field__error[hidden] { display: none; }

/* Square's Web Payments SDK mounts its own iframe here — the border/padding
   matches the plain <input> fields around it so the card element doesn't
   read as a different kind of control. */
#mm-card-container {
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
}

/* "Optional" tag on a label, so nobody wonders whether they must answer. */
.opt {
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--canvas-2);
  color: var(--muted);
  font-family: var(--font-body); font-weight: 600;
  font-size: .75rem; letter-spacing: .04em; text-transform: uppercase;
  vertical-align: middle;
}

/* Trial fee — present only when the trial is a paid one. The row is removed
   (not emptied) when it isn't, so nothing collapses to a stray gap. */
.fee-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  margin: -8px 0 22px;
}
.fee-row[hidden] { display: none; }
.fee-pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 13px;
  border-radius: var(--r-pill);
  background: var(--brand-tint);
  color: var(--brand-darker);
  font-family: var(--font-head); font-weight: 700; font-size: .84rem;
}
.fee-pill svg { width: 14px; height: 14px; flex-shrink: 0; }
.fee-note { color: var(--muted); font-size: .84rem; min-width: 0; }

/* A topic arrived preselected from another page — show it was set for you. */
.field select.is-prefilled {
  border-color: var(--brand);
  background-color: #fff;
  box-shadow: 0 0 0 4px rgba(188, 90, 27, .14);
}

.form-back { margin-top: 20px; text-align: center; font-size: .9rem; }
.form-back a { color: var(--brand-dark); font-weight: 600; font-family: var(--font-head); }
.form-back a:hover { color: var(--brand-darker); }

/* --------------------------------------------------------------------------
   Collaborations — specialists, school partners, partner CTA
   Used by the #collaborations section on the home page.

   .collab-card is not .team-card with extra bits bolted on: a team card is a
   face and a job title, and this one has to carry a bio and a "what they do for
   us" line, which changes the proportions of everything. The photo is a band
   across the top rather than a 1:1 square, or the card runs taller than a
   phone screen before the text even starts.

   Nothing here is styled by its position among its siblings — no :nth-child
   tints, no counters, no measuring against a neighbor. A page builder wraps
   each repeater item in its own div and silently breaks all of those, so the
   card is self-contained and the grid ports to WordPress as-is.
   -------------------------------------------------------------------------- */
.collab-grid {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
}

.collab-card {
  display: flex;
  flex-direction: column;
  gap: 11px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 22px;
  box-shadow: var(--shadow-xs);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease);
}
.collab-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }

/* Portrait and identity on one line. Stacked, the avatar sat alone on a row
   with the whole width beside it empty, and the card paid ~40px of height for
   that gap. Side by side, the dead space becomes the name. */
.collab-card__head {
  display: flex;
  align-items: center;
  gap: 14px;
}
.collab-card__id { min-width: 0; }

/* A fixed-size round avatar, NOT a full-bleed banner — and the fixed size is
   the whole point.

   A banner sized by ratio is sized by the column, so it grows as the column
   grows: 264px tall in a three-up row at 1200px, but 292px in a two-up row at
   900px, and taller still stacked on a phone. The card got LONGER as the screen
   got smaller, which is the opposite of what it should do. Every attempt to cap
   that with max-height just re-crops the image and cuts the faces off.

   In px, the avatar is the same 92px on a 320px phone and a 4K monitor, so card
   height barely moves across the whole range — it is set by the text, which is
   what should set it. That took roughly 170px off the card at desktop.

   The files in images/ are already cropped square with the MIRAI MINDS badge
   removed, so this needs no object-position nudging and no zoom transform. The
   badge was cropped in the file rather than hidden with CSS on purpose: a
   transform tuned to hide it would be tuned to these three photos only, and the
   next portrait someone uploads in WordPress would crop wrong.

   The gradient underneath is not decoration — it is what shows when the image
   field is empty, which in WordPress it will be at least once. Empty, the card
   keeps its exact height rather than collapsing and dragging the row with it. */
.collab-card__photo {
  width: 74px; height: 74px;
  flex-shrink: 0;
  border-radius: 50%;
  display: grid; place-items: center;
  color: #fff;
  font-family: var(--font-head); font-weight: 800; font-size: 1.4rem;
  letter-spacing: .04em;
  background: linear-gradient(150deg, var(--brand), var(--brand-darker));
  overflow: hidden;
}
.collab-card__photo img { width: 100%; height: 100%; object-fit: cover; }
.collab-card__photo svg { width: 30px; height: 30px; }

/* Name first, role underneath — and the role is a plain line, not a pill.

   It used to be a tinted pill sitting ABOVE the name, which inverted the
   hierarchy twice over: the reader met the job title before the person, and the
   pill's fill pulled the eye off the heading that was supposed to be the loudest
   thing on the card. A name is the identity and the role qualifies it, which is
   the order every profile in the world uses.

   Dropping the fill also lets a long title like "Mental Health Specialist" wrap
   quietly beside a 74px avatar instead of wrapping as a two-line pill. */
.collab-card h3 { font-size: 1.1rem; margin-bottom: 3px; }

.collab-card__role {
  display: block;
  color: var(--brand-dark);
  font-family: var(--font-head); font-weight: 600; font-size: .8rem;
  line-height: 1.35;
}

.collab-card__bio { color: var(--muted); font-size: .9rem; }

/* The line the reader came for: what this person actually does for us. Tinted
   so it survives being skimmed past the bio.

   Bottom-aligned, and that only works because the bios are kept to a similar
   length. An earlier version dropped margin-top: auto after one long bio and
   two short ones opened a band of white space in the short cards wide enough to
   look like a rendering fault. The fix for that is even bios, not free-floating
   boxes: without the auto margin each box starts wherever its own bio happens
   to stop, so the row goes ragged again the moment a bio wraps differently at
   some width. With the bios level, this pins the three boxes to one line and
   absorbs a one-line overrun instead of stepping the whole row. */
.collab-card__does {
  margin-top: auto;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--canvas);
  border-left: 3px solid var(--brand);
  color: var(--ink-soft);
  font-size: .88rem;
  line-height: 1.55;
}
.collab-card__does b { font-family: var(--font-head); color: var(--ink); }

/* ---- .collab-card--school : the For Schools variant ----
   Same markup as the home page card, restyled for a different reader. A
   principal is being asked to trust us with their students and their timetable,
   so this reads closer to a credential than to an introduction: square photo
   rather than round, the sage half of the brand palette rather than terracotta,
   and the closing line ruled off instead of sitting in a tinted box.

   It is a modifier and not a second component on purpose — see the note in
   schools.html. Everything below is pure CSS, so the two pages still share one
   WordPress template with one extra class on it. */
.collab-card--school {
  border-left: 3px solid var(--accent-sage);
}
/* Rounded square, and slightly larger. The circle on the home page reads as a
   friendly portrait; a squared frame reads as a staff record, which is the
   register this page is written in. */
.collab-card--school .collab-card__photo {
  width: 84px; height: 84px;
  border-radius: var(--r-md);
}
.collab-card--school .collab-card__role {
  color: #4E7C7A;                 /* sage, darkened to hold AA against white */
  text-transform: uppercase;
  letter-spacing: .05em;
  font-size: .75rem;
}
/* Ruled off rather than boxed. Two tinted callouts with a terracotta edge, one
   per page, made the pages look like the same page; a rule does the same job of
   separating the line without repeating the home page's device. */
.collab-card--school .collab-card__does {
  background: transparent;
  border-left: none;
  border-top: 1px solid var(--line);
  border-radius: 0;
  padding: 12px 0 0;
}

/* An unfilled slot, and it has to look like one. A dashed edge and a muted
   header are the difference between "we are hiring for this" and a person a
   parent believes we already have. */
.collab-card--tbd {
  border-style: dashed;
  border-color: #DCD4C8;
  background: transparent;
  box-shadow: none;
}
/* The placeholder card is not a real partner yet, so it must not answer the
   cursor like one. */
.collab-card--tbd:hover { transform: none; box-shadow: none; }
.collab-card--tbd .collab-card__photo {
  background: var(--canvas-2);
  color: var(--muted);
}
.collab-card--tbd .collab-card__role {
  background: transparent;
  border: 1px dashed #D2C8B9;
  color: var(--muted);
}
.collab-card--tbd h3 { color: var(--muted); }
.collab-card--tbd .collab-card__does {
  background: transparent;
  border-left-color: #D2C8B9;
  color: var(--muted);
  padding-left: 12px;
}

/* ---- School partners ----
   Laid out like the specialist cards: mark left, text right. Centered and
   stacked, each row spent ~90px of height on an avatar sitting alone above its
   own caption, and rows with a short caption grew a band of white space to
   match the tallest one in the row. On its side, the height is set by the text
   and a short caption simply makes a shorter row. ~88px against ~175px before.

   This is also the shape that survives a CMS. Nothing is positional, nothing is
   measured against a sibling, and the row does not care how many others exist —
   so three schools today and twelve later lay out the same way with no rule to
   revisit. */
.collab-schools { margin-top: clamp(44px, 5.5vw, 64px); }
.collab-schools + .collab-schools { margin-top: clamp(36px, 4.5vw, 52px); }
.collab-schools__head { text-align: center; margin-bottom: 24px; }
.collab-schools__head h3 { font-size: clamp(1.2rem, 2.3vw, 1.5rem); margin-bottom: 6px; }
.collab-schools__head p { color: var(--muted); font-size: .93rem; }

.collab-logos {
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr;
}
.collab-logo {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  transition: transform var(--fast) var(--ease), box-shadow var(--base) var(--ease);
}

.collab-logo:hover { transform: translateY(-4px); box-shadow: var(--shadow-sm); }

.collab-logo__mark {
  display: grid; place-items: center;
  width: 52px; height: 52px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  background: var(--canvas-2); color: var(--brand-dark);
  font-family: var(--font-head); font-weight: 800; font-size: .88rem;
  letter-spacing: .5px;
  overflow: hidden;
}
/* contain, not cover — a logo cropped to fill is a logo with its edges cut off.
   The padding keeps it off the tile edge whatever shape it comes in. */
.collab-logo__mark img {
  width: 100%; height: 100%;
  object-fit: contain;
  padding: 7px;
}

/* min-width: 0 is load-bearing, not tidiness. A flex item defaults to
   min-width: auto, so one long unbroken string — a school name pasted from a
   CMS, a URL — would push the row wider than its column instead of wrapping,
   and drag a horizontal scrollbar onto the whole page. */
.collab-logo__text { min-width: 0; }
.collab-logo__text h4 {
  font-size: .98rem;
  margin-bottom: 2px;
  overflow-wrap: anywhere;
}
.collab-logo__text p { color: var(--muted); font-size: .8rem; line-height: 1.45; }

/* ---- Partner school logo carousel ("Schools we teach in") ----
   Same seamless-loop mechanic as .trust__rail: the track holds 2 identical
   rows and travels exactly 1 of them (-50%), so the animation restarts on a
   frame identical to the one it left and the seam is invisible.

   overflow: hidden on the frame is load-bearing — the track is wider than the
   viewport by design, and without it those 28 logos drag a horizontal
   scrollbar onto the whole page. */
.logo-carousel {
  position: relative;
  overflow: hidden;
  padding: 8px 0;
  /* Fades both ends so logos enter and leave instead of being chopped off at
     a hard edge. Duplicated unprefixed for Safari. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.logo-carousel__track {
  display: flex;
  width: max-content;
  animation: logo-carousel-scroll 45s linear infinite;
}

/* Pause on hover and on keyboard focus. focus-within matters for more than
   politeness: without it a tab stop inside the strip would keep sliding out
   from under the user. */
.logo-carousel:hover .logo-carousel__track,
.logo-carousel:focus-within .logo-carousel__track { animation-play-state: paused; }

.logo-carousel__row {
  display: flex;
  align-items: center;
  gap: clamp(28px, 5vw, 56px);
  padding: 0 clamp(14px, 2.5vw, 28px) 0 0;
  margin: 0;
  list-style: none;
  flex: 0 0 auto;
}

/* Every source file is square (1080px lockups and 160px marks alike), so the
   item box is square too. A wider-than-tall box would be the bug, not the
   layout: the global `img { max-width: 100% }` on line 95 sets no max-height,
   so a square image fills the box's WIDTH first and then runs past its shorter
   height — and the frame's overflow:hidden crops the overhang. Keeping the box
   square means width and height constrain the image identically and neither
   can win that race. */
.logo-carousel__item {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: clamp(74px, 8vw, 96px);
  height: clamp(74px, 8vw, 96px);
}

/* object-fit is dropped on purpose: it only does work on a replaced element
   whose box has been forced to a size, and these size themselves from their
   own intrinsic ratio. The two max-* bounds are what actually fit the logo
   into the square box. */
.logo-carousel__item img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  mix-blend-mode: multiply;
  /* Full colour by default. The grayed-at-rest treatment is a hover idea, so
     it is applied only where a pointer can hover — see the block at the end
     of this sheet. On a phone or a tablet these marks are the credibility
     signal and should simply be legible. */
  transition: filter var(--base) var(--ease), opacity var(--base) var(--ease),
              transform var(--fast) var(--ease);
}

@keyframes logo-carousel-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* A sideways-scrolling strip is exactly the vestibular trigger this query
   exists for. Stop the travel and let the row sit still and wrap, rather than
   hiding the logos altogether. */
@media (prefers-reduced-motion: reduce) {
  .logo-carousel__track { animation: none; width: 100%; }
  .logo-carousel { -webkit-mask-image: none; mask-image: none; }
  .logo-carousel__row { flex-wrap: wrap; justify-content: center; }
  /* The second copy is decorative (aria-hidden); with no motion it would just
     print every logo twice. */
  .logo-carousel__row + .logo-carousel__row { display: none; }
  .logo-carousel__item img { transition: none; }
}

/* ---- Partner venue cards (added) ----
   A separate block from .collab-logos above, which is untouched. This one
   carries the real supplied logos and the figures from each partner's own
   material. */
.partner-block { margin-top: clamp(44px, 5.5vw, 64px); }

.partner-grid {
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
}

/* Column flex, not block: it is what lets the CTA sit on the card floor via
   margin-top:auto. The three cards stretch to the tallest of the row, so
   without it a card with a short programs line ends with its button floating
   mid-air while its neighbors' buttons sit at the bottom. */
.partner-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 32px 26px 26px;
  background: color-mix(in srgb, var(--partner-tone) 4%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--partner-tone) 15%, transparent);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
  transition:
    transform var(--fast) var(--ease),
    box-shadow var(--base) var(--ease),
    border-color var(--base) var(--ease),
    background var(--base) var(--ease);
}

/* The house color enters once, as a variable, and the bar and the hover
   border both read it — so a fourth partner needs one new --partner-tone line
   and nothing else. Terracotta, marigold, sage are the existing palette; no
   partner's own brand color is used, since three foreign palettes on one row
   is what makes a partner section look like an ad block. */
.partner-card--brand { --partner-tone: var(--brand); }
.partner-card--sun   { --partner-tone: var(--accent-sun); }
.partner-card--sage  { --partner-tone: var(--accent-sage); }

.partner-card:hover {
  transform: translateY(-8px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
  border-color: color-mix(in srgb, var(--partner-tone) 40%, transparent);
  background: color-mix(in srgb, var(--partner-tone) 8%, var(--surface));
}

/* Fixed height so three logos of three different aspect ratios — a wide
   lockup, a wordmark, a circle — sit on one optical baseline.

   flex, not grid, and --logo-cap is in px rather than a percentage. Both are
   the same lesson: a percentage max-height on a child of an auto-sized track
   has no definite height to resolve against, so it computes to none and the
   logo renders at its full intrinsic size straight over the text below. A px
   ceiling always resolves. --logo-cap comes from the card, since capping three
   different shapes at one height makes the square one look half the size. */
.partner-card__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 64px;
  margin-bottom: 16px;
}
.partner-card__logo img {
  max-width: 100%;
  max-height: var(--logo-cap, 46px);
  width: auto;
  height: auto;
  object-fit: contain;
}

.partner-card__hook {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: .85rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--partner-tone);
  margin-bottom: 6px;
}

.partner-card__name {
  font-size: 1.12rem;
  margin-bottom: 8px;
  overflow-wrap: anywhere;
}
.partner-card__blurb {
  color: var(--ink-soft);
  font-size: .92rem;
  line-height: 1.55;
}

/* <dl> rather than a table: a parent scanning three cards compares Ages to
   Ages, and the fixed label column is what keeps those values on the same line
   across all three. */
.partner-card__facts {
  margin: 16px 0 20px;
  border-top: 1px solid var(--line);
}
.partner-card__facts > div {
  display: grid;
  grid-template-columns: 74px 1fr;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
.partner-card__facts dt {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 3px;
}
/* overflow-wrap: anywhere — the Where values include unspaced Japanese and a
   long program list; without it the longest one widens its column and drags
   a horizontal scrollbar onto the page. */
.partner-card__facts dd {
  font-size: .87rem;
  line-height: 1.5;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

.partner-card__cta { margin-top: auto; }

/* ---- Partner quote ----
   No styling of its own beyond the gap: it is a plain .testi-card, so a school
   voice and a parent voice read as one system rather than two designs. */
.collab-quote { margin-top: clamp(40px, 5vw, 64px); }

/* The section closes on a .newsletter-link — the quiet-link pattern already
   defined further up and used under the blog teasers. It needs nothing here.
   The .collab-cta rules that used to sit at this point are gone with the button
   block they styled. */

/* No phone-specific card rule any more. The avatar-beside-the-name head is
   already a horizontal row at every width, so the card is compact on a 320px
   screen without a second layout to maintain — one less thing to reproduce in
   the WordPress template. */

@media (min-width: 640px) {
  .collab-grid  { grid-template-columns: repeat(2, 1fr); }
  .collab-logos { grid-template-columns: repeat(2, 1fr); }
  .partner-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1000px) {
  .collab-grid  { grid-template-columns: repeat(3, 1fr); }
  .collab-logos { grid-template-columns: repeat(3, 1fr); }
  .partner-grid { grid-template-columns: repeat(3, 1fr); }
}

/* --------------------------------------------------------------------------
   Article sidebar with two sections (Latest + Popular)
   The single-list sidebar fills the viewport and scrolls inside itself; two
   stacked sections must instead size to their own content.
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .article-side__inner--stack { max-height: none; }
  .article-side__inner--stack .latest { display: block; max-height: none; }
  .article-side__inner--stack .latest__list {
    overflow: visible;
    max-height: none;
    padding-right: 0;
  }
}
.article-side__inner--stack .latest + .latest { margin-top: 4px; }

/* --------------------------------------------------------------------------
   17. Card rails — horizontal scroll below 640px

   Below 640px every card grid on the site is a single column, so a phone
   reader scrolls past three full-height cards to reach the next section. These
   rules turn each of those grids into a swipeable rail instead: the cards keep
   their existing look and sit side by side, and the row scrolls horizontally.

   The breakpoint is exactly the one the grids already use. At 640px and up the
   two- and three-column rules in section 16 take over untouched, so nothing
   about the tablet or desktop layout changes.

   Five things have to hold for this not to break, and each is handled below:

     1. The rail must never be wider than .wrap. It is tempting to break out of
        .wrap's gutter with negative inline margins for an edge-to-edge row.
        Do not: body carries overflow-x: hidden while html does not, so the
        page sizes itself to that overflow rather than clipping it, and every
        section on the page renders narrow with a band of white down one side.
        The rail stays inside the gutter — width: 100% and min-width: 0 — and
        the cards align with the section heading above them.

     2. Card width. The children are grid items sized by the column, so once
        the track scrolls they would collapse to their content. Each card gets
        an explicit basis of 86% of the rail: wide enough to read, and it
        leaves a sliver of the next card showing to advertise the scroll.
        Percentages, never vw — the rail is already inset by the gutter, so a
        vw width would exceed the track and widen the page as in (1).

     3. Vertical transforms. The cards no longer lift on hover, and .reveal is
        disabled at this width, so nothing currently overflows the rail
        vertically. The padding stays regardless: overflow-x: auto makes the
        block a scroll container in BOTH axes, so any future vertical transform
        would be clipped top and bottom, and overflow-y: visible does not help
        — it computes back to auto and reintroduces the clip. Padding is the
        only fix that works, and it is cheap to keep.

     4. .testi-card subgrid. That card is grid-row: span 3 + subgrid, which
        only resolves against a grid parent. So .testi-grid stays display: grid
        with a single auto-flow row rather than becoming a flex container, and
        the card's internal three-row rhythm survives.

     5. Scrollbar. A visible bar under a row of cards looks like chrome, not
        content, so it is hidden — the peeking card is the affordance.
   -------------------------------------------------------------------------- */
@media (max-width: 639.98px) {
  /* ---- Rail geometry, in one place ----
     Every number the rails need derives from these two, so the flex rails and
     the grid rail below cannot drift apart, and the prev/next buttons in
     CardRail.jsx measure a real card rather than recomputing any of it.

       --rail-peek  how much of the neighboring card shows, per side
       --rail-gap   the space between two cards
       --rail-pad   the rail's own side padding, and therefore the blank beside
                    a centered card: one gap plus one peek
       --rail-card  what is left for the card itself

     --rail-gap replaces the base grids' 24px/26px for the width of this media
     query. The rails are centered now, so every pixel of gap is paid for twice
     over — once on each side — and two pixels of difference between .grid-3 and
     .class-grid would put their cards at two different widths for no visible
     gain.

     WHY PAD IS EXACTLY GAP + PEEK
     A centered snap position needs the blank beside the card to equal
     (100vw - card) / 2, or the first and last cards can never reach the middle
     and the browser clamps them somewhere short of it. Substituting the card
     width below, that half-blank comes out as gap + peek exactly — which is
     also the padding that puts a peek of --rail-peek on each side. One value
     satisfies both, which is why the centring and the old last-card clamping
     bug are the same fix. */
  .grid-3,
  .class-grid,
  .blog-grid,
  .collab-grid,
  .team-grid {
    --rail-peek: 16px;
    --rail-gap: 16px;
    --rail-pad: calc(var(--rail-gap) + var(--rail-peek));
    --rail-card: calc(100vw - var(--rail-pad) * 2);
  }

  /* The shared rail behavior. */
  .grid-3,
  .class-grid,
  .blog-grid,
  .collab-grid,
  .team-grid {
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: var(--rail-gap);

    overflow-x: auto;
    /* Momentum scrolling on iOS, and a stop at each card rather than a free
       glide that leaves one half off-screen. */
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;

    /* Full-bleed rail. The track runs the whole width of the screen, and the
       gutter is put back as PADDING inside it rather than as a margin outside
       it. The first card therefore still lines up with the section heading,
       but the row's trailing space is one gutter of scrollable track instead
       of a dead strip the cards can never enter — which is what made the right
       side look over-padded next to the left.

       Negative inline margins are what this needs, and they used to be unsafe:
       the pull made the rail wider than .wrap and the excess propagated to the
       document. contain: paint below now scopes each rail's overflow, so the
       break-out no longer widens the page. Verified: documentElement.scrollWidth
       still equals clientWidth at every width with this in place.

       min-width: 0 is what actually lets a flex/grid child become a scroller;
       without it the track resolves to max-content and pushes the page wide.

       The padding is --rail-pad rather than --gutter now that the cards are
       centered: it is the blank a centered card needs on each side, and it is
       what lets the first and last card reach the middle of the screen. See the
       note on the tokens above. It no longer lines the card's left edge up with
       the section heading, which is the trade the centring buys — and the
       headings above these rails are centered themselves, so the card now agrees
       with the heading rather than with the gutter. */
    width: auto;
    min-width: 0;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--rail-pad);

    /* The rail's own box is correctly 335px inside a 375px viewport, but its
       off-screen cards still counted toward the DOCUMENT's scrollable overflow
       region: documentElement.scrollWidth read 791px at a 375px viewport, and
       scaled with it (1045 at 480, 1422 at 639). html { overflow-x: hidden }
       stops that from producing a real scrollbar, so the live page never
       scrolled sideways — but responsive-preview panes and mobile emulators
       size themselves from scrollWidth, which is why the site appeared as a
       narrow column with a white band down the right on small screens.

       contain: paint makes the rail a containing block and a paint boundary,
       so its overflow is scoped to itself and stops propagating to the
       document. scrollWidth drops back to the viewport width at every width.
       Purely a containment hint — the rail still scrolls and nothing about the
       cards' layout or appearance changes. Not overflow-x: hidden/clip on the
       rail: those were measured and do NOT help, because the content is inside
       a scroll container already, not spilling out of it. */
    contain: paint;

    /* Vertical padding is the room for the hover lift and the reveal, which
       would otherwise be clipped by overflow-x. */
    padding-block: 10px 26px;

    /* 0, and it has to be 0. scroll-padding shrinks the snapport, and a centered
       snap position aligns the card's center with the SNAPPORT's center. The
       padding above is already symmetric, so the scrollport's center is the
       screen's center and that is what a card should land on; any scroll-padding
       here would offset the snapport and pull every card off-center by half of
       it. This was --gutter while the cards snapped to `start`, where it was
       what kept them off the screen edge. */
    scroll-padding-inline: 0;

    scrollbar-width: none;
  }

  .grid-3::-webkit-scrollbar,
  .class-grid::-webkit-scrollbar,
  .blog-grid::-webkit-scrollbar,
  .collab-grid::-webkit-scrollbar,
  .team-grid::-webkit-scrollbar,
  .testi-grid::-webkit-scrollbar { display: none; }

  /* flex: none — these must not shrink to fit, that is the whole point.
     The width comes from --rail-card, which is sized against the viewport
     (100vw) rather than as a percentage of the rail: the rail is full-bleed
     with its padding inside it, so a percentage would resolve against the
     padded content box and the peek would drift with the width.

     scroll-snap-align: center, not start. `start` parked every card's left edge
     at the gutter, which meant no card was ever centered and the row read as
     shoved to one side under a centered heading — with the peek on the right
     only, and most of that peek eaten by the gap. Centring puts equal air on
     both sides, and the prev/next buttons carry the "there is more" job the
     one-sided sliver used to do badly.

     min-width: 0 lets the text inside wrap instead of forcing the card to its
     content width. */
  .grid-3 > *,
  .class-grid > *,
  .blog-grid > *,
  .collab-grid > *,
  .team-grid > * {
    flex: 0 0 var(--rail-card);
    min-width: 0;
    scroll-snap-align: center;
  }

  /* min-width: auto is the default on every flex item, and it means "never
     shrink below your longest unbreakable run of content". The card, its body
     and its meta row are all nested flex containers, so that default applies
     at three levels — and .class-card__meta carries strings that cannot break,
     the longest being a school name of ~34 characters. On a 334px screen the
     card is ~253px wide, the name does not fit, and because nothing is allowed
     to shrink the card pushes itself wider than its own flex basis. That
     overflow is what widened the page from the Classes section downward while
     everything above it looked correct.

     Clearing min-width down the whole chain lets the text wrap instead of
     forcing the box. Applied to the other card types too: any of them can be
     given a long word later, and the failure is silent and page-wide when it
     happens. */
  .class-card, .class-card__body, .class-card__meta, .class-card__foot,
  .blog-card, .blog-card__body, .blog-card__foot,
  .collab-card, .collab-card__head, .collab-card__id,
  .feature-card,
  .team-card, .team-card__body, .team-card__certs,
  .testi-card, .testi-card__author {
    min-width: 0;
  }

  /* The meta row is the specific offender: three icon+label pairs on one line.
     Letting it wrap onto several lines is what makes a 34-character school
     name survive a narrow card. The spans keep their icon glued to their own
     label, but a label itself may now break across lines. */
  .class-card__meta { flex-wrap: wrap; }
  .class-card__meta span { min-width: 0; }
  .class-card__meta svg { flex-shrink: 0; }

  /* Same shape in the collab card head: a round photo that must keep its size
     next to a name that must be allowed to wrap. */
  .collab-card__photo { flex-shrink: 0; }

  /* The listing footer puts a price and a link on one row. Allow it to wrap
     rather than stretch the card when both are long. */
  .class-card__foot { flex-wrap: wrap; gap: 10px; }

  /* (The old .testi-grid rail lived here; nothing renders .testi-grid
     since the testimonials block became the .quote-panel rotator.) */

  /* Last line of defense. Everything above fixes the cards we ship today; this
     covers the one we add tomorrow. A single long word — an unbroken school
     name, a URL, a Japanese string with no break opportunity — inside any rail
     card would otherwise re-create the same page-wide overflow. Breaking a
     word mid-way is ugly; a horizontally broken layout is worse, and
     overflow-wrap only acts when the word genuinely cannot fit. */
  .class-grid > * , .blog-grid > *, .collab-grid > *,
  .grid-3 > *, .team-grid > *, .testi-card { overflow-wrap: anywhere; }

  /* ---- Compact the cards ----
     The padding and margins above are tuned for a desktop card roughly 380px
     wide. At 253px the same values run the card to ~594px tall, so a phone
     shows one card and a sliver — the rail stops reading as a row of options
     and the reader has to scroll vertically through a single card to reach the
     price. These are the same proportions, scaled to the narrower box: the
     rhythm between blocks is what shrinks, never the tap targets. */

  /* Body padding is the biggest single win: 26px on four sides at this width
     costs more than the text it frames. */
  .class-card__body { padding: 18px 16px 18px; }

  /* The meta row wraps to 4 lines here (ages, a two-line school name, the
     schedule). Tightening the row gap and the space beneath it takes most of
     the excess without touching legibility. */
  .class-card__meta { gap: 4px 12px; margin-bottom: 10px; font-size: .82rem; }

  /* The title sits between two blocks that both have their own spacing, so it
     only needs a hair beneath it. */
  .class-card h3 { font-size: 1.15rem; margin-bottom: 8px; }

  /* The outcome pill: less internal padding and a much smaller gap to the
     footer, which carries its own border-top and padding already. */
  .class-card__outcome {
    padding: 9px 11px;
    margin-bottom: 12px;
    font-size: .84rem;
  }

  .class-card__foot { padding-top: 12px; }

  /* The photo is a 16/10 band; at this width it is ~158px of the card's height
     before a word of text. 3/2 gives back around 30px and still reads as a
     photograph rather than a strip. */
  .class-card__media { aspect-ratio: 3 / 2; }

  /* Same treatment for the neighboring rails, so the rows stay consistent in
     height with each other. */
  .feature-card { padding: 24px 20px; }
  .feature-card__ico { width: 50px; height: 50px; margin-bottom: 14px; }
  .feature-card__ico svg { width: 25px; height: 25px; }
  .feature-card h3 { font-size: 1.15rem; margin-bottom: 7px; }

  .blog-card__body { padding: 14px 14px 8px; }
  .blog-card h3 { font-size: 1.08rem; margin-bottom: 7px; }
  .blog-card p { margin-bottom: 14px; }

  .testi-card { padding: 22px 20px; }
  .testi-card blockquote { margin-bottom: 16px; }

  .collab-card { padding: 18px; gap: 9px; }
}

/* A rail is a scroll container, so a keyboard user can land on it and needs to
   see where they are. Applies at every width — harmless once the rails become
   ordinary grids again above 640px. */
.grid-3:focus-visible,
.class-grid:focus-visible,
.testi-grid:focus-visible,
.blog-grid:focus-visible,
.collab-grid:focus-visible,
.team-grid:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .grid-3,
  .class-grid,
  .testi-grid,
  .blog-grid,
  .collab-grid,
  .team-grid { scroll-snap-type: none; }
}

/* --------------------------------------------------------------------------
   17b. Rail navigation — prev/next buttons, phones only

   .rail is the positioned wrapper every rail is rendered inside; the buttons
   are its children, NOT the scroller's. They cannot live in the rail itself:
   an absolutely positioned child of a scroll container scrolls away with the
   content, and `contain: paint` above clips anything outside the rail's padding
   box. See components/CardRail.jsx.

   The wrapper is inert at every width — no display, no padding, nothing that
   changes what the grid inside it does — so adding it to the markup could not
   affect the desktop layout. Only `position: relative`, which the buttons
   measure themselves against.
   -------------------------------------------------------------------------- */
.rail { position: relative; }

/* Off at every width by default; the media query below is the only thing that
   turns them on. CardRail also refuses to render them when the rail does not
   overflow, so this is the second of two locks: the CSS one holds during the
   frame after a resize, before the component has remeasured. */
.rail-nav { display: none; }

/* A rail that stays a vertical list on phones. The full Programs listing is
   over a hundred cards; a one-card-wide swipe rail through that is unusable,
   and the filter needs the cards stacked so a parent can scan what is left.
   site-effects.js skips these, so no arrows are ever injected either. */
@media (max-width: 639.98px) {
  .rail--static .class-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    flex-wrap: wrap;
    overflow: visible;
    scroll-snap-type: none;
    margin-inline: 0;
    padding-inline: 0;
    contain: none;
  }
  .rail--static .class-grid > * {
    flex: none;
    width: auto;
    scroll-snap-align: none;
  }
  .rail--static .rail-nav { display: none; }
}

@media (max-width: 639.98px) {
  .rail-nav {
    position: absolute;
    z-index: 2;

    /* The rail carries `padding-block: 10px 26px`, so the cards' own vertical
       center sits 8px above the wrapper's. Without the offset the buttons ride
       visibly low on a tall card. */
    top: calc(50% - 8px);
    transform: translateY(-50%);

    /* 44px is the tap target this file uses everywhere else. The buttons sit at
       the wrapper's edges — one --gutter in from the screen — which lands them
       half over the card and half over the peek beside it, so they read as
       controls on the rail rather than as chrome floating beside it. */
    display: grid;
    place-items: center;
    width: 44px; height: 44px;
    border-radius: 50%;

    /* Terracotta, not marigold. Marigold is the primary-CTA color and there is
       one of those per screen; a pair of marigold circles halfway down the page
       would argue with "Book a Trial" for the same attention. The white ring is
       what keeps the button legible where it overlaps a photograph. */
    background: var(--brand);
    color: #fff;
    border: 2px solid #fff;
    box-shadow: var(--shadow-md);

    /* Held back deliberately. These sit on top of the card, over a photograph
       on most rails and over body copy on the rest, and at full strength two
       solid terracotta discs read as part of the card rather than as controls
       laid over it. At this weight they are visible when looked for and quiet
       when not — the swipe is still the primary gesture, and these are the
       affordance that says so. */
    opacity: .38;

    transition: background var(--fast) var(--ease),
                opacity var(--fast) var(--ease),
                visibility var(--fast) var(--ease),
                transform var(--fast) var(--ease);
  }

  .rail-nav svg { width: 20px; height: 20px; }

  .rail-nav--prev { left: 0; }
  .rail-nav--next { right: 0; }

  .rail-nav:active { background: var(--brand-dark); }

  /* Spent, so gone. At either end of the rail one direction leads nowhere, and
     a control that is present but does nothing asks the reader to work out why
     it will not respond. Fading it out says the same thing without the
     question, and it doubles as a position indicator: one button showing means
     an end of the rail, two means the middle.

     visibility, not display. It is what lets the opacity transition above run —
     display: none would snap the button out mid-fade — and it still takes the
     button out of the accessibility tree, so a screen reader is not offered a
     control that is not there. `disabled` on the element covers the rest: no
     click, and out of the tab order, so a keyboard user cannot land on it
     during the fade either. */
  .rail-nav:disabled {
    opacity: 0;
    visibility: hidden;
    cursor: default;
  }
}

/* --------------------------------------------------------------------------
   Card hover — phones only
   --------------------------------------------------------------------------
   The lift and its shadow are a pointer affordance. On a touch screen :hover
   latches on tap and stays until the next tap elsewhere, so a card sits raised
   with no cursor to explain it — and inside the horizontal rails, a lifted
   card is clipped by the scroll container's top edge.

   transform is what gets neutralised; box-shadow follows it, because a shadow
   with nothing lifted above it reads as a rendering fault. The image zoom on
   .class-card__img / .blog-card__img is deliberately left alone: it happens
   inside an overflow:hidden frame, so it neither moves the card nor clips.

   Last in the file and matched to the 639.98px rail breakpoint, so it beats
   the base rules on source order without needing !important. */
@media (max-width: 639.98px) {
  .feature-card:hover,
  .class-card:hover,
  .testi-card:hover,
  .feature-post:hover,
  .blog-card:hover,
  .outcome-item:hover,
  .value-card:hover,
  .team-card:hover,
  .map-box:hover,
  .case-card:hover,
  .collab-card:hover,
  .collab-logo:hover,
  .partner-card:hover {
    transform: none;
    box-shadow: var(--shadow-xs);
  }
}

/* --------------------------------------------------------------------------
   Bilingual — the header language toggle and Japanese typography.
   The toggle is rendered by mm_language_switcher() (inc/i18n.php) only when
   Polylang is active; 'Noto Sans JP' in the font stacks above is declared by
   fonts-ja.css, which Japanese pages alone load.
   -------------------------------------------------------------------------- */
/* Language switcher — dropdown button */
[data-lang-switcher] {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.lang-switch__btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 12px 7px 14px;
  border: 1.5px solid var(--border, rgba(0,0,0,.12));
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .82rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color .18s ease, background .18s ease, color .18s ease;
  white-space: nowrap;
}
.lang-switch__btn:hover {
  background: var(--brand-tint);
  border-color: var(--brand);
  color: var(--brand-dark);
}
.lang-switch__btn[aria-expanded="true"] {
  background: var(--brand-tint);
  border-color: var(--brand);
  color: var(--brand-dark);
}
.lang-switch__chevron {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  transition: transform .2s ease;
}
.lang-switch__btn[aria-expanded="true"] .lang-switch__chevron {
  transform: rotate(180deg);
}
.lang-switch__dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 130px;
  list-style: none;
  margin: 0;
  padding: 6px;
  background: #fff;
  border: 1.5px solid var(--border, rgba(0,0,0,.10));
  border-radius: 14px;
  box-shadow: 0 8px 28px rgba(0,0,0,.12);
  z-index: 999;
  /* entrance animation */
  transform-origin: top right;
  animation: lang-dropdown-in .15s ease forwards;
}
@keyframes lang-dropdown-in {
  from { opacity: 0; transform: scale(.95) translateY(-4px); }
  to   { opacity: 1; transform: scale(1)  translateY(0); }
}
.lang-switch__dropdown[hidden] { display: none; }
.lang-switch__dropdown li { margin: 0; padding: 0; }
.lang-switch__dropdown a {
  display: block;
  padding: 9px 14px;
  border-radius: 10px;
  text-decoration: none;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .85rem;
  color: var(--muted);
  transition: background .14s ease, color .14s ease;
  white-space: nowrap;
}
.lang-switch__dropdown a:hover {
  background: var(--brand-tint);
  color: var(--ink);
}
.lang-switch__dropdown li[aria-selected="true"] a {
  background: var(--brand-tint);
  color: var(--brand-dark);
}
/* block variant (mobile menu) */
.lang-switch--block {
  display: flex;
  justify-content: center;
  margin-bottom: 14px;
}
.lang-switch--block .lang-switch__dropdown {
  right: auto;
  left: 50%;
  transform-origin: top center;
  transform: translateX(-50%);
}
.lang-switch--block .lang-switch__btn[aria-expanded="true"] ~ .lang-switch__dropdown {
  transform: translateX(-50%) scale(1) translateY(0);
}

/* Japanese runs longer and has no word spaces: looser leading, safe wrapping,
   proportional kana in headings, and none of the Latin letter-spacing. */
:lang(ja) { line-height: 1.85; overflow-wrap: anywhere; letter-spacing: 0; }
:lang(ja) h1, :lang(ja) h2, :lang(ja) h3, :lang(ja) h4 { line-height: 1.4; letter-spacing: 0; font-feature-settings: 'palt'; }
:lang(ja) .btn, :lang(ja) .eyebrow { letter-spacing: 0; }

/* ==========================================================================
   Class page: the teacher's photo, from the linked Educator record. Laid
   over the "Image Coming Soon" box exactly as .class-card__img is.
   ========================================================================== */
.teacher-card__photo img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The closing line under a card grid (Program Cards block): a centred
   paragraph and a button, as the Classes page's "Still unsure?" strip. */
.section-foot--note {
  text-align: center;
  margin-top: 44px;
}
.section-foot--note p {
  margin-bottom: 18px;
}

/* --------------------------------------------------------------------------
   14h. From the classroom — session photos on a program page
   (inc/session-photos.php). Real shots from the CRM, never placeholders.
   -------------------------------------------------------------------------- */
.session-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 18px;
}
.session-gallery__item {
  margin: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  background: var(--canvas-2);
}
.session-gallery__item img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
@media (max-width: 639.98px) {
  .session-gallery { grid-template-columns: repeat(2, 1fr); gap: 12px; }
}

/* A school program's "ask the school" line, where the buttons would be. */
.class-actions__note { margin: 0; font-size: .95rem; color: var(--ink-soft, var(--ink)); }

/* --------------------------------------------------------------------------
   Whole card clickable. The "See Full Details" / "Read" link is stretched
   over its card; buttons and other links inside sit above it.
   -------------------------------------------------------------------------- */
.class-card { position: relative; }
.class-card__link::after,
.blog-card__link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}
/* "Read" and its arrow on one line: the link was inline, so the arrow
   wrapped under the word next to the date. */
.blog-card__link { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }

/* Program card photos: keep faces in the frame when the card ratio crops a
   3:2 photo (Forest School was cut through the child's head at centre). */
.class-card__img { object-position: 50% 35%; }

/* A program whose teacher has no photo: one column, no empty box. */
/* A teacher with no photo (1.5.22): a monogram keeps the two-column card and
   a readable width, instead of one wide box of text under a large heading. */
.teacher-card--mono { grid-template-columns: 148px 1fr !important; max-width: 760px; margin-inline: auto; }
.teacher-card__mono {
  align-self: stretch;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: linear-gradient(150deg, var(--sage-deep), var(--sage-ink));
  color: #fff;
  min-height: 148px;
}
.teacher-card__mono span {
  font-family: var(--font-head); font-size: 2.6rem; letter-spacing: .02em;
}
@media (max-width: 639.98px) {
  .teacher-card--mono { grid-template-columns: 1fr !important; }
  .teacher-card__mono { min-height: 104px; }
  .teacher-card__mono span { font-size: 2rem; }
}

/* Touch targets on phones and tablets: footer, contact and card links sat at
   22 to 35px tall in a dense column. 44px per row with the padding doing
   the work, so the visual rhythm barely changes. */
@media (max-width: 899.98px) {
  .footer__col ul { gap: 0; }
  .footer__col a { min-height: 44px; padding-block: 6px; }
  .footer__bottom a { display: inline-block; padding-block: 10px; }
  /* The credits link sits mid-sentence: as an inline-block its 44px box
     became the height of the whole second line of the note. Inline, the
     padding still widens the tap area but leaves the line spacing alone. */
  .footer__bottom .site-footer__credits a { display: inline; padding-block: 12px; }
  .contact-row a { display: inline-flex; align-items: center; min-height: 44px; padding-block: 6px; }
  .blog-card__link { min-height: 44px; }
}

/* Testimonials on phones: each slide sizes itself. The desktop subgrid made
   every card as tall as the longest quote, so a short one carried a screen
   of white between the quote and the author. */
@media (max-width: 639.98px) {
  .testi-grid { grid-template-rows: none; align-items: start; }
  .testi-card { grid-row: auto; grid-template-rows: auto auto auto; }
}
.class-card .btn, .class-card__aside, .blog-card .blog-card__cat, .class-card__foot--book a { position: relative; z-index: 2; }
.class-card:hover, .blog-card:hover { box-shadow: 0 14px 34px rgba(30, 20, 14, .10); }
.class-card:focus-within, .blog-card:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The "View all programs" / "Read the blog" row under a section grid. */
.section-foot { margin-top: clamp(28px, 4vw, 44px); display: flex; justify-content: center; }
.section-foot .btn--secondary { background: #fff; }

/* The closing line under the venue cards on the Programs page. The generic
   .section-foot flex row above put the sentence and the button side by side
   with the button hanging off the right edge; the note is a centred column
   in a soft box instead: sentence, then button. */
.section-foot--note {
  flex-direction: column;
  align-items: center;
  gap: 18px;
  max-width: 720px;
  margin-inline: auto;
  padding: clamp(24px, 3vw, 36px) clamp(20px, 3vw, 40px);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}
.section-foot--note p { margin: 0; color: var(--ink); font-size: clamp(1rem, 1.4vw, 1.12rem); max-width: 560px; }
.section--tint .section-foot--note { background: var(--surface); }
/* Phone: the large button's nowrap label came to 332px in a 280px box and
   spilled out of both sides of it. The box drops its frame and padding, so
   the button has the full column, a step down in size fits the label on one
   line, and it may wrap rather than overflow if an editor writes a longer one. */
@media (max-width: 639.98px) {
  .section-foot.section-foot--note,
  .section--tint .section-foot.section-foot--note { padding: 0; background: none; border: 0; box-shadow: none; }
  .section-foot--note .btn { max-width: 100%; padding-inline: 20px; font-size: .92rem; white-space: normal; text-align: center; }
}

/* Four venue cards: two by two, then four across on a wide screen, instead
   of three plus one orphan on its own row. */
@media (min-width: 640px) {
  .grid-3--four { grid-template-columns: repeat(2, 1fr); }
}

/* Hero avatars as photos */
.hero__avatars span { background-size: cover; background-position: center; background-color: var(--line); }

.site-footer__credits { font-size: .78rem; opacity: .7; margin-top: 6px; }

/* A partner school's page: the mark above the title. */
/* The mark sits centred in a white card above the eyebrow. The global
   img { display: block } rule left it hugging the left edge of a centred hero;
   the flex wrapper centres it and the card gives a transparent or white-on-
   white mark an edge to sit against. */
.school-hero__mark { display: flex; justify-content: center; margin-bottom: 22px; }
.school-hero__mark span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 26px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}
.school-hero__mark img { max-height: 72px; width: auto; max-width: 220px; object-fit: contain; }
.school-hero__mark + .eyebrow { margin-top: 0; }

/* The program grid follows the hero directly: one section's worth of padding
   between them, not the hero's bottom plus the section's top. */
.school-programs { padding-top: clamp(36px, 5vw, 56px); }
.school-programs .section-head { margin-bottom: clamp(24px, 3vw, 36px); }
.logo-carousel__item a { display: grid; place-items: center; width: 100%; height: 100%; }
.trust__logo a { display: inline-flex; align-items: center; height: 100%; }

/* --------------------------------------------------------------------------
   Hero: one screen tall, the photo first on phones and small tablets.
   -------------------------------------------------------------------------- */
.hero {
  /* Tall enough to feel like a first screen on a laptop, never a full
     monitor: a 1440x900 window shows the trust bar under the hero. */
  min-height: min(calc(100svh - var(--header-h)), 700px);
  display: flex;
  align-items: center;
  padding-top: clamp(20px, 3vw, 40px);
  padding-bottom: clamp(20px, 3vw, 40px);
}
.hero > .wrap { width: 100%; }

/* The copy column is what overran the screen: a five-line title at 4.1rem
   plus the sub, button, proof and rating row came to ~830px. The title now
   also scales with the viewport height, so a short window gets a smaller
   heading, and the blocks sit closer together. Measured at 1680×928: copy
   662px, hero 852px against 851px available. */
.hero__title { font-size: clamp(2.2rem, min(4.6vw, 6.5vh), 3.5rem); margin-bottom: 16px; }
.hero__sub { font-size: clamp(1rem, 1.4vw, 1.15rem); margin-bottom: 22px; }
.hero__actions { margin-bottom: 18px; }

/* The photo is the tallest thing in the hero. Capping it keeps copy and photo
   inside one viewport on a short desktop window; the picture is object-fit,
   so a shorter frame crops rather than distorts. */
.hero__photo {
  max-height: calc(100svh - var(--header-h) - 200px);
  min-height: 320px;
}

@media (max-width: 899.98px) {
  .hero { min-height: 0; }

  /* Phones and small tablets read badge, heading, then the photo and its
     cards, then the rest of the copy. The copy column dissolves into the
     grid (display: contents) so the badge and title can be ordered ahead of
     the visual while the sub, button, proof and rating stay after it. The
     .reveal transition on .hero__copy no longer applies to a box-less element,
     which is what the phone rule below wants anyway. Stretch, not the
     desktop grid's center: in a column, center shrink-wraps each child, so
     the photo collapsed to 0px wide on a tablet and the sub and buttons
     drifted off the title's left edge. */
  .hero__grid { display: flex; flex-direction: column; align-items: stretch; gap: 24px; }
  .hero__copy { display: contents; }
  .hero__badge { order: -3; margin-bottom: 0; align-self: flex-start; }
  .hero__title { order: -2; margin-bottom: 0; }
  .hero__visual { order: -1; }

  /* The two floating cards become a flexible row under the photo instead of
     boxes pinned over its corners, where they covered the picture and clipped
     at the screen edge on narrow phones. The photo takes the full first row,
     the cards share the second and wrap when it gets too tight. */
  .hero__visual { display: flex; flex-wrap: wrap; gap: 10px; }
  .hero__photo { flex: 1 1 100%; width: 100%; min-height: 0; max-height: none; aspect-ratio: 16 / 9; }
  .hero__card {
    position: static;
    animation: none;
    flex: 1 1 150px;
    min-width: 0;
    padding: 10px 12px;
  }
  .hero__card .ico { width: 36px; height: 36px; flex-shrink: 0; }
  .hero__card b { font-size: .95rem; }
}

@media (max-width: 639.98px) {
  .hero { padding-top: 16px; padding-bottom: 28px; }
  .hero__grid { gap: 20px; }
  .hero__photo { aspect-ratio: 16 / 9; }
  .hero__title { font-size: clamp(1.9rem, 8.5vw, 2.4rem); margin-bottom: 14px; }
  .hero__sub { font-size: 1rem; margin-bottom: 20px; }
  .hero__actions { margin-bottom: 16px; }
}

/* Program cards: the columns follow the width instead of snapping at two
   breakpoints, so a card is never squeezed on a small tablet or a narrow
   window. auto-fill, not auto-fit: empty tracks stay, so a school with one
   program shows one card the same size as a card in a full row rather than a
   card stretched across the page. 236px min gives three columns on a
   tablet in portrait (810px iPad, where 340px left the third card alone on
   a second row); 340px from 900px gives three at the 1200px wrap. Phones
   keep the swipe rail / stacked list. */
@media (min-width: 640px) {
  .class-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 236px), 1fr)); }
}
/* Three columns hold through the laptop-window range too (a 944px window
   fell back to two with the 340px minimum). */
@media (min-width: 900px) {
  .class-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 256px), 1fr)); }
}
@media (min-width: 1140px) {
  .class-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); }
}

/* --------------------------------------------------------------------------
   Schools slider (For Schools page, case-studies block): every school, one
   card each, stepping one card at a time at a walking pace. Three cards on a
   desktop, two on a tablet, one on a phone; the JS clones the first cards to
   the end so the loop has no visible jump.
   -------------------------------------------------------------------------- */
.case-slider {
  --case-gap: 24px;
  --case-per-view: 1;
  position: relative;
}
/* Room inside the clip for the cards' shadow and their 6px hover lift, which
   were cut off flat at the viewport's edges. The padding is taken back with
   a negative margin, so the slider keeps its size; it stays under the 24px
   gap, so no neighbouring card shows in it. */
.case-slider__viewport { overflow: hidden; padding: 8px 8px 16px; margin: -8px -8px -16px; }
.case-track {
  display: flex;
  gap: var(--case-gap);
  will-change: transform;
}
.case-track.is-animating { transition: transform .75s cubic-bezier(.4, 0, .2, 1); }
.case-track > .case-card {
  flex: 0 0 calc((100% - (var(--case-per-view) - 1) * var(--case-gap)) / var(--case-per-view));
  min-width: 0;
  color: inherit;
}
.case-track > .case-card h3 { color: var(--ink); }
.case-slider__btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: #fff;
  color: var(--brand-dark);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.case-slider__btn:hover { background: var(--brand); color: #fff; border-color: var(--brand); }
.case-slider__btn svg { width: 20px; height: 20px; }
.case-slider__btn--prev { left: -22px; }
.case-slider__btn--next { right: -22px; }
@media (min-width: 640px) { .case-slider { --case-per-view: 2; } }
@media (min-width: 900px) { .case-slider { --case-per-view: 3; } }
/* On a phone the buttons sit inside the one card, so they ride level with
   the logo well (card padding + half the 64px well), beside the mark rather
   than over the school's name, which wraps to the card's full width. */
@media (max-width: 639.98px) {
  .case-slider__btn { top: calc(26px + 32px); }
  .case-slider__btn--prev { left: 6px; }
  .case-slider__btn--next { right: 6px; }
}
@media (prefers-reduced-motion: reduce) {
  .case-track.is-animating { transition: none; }
}

/* ==== Booking pages (trial / enrollment) ==== */
/* Two columns from 900px: the program summary (sticky) beside the form card.
   Below that, one column with the summary first, so the parent sees what
   they're booking before the fields. Tokens only; radii: cards var(--r-lg),
   photo var(--r-md), inputs keep their own. */
.booking {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}
.booking__summary {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: clamp(20px, 3vw, 28px);
}
.booking__summary .eyebrow { margin-bottom: 6px; }
.booking__photo {
  display: block;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 16 / 10;
  height: auto;
  object-fit: cover;
  border-radius: var(--r-md);
  margin-bottom: 18px;
  background: var(--canvas-2);
}
.booking__title {
  font-size: clamp(1.35rem, 2.4vw, 1.7rem);
  line-height: 1.2;
  margin: 0 0 14px;
  overflow-wrap: anywhere;
}
.booking__facts {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  display: flex; flex-direction: column; gap: 8px;
  color: var(--muted); font-size: .92rem;
}
.booking__facts li { display: flex; align-items: center; gap: 9px; min-width: 0; }
.booking__facts svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--brand); }
.booking__facts span { min-width: 0; overflow-wrap: anywhere; }

/* The fee line reads as information, not a warning: tinted, no red. */
.booking__summary .fee-row,
.booking__form .fee-row {
  display: block;
  margin: 0 0 18px;
  padding: 12px 14px;
  border-radius: var(--r-sm);
  background: var(--brand-tint-2);
  border: 1px solid var(--brand-tint);
  color: var(--ink);
  font-size: .92rem;
  line-height: 1.55;
}
.booking__summary .fee-row strong,
.booking__form .fee-row strong {
  font-family: var(--font-head); font-weight: 700;
  color: var(--brand-dark);
  margin-right: 6px;
}
.booking__summary .partner-note { margin: 0 0 18px; }
.booking__summary .form-back { margin-top: 16px; text-align: left; }

.booking__steps-title {
  font-family: var(--font-head); font-weight: 700;
  font-size: .78rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px;
}
.booking__steps {
  list-style: none;
  counter-reset: booking-step;
  margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 10px;
}
.booking__steps li {
  counter-increment: booking-step;
  display: flex; align-items: flex-start; gap: 12px;
  color: var(--ink-soft, var(--ink));
  font-size: .92rem; line-height: 1.5;
}
.booking__steps li::before {
  content: counter(booking-step);
  flex-shrink: 0;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-family: var(--font-head); font-weight: 700; font-size: .8rem;
  display: inline-flex; align-items: center; justify-content: center;
  margin-top: 1px;
}

.booking__form { min-width: 0; }
.booking__form .form-card { border-radius: var(--r-lg); }

/* Inline error summary: the same tokens the field errors use, focusable so
   the page can land keyboard / screen-reader users on it after a redirect. */
.booking .form-error { outline-offset: 3px; }
.booking .form-error:focus { outline: 3px solid #8C271F; }
.booking .form-error:focus:not(:focus-visible) { outline: none; }

@media (min-width: 900px) {
  .booking {
    grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
    gap: 32px;
  }
  .booking__summary {
    position: sticky;
    top: calc(var(--header-h) + 24px);
  }
}

/* /trial/ chooser: three numbered chips under the hero — pick, choose a
   date, we confirm and send the link. Wrap on phones, one row otherwise. */
.steps-strip {
  list-style: none;
  margin: 0 0 clamp(24px, 4vw, 40px);
  padding: 0;
  display: flex; flex-wrap: wrap; gap: 10px 12px;
}
.steps-strip li {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 10px 16px 10px 10px;
  border-radius: var(--r-pill);
  background: #fff;
  border: 1px solid var(--line);
  box-shadow: var(--shadow-xs);
  font-family: var(--font-head); font-weight: 600; font-size: .9rem;
  color: var(--ink);
  min-height: 44px;
}
.steps-strip__num {
  flex-shrink: 0;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--brand-tint);
  color: var(--brand-dark);
  font-weight: 700; font-size: .8rem;
  display: inline-flex; align-items: center; justify-content: center;
}
@media (max-width: 639.98px) {
  .steps-strip li { flex: 1 1 100%; }
}


/* ==========================================================================
   Home sections, redesign (brand guidelines pass)
   Each section its own layout family: photo band, compact grid, quote panel,
   photo panel + 2x2, logo row, profile rail, featured + list, photo CTA.
   ========================================================================== */

/* ---- Why: full-bleed photo band ---- */
.why { position: relative; isolation: isolate; overflow: hidden; }
.why--photo { color: #fff; padding-block: var(--band-lg); }
.why__bg { position: absolute; inset: 0; z-index: -2; }
.why__bg img, .why__img { width: 100%; height: 100%; object-fit: cover; object-position: center 40%; }
/* Terracotta tint, darker where the text sits. */
.why--photo::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(180deg, rgba(60, 28, 10, .72) 0%, rgba(132, 62, 17, .62) 55%, rgba(60, 28, 10, .78) 100%);
}
.why--photo .section-title { color: #fff; }
.why--photo .section-sub { color: rgba(255, 255, 255, .86); }
.why__points {
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr;
}
.why__point {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 18px 20px;
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, .92);
  border: 1px solid rgba(255, 255, 255, .6);
  box-shadow: var(--shadow-sm);
  color: var(--ink);
}
.why--photo .why__point {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .28);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25);
}
.why__ico {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--brand-tint); color: var(--brand-dark);
}
.why--photo .why__ico { background: rgba(255, 255, 255, .18); color: #fff; }
.why__ico svg { width: 22px; height: 22px; }
.why__point h3 { font-size: 1.08rem; margin-bottom: 4px; color: inherit; }
.why__point p { font-size: .94rem; color: var(--muted); }
.why--photo .why__point p { color: rgba(255, 255, 255, .86); }
@media (min-width: 760px) {
  .why__points { grid-template-columns: repeat(3, 1fr); gap: 18px; }
}

/* ---- Class card, compact ---- */
.class-card { border-radius: var(--r-lg); }
/* 16/9 rather than 4/3. On a 352px card a 4/3 photo is 263px - more than a
   third of the card's height before a word of text, and the reason a list of
   programmes reads as a wall. A phone already used 16/9 for exactly this
   reason (see "a shorter photo so a list shows more than one card per
   screen"); this is the same decision at desktop width, and it still reads as
   a photograph rather than a strip. */
.class-card__media { display: block; aspect-ratio: 16 / 9; }
.class-card__tag {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  padding: 6px 11px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .94);
  color: var(--brand-dark);
  font-family: var(--font-head); font-weight: 700; font-size: .74rem;
  letter-spacing: .02em;
  box-shadow: var(--shadow-xs);
}
.class-card__body { padding: 18px 20px 20px; }
.class-card__meta { margin-bottom: 8px; font-size: .82rem; }
.class-card__title { font-size: 1.18rem; margin-bottom: 8px; line-height: 1.25; }
.class-card__title a { color: inherit; }
/* The title link covers the card, so a click anywhere opens the program. */
.class-card { position: relative; }
.class-card__title a::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.class-card__foot, .class-card__foot--book { position: relative; z-index: 2; }
.class-card__when {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .82rem; color: var(--muted);
  margin-bottom: 14px;
}
.class-card__when svg { width: 15px; height: 15px; color: var(--brand); flex: none; }
.class-card__foot { padding-top: 10px; }
.class-card:hover { transform: translateY(-4px); }
.class-card:focus-within { box-shadow: var(--shadow-md); }
.class-card:nth-child(2) .class-card__media,
.class-card:nth-child(3) .class-card__media { background: linear-gradient(150deg, var(--sage-tint), var(--canvas-2)); color: var(--sage-deep); }

/* ---- Testimonials: one quote at a time ---- */
.quote-panel {
  position: relative;
  max-width: 860px;
  margin-inline: auto;
  padding: clamp(28px, 5vw, 56px) clamp(22px, 5vw, 64px) clamp(24px, 4vw, 40px);
  border-radius: var(--r-xl);
  background:
    url(../images/pattern-crane.svg) right -40px bottom -60px / 260px 260px no-repeat,
    var(--gold-tint);
  border: 1px solid #F0E6C0;
  text-align: center;
}
.quote-panel__mark {
  font-family: var(--font-head); font-size: 4.4rem; line-height: .5;
  color: var(--brand); opacity: .35;
  height: 30px; margin-bottom: 8px;
}
/* pan-y: a vertical drag still scrolls the page, a horizontal one is the
   swipe in site-effects.js. Without it the browser claims both axes and the
   gesture never reaches the script. */
/* pan-y: a vertical drag still scrolls the page, a horizontal one is the
   swipe in site-effects.js. Without it the browser claims both axes and the
   gesture never reaches the script.
   The grab cursor is the only thing that says the panel can be dragged with
   a mouse at all — without it the feature is invisible on a laptop. */
.quote-panel__slides { position: relative; display: grid; touch-action: pan-y; cursor: grab; }
.quote-panel__slides.is-dragging { cursor: grabbing; }
.quote-panel__slides:has(> :only-child) { cursor: auto; }
.quote-panel__slide {
  grid-area: 1 / 1;
  margin: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity .5s var(--ease), transform .5s var(--ease), visibility .5s;
}
.quote-panel__slide.is-active { opacity: 1; visibility: visible; transform: none; position: relative; }
.quote-panel blockquote { margin: 0 auto 22px; max-width: 34em; }
.quote-panel blockquote p {
  font-family: var(--font-head);
  font-size: clamp(1.15rem, 2vw, 1.5rem);
  line-height: 1.4;
  color: var(--ink);
  font-weight: 500;
}
.quote-panel__author { display: inline-flex; align-items: center; gap: 12px; text-align: left; }
.quote-panel__avatar {
  width: 46px; height: 46px; border-radius: 50%; flex: none;
  background: linear-gradient(140deg, var(--brand), var(--brand-darker));
  display: grid; place-items: center; color: #fff;
  font-family: var(--font-head); font-weight: 700; font-size: 1rem;
}
.quote-panel__author b { display: block; color: var(--ink); font-family: var(--font-head); font-size: .98rem; }
.quote-panel__author small { color: var(--muted); font-size: .82rem; }
.quote-panel__dots { display: flex; justify-content: center; gap: 6px; margin-top: 22px; }
.quote-panel__dot {
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border-radius: 50%;
}
.quote-panel__dot::before {
  content: "";
  width: 9px; height: 9px; border-radius: 50%;
  background: rgba(188, 90, 27, .3);
  transition: transform var(--fast), background var(--fast);
}
.quote-panel__dot:hover::before { background: rgba(188, 90, 27, .6); }
.quote-panel__dot.is-active::before { background: var(--brand); transform: scale(1.25); }
@media (prefers-reduced-motion: reduce) {
  .quote-panel__slide { transition: none; }
}

/* ---- Safety: photo panel + 2x2 ---- */
.safety { align-items: stretch; }
.safety__panel { position: relative; isolation: isolate; overflow: hidden; display: flex; align-items: flex-end; }
.safety__bg { position: absolute; inset: 0; z-index: -2; }
.safety__bg img, .safety__img { width: 100%; height: 100%; object-fit: cover; }
.safety__panel--photo::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(60, 28, 10, .15) 0%, rgba(60, 28, 10, .55) 45%, rgba(132, 62, 17, .92) 100%);
}
.safety__panel--photo { min-height: 360px; }
.safety__copy { position: relative; }
.safety__panel > p, .safety__copy p { color: rgba(255,255,255,.9); }
.safety__list { grid-template-columns: 1fr; gap: 14px; }
.safety__row { border-radius: var(--r-lg); padding: 18px 20px; }
.safety__row .ico { background: var(--sage-tint); color: var(--sage-deep); }
@media (min-width: 640px) {
  .safety__list { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 900px) {
  .safety { grid-template-columns: 1fr 1.15fr; }
}

/* ---- Partners: static logo row ---- */
.partners { padding-block: clamp(40px, 6vw, 72px); }
.partners .section-head { margin-bottom: clamp(20px, 3vw, 32px); }
.partners__row {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 18px clamp(28px, 4vw, 56px);
  padding-right: 0;
}
.partners__item { flex: 0 0 auto; }
.partners__mark { width: 150px; height: 72px; }
.partners__mark img { max-height: 56px; max-width: 140px; mix-blend-mode: multiply; transition: filter var(--base), opacity var(--base); }

/* ---- Educators: profile rail ---- */
.collab-grid { gap: 16px; }
.collab-card { padding: 18px; gap: 10px; border-radius: var(--r-lg); }
.collab-card__photo { width: 64px; height: 64px; }
.collab-card__bio {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: .88rem;
}
.collab-card__does { display: none; }
@media (min-width: 640px) {
  .collab-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 900px) {
  .collab-grid { grid-template-columns: repeat(4, 1fr); }
  .collab-grid--three { grid-template-columns: repeat(3, 1fr); }
}

/* ---- Blog: featured + list ---- */
.blog-split { display: grid; gap: 18px; grid-template-columns: 1fr; }
.blog-card--featured { padding: 0; overflow: hidden; }
.blog-card--featured .blog-card__media { border-radius: 0; aspect-ratio: 16 / 9; }
.blog-card--featured .blog-card__body { padding: 20px 22px 18px; }
.blog-card--featured h3 { font-size: clamp(1.25rem, 2vw, 1.6rem); }
.blog-split__list { display: grid; gap: 12px; align-content: start; }
.blog-row {
  display: grid; grid-template-columns: 108px 1fr; gap: 14px; align-items: center;
  padding: 10px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  transition: transform var(--base) var(--ease), box-shadow var(--base) var(--ease), border-color var(--base);
}
.blog-row:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--brand-tint); }
.blog-row__media {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  border-radius: var(--r-sm);
  background: linear-gradient(150deg, var(--sage-tint), var(--canvas-2));
}
.blog-row__img { width: 100%; height: 100%; object-fit: cover; }
.blog-row__cat {
  display: block;
  font-family: var(--font-head); font-weight: 700; font-size: .72rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--brand-dark);
  margin-bottom: 4px;
}
.blog-row h3 { font-size: 1rem; line-height: 1.3; margin-bottom: 4px; }
.blog-row small { color: var(--muted); font-size: .8rem; }
@media (min-width: 900px) {
  .blog-split { grid-template-columns: 1.3fr 1fr; gap: 24px; }
}

/* ---- CTA strip: photo under a terracotta tint ---- */
.cta-strip__bg { position: absolute; inset: 0; z-index: 0; }
.cta-strip__bg img, .cta-strip__img { width: 100%; height: 100%; object-fit: cover; object-position: center 35%; }
.cta-strip__box--photo::before {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background:
    radial-gradient(90% 120% at 50% 100%, rgba(60, 28, 10, .55), transparent 70%),
    linear-gradient(180deg, rgba(132, 62, 17, .78), rgba(188, 90, 27, .72));
}
.cta-strip__box { border-radius: var(--r-xl); }
.cta-strip__inner { position: relative; z-index: 2; }
.cta-strip p { max-width: 32em; }


/* ==========================================================================
   Sage pass. The palette has two families: warm (terracotta, marigold, gold)
   and cool (sage). Terracotta is kept for the actions and the two full stops
   (closing CTA, footer); everything that used to be a terracotta panel or
   pill in between is sage, so the page breathes between warm and cool.
   ========================================================================== */
:root {
  --sage-deep:  #3F6664;   /* text on sage tint, AA */
  --sage-ink:   #2B4A48;   /* scrims */
  --sage-tint:  #E4EDEC;
  --sage-tint-2: #F0F5F4;
}

/* Section pills: cool, not orange. */
.eyebrow { background: var(--sage-tint); color: var(--sage-deep); }
.cta-strip__eyebrow { background: rgba(255, 255, 255, .14); color: #fff; }

/* Trust bar on sage. */
.trust { background: var(--sage-tint-2); border-color: var(--sage-tint); }
.trust__stat b { color: var(--sage-deep); }

/* Why band: a cool scrim so the warm CTA later reads as the turn. */
.why--photo::before {
  background:
    linear-gradient(180deg, rgba(43, 74, 72, .78) 0%, rgba(63, 102, 100, .66) 55%, rgba(43, 74, 72, .82) 100%);
}
.why__ico { background: var(--sage-tint); color: var(--sage-deep); }

/* Program cards: sage for the meta, the tag and the media fallback. */
.class-card__meta svg, .class-card__when svg { color: var(--sage-deep); }
.class-card__tag { background: var(--sage-tint); color: var(--sage-deep); }
.class-card__media { background: linear-gradient(150deg, var(--sage-tint), var(--canvas-2)); }
.class-card__link { color: var(--brand-dark); }

/* Hero: the second floating card is sage, so the two cards are not a pair of
   orange squares. */
.hero__card--br .ico { background: var(--accent-sage); color: var(--ink); }

/* Safety panel: sage scrim over the photo; the icons stay sage. */
.safety__panel { background: linear-gradient(150deg, var(--sage-ink), var(--sage-deep)); }
.safety__panel--photo::before {
  background: linear-gradient(180deg, rgba(43, 74, 72, .15) 0%, rgba(43, 74, 72, .55) 45%, rgba(43, 74, 72, .92) 100%);
}
.safety__panel .eyebrow { background: rgba(255, 255, 255, .18); color: #fff; }

/* Alternating grounds: educators and the trial summary sit on sage tint,
   blog stays on cream, so no two tinted sections in a row share a colour. */
#collaborations.section--tint { background: var(--sage-tint-2); }
.collab-card__role { color: var(--sage-deep); }

/* Blog: category labels cool, read link warm. */
.blog-card__cat, .blog-row__cat { color: var(--sage-deep); }
.blog-card__cat::before { background: var(--accent-sage); }

/* Class page hero: sage scrim, the marigold tags stay warm on it. */
.detail-hero--photo::before {
  background:
    linear-gradient(90deg, rgba(43, 74, 72, .88) 0%, rgba(43, 74, 72, .74) 45%, rgba(63, 102, 100, .38) 100%),
    linear-gradient(180deg, rgba(43, 74, 72, .2), rgba(43, 74, 72, .5));
}
.tag-pill { background: var(--sage-tint); color: var(--sage-deep); border-color: transparent; }
.tag-pill svg { color: var(--sage-deep); }
.detail-hero--photo .tag-pill { background: rgba(255, 255, 255, .14); color: #fff; }

/* Page heroes (Programs, Trial, Schools, About): cool tint instead of orange. */
.page-hero {
  background:
    radial-gradient(60% 120% at 80% -20%, var(--sage-tint) 0%, transparent 55%),
    radial-gradient(46% 90% at 8% 0%, var(--gold-tint) 0%, transparent 52%),
    var(--canvas-2);
}

/* Structure tiles, outcome checks, FAQ toggles: sage icons. */
.structure-card .ico, .outcome-card .ico, .safety__row .ico { background: var(--sage-tint); color: var(--sage-deep); }
.faq-q .faq-toggle, .faq__toggle { background: var(--sage-tint); color: var(--sage-deep); }

/* Booking pages: the summary card tinted sage, the fee row calm. */
.booking__summary { background: var(--sage-tint-2); border: 1px solid var(--sage-tint); }
.booking__steps li::before, .booking__step-num { background: var(--sage-deep); }
.steps-strip li > span:first-child, .steps-strip__num { background: var(--sage-deep); color: #fff; }
.fee-row { background: var(--gold-tint); border-color: #EFDC95; }

/* Focus rings stay terracotta; links inside sage areas stay readable. */
.trust__families strong, .section-title { color: var(--ink); }


/* ---- No orange boxes: icon tiles, role pills and avatars go sage; the
   closing CTA and the footer go sage-ink. Terracotta remains for buttons,
   links, the wordmark and focus rings only. ---- */
.hero__card--tl .ico,
.outcome-item__ico,
.stat-tile__ico,
.value-card__ico,
.map-box__foot .ico,
.catalog__ico,
.step__ico,
.partner-note__ico,
.faq-item__q .icon,
.rail-nav {
  background: var(--sage-tint);
  color: var(--sage-deep);
}
.faq-item.is-open .faq-item__q .icon { background: var(--sage-deep); color: #fff; }
.testi-card__avatar,
.story__sign-avatar,
.quote-panel__avatar,
.collab-card__photo,
.team-card__photo,
.teacher-card__photo { background: linear-gradient(150deg, var(--sage-deep), var(--sage-ink)); }
.teacher-card__role, .team-card__role { color: var(--sage-deep); }
.team-section__pill, .fee-pill { background: var(--sage-tint); color: var(--sage-deep); }
.tag-pill--brand { background: var(--sage-deep); border-color: var(--sage-deep); color: #fff; }
.chip[aria-pressed="true"] { background: var(--sage-deep); border-color: var(--sage-deep); color: #fff; }
.filter-drop__opt[aria-selected="true"] .filter-drop__check { background: var(--sage-deep); color: #fff; }
.filter-reset:hover { background: var(--sage-tint); color: var(--sage-deep); }
.mobile-menu a:hover { background: var(--sage-tint); color: var(--sage-deep); }
.feature-card { background: linear-gradient(135deg, var(--sage-deep), var(--sage-ink)); }
.steps::before { background: var(--sage-tint); }
.quote-panel__dot.is-active::before { background: var(--sage-deep); }
.quote-panel__dot::before { background: rgba(63, 102, 100, .3); }
.quote-panel__dot:hover::before { background: rgba(63, 102, 100, .6); }
.blog-card:hover .blog-card__go { background: var(--sage-deep); border-color: var(--sage-deep); }
.case-slider__btn:hover { background: var(--sage-deep); border-color: var(--sage-deep); }
.lang-switch__btn:hover, .lang-switch__btn[aria-expanded="true"], .lang-switch__dropdown a:hover { background: var(--sage-tint); color: var(--sage-deep); }
.lang-switch__dropdown li[aria-selected="true"] a { background: var(--sage-tint); color: var(--sage-deep); }
.form-success { background: var(--sage-tint); color: var(--sage-ink); border-color: var(--accent-sage); }

/* Closing CTA: photo under a sage-ink scrim; the terracotta button is now
   the only warm thing in the panel, which is what makes it the action. */
.cta-strip__box {
  background:
    radial-gradient(120% 140% at 12% 0%, rgba(165, 195, 194, .35), transparent 58%),
    linear-gradient(135deg, var(--sage-ink), var(--sage-deep) 60%, var(--sage-ink));
  border-color: rgba(255, 255, 255, .12);
}
.cta-strip__box--photo::before {
  background:
    radial-gradient(90% 120% at 50% 100%, rgba(43, 74, 72, .6), transparent 70%),
    linear-gradient(180deg, rgba(43, 74, 72, .82), rgba(63, 102, 100, .74));
}
.cta-strip__actions .btn--primary { box-shadow: 0 14px 34px rgba(0, 0, 0, .28); }
.cta-strip__points svg, .cta-strip__eyebrow svg { color: var(--accent-yellow); }

/* Footer: terracotta, the official brand treatment — a white logo on the
   brand colour, which is what section 14 has always specified. The 1.5.2
   sage pass had painted it sage-ink; 1.5.20 puts the terracotta back.

   The logo is whitened here rather than in section 14 because the footer
   takes the same multi-colour mark as the header (footer.php, 1.5.17), and
   its wordmark is terracotta — unfiltered it would vanish into this
   background. brightness(0) flattens every colour to black, invert(1) lifts
   that to white, so the mark reads as one solid white logo. */
.site-footer { background: var(--brand); color: rgba(255, 255, 255, .86); }
.brand__logo--footer { filter: brightness(0) invert(1); }
.footer__col a:hover { color: var(--accent-yellow); }

/* Summer banner and promo bars follow. */
.banner { background: linear-gradient(115deg, var(--sage-ink) 0%, var(--sage-deep) 62%, var(--accent-sage) 140%); }
.promo--brand .promo__card { background: var(--sage-ink); }
.promo__custom .wp-block-button__link { background: var(--brand); }


/* ---- Program guide cards: light, not dark boxes. White card on the cream
   ground, sage icon tile, the copy in ink. Four cards run as two columns
   with the icon beside the text, so the section reads as a short guide. ---- */
.feature-card {
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-xs);
  padding: 26px 24px;
}
.feature-card::before { background: var(--accent-sage); height: 3px; }
.feature-card:hover { transform: translateY(-4px); border-color: var(--sage-tint); }
.feature-card__ico {
  width: 52px; height: 52px;
  border-radius: var(--r-sm);
  background: var(--sage-tint); color: var(--sage-deep);
  margin-bottom: 16px;
}
.feature-card__ico svg { width: 26px; height: 26px; }
.feature-card h3 { color: var(--ink); font-size: 1.15rem; margin-bottom: 8px; }
.feature-card p { color: var(--muted); font-size: .95rem; }

/* Four-up guide: two columns, icon left. */
.feature-card--row { display: flex; gap: 18px; align-items: flex-start; }
.feature-card--row .feature-card__ico { flex: none; margin-bottom: 0; }
/* Phones and tablets: beside the text, the icon's column left the copy about
   160px wide on a phone (210px in a tablet's two-up grid) and a four-sentence
   card ran to 750px. Icon above the text instead. */
@media (max-width: 899.98px) {
  .feature-card--row { flex-direction: column; gap: 14px; }
}
@media (min-width: 640px) {
  .grid-3--four { grid-template-columns: repeat(2, 1fr); gap: 18px; }
}
@media (min-width: 1100px) {
  .grid-3--four { grid-template-columns: repeat(2, 1fr); }
  .grid-3--four .feature-card { padding: 26px 24px; }
}

/* The title stacks over the text; the icon sits beside both. */
.feature-card__body { min-width: 0; flex: 1 1 auto; }
.feature-card--row .feature-card__body h3 { margin-top: 4px; }
.feature-card h3 { text-wrap: balance; overflow-wrap: normal; }


/* ---- Outcomes on the class page: a checklist, not four empty boxes.
   Heading left, the list right in one quiet panel, rows divided by a
   hairline, each row a sage check circle and the milestone. Rows stagger in
   with the reveal. ---- */
/* ---- 1.5.23: three parts, none of them stretching to match another.
   Two columns only worked while both held the same amount, and they never do
   here: four one-line facts against four one-line milestones left whichever
   column ran out first as a dead corner of the band. Now the heading and the
   facts share the top row and the milestones run the full width beneath, four
   across — so the band fills edge to edge, and the numbers read left to right
   as the sequence they are. ---- */
/* The facts strip: one row of at-a-glance tiles straight under the hero. No
   heading — each tile labels itself — so it takes far less vertical air than a
   section with a title and copy in it. */
.section.facts-band { padding-block: clamp(26px, 3.5vw, 40px); }

/* The milestones band: heading, its line of copy, then its own cards. It
   holds four one-line titles, so at the site-wide 104px it read as a
   half-empty screen — the complaint that started this. */
.section.outcomes { padding-block: clamp(44px, 5.5vw, 72px); }
.outcomes .outcomes__head { margin-bottom: clamp(22px, 3vw, 32px); }
/* Milestones as their own cards rather than rows of one panel: four rows in a
   single bordered box left a tall thin column that could not sit beside
   anything, and a card each lets them run across the page. */
.outcome-list {
  margin: 0; padding: 0; list-style: none;
  display: grid; gap: 14px;
  /* Counted, not auto-fit: auto-fit put three across at tablet width and left
     the fourth alone on a row of its own. */
  grid-template-columns: 1fr;
}
@media (min-width: 560px) { .outcome-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .outcome-list { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.outcome-list__item {
  display: flex; flex-direction: column; gap: 12px; align-items: flex-start;
  padding: 20px 20px 22px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}
/* The milestone number. A rounded square rather than a circle, so it reads as
   a step in a sequence and not as another status tick. */
.outcome-list__num {
  width: 32px; height: 32px; flex: none; margin-top: 1px;
  display: grid; place-items: center;
  border-radius: 11px;
  background: var(--sage-tint); color: var(--sage-deep);
  font-family: var(--font-head); font-size: .95rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.outcome-list__body { min-width: 0; }
.outcome-list h3 { font-size: 1.05rem; line-height: 1.3; margin: 0; }
.outcome-list p { color: var(--muted); font-size: .93rem; margin-top: 4px; }
/* Stagger: each row follows the previous one by 60ms once the panel reveals. */
.outcome-list.reveal .outcome-list__item { opacity: 0; transform: translateY(8px); transition: opacity .45s var(--ease), transform .45s var(--ease); transition-delay: calc(var(--i, 0) * 60ms + 80ms); }
.outcome-list.reveal.is-visible .outcome-list__item { opacity: 1; transform: none; }
@media (max-width: 639.98px) {
  .outcome-list.reveal .outcome-list__item { opacity: 1; transform: none; transition: none; }
}
@media (prefers-reduced-motion: reduce) {
  .outcome-list.reveal .outcome-list__item { opacity: 1; transform: none; transition: none; }
}
/* ---- The facts under the heading (1.5.22).
   They used to be a row of four big tiles in a band of their own under a
   second centred title. Here they sit in the column the heading left empty,
   two across, small enough to read as supporting detail rather than as a
   competing section. ---- */
.facts-rail {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 12px;
  /* Two across from phone-landscape, four once the strip has the full width
     of the page to itself (1.5.29). Counted rather than auto-fit, which used
     to squeeze "Mondays and Fridays 4-5 PM" onto three lines at tablet. */
  grid-template-columns: 1fr;
}
@media (min-width: 480px) { .facts-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .facts-rail { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* The marker sits on the label's centre line, not at the top of it.
   Top-aligning a 34px tile against a 20px line of type leaves the label
   riding about 7px high — small, but it reads as a misalignment on every
   row at once, which is what the site owner spotted on the old tick list.
   These bodies are two or three short lines, so centring the row is the
   honest fix; it is not used where a body can run long, because the marker
   would then drift to the middle of a paragraph. */
.facts-rail__item {
  display: flex; gap: 12px; align-items: center;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md, 14px);
}
.facts-rail__ico {
  width: 34px; height: 34px; flex: none;
  display: grid; place-items: center;
  border-radius: 11px;
  background: var(--sage-tint); color: var(--sage-deep);
}
.facts-rail__ico svg { width: 19px; height: 19px; }
.facts-rail__body { min-width: 0; }
.facts-rail__body b {
  display: block;
  font-family: var(--font-head); font-size: 1rem; line-height: 1.25; color: var(--ink);
}
.facts-rail__body small { display: block; margin-top: 2px; color: var(--muted); font-size: .84rem; }

/* The heading sits above its own cards now, so it takes a line of its own and
   the copy under it keeps a comfortable measure rather than a column width. */
.outcomes__head { max-width: 62ch; margin-inline: 0; }
.outcomes__head .section-sub { margin-inline: 0; }

/* Trust-bar marks: the display-only wrapper sizes like the old link did, so
   the image scales into the 44px row instead of rendering at file size. */
.trust__track .logos .trust__logo > span { display: inline-flex; align-items: center; height: 100%; }
.trust__track .logos .trust__logo img { height: 44px; width: auto; max-width: 150px; object-fit: contain; }
.trust__rail { padding-block: 2px; }
/* The hero photo frame: keep faces in view on the tall crop. */
.hero__img { object-position: center 30%; }

/* --------------------------------------------------------------------------
   Split hero (1.5.7): left-aligned copy with a panel on the right.
   Programs: the listing block draws it with a venue panel built from the
   cards. For Schools (1.5.8): the page-hero block draws it with an
   at-a-glance facts panel. On both pages it replaced two trailing sections
   that restated what the page already said.
   -------------------------------------------------------------------------- */
.listing-hero,
.page-hero--split {
  position: relative;
  overflow: hidden;
  padding-block: clamp(28px, 4vw, 48px) clamp(32px, 4.5vw, 56px);
  background:
    radial-gradient(52% 110% at 92% -10%, var(--sage-tint) 0%, transparent 58%),
    radial-gradient(40% 80% at 4% 100%, var(--gold-tint) 0%, transparent 55%),
    var(--canvas-2);
}
/* Cranes on the far right only, behind the panel, never under the copy. */
.listing-hero::before,
.page-hero--split::before {
  content: "";
  position: absolute; inset: 0;
  background: url(../images/pattern-crane.svg) 0 0 / 300px 300px repeat;
  opacity: .2; /* 1.5.10: from .42; size and fade unchanged */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 55%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 0%, transparent 55%, #000 100%);
  pointer-events: none;
}
.page-hero--split { text-align: left; }
.listing-hero > .wrap { position: relative; }
.split-hero__grid {
  display: grid;
  gap: 28px;
  align-items: center;
}
.listing-hero .breadcrumb,
.page-hero--split .breadcrumb { justify-content: flex-start; margin-bottom: 16px; }
.split-hero__copy { max-width: 620px; }
.split-hero__copy h1,
.page-hero--split h1 {
  font-size: clamp(2.1rem, 4.6vw, 3.3rem);
  letter-spacing: -0.03em;
  margin-bottom: 14px;
  text-wrap: balance;
}
.split-hero__lead,
.page-hero--split .split-hero__copy > p {
  font-size: clamp(1rem, 1.4vw, 1.14rem);
  color: var(--muted);
  max-width: 52ch;
  margin-inline: 0;
  text-wrap: pretty;
}
/* Three facts read off the cards. Sage icons, quiet weight: it is a caption
   to the heading, not a stat block. */
.split-hero__facts {
  display: flex; flex-wrap: wrap;
  gap: 8px 22px;
  margin-top: 20px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .9rem;
  color: var(--ink-soft);
}
.split-hero__facts li { display: inline-flex; align-items: center; gap: 7px; }
.split-hero__facts svg { width: 17px; height: 17px; color: var(--sage-deep); flex: none; }
/* The one nudge to a trial: a text link, in the CTA colour, no box. */
.split-hero__cta {
  /* Inline-block, not inline-flex: when the sentence wraps on a phone the
     arrow (an inline svg after the text) stays glued to the last word
     instead of floating to the right edge. */
  display: inline-block;
  margin-top: 18px;
  padding-block: 10px;
  line-height: 1.5;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: .98rem;
  color: var(--brand-dark);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 5px;
  text-decoration-color: rgba(188, 90, 27, .35);
  transition: color var(--fast) var(--ease), text-decoration-color var(--fast) var(--ease);
}
.split-hero__cta svg { display: inline-block; vertical-align: -3px; margin-left: 6px; width: 17px; height: 17px; transition: transform var(--base) var(--ease); }
.split-hero__cta:hover { color: var(--brand-darker); text-decoration-color: currentColor; }
.split-hero__cta:hover svg { transform: translateX(4px); }
.split-hero__cta:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 4px; }

/* The venue panel: a white card on the tinted band. Rows are buttons that
   set the Schools filter (class-filter.js), pressed while it is the only
   venue selected. */
.venue-panel {
  background: #fff;
  border: 1px solid rgba(78, 124, 122, .16);
  border-radius: var(--r-xl);
  padding: 22px 16px 10px;
  box-shadow: var(--shadow-md);
}
.venue-panel__head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 4px 14px;
  padding-inline: 8px;
  margin-bottom: 10px;
}
.venue-panel__title {
  font-size: 1.02rem;
  letter-spacing: -0.01em;
  margin: 0;
}
.venue-panel__hint { color: var(--muted); font-size: .8rem; }
.venue-panel__list > li + li { border-top: 1px solid var(--line); }

/* ---- More venues than fit (1.5.25).
   The panel is built from the programs on the page, so its length is whatever
   the venue list happens to be: four today, ten if the school signs four more
   campuses. Nothing capped it, so the hero simply grew — ten rows is about
   780px of panel, which pushes the programs the page exists to show below the
   fold and leaves the hero's left column stranded beside a column of pins.

   From six rows the list scrolls inside the panel instead, so the hero is the
   same height whatever the number, and the rows tighten a little so more of
   them are in view at once. The count is not hidden by this: the hero's facts
   line already reads "N venues", and the filter bar below reports the result
   count. A short fade marks that the list continues.

   :has() is the whole trick — no PHP, no JavaScript, no cap to keep in sync
   with the data. A browser too old for :has() gets today's behaviour, the
   list growing, which is a graceful place to land. ---- */
.venue-panel__list:has(> li:nth-child(6)) {
  max-height: min(52vh, 430px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  /* The fade is fixed to the panel, so the last row would sit under it at the
     end of the scroll. The list carries the same amount as bottom padding,
     which scrolls the final row clear of it. */
  padding-bottom: 30px;
  -webkit-mask-image: linear-gradient(#000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(#000 calc(100% - 28px), transparent);
}
.venue-panel__list:has(> li:nth-child(6)) .venue-pick { padding-block: 9px; gap: 11px; grid-template-columns: 34px minmax(0, 1fr) auto; }
.venue-panel__list:has(> li:nth-child(6)) .venue-pick__ico { width: 34px; height: 34px; }
.venue-panel__list:has(> li:nth-child(6)) .venue-pick__ico svg { width: 16px; height: 16px; }
@media (prefers-reduced-motion: no-preference) {
  .venue-panel__list { scroll-behavior: smooth; }
}
.venue-pick {
  width: 100%;
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 11px 8px;
  border-radius: var(--r-md);
  text-align: left;
  color: var(--ink);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.venue-pick__ico {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--sage-tint);
  color: var(--sage-deep);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.venue-pick__ico svg { width: 18px; height: 18px; }
.venue-pick__text { display: block; min-width: 0; }
.venue-pick__name {
  display: block;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .96rem;
  line-height: 1.25;
}
.venue-pick__meta { display: block; margin-top: 2px; color: var(--muted); font-size: .8rem; }
.venue-pick__arrow { width: 16px; height: 16px; color: var(--muted); opacity: .6; transition: transform var(--base) var(--ease), opacity var(--fast); }
.venue-pick:hover { background: var(--sage-tint-2); }
.venue-pick:hover .venue-pick__arrow { transform: translateX(3px); opacity: 1; color: var(--sage-deep); }
.venue-pick:active { transform: translateY(1px); }
.venue-pick:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.venue-pick[aria-pressed="true"] { background: var(--brand-tint-2); color: var(--brand-darker); }
.venue-pick[aria-pressed="true"] .venue-pick__ico { background: var(--brand); color: #fff; }
.venue-pick[aria-pressed="true"] .venue-pick__meta { color: var(--brand-dark); }
.venue-pick[aria-pressed="true"] .venue-pick__arrow { opacity: 1; color: var(--brand-dark); transform: rotate(90deg); }

/* The facts panel (For Schools): the venue panel's shell, with a plain list
   instead of buttons — what a school gets, in four lines, where the FAQ used
   to repeat it at the bottom of the page. */
.facts-panel {
  background: #fff;
  border: 1px solid rgba(78, 124, 122, .16);
  border-radius: var(--r-xl);
  padding: 22px 22px 18px;
  box-shadow: var(--shadow-md);
}
.facts-panel__title {
  font-size: 1.02rem;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
}
.facts-panel__list li {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  align-items: center;
  gap: 14px;
  padding-block: 12px;
  font-size: .95rem;
  line-height: 1.45;
  color: var(--ink-soft);
}
.facts-panel__list li + li { border-top: 1px solid var(--line); }
.facts-panel__list b { font-family: var(--font-head); font-weight: 700; color: var(--ink); }
.facts-panel__ico {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--sage-tint);
  color: var(--sage-deep);
}
.facts-panel__ico svg { width: 18px; height: 18px; }
.facts-panel__link {
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: 10px;
  min-height: 44px;
  font-family: var(--font-head); font-weight: 600; font-size: .94rem;
  color: var(--brand-dark);
  transition: color var(--fast) var(--ease);
}
.facts-panel__link svg { width: 16px; height: 16px; transition: transform var(--base) var(--ease); }
.facts-panel__link:hover { color: var(--brand-darker); }
.facts-panel__link:hover svg { transform: translateY(3px); }
.facts-panel__link:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 4px; }

/* Catalog form: the quiet second option under the form, where the closing
   CTA strip used to ask a second time. */
.catalog__note { margin-top: 14px; font-size: .92rem; color: var(--muted); }
.catalog__note a { color: var(--brand-dark); font-weight: 600; text-decoration: underline; text-underline-offset: 4px; text-decoration-color: rgba(188, 90, 27, .35); }
.catalog__note a:hover { color: var(--brand-darker); text-decoration-color: currentColor; }

@media (min-width: 900px) {
  .split-hero__grid {
    grid-template-columns: minmax(0, 1.15fr) minmax(340px, .85fr);
    gap: clamp(32px, 5vw, 72px);
  }
  .venue-panel { padding: 24px 18px 12px; }
}
@media (max-width: 639.98px) {
  .listing-hero { padding-block: 22px 32px; }
  .listing-hero::before,
.page-hero--split::before { -webkit-mask-image: linear-gradient(180deg, transparent 0%, transparent 60%, #000 100%); mask-image: linear-gradient(180deg, transparent 0%, transparent 60%, #000 100%); }
  .split-hero__facts { gap: 6px 16px; font-size: .86rem; }
  .facts-panel { padding: 16px 12px; border-radius: var(--r-lg); }
  .facts-panel__list li { gap: 12px; padding-block: 10px; }
  .venue-panel { padding: 16px 10px 6px; border-radius: var(--r-lg); }
  .venue-panel__head { padding-inline: 6px; }
  .venue-pick { padding: 10px 6px; gap: 10px; grid-template-columns: 36px minmax(0, 1fr) auto; }
  .venue-pick__ico { width: 36px; height: 36px; }
  /* The venue name alone on a phone (MIRAI-100). The count and age range
     help a parent comparing venues on a wide screen; stacked on a phone they
     are one more line per venue above the programs the page exists to show,
     and every card below carries its own venue and ages. Tablet up keeps
     them. */
  .venue-pick__meta { display: none; }
}

/* Filter bar: dropdowns on the left, count + Reset on the right. */
.filter-bar__inner {
  justify-content: space-between;
  gap: 12px 20px;
  padding-block: 14px;
}
.filter-bar__drops {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 12px;
}
.filter-bar__meta {
  display: flex; align-items: center;
  gap: 4px 12px;
  margin-left: auto;
}
.filter-bar__count { color: var(--muted); font-size: .92rem; white-space: nowrap; }
.filter-bar__count b { color: var(--ink); font-family: var(--font-head); font-size: 1.02rem; }
@media (max-width: 639.98px) {
  .filter-bar__inner { padding-block: 14px 10px; }
  .filter-bar__drops { width: 100%; }
  .filter-bar__meta { width: 100%; justify-content: space-between; margin-left: 0; }
}

/* The grid sits right under the bar: less top padding than a section that
   follows a heading. */
.section--listing { padding-top: clamp(26px, 3.5vw, 40px); }

/* Card body, title first: the venue line follows the title, so a two-line
   venue name never pushes a title out of line with its neighbours. */
.class-card__body { padding: 18px 22px 20px; }
.class-card h3 { margin-bottom: 8px; }
.class-card__meta { margin-bottom: 8px; }
.class-card__when { color: var(--ink-soft); }

/* --------------------------------------------------------------------------
   Phone pass (1.5.9). Every page at 390px, in one place. Desktop is
   untouched except where a rule says so.
   -------------------------------------------------------------------------- */

/* The drawer. It now sits outside <header> (see header.php), so fixed
   positioning fills the viewport again. Height is spelled out as well as
   inset, for browsers that resolve dvh but not inset shorthand the same way. */
.mobile-menu {
  height: calc(100vh - var(--header-h));
  height: calc(100dvh - var(--header-h));
  overscroll-behavior: contain;
}

/* The header's action on phones and tablets: a small pill between the logo
   and the hamburger. Gone from 900px, where .nav__cta shows. */
.nav__cta-mini {
  min-height: 44px;
  flex-shrink: 0;
  padding: 8px 14px;
  margin-left: auto;
  font-size: .86rem;
}
.nav__cta-mini .arrow { display: none; }
@media (min-width: 900px) {
  .nav__cta-mini { display: none; }
}
@media (max-width: 379.98px) {
  .nav__cta-mini { padding: 8px 11px; font-size: .8rem; }
}

/* Anchor jumps and the class page's sticky bar: land above it. */
body.has-sticky-cta { scroll-padding-bottom: 90px; }

@media (max-width: 899.98px) {
  /* Class page: the sticky bar owns "Book a trial". The same button in
     the hero was on screen at the same time. */
  body.has-sticky-cta .detail-hero__actions .btn--primary { display: none; }
}

@media (max-width: 639.98px) {
  /* Home hero: one screen to the photo. Title floor 2rem, the photo capped
     by the viewport height, the two cards a tight row. */
  .hero__title { font-size: clamp(2rem, 8.5vw, 2.4rem); }
  .hero__photo { max-height: 46svh; min-height: 200px; }
  .hero__card { padding: 8px 10px; gap: 8px; }
  .hero__card .ico { width: 32px; height: 32px; }
  .hero__card b { font-size: .88rem; }
  .hero__card small, .hero__card span { font-size: .78rem; }
  .hero__actions { margin-bottom: 14px; }

  /* Class hero pills: the short ones (ages, fee) first so they share a
     row, the long venue and schedule pills under them. Compact too. */
  .detail-hero__tags { gap: 8px; }
  .detail-hero .tag-pill { padding: 6px 11px; font-size: .8rem; gap: 6px; }
  .detail-hero .tag-pill--short { order: -1; }

  /* The four facts under "Small groups, big attention": two by two, icon
     beside the text, instead of four 180px tiles for four one-liners. */
  .structure-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .stat-tile { padding: 12px; display: grid; grid-template-columns: 34px minmax(0, 1fr); column-gap: 10px; row-gap: 2px; align-items: start; }
  .stat-tile__ico { grid-column: 1; grid-row: 1 / span 2; width: 34px; height: 34px; border-radius: 10px; margin: 0; }
  .stat-tile__ico svg { width: 18px; height: 18px; }
  .stat-tile h3, .stat-tile p { grid-column: 2; }
  .stat-tile h3 { font-size: .98rem; margin: 0; line-height: 1.25; }
  .stat-tile p { font-size: .8rem; line-height: 1.35; margin: 0; }

  /* About values: a row per value, icon beside the text, number as a
     small label under the icon. Four rows instead of four tall cards. */
  .values-grid { gap: 12px; }
  .value-card { display: grid; grid-template-columns: 44px 1fr; column-gap: 14px; padding: 16px; }
  .value-card__num { grid-column: 1; grid-row: 2; font-size: .72rem; margin: 6px 0 0; text-align: center; }
  .value-card__ico { grid-column: 1; grid-row: 1; width: 44px; height: 44px; border-radius: 12px; margin: 0; }
  .value-card__ico svg { width: 22px; height: 22px; }
  .value-card h3 { grid-column: 2; grid-row: 1; font-size: 1.05rem; margin: 0 0 4px; align-self: center; }
  .value-card p { grid-column: 2; grid-row: 2; font-size: .9rem; }

  /* Team cards ride the swipe rail; a shorter photo keeps the card to a
     screen. (The founder row is one card: a one-card rail is just a card,
     and its arrows hide themselves, so it needs no special case.) */
  .team-card__photo { aspect-ratio: 4 / 3; }
  .team-card__body { padding: 16px 18px 18px; }

  /* Contact venues: the same swipe rail as the card grids (the wrapper is
     .rail, so initRails() adds the arrows). */
  .map-grid {
    --rail-peek: 16px;
    --rail-gap: 14px;
    --rail-pad: calc(var(--rail-gap) + var(--rail-peek));
    --rail-card: calc(100vw - var(--rail-pad) * 2);
    display: flex; flex-wrap: nowrap; align-items: stretch;
    gap: var(--rail-gap);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--rail-pad);
    scroll-padding-inline: 0;
    contain: paint;
    padding-block: 10px 26px;
    scrollbar-width: none;
  }
  .map-grid::-webkit-scrollbar { display: none; }
  .map-grid > * { flex: 0 0 var(--rail-card); scroll-snap-align: center; min-width: 0; }
  .map-box:hover { transform: none; box-shadow: var(--shadow-xs); }
  .map-box__frame { aspect-ratio: 16 / 10; }

  /* Program cards: a shorter photo so a list shows more than one card per
     screen. */
  .class-card__media { aspect-ratio: 16 / 9; }
  .class-card__body { padding: 16px 16px 16px; }

  /* Booking summary photo: the form is what the parent came for. */
  .booking__photo { max-height: 180px; }
}

/* Map frame: a calm placeholder under the lazy iframe instead of stripes. */
.map-box__frame {
  background: var(--sage-tint-2);
}
.map-box__ph {
  position: absolute; inset: 0;
  display: grid; place-items: center; align-content: center; gap: 8px;
  color: var(--sage-deep);
  font-family: var(--font-head); font-weight: 600; font-size: .9rem;
  text-align: center; padding: 16px;
}
.map-box__ph svg { width: 28px; height: 28px; }
.map-box__frame iframe { position: relative; z-index: 1; background: transparent; }

/* ==========================================================================
   Photo bands (1.5.11)
   A pool photo behind the page heroes, the listing hero and the home
   testimonials. .photo-band holds the <img>; the section's ::before, which
   draws the crane pattern on cream, becomes the sage scrim over a photo (the
   same scrim as the Why band and the class hero), and the copy turns white.
   The panels — facts, venues, the gold quote — keep their own colour.
   -------------------------------------------------------------------------- */
.photo-band { position: absolute; inset: 0; z-index: -2; }
.photo-band__img { width: 100%; height: 100%; object-fit: cover; object-position: center 35%; }

.page-hero--photo,
.listing-hero--photo,
.testimonials--photo {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: #fff;
}

/* Room for the photo: the text-only band was kept short on purpose, a photo
   band needs height to read as one. */
.page-hero--photo { padding-block: var(--band); }
.listing-hero--photo,
.page-hero--split.page-hero--photo { padding-block: clamp(40px, 6vw, 72px) clamp(44px, 6.5vw, 80px); }

.page-hero--photo::before,
.listing-hero--photo::before {
  z-index: -1;
  opacity: 1;
  background: linear-gradient(180deg, rgba(43, 74, 72, .80) 0%, rgba(63, 102, 100, .66) 55%, rgba(43, 74, 72, .84) 100%);
  -webkit-mask-image: none;
  mask-image: none;
}
/* Split shape: darker under the copy on the left, lighter behind the panel. */
.listing-hero--photo::before,
.page-hero--split.page-hero--photo::before {
  background:
    linear-gradient(90deg, rgba(43, 74, 72, .88) 0%, rgba(43, 74, 72, .76) 48%, rgba(63, 102, 100, .46) 100%),
    linear-gradient(180deg, rgba(43, 74, 72, .15), rgba(43, 74, 72, .45));
}

.page-hero--photo h1,
.listing-hero--photo h1 { color: #fff; }
.page-hero--photo p,
.page-hero--split.page-hero--photo .split-hero__copy > p,
.listing-hero--photo .split-hero__lead { color: rgba(255, 255, 255, .88); }
.page-hero--photo .breadcrumb,
.listing-hero--photo .breadcrumb { color: rgba(255, 255, 255, .72); }
.page-hero--photo .breadcrumb a,
.listing-hero--photo .breadcrumb a,
.page-hero--photo .breadcrumb [data-crumb],
.listing-hero--photo .breadcrumb [data-crumb] { color: #fff; }
.page-hero--photo .breadcrumb svg,
.listing-hero--photo .breadcrumb svg { opacity: .7; }
.page-hero--photo .eyebrow,
.listing-hero--photo .eyebrow {
  background: rgba(255, 255, 255, .16);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .28);
}
.listing-hero--photo .split-hero__facts { color: rgba(255, 255, 255, .92); }
.listing-hero--photo .split-hero__facts svg { color: var(--accent-yellow); }
.listing-hero--photo .split-hero__cta { color: #fff; text-decoration-color: rgba(255, 255, 255, .5); }
.listing-hero--photo .split-hero__cta:hover { color: var(--accent-yellow); text-decoration-color: currentColor; }
.listing-hero--photo .split-hero__cta:focus-visible { outline-color: #fff; }
.page-hero--photo .btn--primary { box-shadow: 0 14px 34px rgba(0, 0, 0, .28); }

/* Testimonials: the quote stays on its gold panel, lifted off the photo. */
.testimonials--photo { padding-block: var(--band-lg); }
.testimonials--photo::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(43, 74, 72, .78) 0%, rgba(63, 102, 100, .62) 55%, rgba(43, 74, 72, .82) 100%);
}
.testimonials--photo .photo-band__img { object-position: center 30%; }
.testimonials--photo .section-title { color: #fff; }
.testimonials--photo .section-sub { color: rgba(255, 255, 255, .86); }
.testimonials--photo .quote-panel {
  border-color: rgba(255, 255, 255, .55);
  box-shadow: var(--shadow-lg);
}

@media (max-width: 639.98px) {
  .page-hero--photo { padding-block: 44px 48px; }
  .listing-hero--photo,
  .page-hero--split.page-hero--photo { padding-block: 36px 40px; }
  .testimonials--photo { padding-block: 56px; }
}

/* Onboarding steps on a photo (1.5.12): frosted nodes, white line and copy.
   The node ring that hides the line behind each icon takes the scrim's
   colour, so the line still stops short of the circle. */
.steps-section { position: relative; }
.steps-section--photo {
  isolation: isolate;
  overflow: hidden;
  color: #fff;
  padding-block: var(--band-lg);
}
.steps-section--photo::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(43, 74, 72, .80) 0%, rgba(63, 102, 100, .66) 55%, rgba(43, 74, 72, .84) 100%);
}
.steps-section--photo .section-title { color: #fff; }
.steps-section--photo .section-sub { color: rgba(255, 255, 255, .86); }
.steps-section--photo .step h3 { color: #fff; }
.steps-section--photo .step p { color: rgba(255, 255, 255, .86); }
.steps-section--photo .step__ico {
  background: rgba(255, 255, 255, .16);
  color: #fff;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 0 0 6px rgba(43, 74, 72, .55), inset 0 1px 0 rgba(255, 255, 255, .25);
}
.steps-section--photo .steps::before { background: linear-gradient(rgba(255, 255, 255, .4) 0%, rgba(255, 255, 255, .4) 88%, transparent 100%); }
@media (min-width: 900px) {
  .steps-section--photo .steps::before { background: rgba(255, 255, 255, .4); }
  .steps-section--photo .step__ico { box-shadow: 0 0 0 8px rgba(43, 74, 72, .55), inset 0 1px 0 rgba(255, 255, 255, .25); }
}

/* Core values as photo rows (1.5.13). With a photo on any card the grid is
   two across and each card is photo | words: the classroom moment on the
   left with the number on it, the icon, title and text on the right. On a
   phone the photo sits on top. Without photos the four-up card grid above
   still applies untouched. */
.values-grid--media { grid-template-columns: 1fr; gap: 20px; }
.value-card--media {
  display: grid;
  grid-template-columns: 1fr;
  padding: 0;
  overflow: hidden;
}
.value-card__photo {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--sage-tint);
}
.value-card__photo img,
.value-card__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.value-card__photo--empty {
  background:
    url(../images/pattern-crane.svg) center / 160px 160px repeat,
    var(--sage-tint);
}
.value-card__photo .value-card__num {
  position: absolute; top: 14px; left: 14px;
  margin: 0;
  padding: 5px 10px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .9);
  color: var(--brand);
  opacity: 1;
  font-size: .82rem;
  letter-spacing: .06em;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.value-card--media .value-card__body { padding: 24px 24px 26px; }
.value-card--media .value-card__ico { width: 48px; height: 48px; border-radius: 14px; margin-bottom: 14px; }
.value-card--media .value-card__ico svg { width: 24px; height: 24px; }
.value-card--media h3 { font-size: 1.18rem; }
@media (min-width: 640px) {
  .values-grid--media { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 900px) {
  .values-grid--media { grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .value-card--media { grid-template-columns: minmax(200px, 42%) minmax(0, 1fr); align-items: stretch; }
  .value-card__photo { aspect-ratio: auto; min-height: 240px; height: 100%; }
  .value-card--media .value-card__body { padding: 28px 28px 30px; align-self: center; }
}

/* Program cards with a photo on top (1.5.13): the icon sits on the photo's
   lower edge with a white ring, the copy follows in the usual card body. */
.feature-card--photo { padding: 0; display: flex; flex-direction: column; }
.feature-card--photo::before { display: none; }
.feature-card__photo {
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--sage-tint);
}
.feature-card__photo img,
.feature-card__img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .6s var(--ease); }
.feature-card--photo:hover .feature-card__img { transform: scale(1.04); }
.feature-card--photo .feature-card__ico {
  margin: -26px 0 12px 22px;
  position: relative; z-index: 1;
  box-shadow: 0 0 0 4px var(--surface), var(--shadow-sm);
}
.feature-card--photo .feature-card__body { padding: 0 24px 26px; }
.feature-card--photo .feature-card__body:first-child { padding-top: 24px; }

/* --------------------------------------------------------------------------
   Split closing CTA (1.5.15)

   The centred panel said one thing and asked once. With two or more
   reassurance lines the panel splits: the ask on the left, the lines in a
   frosted rail on the right, so a parent reading the last screen gets an
   answer to "what actually happens if I book?" next to the button rather
   than nowhere. One line keeps the centred panel, so the home page is
   untouched.
   -------------------------------------------------------------------------- */
.cta-strip__box--split { text-align: left; }
.cta-strip__box--split .cta-strip__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(26px, 3.5vw, 44px);
  align-items: center;
}
.cta-strip__box--split h2,
.cta-strip__box--split p { margin-inline: 0; }
.cta-strip__box--split .cta-strip__actions { justify-content: flex-start; }

.cta-strip__rail {
  list-style: none;
  margin: 0;
  padding: 20px 22px;
  display: grid;
  gap: 14px;
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .24);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22);
}
.cta-strip__rail li {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  /* .94 rather than the .82 the centred line uses: this text is the reason to
     click, and it sits on a lighter frosted panel. */
  color: rgba(255, 255, 255, .94);
  font-size: .95rem;
  line-height: 1.45;
}
.cta-strip__rail svg {
  width: 26px; height: 26px;
  padding: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .18);
  color: #fff;
  flex: none;
}
/* Same mechanics as the class-page outcome list: the card reveals, its lines
   follow in order. 300ms with a 40ms step — long enough to read as a
   sequence, short enough that the last line is not still arriving when the
   eye reaches it. */
.cta-strip__rail.reveal li {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
  transition-delay: calc(var(--i, 0) * 40ms + 60ms);
}
.cta-strip__rail.reveal.is-visible li { opacity: 1; transform: none; }

@media (min-width: 900px) {
  .cta-strip__box--split .cta-strip__inner {
    grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
    gap: clamp(36px, 5vw, 72px);
  }
}

/* The two guards every staggered list in this file carries: on a phone the
   lines are in view at load, and a visitor who asked for less motion gets the
   final state. Without these the lines stay at opacity 0 whenever .reveal is
   force-shown. */
@media (max-width: 639.98px) {
  .cta-strip__rail.reveal li { opacity: 1; transform: none; transition: none; }
}
@media (prefers-reduced-motion: reduce) {
  .cta-strip__rail.reveal li { opacity: 1; transform: none; transition: none; }
}

/* --------------------------------------------------------------------------
   18. Warm build

   The homepage kept its structure but lost the character the earlier site had:
   colour used as a ground rather than as an accent, the brand pattern actually
   visible, curved band edges, and one child photographed out of their box. The
   palette already paid for all of it — cream, terracotta, sage, soft gold and
   marigold are declared in section 1 — so nothing new is introduced here. This
   section only spends what is already there.

   It sits last on purpose. Everything below is an override of an earlier rule,
   which keeps the change reviewable in one place and revertible by deleting
   one section rather than by unpicking edits through six thousand lines.
   -------------------------------------------------------------------------- */

/* ---- 18.1 The inverted ground ------------------------------------------
   body is cream now (section 2). The pair therefore has to invert everywhere
   else, or the "tinted" sections would be the same colour as the page and the
   rhythm would flatten out. Tinted means white from here on. */
.section--tint { background: var(--surface); }
.section--tint .section-foot--note { background: var(--canvas-2); }

/* White cards on a white tinted section need the hairline back, since the
   warm shadow alone no longer separates them. On cream they are fine. */
.section--tint .class-card,
.section--tint .blog-card { border: 1px solid var(--line); }

/* ---- 18.2 What a band is --------------------------------------------------
   A band is a full-bleed run of brand colour, a pattern over it, and a shallow
   curve on the edge that meets the cream. Only the three heroes are bands, so
   the treatment is written straight onto .hero, .page-hero and .listing-hero
   below rather than into helper classes: there were two, nothing ever carried
   them, and a utility with no callers is a promise the markup never made.

   The curve is a percentage radius on one axis only. A fixed radius would turn
   into a half-circle on a phone, so the arc stays shallow at every width, and
   700px down it shallows again where a tall narrow band would otherwise eat
   the top of whatever sits beneath it.

   The pattern is pattern-elements.svg and it is the hero's alone. See
   section 19 for why, and for everywhere it was taken away from. */

/* ---- 18.3 Hero ------------------------------------------------------------
   Was a near-white ground with two faint radial tints, which is the layout an
   analytics dashboard uses. It is a terracotta band now: the first screen is
   the brand colour, the way the old site opened. */
.hero {
  background: var(--brand);
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
  padding-bottom: clamp(64px, 9vw, 112px);
}
/* Replaces the masked coloured tile from section 6. Full width, no mask: on a
   flat ground the pattern should run right across rather than fade out
   halfway, which is what made it look like a rendering fault. */
.hero::before {
  inset: 0;
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  opacity: .22;
  -webkit-mask-image: none;
  mask-image: none;
}

.hero__title { color: #fff; }
.hero__sub { color: rgba(255, 255, 255, .93); }
/* The accent word was a gradient fill, which is unreadable on terracotta and
   was an AI-era tic besides. Soft gold, flat. */
.hero__title .grad {
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  -webkit-text-fill-color: currentColor;
  color: var(--accent-yellow);
}
.hero__badge {
  background: rgba(255, 255, 255, .2);
  border-color: rgba(255, 255, 255, .28);
  box-shadow: none;
  color: #fff;
}
.hero__badge b { color: #fff; }
.hero__badge .pill { background: rgba(255, 255, 255, .92); color: var(--brand-dark); }

/* Terracotta on terracotta is invisible, and white on soft gold fails AA. The
   hero's primary button is soft gold with ink on it, which clears 4.5:1. */
.hero .btn--primary {
  background: var(--accent-yellow);
  color: var(--ink);
  box-shadow: 0 14px 30px rgba(36, 26, 18, .22);
}
.hero .btn--primary:hover { background: #E8D07A; color: var(--ink); }
.hero__alt {
  color: #fff;
  text-decoration-color: rgba(255, 255, 255, .5);
}
.hero__alt:hover { text-decoration-color: #fff; }
.hero__proof { color: rgba(255, 255, 255, .9); }
.hero__proof li svg { color: var(--accent-yellow); }
.hero__meta small { color: rgba(255, 255, 255, .86); }
.hero__meta .stars { color: var(--accent-yellow); }

/* The two floating chips ("Small groups · 8 to 12 children per class") are
   dashboard vocabulary laid over a photograph of a child. The facts belong in
   a sentence a parent reads, and they already are in the trust bar directly
   below. The block attributes are left untouched so nothing an editor typed is
   lost — this only stops them rendering. */
.hero__card { display: none; }

/* One blob-masked photo per page, and this is it. Organic on one axis only, so
   the crop still reads as a photograph rather than as a sticker. */
.hero__photo {
  border-radius: 50% 50% 46% 54% / 55% 55% 45% 45%;
  overflow: hidden;
}

/* ---- 18.4 Page hero -------------------------------------------------------
   Inner pages open on soft gold rather than on another terracotta band, so
   arriving from the homepage feels like moving rooms, not repeating one. */
.page-hero {
  background: var(--accent-yellow);
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
  padding-bottom: clamp(48px, 6vw, 72px);
}
.page-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  opacity: .34;
  pointer-events: none;
}
.page-hero > .wrap { position: relative; z-index: 1; }
.page-hero .eyebrow { background: rgba(255, 255, 255, .55); color: var(--brand-dark); }

/* ---- 18.5 The closing band ------------------------------------------------
   The CTA box keeps its terracotta. It carried the pattern briefly and lost
   it again in section 19: the pattern is the hero's alone now, and a second
   patterned band at the foot of the page was most of what made the motif feel
   repetitive in the first place. Its button flips to soft gold for the same
   contrast reason as the hero's.

   There was a `.cta-strip__box > *` z-index rule here, left over from that
   pattern layer. .cta-strip__bg is a direct child of the box, so the rule
   lifted the background photograph to the same level as the scrim that is
   supposed to darken it, and the photo won on paint order: white copy over an
   unscrimmed forest. The stacking the box actually wants is already written
   further up — bg 0, scrim 1, .cta-strip__inner 2 — so the rule is simply
   gone. */
.cta-strip__box .btn--primary,
.cta-strip__box .btn--light {
  background: var(--accent-yellow);
  color: var(--ink);
}
.cta-strip__box .btn--primary:hover,
.cta-strip__box .btn--light:hover { background: #E8D07A; color: var(--ink); }

/* ---- 18.6 Partner logos ---------------------------------------------------
   Every mark at one optical height, no category label underneath. A logo wall
   where the marks are all different sizes reads as a bug, and the label adds
   nothing a parent does not already know. */
.partners__mark { height: 56px; }
.partners__mark img { max-height: 56px; max-width: 150px; }

/* ---- 18.7 Reveal, hardened -----------------------------------------------
   The reveal already has three safety nets in site-effects.js. This is the
   fourth and it is the only unconditional one: if the stylesheet loads and the
   script does not run at all, nothing on the page can stay invisible. Content
   arriving late is a nuisance; content that never arrives is a blank page. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    animation: mm-reveal-failsafe 0s linear 4s forwards;
  }
  .reveal.is-visible { animation: none; }
}
@keyframes mm-reveal-failsafe {
  to { opacity: 1; transform: none; }
}

/* ---- 18.8 The enquiry bar -------------------------------------------------
   The old site kept one way to act on screen at every scroll position and it
   is the single clearest thing it did for conversion. Marigold, because it is
   the one colour with no other job, and because it has to separate from both
   the cream page and the terracotta bands.

   It is sticky rather than fixed so it cannot cover the footer, and the footer
   carries matching bottom padding on small screens where it overlaps. */
.cta-bar {
  position: sticky;
  bottom: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 3vw, 28px);
  padding: 14px var(--gutter);
  background: var(--accent-sun);
  color: var(--ink);
  box-shadow: 0 -8px 26px rgba(60, 30, 10, .14);
}
.cta-bar__text {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: clamp(.95rem, 2.2vw, 1.12rem);
  line-height: 1.45;
  margin: 0;
}
.cta-bar .btn {
  background: #fff;
  color: var(--ink);
  flex: none;
  min-height: 48px;
}
.cta-bar .btn:hover { background: var(--canvas); color: var(--ink); }
@media (max-width: 560px) {
  .cta-bar { gap: 12px; padding: 10px 16px; }
  .cta-bar__text { font-size: .9rem; }
  .cta-bar .btn { padding-inline: 18px; font-size: .9rem; }
}
@media print {
  .cta-bar { display: none; }
}

/* ---- 18.9 Temperature -----------------------------------------------------
   Two leftovers from the 1.5.2 sage pass now fight the warm ground. The photo
   scrims on the safety panel and the closing CTA were re-tinted to sage-deep
   there, which reads fine on a white page and reads as a cold slate rectangle
   dropped into a terracotta one here. Both go back to the warm scrim the rules
   above them still describe.

   Sage is not being retired: it keeps every job it has in the components —
   icons, pills, tags, avatars, the filter states. It just stops being the
   colour of two full-width panels on a page the brand colour now leads. */
.safety__panel--photo::before {
  background: linear-gradient(180deg, rgba(60, 28, 10, .15) 0%, rgba(60, 28, 10, .55) 45%, rgba(132, 62, 17, .92) 100%);
}
.cta-strip__box--photo::before {
  background:
    radial-gradient(90% 120% at 50% 100%, rgba(60, 28, 10, .55), transparent 70%),
    linear-gradient(180deg, rgba(132, 62, 17, .78), rgba(188, 90, 27, .72));
}

/* The bar sat on white while the page under it was cream, so it read as a
   panel floating above the page rather than as the top of it. Same cream,
   same translucency, so the blur still does its job when content passes. */
.site-header { background: rgba(247, 245, 236, .92); }
.site-header.is-scrolled { background: rgba(247, 245, 236, .96); }

/* ---- 18.10 The rest of the temperature ------------------------------------
   18.9 caught two scrims; the sage pass had set six. The remaining four are
   all band-scale surfaces — the safety panel's own ground, the closing CTA's
   own ground, and the two photo heroes on the inner pages — and each one puts
   a cold slate rectangle the full width of a page whose ground is now warm.

   Every value below is the warm scrim that the rule directly above it in this
   stylesheet already describes, so this is a revert for these four surfaces
   rather than a new colour. Components keep their sage: feature cards, icons,
   pills, tags, avatars, filter states and the form success panel are all
   untouched, which is what keeps sage a real second colour instead of a
   memory. */

/* The safety panel behind the photo, and on installs with no photo set. */
.safety__panel {
  background:
    radial-gradient(120% 130% at 100% 0%, rgba(255, 255, 255, .1), transparent 50%),
    linear-gradient(150deg, var(--brand-darker), var(--brand));
}

/* The closing CTA, same. */
.cta-strip__box {
  background:
    radial-gradient(120% 140% at 12% 0%, rgba(235, 148, 14, .40), transparent 58%),
    radial-gradient(110% 130% at 92% 100%, rgba(165, 195, 194, .30), transparent 55%),
    linear-gradient(135deg, var(--brand-darker), var(--brand) 55%, var(--brand-dark));
}

/* The inner-page photo heroes: Programs, For Schools, the blog and the class
   pages all run one of these two. Centred and split take the same warm wash at
   the same strengths the sage versions used, so nothing shifts but the hue. */
.page-hero--photo::before {
  background: linear-gradient(180deg, rgba(60, 28, 10, .80) 0%, rgba(132, 62, 17, .66) 55%, rgba(60, 28, 10, .84) 100%);
}
.page-hero--split.page-hero--photo::before {
  background:
    linear-gradient(90deg, rgba(60, 28, 10, .88) 0%, rgba(60, 28, 10, .76) 48%, rgba(132, 62, 17, .46) 100%),
    linear-gradient(180deg, rgba(60, 28, 10, .2), rgba(60, 28, 10, .5));
}

/* A photo hero already carries a photograph, so the soft gold band from 18.4
   would only show through as a haze at the edges. It steps aside for them and
   keeps the text-only heroes it was written for. */
.page-hero--photo { background: var(--brand-dark); }

/* ---- 18.11 The listing hero -----------------------------------------------
   Programs and For Schools do not use .page-hero at all: they run
   .listing-hero, a near-twin with a facts panel beside the copy. It was left
   out of 18.4 and 18.10 and so stayed the one cold band on the site, which is
   conspicuous because Programs is the page most parents land on second.

   Text-only gets the same soft gold band as the other inner pages; with a
   photograph it gets the warm wash at the strengths the sage version used. */
.listing-hero {
  background: var(--accent-yellow);
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
}
.listing-hero::before {
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  opacity: .34;
  -webkit-mask-image: none;
  mask-image: none;
}
.listing-hero .eyebrow { background: rgba(255, 255, 255, .55); color: var(--brand-dark); }

.listing-hero--photo { background: var(--brand-dark); }
.listing-hero--photo::before,
.page-hero--split.page-hero--photo::before {
  background:
    linear-gradient(90deg, rgba(60, 28, 10, .88) 0%, rgba(60, 28, 10, .76) 48%, rgba(132, 62, 17, .46) 100%),
    linear-gradient(180deg, rgba(60, 28, 10, .15), rgba(60, 28, 10, .45));
}
.listing-hero--photo .eyebrow { background: rgba(255, 255, 255, .18); color: #fff; }

@media (max-width: 700px) {
  .listing-hero { border-bottom-left-radius: 44% 6%; border-bottom-right-radius: 44% 6%; }
}

/* --------------------------------------------------------------------------
   19. The pattern belongs to the hero

   Two changes, one idea. The crane tile repeated five marks on a 320px square,
   which at hero height put the same bird on screen a dozen times and read as
   wallpaper rather than as brand. And the pattern had spread: the footer, the
   closing CTA, the teacher and value placeholders and the hero's own empty
   photo frame all carried it, so a visitor met it four or five times on one
   scroll.

   pattern-elements.svg replaces it on the three hero bands: the fifteen marks
   of the brand's own element sheet on a 640px repeat. Everywhere else the
   pattern is removed outright and the surface keeps its plain tint. One
   patterned moment per page, at the top, where it sets the tone and then gets
   out of the way.

   pattern-crane.svg stays in the theme: it is still the brand tile, still
   correct on print and on anything that wants the mark on its own, and the
   quote panel's single corner mark is not a repeat.
   -------------------------------------------------------------------------- */

/* The footer had the crane at .45 across its whole width, which is the second
   heaviest patterned surface on the site after the hero and sat directly under
   the closing CTA that also had one. Terracotta on its own is enough. */
.site-footer::before { display: none; }

/* Placeholders. Nine of them share one rule, and every one shows only when a
   photo is missing: hero, class card, blog card, article figure, team and
   teacher portraits, the story and detail media. A patterned placeholder is a
   louder way of saying "nothing here" than a plain one, and because these
   stand in for photographs they appear in rows, which is where a repeat is
   most obvious of all. The tint stays, the tile goes. */
.hero__photo::after,
.detail-hero__media::after,
.story__media::after,
.class-card__media::after,
.blog-card__media::after,
.feature-post__media::after,
.article__figure-media::after,
.team-card__photo::after,
.teacher-card__photo::after {
  background: linear-gradient(150deg, var(--sage-tint), var(--canvas-2));
}
.value-card__photo--empty { background: var(--sage-tint); }

/* Phones get the tile at 420px. The marks would otherwise be the same size on
   a 390px screen as on a 1440px one, so barely two would fit across and the
   band would read as three big stickers instead of as a texture. */
@media (max-width: 700px) {
  .hero::before,
  .page-hero::before,
  .listing-hero::before { background-size: 420px 420px; }
}

/* --------------------------------------------------------------------------
   20. The rest of the sections

   Section 18 made the ground warm and turned the heroes into bands, and then
   stopped. Everything between the hero and the footer was still the earlier
   design, so the page opened like the new site and continued like the old one.
   This section brings the remaining blocks across.

   Three of them needed their markup rearranged as well (why, safety and
   testimonials render.php). Nothing was added to or removed from any page:
   the same blocks appear in the same order carrying the same content.
   -------------------------------------------------------------------------- */

/* ---- 20.1 Why: photograph left, reasons right -----------------------------
   The old treatment reversed white copy out of a darkened photo band. That is
   worth doing once a page and the closing CTA already does it, so here the
   photograph is simply a photograph and the reasons are ink on cream. The
   frame carries the second and last organic crop on the page. */
.why { background: var(--canvas-2); }
.why--split .why__inner {
  display: grid;
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
}
@media (min-width: 900px) {
  .why--split .why__inner { grid-template-columns: minmax(0, .82fr) minmax(0, 1fr); }
}
.why__frame {
  border-radius: 48% 52% 44% 56% / 56% 44% 56% 44%;
  overflow: hidden;
  aspect-ratio: 5 / 4.6;
  box-shadow: var(--shadow-md);
}
.why__frame img, .why__frame .why__img { width: 100%; height: 100%; object-fit: cover; }
/* Tablet: stacked at the full 690px column, the near-square frame was 640px
   of photograph before the first line of copy. Landscape there, like the
   hero and safety photos; phones keep the taller crop. */
@media (min-width: 640px) and (max-width: 899.98px) {
  .why--split .why__frame { aspect-ratio: 3 / 2; }
}

.why__copy .section-head { margin-bottom: clamp(22px, 3vw, 30px); max-width: 46ch; }
.why__copy .section-title { color: var(--ink); }
.why__copy .section-sub { color: var(--muted); }

/* Stacked, not three across. Each reason is a sentence rather than a card, and
   in a column they read as a list of reasons instead of as a feature row. */
.why__points { grid-template-columns: 1fr; gap: 16px; }
.why__point {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  background: none;
  border: 0;
  padding: 0;
  box-shadow: none;
}
.why__ico {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
}
.why__ico svg { width: 22px; height: 22px; }
/* The three tints cycle, so a fourth reason starts the run again rather than
   inventing a colour the palette does not have. */
.why__point:nth-child(3n+1) .why__ico { background: var(--brand-tint); color: var(--brand-dark); }
.why__point:nth-child(3n+2) .why__ico { background: var(--sage-tint);  color: var(--sage-deep); }
.why__point:nth-child(3n+3) .why__ico { background: var(--gold-tint);  color: var(--brand-dark); }
.why__point h3 { font-size: 1.08rem; color: var(--ink); margin-bottom: 4px; }
.why__point p  { font-size: .95rem; color: var(--muted); }

/* ---- 20.2 Safety: a sage band ---------------------------------------------
   Sage is the brand's other ground and this is the block it suits: the subject
   is reassurance, and ink on a calm colour says it more plainly than white
   reversed out of a dark scrim. It also stops the page carrying three heavy
   terracotta blocks. */
.safety-band {
  position: relative;
  background: var(--accent-sage);
  overflow: hidden;
}
.safety-band .safety {
  display: grid;
  gap: clamp(28px, 4vw, 52px);
  align-items: center;
}
@media (min-width: 900px) {
  .safety-band--photo .safety { grid-template-columns: minmax(0, 1fr) minmax(0, .62fr); }
}
.safety-band .eyebrow { background: rgba(255, 255, 255, .55); color: var(--brand-dark); }
.safety-band h2 { color: var(--ink); font-size: clamp(1.7rem, 3.5vw, 2.4rem); margin-bottom: 14px; }
.safety-band .safety__copy > p { color: var(--ink); opacity: .84; max-width: 46ch; }

.safety__list { display: grid; gap: 12px; margin-top: clamp(22px, 3vw, 30px); }
@media (min-width: 620px) { .safety__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.safety__row {
  background: rgba(255, 255, 255, .78);
  border-radius: var(--r-md);
  padding: 16px 18px;
  display: flex;
  align-items: center;
  gap: 13px;
}
.safety__row .ico {
  width: 36px; height: 36px; flex: none;
  background: #fff; color: var(--sage-deep);
  border-radius: 10px;
  display: grid; place-items: center;
}
.safety__row .ico svg { width: 19px; height: 19px; }
.safety__row h3 { font-size: .98rem; color: var(--ink); margin-bottom: 2px; }
.safety__row p  { font-size: .86rem; color: var(--ink-soft); }

.safety__frame {
  border-radius: var(--r-xl);
  overflow: hidden;
  aspect-ratio: 4 / 3.6;
  box-shadow: var(--shadow-lg);
}
.safety__frame img, .safety__frame .safety__img { width: 100%; height: 100%; object-fit: cover; }

/* ---- 20.3 Testimonials: three at once -------------------------------------
   The card itself is unchanged; it only needed its tints back. Terracotta,
   sage, soft gold, cycling, so three quotes read as three voices rather than
   as one component repeated. */
.testi-card--t1 { background: var(--brand-tint-2); border-color: #F2E2D2; }
.testi-card--t2 { background: var(--sage-tint);    border-color: #D6E4E3; }
.testi-card--t3 { background: var(--gold-tint);    border-color: #F0E6C0; }
.testi-card--t2 .quote-mark { color: var(--sage-deep); }
.testi-card--t3 .quote-mark { color: var(--accent-sun); }
.testi-card--t2 .testi-card__avatar { background: linear-gradient(140deg, var(--sage-deep), var(--accent-sage)); }
.testi-card--t3 .testi-card__avatar { background: linear-gradient(140deg, var(--accent-sun), var(--accent-yellow)); color: var(--ink); }

/* ---- 20.4 Partners: one panel, logos only ---------------------------------
   A rail that scrolled marks past at assorted sizes read as a widget. One
   white panel on the cream, every mark on the same optical line, nothing
   underneath them. The brand name stays in the markup for screen readers and
   for the link, it is just not printed: a parent does not need "Cambridge"
   spelled out beneath the Cambridge logo. */
.partners__row {
  background: var(--surface);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
  padding: clamp(24px, 3vw, 34px) clamp(20px, 4vw, 40px);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(26px, 5vw, 62px);
}
.partners__item { display: flex; align-items: center; justify-content: center; }
.partners__name {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- 20.5 Educators -------------------------------------------------------
   The portraits take the same soft crop the hero photo does, at a gentler
   amount: a row of four hard circles reads as a directory, and a row of four
   rectangles reads as a staff list. */
.team-card__photo,
.teacher-card__photo,
.collab-card__photo {
  border-radius: 50% 50% 46% 54% / 54% 54% 46% 46%;
  overflow: hidden;
}

/* ---- 20.6 The closing band ------------------------------------------------
   It was a rounded box floating inside the content width, which left a cream
   margin around the last thing on the page and made it read as one more card.
   Full bleed with a curve on the edge that meets the cream, like the hero at
   the other end, so the page opens and closes the same way. */
.cta-strip { padding-block: 0; }
.cta-strip > .wrap { max-width: none; padding-inline: 0; }
.cta-strip__box {
  border: 0;
  border-radius: 0;
  border-top-left-radius: 46% 13%;
  border-top-right-radius: 46% 13%;
  padding: clamp(64px, 8vw, 104px) var(--gutter) clamp(56px, 7vw, 92px);
}
.cta-strip__inner { max-width: var(--wrap); margin-inline: auto; }
@media (max-width: 700px) {
  .cta-strip__box { border-top-left-radius: 44% 6%; border-top-right-radius: 44% 6%; }
}

/* ---- 20.7 Three details the first pass got wrong ---------------------------
   Caught on the page rather than in the markup, which is the only place any of
   them shows. */

/* The Why heading came through centred, because the block passes the centred
   modifier and the old full-bleed treatment wanted it that way. In a two
   column composition a centred heading floats away from the column it belongs
   to: it reads as a title for the whole section, including the photograph. */
.why__copy .section-head,
.why__copy .section-head--center {
  margin-inline: 0;
  text-align: left;
  max-width: 46ch;
}

/* Testimonials sit on the cream. A background photograph put three tinted
   cards on a dark scrim, which is three warm rectangles floating on a cold
   one, and the tints stop doing their job the moment the ground behind them
   is not the page. The attribute stays on the block for anyone who wants it
   elsewhere; on this section it is not painted. */
.testimonials--photo .photo-band { display: none; }
.testimonials--photo .section-title { color: var(--ink); }
.testimonials--photo .section-sub { color: var(--muted); }

/* Seven partner marks wrapped six-and-one, which reads as a mistake rather
   than as two rows. A grid distributes them instead of packing a line and
   spilling the remainder: seven fit across a desktop, and narrower screens
   break to four-and-three or three-and-two rather than stranding one. */
/* Ten marks now, and seven across a desktop stranded three on the second
   row. The 17% floor caps a row at five, so ten land five-and-five. */
.partners__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(max(118px, 17%), 1fr));
  gap: clamp(18px, 2.6vw, 30px);
  justify-items: center;
}
/* The item fills its track and the mark never outgrows it. Sized to content,
   a 150px wordmark overran a 110px tablet column into its neighbour. */
.partners__item { width: 100%; min-width: 0; }
.partners__mark { width: 100%; max-width: 150px; }
.partners__mark img { max-width: 100%; }
@media (max-width: 900px) {
  .partners__row { grid-template-columns: repeat(auto-fit, minmax(max(98px, 17%), 1fr)); }
  .partners__mark { height: 48px; }
  .partners__mark img { max-height: 48px; }
}

/* ---- 20.8 Blog: three equal cards -----------------------------------------
   Same grid as the programs row above it, so the two card sections on the home
   page share one rhythm instead of each inventing their own. */
.blog-grid {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) { .blog-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 960px) { .blog-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.blog-grid .blog-card { height: 100%; }

/* Fewer than three cards (an article's "More from the blog" when the site has
   three posts). Three fixed tracks packed them to the left under a centred
   heading with a card-sized hole on the right. They keep a third of the row
   each and sit in the middle instead; auto-fit drops the empty tracks. */
@media (min-width: 960px) {
  .blog-grid:not(:has(> :nth-child(3))) {
    grid-template-columns: repeat(auto-fit, calc((100% - 48px) / 3));
    justify-content: center;
  }
}

/* Three cards on two columns left the third alone on a row with an empty cell
   beside it. It takes the whole row instead, photo left and text right, the
   photo on the same line as the photo in the card above it. */
@media (min-width: 640px) and (max-width: 959.98px) {
  .blog-grid > .blog-card:last-child:nth-child(odd):not(:first-child) {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: calc(50% - 24px) minmax(0, 1fr);
    column-gap: 24px;
    align-items: center;
  }
  .blog-grid > .blog-card:last-child:nth-child(odd):not(:first-child) .blog-card__body { padding-block: 8px; }
}

/* ---- 20.9 The last cold band ----------------------------------------------
   The For Schools onboarding steps were the one photo band the temperature
   passes in 18.9 and 18.10 missed, because they went looking for scrims on
   heroes and panels and this one is a section. It is the only slate rectangle
   left on the site.

   The icon ring goes with it. It is a 6px halo drawn in the scrim's own colour
   so the marker looks cut out of the band rather than stuck on top, which only
   works while the ring and the band agree.

   (.testimonials--photo's scrim is left where it is. The home block no longer
   emits that class at all, so the rule cannot match anything; it is dead
   rather than cold, and deleting rules nothing renders is a separate job from
   this one.) */
.steps-section--photo::before {
  background: linear-gradient(180deg, rgba(60, 28, 10, .80) 0%, rgba(132, 62, 17, .66) 55%, rgba(60, 28, 10, .84) 100%);
}
.steps-section--photo .step__ico {
  box-shadow: 0 0 0 6px rgba(60, 28, 10, .55), inset 0 1px 0 rgba(255, 255, 255, .25);
}
@media (min-width: 900px) {
  .steps-section--photo .step__ico {
    box-shadow: 0 0 0 8px rgba(60, 28, 10, .55), inset 0 1px 0 rgba(255, 255, 255, .25);
  }
}

/* ---- 20.10 Trust bar ------------------------------------------------------
   The canvas puts a quiet, compact row directly under the hero: the marks a
   parent recognises, each in its own ring, with one supporting line beside
   them. What was here instead was a scrolling marquee of logos next to a
   statistics block, which is a widget, and it moved while the hero above it
   had just stopped.

   The marquee stops. A logo wall that slides cannot be read, and these are
   exactly the marks a parent is scanning for. Duplicate track copies exist
   only to make the loop seamless, so with the animation off they are hidden.

   The proof itself stays. The canvas drew a generic supporting sentence in
   this slot and the real numbers are better content than anything I would
   write there, so 45+ partner schools and 500+ families keep the slot, just
   set quietly rather than as a headline. */
.trust { padding-block: clamp(26px, 3.4vw, 40px); background: var(--canvas-2); }
.trust__inner {
  display: flex;
  align-items: center;
  gap: clamp(20px, 3.4vw, 44px);
  flex-wrap: wrap;
}

/* Marks first, proof beside them, which is the canvas's reading order. */
.trust__rail { order: 1; overflow: visible; flex: 1 1 auto; -webkit-mask-image: none; mask-image: none; }
.trust__lead { order: 2; flex: 0 1 auto; }

/* The two lines that stopped the marquee — `animation: none !important` and a
   rule hiding every track copy after the first — are gone, because 43 turns it
   back on at 104px and they would have had to be fought with more !important
   rather than answered. The paragraph above stays as the record of why it was
   stopped at 52px, which still reads true at that size. */
.trust__track .logos { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 30px); flex-wrap: wrap; }

/* Each mark in its own ring, as on the canvas. The ring is what turns a row
   of assorted logo shapes into one row of equal objects. */
.trust__logo {
  width: 64px; height: 64px;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  display: grid; place-items: center;
  padding: 10px;
  flex: none;
}
.trust__logo > span { display: grid; place-items: center; width: 100%; height: 100%; }
.trust__logo img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }

/* The numbers, set down to supporting weight. */
.trust__lead { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.trust__stat b { font-size: 1.5rem; }
.trust__stat small { font-size: .82rem; }
.trust__families .stars { font-size: .82rem; }
.trust__families small { font-size: .82rem; }

/* ---- 20.11 Educators ------------------------------------------------------
   A portrait, a name, what they teach. The card around it added a border, a
   role in caps, a biography and a second line about what the person does,
   which is four pieces of chrome around the two facts a parent wants on a
   home page. The detail belongs on About, where there is room to read it.

   Markup untouched: the card is unwrapped in CSS, so every field is still in
   the page for search engines and still renders in full wherever else the
   block is used. */
/* Three across on a tablet, four from 1000px: four columns at 760px left
   each card about 150px, a name and a subject line to a word or two. */
.collab-grid { display: grid; gap: clamp(20px, 3vw, 30px); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 760px) { .collab-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .collab-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

#educators .collab-card,
.section--tint .collab-card {
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
  text-align: center;
}
/* A card on its own ground, two to a phone row, gives its text the room its
   side padding was taking. */
@media (max-width: 639.98px) {
  .collab-card { padding-inline: 12px; }
}
/* Centred text as well as a centred column: on a card with its own ground
   (For Schools) the name block shrank to its widest line and the role sat
   flush left under it, so each card's lines started at a different place. */
.collab-card__head { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; }
/* Never wider than the card: at 360px a card on its own ground has about
   114px inside its padding, and the 120px floor pushed the portrait out of it. */
.collab-card__photo {
  width: min(clamp(120px, 14vw, 156px), 100%);
  height: auto;
  aspect-ratio: 1;
  border-radius: 50% 50% 46% 54% / 54% 54% 46% 46%;
  overflow: hidden;
  flex: none;
}
.collab-card__photo img { width: 100%; height: 100%; object-fit: cover; }
.collab-card__id h3 { font-size: 1.06rem; margin-bottom: 3px; }
.collab-card__role {
  font-size: .84rem;
  color: var(--muted);
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
}
.collab-card__bio,
.collab-card__does { display: none; }

/* ---- 20.12 Blog header ----------------------------------------------------
   Heading on the left, the link to everything else on the right, on one line.
   Centred with a button underneath is how a section that wants attention
   behaves, and this one is the quietest on the page: three articles offered
   on the way past. */
@media (min-width: 760px) {
  #blog .wrap {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "head link"
      "grid grid";
    align-items: end;
    column-gap: 24px;
  }
  #blog .section-head { grid-area: head; margin-inline: 0; text-align: left; margin-bottom: clamp(26px, 3.5vw, 36px); }
  /* The grid is inside .rail now (blog-teasers/render.php), so .rail is the
     item this wrap places. Left unplaced it auto-flowed into the heading's
     column only, and the three cards stopped a button's width short of the
     right edge (and ran two narrow columns at tablet width). */
  #blog .rail,
  #blog .blog-grid { grid-area: grid; min-width: 0; }
  /* The cards now sit inside a .rail, and it is the rail that is the grid
     item. Left to auto-placement it took the head column only, so three
     cards filled 910px of a 1104px row and a tablet got two-and-one. */
  #blog .wrap > .rail { grid-area: grid; }
  /* The home row is always three teasers: three across from here, not the
     blog index's two-and-one, which stranded the third card at 760-959px. */
  #blog .blog-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(16px, 2vw, 24px); }
  #blog .section-foot { grid-area: link; margin: 0 0 clamp(26px, 3.5vw, 36px); text-align: right; }
}

/* ---- 20.13 Two things 20.10 and 20.11 did not land ------------------------- */

/* The rings. Sizing them from .trust__logo lost to the rail's own rules, which
   are written against .trust__track and therefore more specific. These are the
   same declarations aimed properly, plus the rail's mask and scroll behaviour
   turned off now that nothing moves: fifteen marks at 52px fit one row inside
   the content width, and wrap evenly below it. */
.trust__rail {
  overflow: visible;
  -webkit-mask-image: none;
  mask-image: none;
}
.trust__track {
  animation: none;
  transform: none;
  display: block;
  width: 100%;
}
.trust__track .logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: clamp(10px, 1.4vw, 16px);
  animation: none;
  transform: none;
}
.trust__track .logos .trust__logo {
  width: 52px;
  height: 52px;
  min-width: 0;
  flex: none;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  display: grid;
  place-items: center;
  padding: 8px;
  filter: none;
  opacity: 1;
}
.trust__track .logos .trust__logo > span {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
}
.trust__track .logos .trust__logo img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* The educator portraits. The image carries width and height attributes from
   WordPress, so `height: 100%` lost to the base `img { height: auto }` and the
   picture stood 183px tall inside a 156px circle with overflow hidden: what
   showed was the empty tile behind it. Taking it out of flow and pinning it to
   the box makes object-fit the only thing deciding the crop. */
.collab-card__photo { position: relative; }
.collab-card .collab-card__photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- 20.14 Blog card meta -------------------------------------------------
   The read time moved out of the photograph and joined the category on one
   line above the title, so .blog-card__time is no longer a pill floating on an
   image: it is the second half of a sentence. Everything that made it a pill
   comes off. */
.blog-card__time {
  position: static;
  inset: auto;
  background: none;
  color: inherit;
  padding: 0;
  border-radius: 0;
  box-shadow: none;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.blog-card__cat {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0;
}
.blog-card__sep { opacity: .55; }

/* ---- 20.15 The meta line, set properly ------------------------------------
   Two things the previous rule left. The category carried a small coloured dot
   in front of it, which is decoration standing in for punctuation and now sits
   oddly at the head of a two-part line. And the separator collapsed: the
   middot is a text node inside an inline-flex box, so its surrounding spaces
   were thrown away and "AFTER SCHOOL" ran straight into "3 min read". */
.blog-card__cat::before,
.blog-card__cat::after { content: none; }
.blog-card__sep {
  display: inline-block;
  margin-inline: .42em;
  opacity: .5;
}
.blog-card__cat { gap: 0; letter-spacing: .06em; }
.blog-card__time { letter-spacing: 0; text-transform: none; opacity: .8; }

/* --------------------------------------------------------------------------
   21. Footer and the greeting

   The last two places the page and the design disagreed.
   -------------------------------------------------------------------------- */

/* ---- 21.1 The footer ------------------------------------------------------
   Ink, not terracotta.

   1.5.20 made it terracotta deliberately, as the brand's own footer treatment,
   and that was a reasonable call on a white page. It is not one on this page:
   the closing CTA immediately above it is a full-bleed terracotta band, so two
   terracotta blocks now meet with nothing between them and the CTA loses its
   edge exactly where it is asking for the click. Ink is a brand neutral, it
   puts a full stop under the page, and it is what the approved design shows.

   Reverting is one declaration if it is ever wanted back. */
.site-footer { background: var(--ink); color: rgba(239, 238, 228, .72); }
.site-footer a { color: rgba(239, 238, 228, .72); }
.site-footer a:hover { color: #fff; }
.site-footer h4,
.site-footer .footer__title { color: var(--canvas); }
.site-footer__credits { color: rgba(239, 238, 228, .5); }
.site-footer hr,
.site-footer__bottom { border-color: rgba(239, 238, 228, .16); }

/* ---- 21.2 The greeting ----------------------------------------------------
   A small hand-addressed badge on the corner of the Why photograph, the way
   the earlier site had one. Everything else on the page talks about the
   school; this is the only thing that talks to the reader.

   It is anchored to the frame rather than the column so it stays on the
   photograph's edge at any width, and it sits above the crop because the
   frame clips its own overflow. */
.why__media { position: relative; }
.why__ribbon {
  position: absolute;
  right: clamp(-6px, -0.5vw, 0px);
  bottom: clamp(18px, 3vw, 34px);
  z-index: 2;
  background: var(--accent-sun);
  color: var(--ink);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: clamp(.82rem, 1.5vw, .95rem);
  line-height: 1.3;
  padding: 11px 20px;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-md);
  transform: rotate(-3deg);
  max-width: 78%;
  text-align: center;
}
@media (max-width: 760px) {
  .why__ribbon { right: 8px; bottom: 10px; transform: rotate(-2deg); }
}
@media (prefers-reduced-motion: reduce) {
  .why__ribbon { transform: none; }
}

/* --------------------------------------------------------------------------
   22. Flat heroes, the footer back, and motion
   -------------------------------------------------------------------------- */

/* ---- 22.1 The footer, terracotta again ------------------------------------
   21.1 took it to ink to stop two terracotta blocks meeting. Terracotta is the
   brand's own footer treatment and it is wanted back, so the separation is
   solved the other way instead: the closing CTA keeps its curve, and the
   footer sits under it as the darker, flatter block. */
.site-footer { background: var(--brand); color: rgba(255, 255, 255, .86); }
.site-footer a { color: rgba(255, 255, 255, .86); }
.site-footer a:hover { color: #fff; }
.site-footer h4,
.site-footer .footer__title { color: #fff; }
.site-footer__credits { color: rgba(255, 255, 255, .62); }

/* ---- 22.2 Heroes lose their photographs -----------------------------------
   Every inner page opened on a photograph under a heavy scrim: a dark
   rectangle with white type on it, the same shape on Programs, About, the
   blog, the trial page and every class page. The photograph was never really
   seen, because it was there to be darkened, and the scrim had to be heavy
   enough for AA on whatever it happened to cover.

   Flat brand colour instead, as the design has it, which lets the pattern show
   and the type sit in ink at full contrast. The photographs are still in the
   blocks, so nothing an editor set is lost and putting one back is a matter of
   dropping these two rules. */
.page-hero > .photo-band,
.listing-hero > .photo-band,
.detail-hero__bg { display: none; }

/* The scrims those photographs needed go with them, which hands ::before back
   to the pattern it carries on every other hero. */
.page-hero--photo::before,
.page-hero--split.page-hero--photo::before,
.listing-hero--photo::before,
.detail-hero--photo::before {
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  opacity: .34;
  z-index: 0;
}
@media (max-width: 700px) {
  .page-hero--photo::before,
  .listing-hero--photo::before,
  .detail-hero--photo::before { background-size: 420px 420px; }
}

/* ---- 22.3 A colour per kind of page ---------------------------------------
   From the design: the home hero is terracotta, the listing and form pages are
   soft gold, and a single programme page is sage. Three grounds, so moving
   from the home page to a list to one programme feels like moving through
   rooms rather than repeating one. */
.page-hero,
.page-hero--photo,
.listing-hero,
.listing-hero--photo { background: var(--accent-yellow); }

.detail-hero,
.detail-hero--photo {
  background: var(--accent-sage);
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
  overflow: hidden;
}
@media (max-width: 700px) {
  .detail-hero,
  .detail-hero--photo { border-bottom-left-radius: 44% 6%; border-bottom-right-radius: 44% 6%; }
}
.detail-hero--photo::before { content: ""; position: absolute; inset: 0; pointer-events: none; }
.detail-hero > .wrap { position: relative; z-index: 1; }

/* Ink, not white. Every one of these grounds is light now, so the reversed
   type the scrims required would fail against all three. */
.page-hero--photo,
.page-hero--photo h1,
.listing-hero--photo,
.listing-hero--photo h1,
.detail-hero--photo,
.detail-hero--photo h1,
.detail-hero--photo h2 { color: var(--ink); }

.page-hero--photo .page-hero__lead,
.page-hero--photo .split-hero__lead,
.listing-hero--photo .split-hero__lead,
.detail-hero--photo .detail-hero__lead,
.detail-hero--photo p { color: var(--ink-soft); }

.page-hero--photo .breadcrumb,
.listing-hero--photo .breadcrumb,
.detail-hero--photo .breadcrumb { color: var(--muted); }
.page-hero--photo .breadcrumb a,
.page-hero--photo .breadcrumb [data-crumb],
.listing-hero--photo .breadcrumb a,
.listing-hero--photo .breadcrumb [data-crumb],
.detail-hero--photo .breadcrumb a { color: var(--brand-dark); }

.page-hero--photo .eyebrow,
.listing-hero--photo .eyebrow,
.detail-hero--photo .eyebrow { background: rgba(255, 255, 255, .55); color: var(--brand-dark); }

.listing-hero--photo .split-hero__facts { color: var(--ink-soft); }
.listing-hero--photo .split-hero__facts svg { color: var(--brand-dark); }
.listing-hero--photo .split-hero__cta { color: var(--brand-dark); text-decoration-color: var(--brand-tint); }
.detail-hero--photo .pill,
.detail-hero--photo .tag-pill { background: rgba(255, 255, 255, .62); color: var(--ink); }

/* ---- 22.4 Motion ----------------------------------------------------------
   Four things move, and each one has a job.

   The hero arrives in sequence, top to bottom, so the eye is walked through
   badge, headline, sentence, button rather than meeting all four at once. The
   pattern drifts, slowly enough that you notice it only if you stay, which is
   the one thing on the page that says it is alive. Cards lift under the cursor
   because they are clickable and nothing else about them says so. Buttons take
   a press, because a control that does not move under the finger feels broken
   on touch.

   Nothing else loops, nothing parallaxes, and the whole block sits inside
   prefers-reduced-motion: no-preference. Scroll reveal is separate and already
   has its four safety nets. */
@keyframes mm-rise {
  from { opacity: 0; transform: translate3d(0, 16px, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes mm-rise-soft {
  from { opacity: 0; transform: translate3d(0, 26px, 0) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes mm-bar-in {
  from { transform: translate3d(0, 100%, 0); }
  to   { transform: none; }
}
@keyframes mm-pop {
  0%   { opacity: 0; transform: rotate(-3deg) scale(.86); }
  70%  { opacity: 1; transform: rotate(-3deg) scale(1.04); }
  100% { opacity: 1; transform: rotate(-3deg) scale(1); }
}
/* One tile of travel, so the loop is seamless whatever the band's size. */
@keyframes mm-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-640px, -640px, 0); }
}

@media (prefers-reduced-motion: no-preference) {

  /* The hero, in sequence. `backwards` holds the first frame during the delay,
     so nothing flashes in at full opacity before its turn. */
  .hero__badge   { animation: mm-rise .62s var(--ease) .04s backwards; }
  .hero__title   { animation: mm-rise .62s var(--ease) .12s backwards; }
  .hero__sub     { animation: mm-rise .62s var(--ease) .20s backwards; }
  .hero__actions { animation: mm-rise .62s var(--ease) .28s backwards; }
  .hero__visual  { animation: mm-rise-soft .85s var(--ease) .18s backwards; }

  .page-hero .eyebrow,
  .listing-hero .eyebrow { animation: mm-rise .55s var(--ease) .04s backwards; }
  .page-hero h1,
  .listing-hero h1 { animation: mm-rise .6s var(--ease) .1s backwards; }

  /* The pattern drift. The layer is grown by one tile in each direction so the
     travel never exposes an edge, and transform keeps it off the paint path. */
  .hero::before,
  .page-hero::before,
  .listing-hero::before {
    top: -640px; left: -640px; right: -640px; bottom: -640px;
    width: auto; height: auto;
    animation: mm-drift 150s linear infinite;
    will-change: transform;
  }
  @media (max-width: 700px) {
    .hero::before,
    .page-hero::before,
    .listing-hero::before { top: -420px; left: -420px; right: -420px; bottom: -420px; }
  }

  /* The enquiry bar slides up once, after the hero has settled. */
  .cta-bar { animation: mm-bar-in .5s var(--ease) .65s backwards; }

  /* The greeting lands last, with a small overshoot. */
  .why__ribbon { animation: mm-pop .55s var(--ease) .25s backwards; }

  /* Cards say they are clickable. */
  .class-card,
  .blog-card,
  .collab-card__photo,
  .partners__mark {
    transition: transform .34s var(--ease), box-shadow .34s var(--ease);
  }
  .class-card:hover,
  .blog-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
  .collab-card:hover .collab-card__photo { transform: translateY(-4px) scale(1.02); }
  .partners__mark:hover { transform: translateY(-3px); }

  /* Photographs settle very slightly under the cursor on the cards that have
     one, which is the whole of the hover on an image-led card. */
  .class-card__media img,
  .blog-card__media img { transition: transform .6s var(--ease); }
  .class-card:hover .class-card__media img,
  .blog-card:hover .blog-card__media img { transform: scale(1.04); }

  /* A control that does not move under the finger feels broken on touch. */
  .btn { transition: transform .16s var(--ease), box-shadow .22s var(--ease), background-color .22s var(--ease), color .22s var(--ease); }
  .btn:active { transform: translateY(1px) scale(.985); }
}

/* Belt and braces: the reduced-motion block in section 15 already kills every
   animation, and this repeats it for the four declared above so a future edit
   to that rule cannot quietly switch them back on. */
@media (prefers-reduced-motion: reduce) {
  .hero__badge, .hero__title, .hero__sub, .hero__actions, .hero__visual,
  .page-hero .eyebrow, .listing-hero .eyebrow, .page-hero h1, .listing-hero h1,
  .hero::before, .page-hero::before, .listing-hero::before,
  .cta-bar, .why__ribbon { animation: none !important; }
  .hero::before, .page-hero::before, .listing-hero::before { inset: 0; }
}

/* --------------------------------------------------------------------------
   23. The hero pattern gets out of the way of the reading

   The symbols were set at a strength that made them read as small pictures
   rather than as texture, and they ran straight under the headline and the
   sentence beneath it. A crane behind a comma is not a background, it is
   competition: the type stopped being the first thing on the page.

   Two changes, together. It drops to about half the strength, so it reads as
   something the paper is made of rather than as a row of icons. And it is
   masked out of the band the copy occupies, so no symbol ever sits behind a
   letter: on the home page the copy is on the left, so the pattern fades in
   across the middle and lives on the photograph's side; on the centred inner
   heroes it clears the middle and stays at the edges.

   The mask is on the pattern layer only, never on the band, so the colour is
   still edge to edge.
   -------------------------------------------------------------------------- */

/* Home: copy left, photograph right. Nothing in the left 52%. */
.hero::before {
  opacity: .12;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 44%, #000 72%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 0%, transparent 44%, #000 72%, #000 100%);
}

/* Inner heroes are centred, so the copy runs down the middle. Clear the middle,
   keep the outer thirds. */
.page-hero::before,
.listing-hero::before,
.detail-hero--photo::before {
  opacity: .2;
  -webkit-mask-image: linear-gradient(90deg, #000 0%, transparent 26%, transparent 74%, #000 100%);
  mask-image: linear-gradient(90deg, #000 0%, transparent 26%, transparent 74%, #000 100%);
}

/* The Programs and For Schools heroes are left-aligned with a panel on the
   right, so the centre-clearing mask would put symbols under the headline.
   They take the home page's shape instead. */
.listing-hero--split::before,
.page-hero--split::before {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 46%, #000 74%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 0%, transparent 46%, #000 74%, #000 100%);
}

/* A phone is one column wide and the copy fills it, so there is no clear side
   to put anything on. The pattern stays as a faint tint at the very top and
   bottom of the band and never crosses the text. */
@media (max-width: 760px) {
  .hero::before,
  .page-hero::before,
  .listing-hero::before,
  .listing-hero--split::before,
  .page-hero--split::before,
  .detail-hero--photo::before {
    opacity: .1;
    -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 22%, transparent 82%, #000 100%);
    mask-image: linear-gradient(180deg, #000 0%, transparent 22%, transparent 82%, #000 100%);
  }
}

/* The drift moved the pattern under a mask that is anchored to the band, which
   would slide symbols through the cleared zone. One or the other, and the
   reading wins: the pattern holds still and the motion on the page is the
   hero's entrance, the card hovers and the button presses. */
@media (prefers-reduced-motion: no-preference) {
  .hero::before,
  .page-hero::before,
  .listing-hero::before {
    animation: none;
    inset: 0;
    top: auto; left: auto; right: auto; bottom: auto;
    width: auto; height: auto;
    will-change: auto;
  }
}

/* --------------------------------------------------------------------------
   24. A ground per page

   Every inner page opened on the same soft gold, which told a visitor nothing
   about where they had landed. The palette has enough in it to give each page
   its own ground, so moving through the site feels like moving through rooms:
   you can tell you have changed page before you have read a word.

   Nine grounds, all from section 1, none invented. Each is paired with the
   text colour that clears WCAG AA on it, which is the only thing that decides
   whether a ground is usable: white on the two dark ones, ink on the seven
   light ones. The band is selected by a slug on <body> (inc/setup.php) rather
   than by page id, because ids differ per install and a stylesheet written
   against them styles the wrong page on the next one.
   -------------------------------------------------------------------------- */

/* ---- Light grounds --------------------------------------------------------
   Ink on all of them, the eyebrow a white wash that reads on every one. */
.mm-page-classes  .listing-hero,
.mm-page-classes  .page-hero  { background: var(--accent-yellow); }   /* soft gold */
.mm-page-schools  .listing-hero,
.mm-page-schools  .page-hero  { background: var(--accent-sage); }     /* sage */
.mm-page-about    .page-hero  { background: var(--accent-sun); }      /* marigold */
.mm-page-blog     .page-hero,
.mm-view-post     .page-hero  { background: var(--brand-tint); }      /* clay wash */
.mm-page-trial    .page-hero  { background: var(--gold-tint); }       /* pale gold */
.mm-page-contact  .page-hero  { background: var(--sage-tint); }       /* pale sage */
.mm-page-404      .page-hero  { background: var(--canvas); }          /* cream */

.mm-page-classes .listing-hero, .mm-page-classes .page-hero,
.mm-page-schools .listing-hero, .mm-page-schools .page-hero,
.mm-page-about .page-hero,
.mm-page-blog .page-hero, .mm-view-post .page-hero,
.mm-page-trial .page-hero,
.mm-page-contact .page-hero,
.mm-page-404 .page-hero { color: var(--ink-soft); }

.mm-page-classes .listing-hero h1, .mm-page-classes .page-hero h1,
.mm-page-schools .listing-hero h1, .mm-page-schools .page-hero h1,
.mm-page-about .page-hero h1,
.mm-page-blog .page-hero h1, .mm-view-post .page-hero h1,
.mm-page-trial .page-hero h1,
.mm-page-contact .page-hero h1,
.mm-page-404 .page-hero h1 { color: var(--ink); }

/* ---- Dark grounds ---------------------------------------------------------
   A single programme page takes the deep terracotta rather than the brand one,
   so it is recognisably not the home page while still clearly the same site.
   White clears AA on it at 5.9:1, where the lighter sage would have come in
   at 4.4 and failed body copy. */
.mm-view-mm_class .detail-hero,
.mm-view-mm_class .page-hero { background: var(--brand-dark); }
.mm-view-mm_class .detail-hero,
.mm-view-mm_class .detail-hero h1,
.mm-view-mm_class .detail-hero h2,
.mm-view-mm_class .page-hero,
.mm-view-mm_class .page-hero h1 { color: #fff; }
.mm-view-mm_class .detail-hero p,
.mm-view-mm_class .detail-hero .detail-hero__lead,
.mm-view-mm_class .page-hero p { color: rgba(255, 255, 255, .9); }
.mm-view-mm_class .detail-hero .breadcrumb { color: rgba(255, 255, 255, .72); }
.mm-view-mm_class .detail-hero .breadcrumb a { color: #fff; }
.mm-view-mm_class .detail-hero .eyebrow,
.mm-view-mm_class .detail-hero .pill,
.mm-view-mm_class .detail-hero .tag-pill,
.mm-view-mm_class .page-hero .eyebrow { background: rgba(255, 255, 255, .2); color: #fff; }
/* The pattern needs more strength to register on a dark ground. */
.mm-view-mm_class .detail-hero::before,
.mm-view-mm_class .page-hero::before { opacity: .14; }

/* ---- Page-specific pattern shape ------------------------------------------
   Programs and For Schools are left-aligned with a panel on the right, so they
   take the home page's mask rather than the centre-clearing one. The rest are
   centred and keep the mask from section 23. */
.mm-page-classes .listing-hero::before,
.mm-page-schools .listing-hero::before {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 46%, #000 74%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 0%, transparent 46%, #000 74%, #000 100%);
}

/* --------------------------------------------------------------------------
   25. Nothing reversed out of a light hero

   The scrims are gone but their text colours were written against a dozen
   different selectors, and the ones aimed at bare elements survived: the About
   lead was still rgba(255,255,255,.88), which was correct over a dark
   photograph and is 1.6:1 on marigold.

   Rather than chase each one, everything inside a light hero inherits ink, and
   the two links that should stay brand-coloured are named back. The one dark
   hero left (a single programme page) re-asserts white after, so the order
   here matters: general first, exception second.
   -------------------------------------------------------------------------- */

.page-hero:not(.mm-dark) *,
.listing-hero:not(.mm-dark) * {
  color: inherit;
}
.page-hero,
.listing-hero { color: var(--ink-soft); }
.page-hero h1,
.page-hero h2,
.listing-hero h1,
.listing-hero h2 { color: var(--ink); }

/* Links and markers keep the brand colour so they still read as links. */
.page-hero a,
.listing-hero a,
.page-hero .breadcrumb a,
.listing-hero .breadcrumb a,
.page-hero .split-hero__cta,
.listing-hero .split-hero__cta { color: var(--brand-dark); }
.page-hero .eyebrow,
.listing-hero .eyebrow { color: var(--brand-dark); background: rgba(255, 255, 255, .55); }
.page-hero .breadcrumb,
.listing-hero .breadcrumb { color: var(--muted); }
.page-hero svg,
.listing-hero svg { color: var(--brand-dark); }

/* The facts panel on Programs and For Schools is a white card sitting on the
   band, so it keeps its own colours rather than inheriting the band's. */
.listing-hero .split-hero__panel,
.listing-hero .split-hero__panel * { color: var(--ink-soft); }
.listing-hero .split-hero__panel strong,
.listing-hero .split-hero__panel b,
.listing-hero .split-hero__panel h3 { color: var(--ink); }

/* ---- The one dark hero ----------------------------------------------------
   A single programme page, on deep terracotta. Stated after the rules above so
   it wins, and stated in full so nothing inherits ink onto it. */
.mm-view-mm_class .detail-hero,
.mm-view-mm_class .detail-hero *,
.mm-view-mm_class .page-hero,
.mm-view-mm_class .page-hero * { color: #fff; }
.mm-view-mm_class .detail-hero p,
.mm-view-mm_class .page-hero p { color: rgba(255, 255, 255, .9); }
.mm-view-mm_class .detail-hero .breadcrumb,
.mm-view-mm_class .page-hero .breadcrumb { color: rgba(255, 255, 255, .72); }
.mm-view-mm_class .detail-hero .eyebrow,
.mm-view-mm_class .page-hero .eyebrow { background: rgba(255, 255, 255, .2); color: #fff; }
/* Its own panels are still white cards, so they keep ink. */
.mm-view-mm_class .detail-hero .detail-hero__panel,
.mm-view-mm_class .detail-hero .detail-hero__panel * { color: var(--ink-soft); }

/* --------------------------------------------------------------------------
   26. The opening

   A terracotta panel with the crane on it, held for about half a second, then
   lifted away to leave the hero underneath. It exits with the same curve the
   hero band carries on its bottom edge, so the panel reads as the hero
   arriving rather than as a splash screen getting out of the way.

   Total 1.2s, front page only, once per session (header.php). Everything
   about it is CSS: there is no state in which a failed script leaves a visitor
   behind a full-screen rectangle, because nothing has to run for it to go.
   -------------------------------------------------------------------------- */
.mm-intro {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: grid;
  place-items: center;
  background: var(--brand);
  /* The curve only becomes visible as the panel rises, which is the moment it
     stops being a cover and becomes the top of the page. */
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
  animation: mm-intro-out .62s var(--ease) .58s forwards;
  will-change: transform;
}
.mm-intro__mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  animation: mm-intro-mark .58s var(--ease) both;
}
.mm-intro__word {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: clamp(1.15rem, 3.4vw, 1.6rem);
  letter-spacing: .12em;
  color: #fff;
}

@keyframes mm-intro-mark {
  from { opacity: 0; transform: translate3d(0, 12px, 0) scale(.92); }
  to   { opacity: 1; transform: none; }
}
@keyframes mm-intro-out {
  to { transform: translate3d(0, -101%, 0); visibility: hidden; }
}

/* Seen it already this session. */
html.mm-intro-done .mm-intro { display: none; }

/* The hero's own entrance waits for the panel to clear, so the two read as one
   movement instead of playing over each other. */
@media (prefers-reduced-motion: no-preference) {
  .mm-page-home .hero__badge   { animation-delay: 1.02s; }
  .mm-page-home .hero__title   { animation-delay: 1.10s; }
  .mm-page-home .hero__sub     { animation-delay: 1.18s; }
  .mm-page-home .hero__actions { animation-delay: 1.26s; }
  .mm-page-home .hero__visual  { animation-delay: 1.14s; }
  .mm-page-home .cta-bar       { animation-delay: 1.6s; }
  /* Second visit in the session: no panel, so nothing to wait for. */
  html.mm-intro-done .hero__badge   { animation-delay: .04s; }
  html.mm-intro-done .hero__title   { animation-delay: .12s; }
  html.mm-intro-done .hero__sub     { animation-delay: .20s; }
  html.mm-intro-done .hero__actions { animation-delay: .28s; }
  html.mm-intro-done .hero__visual  { animation-delay: .18s; }
  html.mm-intro-done .cta-bar       { animation-delay: .65s; }
}

/* Never for a visitor who asked for less motion: the panel is not rendered at
   all, rather than rendered and rushed. */
@media (prefers-reduced-motion: reduce) {
  .mm-intro { display: none; }
}

/* --------------------------------------------------------------------------
   27. The icons come back, and the text is still readable

   Section 23 kept the copy clear by cutting the pattern out of the band it
   occupies. It worked, and it cost too much: on the home page the copy is on
   the left and the photograph is on the right, so the only strip the mask left
   showing was the strip the photograph covers. The icons were still there and
   almost nowhere visible.

   The mask goes. The pattern runs the full width again at a strength you can
   actually see, and the copy is protected by a scrim instead: a soft wash in
   the band's own tone, sitting between the pattern and the text. Behind the
   words the pattern fades into the ground; everywhere else it reads at full
   strength. The scrim also lifts the type's contrast, which the mask never
   did.

   Light grounds take a white wash, dark grounds a warm dark one, so the wash
   always disappears into the colour behind it rather than showing as a panel.
   -------------------------------------------------------------------------- */

/* Pattern at full width again, and stronger than 23 left it. */
.hero::before {
  opacity: .2;
  -webkit-mask-image: none;
  mask-image: none;
}
.page-hero::before,
.listing-hero::before,
.listing-hero--split::before,
.page-hero--split::before,
.detail-hero--photo::before {
  opacity: .34;
  -webkit-mask-image: none;
  mask-image: none;
}
.mm-view-mm_class .detail-hero::before,
.mm-view-mm_class .page-hero::before { opacity: .2; }

@media (max-width: 760px) {
  .hero::before,
  .page-hero::before,
  .listing-hero::before,
  .listing-hero--split::before,
  .page-hero--split::before,
  .detail-hero--photo::before {
    opacity: .22;
    -webkit-mask-image: none;
    mask-image: none;
  }
}

/* ---- The scrim ------------------------------------------------------------
   ::after, so it paints over the pattern in ::before and under the content,
   which carries z-index 1. pointer-events off: it covers the whole band and
   the buttons are underneath it. */
.hero::after,
.page-hero::after,
.listing-hero::after,
.detail-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Home and the split inner heroes: copy on the left, so the wash is strongest
   at the left edge and gone by the time it reaches the photograph. */
.hero::after {
  background: linear-gradient(90deg, rgba(102, 46, 12, .55) 0%, rgba(102, 46, 12, .34) 32%, transparent 62%);
}
.listing-hero--split::after,
.page-hero--split::after {
  background: linear-gradient(90deg, rgba(255, 255, 255, .62) 0%, rgba(255, 255, 255, .4) 34%, transparent 64%);
}

/* Centred inner heroes: the copy runs down the middle, so the wash is an
   ellipse under it and the pattern keeps both outer thirds. */
.page-hero::after,
.listing-hero::after {
  background: radial-gradient(58% 72% at 50% 50%, rgba(255, 255, 255, .66) 0%, rgba(255, 255, 255, .42) 46%, transparent 78%);
}
.listing-hero--split::after { background: linear-gradient(90deg, rgba(255, 255, 255, .62) 0%, rgba(255, 255, 255, .4) 34%, transparent 64%); }

/* The one dark inner hero wants a dark wash, not a white one. */
.mm-view-mm_class .detail-hero::after,
.mm-view-mm_class .page-hero::after {
  background: radial-gradient(60% 74% at 50% 50%, rgba(80, 36, 10, .5) 0%, rgba(80, 36, 10, .3) 48%, transparent 80%);
}

/* A phone is one column and the copy fills it, so a directional wash has
   nowhere to go: it becomes a full, very soft veil. The pattern still shows
   at the top and bottom of the band where there is no text. */
@media (max-width: 760px) {
  .hero::after {
    background: linear-gradient(180deg, transparent 0%, rgba(102, 46, 12, .42) 18%, rgba(102, 46, 12, .42) 82%, transparent 100%);
  }
  .page-hero::after,
  .listing-hero::after,
  .listing-hero--split::after,
  .page-hero--split::after {
    background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, .52) 16%, rgba(255, 255, 255, .52) 84%, transparent 100%);
  }
  .mm-view-mm_class .detail-hero::after,
  .mm-view-mm_class .page-hero::after {
    background: linear-gradient(180deg, transparent 0%, rgba(80, 36, 10, .42) 16%, rgba(80, 36, 10, .42) 84%, transparent 100%);
  }
}

/* The content sits above both layers. Stated here because the scrim is new and
   a hero whose wrap had no stacking context would now be behind it. */
.hero > .wrap,
.page-hero > .wrap,
.listing-hero > .wrap,
.detail-hero > .wrap { position: relative; z-index: 1; }

/* ---- 27.1 The pattern layer had no size -----------------------------------
   Why no icon was showing anywhere, mask or not.

   Section 22 grew the layer by a tile in each direction for the drift
   (top/left/right/bottom: -640px). Section 23 dropped the drift and tried to
   put it back with `inset: 0` followed by `top: auto; left: auto; right: auto;
   bottom: auto`. Those four are the same properties inset is shorthand for,
   so they overwrote it: the pseudo-element ended up with auto offsets and auto
   width and height, which for an absolutely positioned box with no content is
   zero by zero. It was painting a 0x0 rectangle of pattern.

   Stated once, in longhand, with nothing after it to undo it. */
.hero::before,
.page-hero::before,
.listing-hero::before,
.detail-hero--photo::before,
.hero::after,
.page-hero::after,
.listing-hero::after,
.detail-hero::after {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: auto;
  height: auto;
}

@media (prefers-reduced-motion: no-preference) {
  .hero::before,
  .page-hero::before,
  .listing-hero::before {
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    height: auto;
  }
}

/* --------------------------------------------------------------------------
   28. Light heroes: the icons and the type agree, and everything clears AA

   pattern-elements.svg is white, which is right on terracotta and wrong
   on every light ground the pages took in section 24. White strokes on soft
   gold or sage come out as pale grey, so the band carried grey icons under
   near-black type: two different inks on one surface, which is what reads as
   not matching rather than as texture.

   The tile is inverted instead of duplicated. It holds nothing but white
   strokes on transparent, so invert(1) turns them ink and leaves the alpha
   alone: one file still serves every ground, and there is no second asset to
   keep in step with the first.

   The accents move with it. A terracotta link on the sage band was the other
   half of the complaint, and it is the half that also failed: #A34D16 on
   #A5C3C2 is 3.8:1, under the 4.5 body copy needs. Everything that is not a
   heading or a white chip is now ink, which clears AA on all seven light
   grounds at once and stops the page arguing with itself about which warm
   colour it is.

   Measured, darkest ground first (ink #241A12, ink-soft #3D3630):
     marigold #EB940E   ink 7.3:1   ink-soft 5.2:1
     sage     #A5C3C2   ink 8.5:1   ink-soft 6.0:1
     gold     #EFDC95   ink 11.9:1  ink-soft 8.5:1
     clay     #F7E7DA   ink 13.4:1  ink-soft 9.5:1
     cream    #EFEEE4   ink 13.0:1  ink-soft 9.2:1
     pale gold/sage     ink 13.5:1+ ink-soft 9.6:1+
   -------------------------------------------------------------------------- */

/* ---- The pattern goes ink on light grounds -------------------------------- */
.page-hero::before,
.listing-hero::before,
.listing-hero--split::before,
.page-hero--split::before {
  filter: invert(1);
  opacity: .1;
}
/* Marigold is the darkest of the light grounds, so ink strokes need a little
   more to register on it; the palest need less or they turn to dirt. */
.mm-page-about .page-hero::before { opacity: .13; }
.mm-page-schools .listing-hero::before,
.mm-page-schools .page-hero::before { opacity: .12; }
.mm-page-trial .page-hero::before,
.mm-page-contact .page-hero::before,
.mm-page-404 .page-hero::before,
.mm-page-blog .page-hero::before,
.mm-view-post .page-hero::before { opacity: .085; }

/* The two dark grounds keep the white tile: inverting there would paint black
   strokes on terracotta and lose them entirely. */
.hero::before { filter: none; }
.mm-view-mm_class .detail-hero::before,
.mm-view-mm_class .page-hero::before { filter: none; opacity: .2; }

/* ---- One ink for everything that is not a heading or a chip --------------- */
.page-hero,
.listing-hero { color: var(--ink-soft); }
.page-hero h1, .page-hero h2,
.listing-hero h1, .listing-hero h2 { color: var(--ink); }
.page-hero p, .page-hero li,
.listing-hero p, .listing-hero li { color: var(--ink-soft); }

/* Links read as links by weight and underline rather than by hue, which is
   what lets one colour work on a warm ground and a cool one. */
.page-hero a,
.listing-hero a,
.page-hero .split-hero__cta,
.listing-hero .split-hero__cta {
  color: var(--ink);
  text-decoration-color: rgba(36, 26, 18, .38);
}
.page-hero a:hover,
.listing-hero a:hover,
.page-hero .split-hero__cta:hover,
.listing-hero .split-hero__cta:hover {
  color: var(--brand-darker);
  text-decoration-color: var(--brand-darker);
}
.page-hero svg,
.listing-hero svg { color: var(--ink); }

/* The breadcrumb stays quieter than the copy but still clears AA on marigold,
   which is the ground that decides this number. */
.page-hero .breadcrumb,
.listing-hero .breadcrumb { color: rgba(36, 26, 18, .72); }
.page-hero .breadcrumb a,
.listing-hero .breadcrumb a { color: var(--ink); }
.page-hero .breadcrumb svg,
.listing-hero .breadcrumb svg { color: rgba(36, 26, 18, .5); }

/* The eyebrow is a white chip, so it is measured against its own fill rather
   than against the band. Opaque enough to read as a chip on every ground,
   which is what lets it keep the brand colour when nothing else does. */
.page-hero .eyebrow,
.listing-hero .eyebrow {
  background: rgba(255, 255, 255, .82);
  color: var(--brand-dark);
}

/* The facts row and its markers sit on the band, not on a chip. */
.listing-hero .split-hero__facts,
.page-hero .split-hero__facts { color: var(--ink-soft); }
.listing-hero .split-hero__facts svg,
.page-hero .split-hero__facts svg { color: var(--brand-dark); }

/* White cards on the band keep their own colours: they are a different
   surface and the rules above would otherwise reach into them. */
.listing-hero .split-hero__panel,
.listing-hero .split-hero__panel p,
.listing-hero .split-hero__panel li { color: var(--ink-soft); }
.listing-hero .split-hero__panel a,
.listing-hero .split-hero__panel h3,
.listing-hero .split-hero__panel strong,
.listing-hero .split-hero__panel b { color: var(--ink); }
.listing-hero .split-hero__panel svg { color: var(--sage-deep); }

/* ---- 28.1 The lead paragraph ----------------------------------------------
   Still reversed out on For Schools. The scrims left behind a set of rules
   written as `.listing-hero--photo .split-hero__lead`, and a two-class
   selector beats the `.listing-hero p` in 28, so the one sentence under each
   inner-page headline stayed white: 1.9:1 on sage, the least readable text on
   the site and the only text on that band anyone still had to read.

   Matched at the same weight, with the --photo compound included so it lands
   whether or not the block still carries a background image. */
.listing-hero .split-hero__lead,
.listing-hero--photo .split-hero__lead,
.listing-hero--split .split-hero__lead,
.page-hero .split-hero__lead,
.page-hero--photo .split-hero__lead,
.page-hero .page-hero__lead,
.page-hero--photo .page-hero__lead,
.listing-hero .split-hero__copy > p,
.page-hero .page-hero__inner > p {
  color: var(--ink-soft);
}

/* And the same for anything else those rule sets reversed out: the facts row,
   the text link under it, and the small print. */
.listing-hero--photo .split-hero__facts,
.page-hero--photo .split-hero__facts { color: var(--ink-soft); }
.listing-hero--photo .split-hero__cta,
.page-hero--photo .split-hero__cta {
  color: var(--ink);
  text-decoration-color: rgba(36, 26, 18, .38);
}
.listing-hero--photo small,
.page-hero--photo small { color: rgba(36, 26, 18, .72); }

/* The dark programme hero is the one place all of that should stay white. */
.mm-view-mm_class .detail-hero .split-hero__lead,
.mm-view-mm_class .detail-hero .detail-hero__lead,
.mm-view-mm_class .page-hero .page-hero__lead,
.mm-view-mm_class .detail-hero p { color: rgba(255, 255, 255, .9); }

/* --------------------------------------------------------------------------
   29. The glow goes, and the lead finally lands

   The scrim from section 27 was solving a problem that no longer exists. It
   was there to hold the copy readable against a white pattern at full
   strength; section 28 turned the pattern ink and dropped it to a tenth, so
   the band is already quiet behind the text and all the wash does now is sit
   there as a pale bloom in the middle of the hero. It reads as a smudge on the
   screen rather than as anything anyone designed.

   Removed outright rather than softened. Legibility is carried by the pattern
   being faint and the type being ink, which is the right place for it: a scrim
   is a patch over a background that is too loud, and this one is not any more.

   The home hero keeps no wash either; its pattern comes down a little instead,
   which does the same job for white type on terracotta without putting a dark
   cloud behind the headline.
   -------------------------------------------------------------------------- */
.hero::after,
.page-hero::after,
.listing-hero::after,
.detail-hero::after {
  content: none;
  background: none;
}
.hero::before { opacity: .14; }
.mm-view-mm_class .detail-hero::before,
.mm-view-mm_class .page-hero::before { opacity: .15; }

/* ---- The lead paragraph, at the weight that wins --------------------------
   It is `.page-hero.page-hero--split.page-hero--photo .split-hero__lead` in
   the stylesheet the scrims left behind: three classes, so every attempt at it
   with two has quietly lost. For Schools was the page showing it, and its one
   sentence was the least readable text on the site at 1.9:1.

   Matched at three classes here, and stated for the listing twin at the same
   weight so the two pages cannot drift apart again. */
.page-hero.page-hero--split.page-hero--photo .split-hero__lead,
.page-hero.page-hero--split .split-hero__lead,
.listing-hero.listing-hero--split.listing-hero--photo .split-hero__lead,
.listing-hero.listing-hero--split .split-hero__lead,
.page-hero.page-hero--photo .page-hero__lead,
.page-hero.page-hero--photo .split-hero__lead {
  color: var(--ink-soft);
}

/* Same three-class weight for everything else on that band. */
.page-hero.page-hero--split.page-hero--photo .split-hero__facts,
.page-hero.page-hero--split.page-hero--photo small,
.listing-hero.listing-hero--split.listing-hero--photo .split-hero__facts,
.listing-hero.listing-hero--split.listing-hero--photo small { color: var(--ink-soft); }

.page-hero.page-hero--split.page-hero--photo .split-hero__cta,
.listing-hero.listing-hero--split.listing-hero--photo .split-hero__cta {
  color: var(--ink);
  text-decoration-color: rgba(36, 26, 18, .38);
}

.page-hero.page-hero--split.page-hero--photo h1,
.listing-hero.listing-hero--split.listing-hero--photo h1 { color: var(--ink); }

/* The dark programme hero is the exception, stated last so it holds. */
.mm-view-mm_class .detail-hero .detail-hero__lead,
.mm-view-mm_class .detail-hero p,
.mm-view-mm_class .page-hero.page-hero--photo .split-hero__lead,
.mm-view-mm_class .page-hero.page-hero--photo .page-hero__lead { color: rgba(255, 255, 255, .9); }
.mm-view-mm_class .detail-hero h1,
.mm-view-mm_class .page-hero.page-hero--photo h1 { color: #fff; }

/* --------------------------------------------------------------------------
   30. Program groups on the home page

   The row used to be three individual programmes, which made the home page
   promise less than the site holds: whichever three an editor picked were the
   only ones anyone saw, and they repeated the Programs page almost exactly one
   click away. It is the groups now, each card saying how many programmes are
   inside it and what ages they cover, and each linking to the Programs page
   with that filter already set.

   Same card vocabulary as everything else: white on the tint, 20px radius,
   photo on top, the lift on hover that says it is clickable.
   -------------------------------------------------------------------------- */
.group-grid {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
}
/* Four venues, four across on a desktop: the row is the list, so breaking it
   over two lines would suggest the fourth is an afterthought. Two up on a
   tablet, one on a phone. */
@media (min-width: 560px)  { .group-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .group-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; } }

/* Venue names run long ("iForest - International Forest School"), so the name
   gets room to wrap to two lines without the cards below it shifting. */
@media (min-width: 1000px) {
  .group-card__name { font-size: 1.1rem; min-height: 2.6em; }
  .group-card__body { padding: 20px 20px 22px; }
}

.group-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-xs);
  color: inherit;
  height: 100%;
}
.group-card__media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  background: linear-gradient(150deg, var(--sage-tint), var(--canvas-2));
  overflow: hidden;
}
.group-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* How many programmes are in the group. It is the one number that makes a
   group card worth more than the programme card it replaced, so it sits on the
   photograph where it is read first. */
.group-card__count {
  position: absolute;
  left: 14px;
  top: 14px;
  background: rgba(255, 255, 255, .92);
  color: var(--ink);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .8rem;
  padding: 6px 13px;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-xs);
}

.group-card__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 22px 24px 24px;
  flex-grow: 1;
}
.group-card__name {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: 1.24rem;
  line-height: 1.3;
  color: var(--ink);
}
.group-card__ages { font-size: .92rem; color: var(--muted); }

/* Pushed to the bottom so the link sits on one line across the row however
   long the group names run. */
.group-card__link {
  margin-top: auto;
  padding-top: 14px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .95rem;
  color: var(--brand-dark);
}
.group-card__link svg { width: 17px; height: 17px; }

@media (prefers-reduced-motion: no-preference) {
  .group-card { transition: transform .34s var(--ease), box-shadow .34s var(--ease); }
  .group-card__img { transition: transform .6s var(--ease); }
  .group-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }
  .group-card:hover .group-card__img { transform: scale(1.04); }
  .group-card:hover .group-card__link { gap: 11px; }
}
.group-card:focus-visible { outline: 3px solid var(--brand); outline-offset: 3px; }

/* --------------------------------------------------------------------------
   31. The safety band, as the design has it

   It went in as a flat sage rectangle with square edges and nothing on it: a
   slab of colour dropped between two cream sections, which is the one shape
   the design never uses. Every other band on the site curves into the cream
   and carries the pattern, and this was the only one that did neither, so it
   read as a different site for the length of one section.

   Curved on both edges, because it has cream above and below it, and patterned
   like the heroes. The pattern is inverted to ink here for the same reason as
   on the light heroes: sage is a light ground, and white strokes on it come
   out as grey haze next to ink type.
   -------------------------------------------------------------------------- */
.safety-band {
  border-top-left-radius: 46% 13%;
  border-top-right-radius: 46% 13%;
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
  /* The curve that needed extra clearance here was taken back out (the bands
     are square-edged again), so the band keeps the same rhythm as every other
     section rather than 128px against their 104px. */
  padding-block: var(--band);
}
.safety-band::before {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 0;
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  filter: invert(1);
  opacity: .1;
  pointer-events: none;
}
.safety-band > .wrap { position: relative; z-index: 1; }

@media (max-width: 760px) {
  .safety-band {
    border-top-left-radius: 44% 5%;
    border-top-right-radius: 44% 5%;
    border-bottom-left-radius: 44% 5%;
    border-bottom-right-radius: 44% 5%;
    padding-block: var(--band);
  }
  .safety-band::before { background-size: 420px 420px; opacity: .08; }
}

/* The white cards sit on the pattern, so they need to be opaque enough to be a
   surface rather than a tint. They were .78, which let the symbols show
   through the text. */
.safety-band .safety__row { background: rgba(255, 255, 255, .92); }

/* --------------------------------------------------------------------------
   32. The onboarding steps, rebuilt

   It was four labels and four paragraphs laid over a photograph of a sports
   day, under a scrim heavy enough to make the type legible and therefore heavy
   enough to make the photograph pointless. The picture showed nothing about
   onboarding, the copy was the only thing anyone needed, and it was the one
   part of the page fighting to be read.

   Flat cream instead, curved like every other band, with the four steps as
   white cards. The step is the unit here, so it gets a card: each one is a
   thing that happens on a date, and a card says that better than an icon
   floating on a line does.

   The number comes back too. "Inquire, Consult, Plan, Launch" is a sequence,
   and nothing in the old treatment said which came first except left-to-right
   order, which is no order at all once it wraps on a phone.
   -------------------------------------------------------------------------- */
.steps-section--photo > .photo-band,
.steps-section > .photo-band { display: none; }

.steps-section,
.steps-section--photo {
  position: relative;
  background: var(--canvas);
  color: var(--ink-soft);
  border-top-left-radius: 46% 13%;
  border-top-right-radius: 46% 13%;
  border-bottom-left-radius: 46% 13%;
  border-bottom-right-radius: 46% 13%;
  padding-block: var(--band-lg);
  overflow: hidden;
}
.steps-section--photo::before {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 0;
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  filter: invert(1);
  opacity: .07;
  pointer-events: none;
}
.steps-section > .wrap,
.steps-section--photo > .wrap { position: relative; z-index: 1; }

.steps-section--photo .section-title { color: var(--ink); }
.steps-section--photo .section-sub { color: var(--muted); }
.steps-section--photo .eyebrow { background: rgba(255, 255, 255, .82); color: var(--brand-dark); }

/* The connector line was drawn behind circular icons. There are cards now, so
   there is nothing to connect and the rule would run through them. */
.steps-section--photo .steps::before,
.steps-section .steps::before { display: none; }

.steps {
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
  counter-reset: mm-step;
}
@media (min-width: 620px) { .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.step {
  position: relative;
  background: var(--surface);
  border-radius: var(--r-lg);
  padding: 26px 24px 28px;
  box-shadow: var(--shadow-sm);
  text-align: left;
  counter-increment: mm-step;
}

/* The icon keeps its tile, sized down: on a card it is a marker beside a
   number rather than the whole of the step's identity. */
.steps-section--photo .step__ico,
.steps-section .step__ico {
  position: static;
  width: 40px;
  height: 40px;
  border-radius: var(--r-sm);
  background: var(--brand-tint);
  color: var(--brand-dark);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  display: grid;
  place-items: center;
  margin: 0 0 16px;
}
.step__ico svg { width: 20px; height: 20px; }

/* The number, set as a counter so the markup does not have to carry it and
   an editor adding a fifth step gets 05 without touching anything. */
.step::after {
  content: counter(mm-step, decimal-leading-zero);
  position: absolute;
  top: 22px;
  right: 24px;
  font-family: var(--font-head);
  font-weight: 800;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--brand);
  opacity: .22;
}

.steps-section--photo .step h3,
.steps-section .step h3 { color: var(--ink); font-size: 1.12rem; margin-bottom: 7px; }
.steps-section--photo .step p,
.steps-section .step p { color: var(--muted); font-size: .94rem; line-height: 1.75; }

@media (max-width: 760px) {
  .steps-section,
  .steps-section--photo {
    border-top-left-radius: 44% 5%;
    border-top-right-radius: 44% 5%;
    border-bottom-left-radius: 44% 5%;
    border-bottom-right-radius: 44% 5%;
    padding-block: clamp(56px, 12vw, 84px);
  }
  .steps-section--photo::before { background-size: 420px 420px; }
}

@media (prefers-reduced-motion: no-preference) {
  .step { transition: transform .34s var(--ease), box-shadow .34s var(--ease); }
  .step:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
}

/* ---- 33. The sentence on a teacher card -----------------------------------
   Sits between the name and the certificates: the certificates say what
   someone is qualified to do, and this says what they are like to have in a
   room with your children. Clamped to four lines so one long biography cannot
   set the height of a whole row of cards. */
.team-card__bio {
  color: var(--muted);
  font-size: .92rem;
  line-height: 1.75;
  margin: 8px 0 0;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.team-card__certs { margin-top: 14px; }

/* ---- 34. No group label above the teachers on For Schools ------------------
   The row is preceded by "Meet Our Expert Educators" and then, immediately
   under it, a pill reading TEAM. It labels a group that only has one group in
   it, so it is naming the obvious twice. The pill earns its place on About,
   where there are several groups to tell apart, so it goes only here. */
.mm-page-schools .team-section__heading,
.mm-page-schools .team-section__pill { display: none; }
.mm-page-schools .team-section { margin-top: 0; }

/* --------------------------------------------------------------------------
   35. The program page

   Three things were wrong with it and none of them were decoration.

   It never said the price. mm_price is on every program record, the listing
   card prints it, and the page a parent opens from that card did not mention
   money again — the only figure on it was a pill reading "Trial ¥3,500",
   which is the session price arriving under the wrong label because
   mm_class_trial_amount() falls back to it.

   Half the hero was empty. 22.2 took the photograph off every hero because
   heroes were using photographs as ground and darkening them until they could
   not be seen; on this template that also hid .detail-hero__media, so a
   program with a picture attached rendered a 600px band of terracotta with
   nothing beside the heading.

   And it said the same four facts three times: the hero pills carried the
   venue, the schedule and the fee, and the strip underneath carried the
   schedule and the venue again.

   So: the pills identify the child and the place, the panel answers what it
   costs and what booking commits you to, the strip states the four facts
   once, and everything below it is the part of the page that was missing —
   how joining actually works, who teaches it, a parent who has been, and the
   other programs at the same venue.
   -------------------------------------------------------------------------- */

/* ---- 35.1 The hero panel --------------------------------------------------
   A photograph as an object on the ground rather than as the ground: at its
   own size, unscrimmed, with the facts a parent came for attached under it.
   22.2's objection stands and .detail-hero__bg is still gone. */
.detail-hero--panel { padding-block: clamp(40px, 6vw, 84px); }
.detail-hero--panel .detail-hero__grid { align-items: center; }
@media (min-width: 900px) {
  .detail-hero--panel .detail-hero__grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
}
@media (min-width: 1100px) {
  .detail-hero--panel .detail-hero__grid { grid-template-columns: minmax(0, 1fr) 420px; }
}

.program-panel {
  background: var(--surface);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  /* The panel is the one object in the band that is not terracotta, so it
     carries the whole of the contrast. A hairline keeps its top edge from
     dissolving into a light photograph. */
  border: 1px solid rgba(255, 255, 255, .5);
}

.program-panel__photo {
  margin: 0;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--canvas-2);
  /* The same shallow curve the hero band carries on the edge that meets the
     cream. Repeated here it reads as the site's own way of ending a coloured
     surface rather than as an ornament on a photograph. */
  border-bottom-left-radius: 44% 10%;
  border-bottom-right-radius: 44% 10%;
}
.program-panel__photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 38%;
  display: block;
}

.program-panel__body { padding: 22px clamp(22px, 3vw, 28px) clamp(22px, 3vw, 26px); }
/* The photo's curve cuts into the body, so the first thing under it needs to
   clear the deepest point of the arc rather than sit in it. */
.program-panel__photo + .program-panel__body { padding-top: 6px; }

.program-panel__label {
  display: block;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
}
.program-panel__figure {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 7px;
  margin: 4px 0 0;
}
.program-panel__figure b {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: clamp(1.7rem, 3vw, 2.05rem);
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.program-panel__figure span { font-size: .95rem; color: var(--muted); }

.program-panel__row {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 0 0;
  padding-top: 15px;
  border-top: 1px solid var(--line);
  font-size: .94rem;
  color: var(--ink-soft);
}
.program-panel__row svg { width: 17px; height: 17px; flex: none; color: var(--accent-sun); }
/* The label takes the slack so the figure is flush right, which lines it up
   under the tuition above it. */
.program-panel__row span { flex: 1 1 auto; }
.program-panel__row b {
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.program-panel__note {
  margin: 14px 0 0;
  font-size: .85rem;
  line-height: 1.65;
  color: var(--muted);
}

/* The band whitens everything inside it (see "The one dark hero"), which would
   take the panel's type with it. Stated after, and per element, so nothing in
   the card inherits white onto white. */
.mm-view-mm_class .detail-hero .program-panel,
.mm-view-mm_class .detail-hero .program-panel * { color: var(--ink-soft); }
.mm-view-mm_class .detail-hero .program-panel__figure b,
.mm-view-mm_class .detail-hero .program-panel__row b { color: var(--ink); }
.mm-view-mm_class .detail-hero .program-panel__label,
.mm-view-mm_class .detail-hero .program-panel__figure span,
.mm-view-mm_class .detail-hero .program-panel__note { color: var(--muted); }
.mm-view-mm_class .detail-hero .program-panel__row svg { color: var(--accent-sun); }

@media (max-width: 899.98px) {
  /* Phone: the panel is under the heading and the button, not above them —
     the heading is what tells a parent they are on the right page, and it
     should not start below the fold. */
  /* The panel runs the full width of the card above it; a 460px cap left it
     short of that card's right edge on a tablet. The photo is held to a
     band instead, so the fee still arrives without a screen of picture. */
  .detail-hero--panel .program-panel__photo { width: 100%; max-height: 18rem; }
}

/* ---- 35.2 At a glance -----------------------------------------------------
   The four tiles keep their order — the fact on top, the sentence that
   explains it underneath — because mm_class_structure() writes `note` as
   prose, not as a label, and a sentence set in small caps over its own
   headline cannot be read. What they gain is room: the tiles were tight
   enough that a three-line venue name touched the edges, and they now sit on
   the page ground rather than in a tinted band, so the white reads as a card
   and the strip stops competing with the hero above it. */
.facts-band { padding-block: clamp(26px, 4vw, 40px); }
.facts-band .facts-rail__item {
  align-items: flex-start;
  padding: 18px 18px 19px;
  box-shadow: var(--shadow-xs);
}
.facts-band .facts-rail__ico { margin-top: 1px; }
.facts-band .facts-rail__body b { font-size: 1.04rem; line-height: 1.3; letter-spacing: -.01em; }
.facts-band .facts-rail__body small { margin-top: 5px; line-height: 1.6; }

/* ---- 35.3 What your child will learn --------------------------------------
   Four across in one flat row gave each milestone about 250px, which holds a
   two-line phrase and nothing else — so where an editor had written the
   supporting sentence the field carries, there was nowhere to put it.

   Heading left and the milestones two-up beside it instead. The heading holds
   still while the list scrolls past it, each card is wide enough for a
   sentence, and the cards cycle the three warm tints rather than sitting in
   four identical white boxes. */
.outcomes__grid { display: grid; gap: clamp(28px, 4vw, 48px); }
@media (min-width: 1000px) {
  .outcomes__grid { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: clamp(40px, 5vw, 72px); align-items: start; }
  .outcomes__head { position: sticky; top: calc(var(--header-h) + 28px); }
}
.outcomes .outcomes__head { max-width: 46ch; margin-bottom: 0; }
.outcomes .outcomes__head .section-title { margin-bottom: 14px; }
.outcomes .outcomes__head .section-sub { margin-inline: 0; }

.outcomes .outcome-list { gap: 16px; grid-template-columns: 1fr; }
@media (min-width: 700px) { .outcomes .outcome-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.outcomes .outcome-list__item {
  gap: 14px;
  padding: clamp(22px, 2.6vw, 28px);
  border: 0;
  border-radius: var(--r-lg);
  box-shadow: none;
}
.outcomes .outcome-list__item--t1 { background: var(--brand-tint-2); }
.outcomes .outcome-list__item--t2 { background: var(--sage-tint); }
.outcomes .outcome-list__item--t3 { background: var(--gold-tint); }
.outcomes .outcome-list__item--t1 .outcome-list__num { background: var(--brand-tint); color: var(--brand-dark); }
.outcomes .outcome-list__item--t2 .outcome-list__num { background: #fff; color: var(--sage-deep); }
.outcomes .outcome-list__item--t3 .outcome-list__num { background: #fff; color: var(--brand-dark); }
.outcomes .outcome-list h3 { font-size: 1.12rem; line-height: 1.35; }
.outcomes .outcome-list p { margin-top: 7px; font-size: .95rem; line-height: 1.8; color: var(--ink-soft); }

/* ---- 35.4 From here to the first lesson -----------------------------------
   The page asked for a booking and never said what booking was like, which on
   a form rather than a checkout is the question that stops people. Three
   steps, joined by a rule that runs behind the numbers so they read as one
   sequence rather than three cards. */
.join-steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 22px;
  grid-template-columns: 1fr;
  position: relative;
}
@media (min-width: 820px) {
  .join-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 30px; }
  /* The rule sits on the numbers' centre line and stops short of the first
     and last, so the sequence has ends rather than running off the page. */
  .join-steps::before {
    content: "";
    position: absolute;
    top: 21px;
    /* From the centre of the first number to the centre of the last. The
       numbers sit at the left edge of their column, not in the middle of it,
       so a symmetrical inset overshoots the third circle by most of a column
       — which is what a flat percentage did here. 21px is the circle's
       radius; the right inset is one column less that radius. */
    left: 21px;
    right: calc((100% - 60px) / 3 - 21px);
    height: 2px;
    background: var(--line);
    z-index: 0;
  }
}
.join-step { position: relative; z-index: 1; }
.join-step__num {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-family: var(--font-head); font-weight: 800; font-size: 1.02rem;
  font-variant-numeric: tabular-nums;
  /* A ring in the page ground, so the rule behind it stops at the circle
     rather than touching it. */
  box-shadow: 0 0 0 7px var(--canvas-2);
}
.join-step__ico { display: none; }
.join-step h3 { font-size: 1.14rem; line-height: 1.35; margin: 18px 0 0; }
.join-step p { margin: 8px 0 0; color: var(--muted); font-size: .95rem; line-height: 1.8; max-width: 34ch; }

/* ---- 35.5 The teacher -----------------------------------------------------
   A name and three lines used to take a centred band the full width of the
   page, which is a lot of paper for very little.

   This band briefly ran the teacher two-up against a parent's quote. The quote
   had to be borrowed — there is one testimonial set for the whole site and
   nothing ties a quote to a program — so a page headed Robotics could show a
   parent talking about summer camp. It is gone, and the teacher has the band:
   a portrait at a size you can actually read a face in, the name set as a
   heading rather than as a caption, and the biography at a comfortable measure
   beside it instead of wrapping in a 300px column.

   The quotes keep the home page, where they are about the school rather than
   about one program. */
.people-grid { display: grid; gap: 22px; }

.people-band .teacher-card {
  margin: 0;
  max-width: none;
  align-items: center;
  background: var(--canvas-2);
  border-color: transparent;
  box-shadow: none;
  border-radius: var(--r-xl);
  padding: clamp(26px, 4vw, 48px);
  grid-template-columns: 1fr !important;
  gap: clamp(24px, 3.5vw, 44px);
}
@media (min-width: 720px) {
  .people-band .teacher-card { grid-template-columns: minmax(0, 300px) minmax(0, 1fr) !important; }
}
.people-band .teacher-card__photo,
.people-band .teacher-card__mono { max-width: 300px; width: 100%; border-radius: var(--r-lg); }
.people-band .teacher-card__mono { aspect-ratio: 1; }
.people-band .teacher-card__mono span { font-size: clamp(2.4rem, 5vw, 3.4rem); }

.people-band .teacher-card__kicker {
  display: block;
  font-family: var(--font-head); font-weight: 700;
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}
.people-band .teacher-card h3 {
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  line-height: 1.2;
  letter-spacing: -.02em;
  margin-bottom: 6px;
}
.people-band .teacher-card__role { margin-bottom: 16px; }
.people-band .teacher-card p {
  font-size: clamp(1rem, 1.3vw, 1.1rem);
  line-height: 1.85;
  max-width: 54ch;
  margin-bottom: 20px;
}
.people-band .teacher-card__certs span { font-size: .88rem; padding: 8px 14px; }

/* ---- 35.6 FAQs ------------------------------------------------------------
   A centred heading over a column of accordions pushed the first question a
   third of the way down the band and left the heading floating over nothing.
   Reading starts at the left edge, and so does the section. */
.faq-band__grid { display: grid; gap: clamp(24px, 3vw, 40px); }
@media (min-width: 1000px) {
  .faq-band__grid { grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: clamp(40px, 5vw, 72px); align-items: start; }
  .faq-band__head { position: sticky; top: calc(var(--header-h) + 28px); }
}
.faq-band .faq-band__head { margin-bottom: 0; }
.faq-band .faq-list { max-width: none; }

/* ---- 35.7 More at this venue ----------------------------------------------
   The page's only exits were "book this one" and the header. A parent whose
   child is the wrong age for this program had to go back to Programs and
   filter again, when the sibling programs at the venue they have already
   chosen are the likeliest answer. */
.siblings-band { background: var(--brand-tint-2); }
.section-head--row {
  max-width: none;
  display: flex; flex-wrap: wrap; gap: 16px 24px;
  align-items: flex-end; justify-content: space-between;
}
.section-head--row .section-title { margin-bottom: 0; }
.section-head--row > div { max-width: 640px; }
.siblings-band .class-card { border: 1px solid var(--line); }

/* ---- 35.8 The bar on a phone ----------------------------------------------
   The button is what a parent is looking at by the time they decide, and the
   panel that states the fee is back at the top of the page. The figure rides
   beside it so the bar answers "how much" without a scroll. */
.sticky-cta--priced { display: none; }
@media (max-width: 899.98px) {
  .sticky-cta--priced { display: flex; align-items: center; gap: 14px; }
  .sticky-cta--priced .btn { width: auto; flex: 1 1 auto; justify-content: center; }
}
/* Out of the way while the booking form is on screen (single-mm_class.php
   sets .is-away): the bar sent the parent there, and left up it covered the
   fields they were filling in. */
.sticky-cta { transition: transform var(--base) var(--ease), visibility var(--base); }
.sticky-cta.is-away { transform: translateY(100%); visibility: hidden; }
@media (prefers-reduced-motion: reduce) {
  .sticky-cta { transition: none; }
}
.sticky-cta__price { display: flex; flex-direction: column; flex: none; line-height: 1.25; }
.sticky-cta__price small {
  font-family: var(--font-head); font-weight: 700;
  font-size: .64rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
.sticky-cta__price b {
  font-family: var(--font-head); font-weight: 800; font-size: 1.05rem;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ---- 35.9 The two buttons in the band -------------------------------------
   "Terracotta on terracotta is invisible, and white on soft gold fails AA" is
   already written twice in this stylesheet — at 18.5 for the closing band and
   at the home hero, which both flip their primary to soft gold with ink on it.
   The program hero is the third terracotta ground on the site and was the one
   that never got the flip: it carried a terracotta button on brand-dark, which
   is the exact pair those two rules exist to avoid.

   The secondary beside it used to be frosted by a .detail-hero--photo rule.
   That modifier described a hero with a photograph used as its ground, which
   this band no longer is, so the treatment moves onto the modifier the band
   actually carries rather than being kept alive by a class that lies about
   what it is. */
.detail-hero--panel .btn--primary {
  background: var(--accent-yellow);
  color: var(--ink);
  box-shadow: 0 14px 30px rgba(36, 26, 18, .22);
}
.detail-hero--panel .btn--primary:hover { background: #E8D07A; color: var(--ink); }
.detail-hero--panel .btn--secondary {
  --btn-bg: rgba(255, 255, 255, .12);
  --btn-fg: #fff;
  background: rgba(255, 255, 255, .12);
  color: #fff;
  border: 1.5px solid rgba(255, 255, 255, .45);
}
.detail-hero--panel .btn--secondary:hover {
  background: rgba(255, 255, 255, .2);
  border-color: rgba(255, 255, 255, .7);
  color: #fff;
}
/* The band whitens every descendant, which would take the ink off the gold
   button with it. */
.mm-view-mm_class .detail-hero--panel .btn--primary,
.mm-view-mm_class .detail-hero--panel .btn--primary * { color: var(--ink); }

/* ---- 35.10 The pattern on this band ---------------------------------------
   Every rule that draws the symbol tile — 22.2, the masks, and 27.1's fix for
   the layer that had no size — is written against .detail-hero--photo, a
   modifier that meant "this hero uses a photograph as its ground". This band
   does not, so it does not carry that class and lost the pattern with it.

   Stated here in full rather than by adding a second selector to eight rules
   spread over a thousand lines: one place to read, and nothing after it to
   undo it (27.1 is the cautionary tale — inset: 0 followed later by four auto
   offsets left the layer painting a 0x0 rectangle).

   No mask. The centre-clearing mask exists for heroes whose copy runs down the
   middle; this one is copy left, panel right, so clearing the centre would put
   symbols behind the headline and take them off the empty edges. Same shape
   the Programs hero uses, at the .2 the class pages already asked for. */
.detail-hero--panel::before {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  width: auto; height: auto;
  z-index: 0;
  pointer-events: none;
  background: url(../images/pattern-elements.svg) 0 0 / 640px 640px repeat;
  opacity: .2;
  -webkit-mask-image: none;
  mask-image: none;
}
@media (max-width: 760px) {
  .detail-hero--panel::before { background-size: 420px 420px; opacity: .22; }
}

/* --------------------------------------------------------------------------
   36. The page closes square

   20.6 gave the closing band a curve on the edge that meets the cream, to
   match the hero at the other end. The footer under it has no curve of its
   own, so that one arc is the whole shape of the bottom of the page: cream,
   then a shallow dome of terracotta, then the footer in the same terracotta
   behind it.

   It goes. The band meets the cream on a straight line and the closing block
   reads as one rectangle of brand colour with the footer inside it, which is
   what it actually is. The hero keeps its curve — that edge has cream below
   it and nothing behind it, so the shape is doing work there that it was not
   doing here.
   -------------------------------------------------------------------------- */
.cta-strip__box,
.cta-strip__box--photo {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
@media (max-width: 700px) {
  .cta-strip__box { border-top-left-radius: 0; border-top-right-radius: 0; }
}

/* --------------------------------------------------------------------------
   37. Square bands

   The site drew a shallow arc on every edge where a coloured band met the
   cream — the hero at the top of each page, the closing CTA at the foot, and
   the safety and onboarding bands in between. 36 took it off the closing band
   and the footer under it; this takes it off the rest, because one page that
   opens square and another that opens on a dome is worse than either rule
   applied consistently.

   Every band now meets the ground on a straight line. Stated in one place and
   after everything that sets these radii, so there is no page left where an
   arc survives because its rule happened to come later in the file.

   The splash panel goes with them: it is the hero arriving, and it carried the
   hero's curve so the two read as one shape. And .program-panel__photo loses
   the echo it was given in 35.1 for exactly that reason — with no arc anywhere
   else on the page, a curved edge on one photograph reads as a leftover rather
   than as the house style.

   The organic blob masks on hero photographs and avatars are a different
   thing — a shape cut into an image, not a band edge — and are untouched.
   -------------------------------------------------------------------------- */
.hero,
.page-hero,
.page-hero--photo,
.page-hero--split,
.listing-hero,
.listing-hero--photo,
.listing-hero--split,
.detail-hero,
.detail-hero--photo,
.detail-hero--panel,
.safety-band,
.steps-section,
.steps-section--photo,
.mm-intro,
.program-panel__photo,
.cta-strip__box,
.cta-strip__box--photo {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* The phone rules restated the same radii at a shallower ratio, so they have
   to be answered at the same width or the arc comes back below 700px. */
@media (max-width: 760px) {
  .hero,
  .page-hero,
  .page-hero--photo,
  .page-hero--split,
  .listing-hero,
  .listing-hero--photo,
  .listing-hero--split,
  .detail-hero,
  .detail-hero--photo,
  .detail-hero--panel,
  .safety-band,
  .steps-section,
  .steps-section--photo,
  .mm-intro,
  .program-panel__photo,
  .cta-strip__box,
  .cta-strip__box--photo {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }
}

/* The panel's photograph still needs its top corners to follow the card it
   sits in, which the blanket reset above just cleared. */
.program-panel__photo {
  border-top-left-radius: var(--r-xl);
  border-top-right-radius: var(--r-xl);
}

/* --------------------------------------------------------------------------
   38. The school mark, half again as big

   The school glyph reads smaller than the marks beside it at the same box
   size — its building sits low and narrow in the 24x24 box while the clock
   and the group of figures fill theirs — so a row of facts or a line of pills
   came out with one mark that looked undersized next to the rest.

   Scaled rather than resized: the box stays whatever its context set, so no
   pill grows, no tile reflows and no row changes height, and only the drawing
   inside gets bigger. Applied through mm-ico--school, so it is corrected
   everywhere the mark appears rather than in the one place it was spotted.
   -------------------------------------------------------------------------- */
.mm-ico--school {
  transform: scale(1.5);
  transform-origin: center;
}

/* --------------------------------------------------------------------------
   39. The school marks under the hero, at twice the size

   Fifteen crests in 52px rings with 8px of padding left each mark about 36px
   to live in — small enough that a school's name inside its own crest was a
   grey smudge, which is the one thing this strip exists to show. These are the
   marks a parent is scanning for; if they cannot be recognised the row is
   decoration.

   104px rings, and the padding doubles with them so the drawing gains the
   whole of the increase rather than half of it. Fifteen no longer fit one row
   inside the content width and wrap to a second, which is the trade: a row
   that can be read, over a row that fits.
   -------------------------------------------------------------------------- */
.trust__track .logos .trust__logo {
  width: 104px;
  height: 104px;
  padding: 16px;
}
.trust__track .logos { gap: clamp(12px, 1.6vw, 20px); }

@media (max-width: 639.98px) {
  /* On a phone the row is already wrapping; 104px would give three per line
     and a very tall strip, so the marks take the middle size instead — still
     half again as big as they were. */
  .trust__track .logos .trust__logo { width: 78px; height: 78px; padding: 12px; }
}

/* --------------------------------------------------------------------------
   40. The educator cards

   A row of four came out with one card 17px out of step with the other three,
   all the way down. The cause is the role: it was set as a tinted pill above
   the name, and "Business Development Manager" is the one role long enough to
   wrap to two lines — so that card's name, biography and credentials each sat
   a line lower than everybody else's. Four cards of the same height with none
   of their contents on the same line reads as broken, and it is the first
   thing you see in the row.

   Three things fix it.

   The name comes first, directly under a photograph that is always the same
   size, so every name in the row is on the same baseline no matter what else
   the record holds. The role moves under it as a small caps line rather than
   a pill — a pill is a lozenge of colour whose width is set by the longest
   job title anyone happens to have, which is why it was the thing that broke
   — and it reserves two lines whether it needs them or not, so the biography
   below starts at the same height in every card. The biography reserves its
   three lines for the same reason, and the credentials are pushed to the foot
   of the card, so the last row lines up even where a record is short.

   The result is a row that aligns on every line, and keeps aligning when
   somebody is given a longer job title.
   -------------------------------------------------------------------------- */
.team-grid { align-items: stretch; }

.team-card {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.team-card__photo {
  flex: none;
  /* The four portraits are supplied with whatever background the photographer
     had — one near-black, one white, one grey, one red — so the frame carries
     a hairline to close the shape against the card behind it. */
  box-shadow: inset 0 0 0 1px rgba(36, 26, 18, .07);
}
.team-card__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 20px clamp(18px, 2vw, 24px) clamp(20px, 2.2vw, 24px);
}

/* The name, first and largest. */
.team-card__body h4 {
  order: 1;
  font-size: 1.16rem;
  line-height: 1.3;
  letter-spacing: -.01em;
  margin: 0 0 5px;
}

/* The role, under the name and out of the way. */
.team-card__role {
  order: 2;
  display: block;
  padding: 0;
  border-radius: 0;
  background: none;
  margin: 0 0 12px;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .72rem;
  line-height: 1.55;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--muted);
}

.team-card__bio {
  order: 3;
  margin: 0 0 16px;
  font-size: .9rem;
  line-height: 1.72;
  color: var(--muted);
  -webkit-line-clamp: 3;
  /* Three lines held open for the same reason as the role's two. The clamp
     still trims anything longer; mm_trim_sentence() has usually done it
     first, and more tidily. */
  min-height: calc(1.72em * 3);
}

/* Last in the card and pinned to its foot, so the chips sit on one line
   across the row however much or little the records above them carry. */
.team-card__certs {
  order: 4;
  margin-top: auto;
  gap: 8px;
}
.team-card__certs span {
  padding: 6px 12px;
  font-size: .78rem;
  background: var(--canvas-2);
  border-color: transparent;
  color: var(--ink-soft);
}

@media (max-width: 639.98px) {
  /* Nothing to line up when the cards are a swipeable rail one at a time, so
     the reserved rows are released. The card itself still fills the rail's
     height: `height: 100%` against a rail of no set height stopped the
     stretch, so each card stood only as tall as its own text and the next
     card peeked in taller, with a gap under the short one. */
  .team-card__role,
  .team-card__bio { min-height: 0; }
  .team-card { height: auto; }
}

/* --------------------------------------------------------------------------
   41. The portraits are square

   20.5 cut every portrait to a soft blob — "a row of four hard circles reads
   as a directory, and a row of four rectangles reads as a staff list" — which
   was a fair argument when the bands around them were curved too. They are not
   any more (36 and 37), and a wobbling oval is now the only rounded thing left
   on a page of straight edges, which reads as an accident rather than as a
   decision.

   Square, full bleed across the top of the card, the way every other card on
   this site already carries its picture: the program cards, the blog cards and
   the values rows all set a photograph flush to three edges with the copy
   under or beside it. The card's own radius and overflow do the top corners,
   so the picture takes the card's shape without being told it twice, and a
   hairline along the bottom separates the photograph from the body.

   The placeholder behind it inherits the radius, so an educator with no
   photograph still gets the pattern in the same square.
   -------------------------------------------------------------------------- */
.team-card__photo {
  border-radius: 0;
  box-shadow: none;
  border-bottom: 1px solid var(--line);
}
/* Headroom, not a centre crop: these are headshots, and a square cut from a
   portrait file centres on the chest. */
.team-card__photo img,
.team-card__img { object-position: center 22%; }

/* --------------------------------------------------------------------------
   42. The core values take the program card's shape

   The values were the one section on the site laid out as a media row —
   photograph on the left, words on the right, two rows of two — while every
   other card that carries a picture puts it on top with the icon sitting on
   its lower edge. Two shapes for the same job, on pages a visitor moves
   between, so the site read as two sites.

   This is the .feature-card--photo arrangement from the programs row, at its
   own metrics: the photograph across the top, the icon overlapping its lower
   edge inside a ring of the card colour, then the heading and the sentence.
   Four across rather than three, because there are four values and they
   belong on one line; the photograph goes 4:3 rather than 16:10 to hold its
   proportion in the narrower card.

   Markup is untouched. The block still emits .value-card--media with the
   number on the photograph and the icon in the body, so nothing an editor
   saved changes and the four-up layout without photographs is still there
   for a site that has not set any.
   -------------------------------------------------------------------------- */
.values-grid--media { grid-template-columns: 1fr; gap: 20px; }
@media (min-width: 560px) {
  .values-grid--media { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* 2x2 on desktop, not four across, and each card is photo | words: the
   classroom moment small on the left, the value large on the right
   (captain, 26 Sep). Below 1000px a half-row card is too narrow to split,
   so it keeps the photo on top. */
@media (min-width: 1000px) {
  .values-grid--media { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
  .values-grid--media .value-card--media {
    display: grid;
    grid-template-columns: 36% minmax(0, 1fr);
    align-items: stretch;
  }
  .values-grid--media .value-card--media .value-card__photo {
    order: 1;
    aspect-ratio: auto;
    height: 100%;
    min-height: 220px;
  }
  .values-grid--media .value-card--media .value-card__body {
    order: 2;
    align-self: center;
    padding: 30px 28px;
  }
  .values-grid--media .value-card--media .value-card__ico { margin: 0 0 16px; box-shadow: none; }
  .values-grid--media .value-card--media h3 { font-size: 1.5rem; margin-bottom: 10px; }
  .values-grid--media .value-card--media p { font-size: 1.02rem; }
}

/* Flex, which drops the two-column media row the 900px rules set up — a
   grid-template-columns on a flex container is simply not consulted. */
.value-card--media {
  display: flex;
  flex-direction: column;
}

/* Beats the 900px rule that stretched the photograph to the full height of
   the row: two class names to its one, so source order does not come into it. */
.value-card--media .value-card__photo {
  aspect-ratio: 4 / 3;
  min-height: 0;
  height: auto;
  flex: none;
}

/* The icon rides the seam between the photograph and the body, ringed in the
   card's own white so it reads as sitting on the edge rather than punched
   through it. The body loses its top padding to make room. */
.value-card--media .value-card__body {
  padding: 0 22px 24px;
  align-self: stretch;
}
.value-card--media .value-card__ico {
  position: relative;
  z-index: 1;
  width: 50px;
  height: 50px;
  border-radius: 15px;
  margin: -25px 0 12px;
  box-shadow: 0 0 0 4px #fff, var(--shadow-sm);
}
.value-card--media .value-card__ico svg { width: 25px; height: 25px; }
.value-card--media h3 { font-size: 1.14rem; margin-bottom: 7px; }
.value-card--media p { font-size: .93rem; line-height: 1.75; }

/* --------------------------------------------------------------------------
   43. The school marks travel again

   This strip was a marquee once and was deliberately stopped: "these are
   exactly the marks a parent is scanning for, and a logo wall that moves
   cannot be read". That was decided when each mark was 52px, where the
   argument holds — a crest that small is barely legible standing still, let
   alone sliding.

   39 doubled them to 104px, which made them readable and pushed fifteen of
   them onto two rows. One slow row travelling is the better answer to that
   than two static ones: the band is half the height, every mark passes
   through the middle of the viewport where it can actually be looked at, and
   nothing is cut off at the edge because the strip fades at both ends.

   Slow on purpose — a full row takes a minute to pass — and it stops the
   moment a pointer or the keyboard enters, so a mark someone is reading
   stays put. Reduced motion never moves it at all and falls back to the
   wrapped rows, which is what this was a moment ago.

   The keyframe is logo-carousel-scroll, already in the stylesheet and already
   doing exactly this for the educators' school rail: it travels -50%, which
   is one full copy while the track holds exactly the two the block emits.
   -------------------------------------------------------------------------- */
.trust__rail {
  overflow: hidden;
  min-width: 0;
  /* Fades both ends so marks enter and leave rather than being chopped off.
     Duplicated unprefixed for Safari. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
}
.trust__track {
  display: flex;
  width: max-content;
  /* The seam between the two copies has to be the same width as the gap
     inside them, or the loop stutters once per lap. */
  gap: clamp(12px, 1.6vw, 20px);
  animation: logo-carousel-scroll 60s linear infinite;
}
/* focus-within matters for more than politeness: without it a tab stop inside
   the strip would keep sliding out from under whoever landed on it. */
.trust__rail:hover .trust__track,
.trust__rail:focus-within .trust__track { animation-play-state: paused; }

.trust__track .logos {
  flex: 0 0 auto;
  flex-wrap: nowrap;
  justify-content: flex-start;
}

@media (prefers-reduced-motion: reduce) {
  /* A sideways-scrolling strip is exactly the vestibular trigger this query
     exists for. Stop the travel and let the marks sit still and wrap. */
  .trust__track {
    animation: none;
    display: block;
    width: 100%;
  }
  .trust__rail {
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .trust__track .logos { flex-wrap: wrap; }
  /* The duplicate is decorative; with nothing moving it would only print
     every mark twice. */
  .trust__track .logos + .logos { display: none; }
}

/* --------------------------------------------------------------------------
   44. The trust strip gets its own ground

   The band was on --canvas-2, the same near-white cream as the page itself,
   with white rings sitting on it. Two creams eight units apart is not a
   contrast, so the rings dissolved into the background and the strip read as
   a gap between the hero and the first section rather than as a band of its
   own.

   --canvas is the brand cream proper and a real step down from the page
   ground. The white rings now separate from it, the row reads as one object,
   and no new colour enters the page: the hero above stays terracotta and the
   section below stays cream, so the strip is the quiet step between them.

   The edge fade is a mask rather than a gradient painted in the old colour,
   so it follows this change without being told.
   -------------------------------------------------------------------------- */
.trust { background: var(--canvas); }

/* --------------------------------------------------------------------------
   45. The proof sits beside the marks, not under them

   20.10 set this band up as "marks first, proof beside them" — .trust__rail
   at order 1 and .trust__lead at order 2 in one flex row. It has been
   stacking anyway, and the reason is the marquee: .trust__rail was
   flex: 1 1 auto, and an auto basis is the content's own width, which for a
   track holding two copies of fifteen marks is getting on for 3800px. The
   rail therefore asked for the whole row every time and pushed the proof onto
   a line of its own.

   A zero basis is what that rule wanted. The rail now takes whatever is left
   after the proof has been measured, and min-width: 0 lets it shrink below
   its content — which it must be allowed to do, because the content is a
   track designed to be wider than its window and clipped by it.

   Below 900px they stack again on purpose: the proof line alone is most of a
   phone's width, and a marquee squeezed into what remains would show a mark
   and a half.
   -------------------------------------------------------------------------- */
.trust__inner { flex-wrap: nowrap; align-items: center; }
.trust__rail { flex: 1 1 0; min-width: 0; }
.trust__lead { flex: 0 0 auto; }

@media (max-width: 899.98px) {
  .trust__inner { flex-wrap: wrap; }
  .trust__rail { flex: 1 1 100%; }
  .trust__lead { flex: 1 1 100%; }
}

/* --------------------------------------------------------------------------
   46. The trust strip, compact

   242px of band around 108px of content, because it was padded twice: 40px
   top and bottom on .trust, and another 26px top and bottom on .trust__inner
   inside it. Two rules from different passes, each reasonable on its own,
   stacking into 132px of air around a single row of marks.

   The inner padding goes — the section already owns the space around its
   contents, and nothing else in this band needs a second inset — and the
   section's own comes down. 152px instead of 242px, with the marks untouched
   at 104px: the strip gets tighter without the crests getting smaller, which
   was the point of enlarging them.

   The gap between the marks and the proof tightens too. That one is
   horizontal, so it spends nothing in height and hands the marquee a little
   more window.
   -------------------------------------------------------------------------- */
.trust { padding-block: clamp(14px, 1.8vw, 22px); }
.trust__inner {
  padding-block: 0;
  gap: clamp(18px, 2.4vw, 32px);
}

@media (max-width: 899.98px) {
  /* Stacked here, so the row gap has to come back or the marks sit on the
     proof line. */
  .trust { padding-block: clamp(16px, 4vw, 24px); }
  .trust__inner { gap: 16px; }

  /* The proof was wrapping onto three lines — the count, then the faces, then
     the rating — because a 34px column gap leaves no room for two of them to
     share a row at 350px. Tighter gaps fit the count and the faces together
     and take the band back under the desktop height. */
  .trust__lead { gap: 8px 14px; }

  /* The faces and the rating were stacking, which cost 44px: at 350px the
     avatars (122px) and the sentence (233px) are five pixels too wide to sit
     together, so the row broke. Holding them on one line and letting the
     sentence wrap inside its own share is much cheaper than giving each a
     row of its own. */
  .trust__families { gap: 10px; flex-wrap: nowrap; align-items: center; }
  .trust__families > :last-child { min-width: 0; }
  .trust__families .hero__avatars { flex: none; }
}


/* --------------------------------------------------------------------------
   47. Bigger type on the feature cards, and an educator card that says
       everything about the person

   The dark grounds are gone. They were applied to .feature-card,
   .value-card--media and .team-card and are reverted whole: the cards are
   back on white, and the dark treatment waits for the specific cards it is
   actually wanted on.

   What stays from that pass is the type, a quarter up on the same three
   components — headings 1.15 -> 1.44rem, body .93 -> 1.16rem.

   And the educator card changes shape. It carried a biography cut at twenty
   words and then two chips repeating pieces of the sentence it had just cut:
   "M.S. Education (TESOL & Linguistics), B.A. Journalism & Public…" above a
   chip reading "M.S. TESOL & Linguistics". The chips go and the biography
   runs in full, which is the same information said once, in sentences, at a
   size you can read.
   -------------------------------------------------------------------------- */

/* ---- Type ---------------------------------------------------------------
   The quarter these three cards were given individually is gone: 49 raises
   the root instead, so every rem on the site moves together and these cards
   would otherwise have been scaled twice. Only the line height stays, which
   the larger setting genuinely wanted. */
.feature-card p       { line-height: 1.7; }
.value-card--media p  { line-height: 1.7; }

/* ---- The role, at the size the rest of the card moved to --------------- */
/* 40 held two lines open for "Business Development Manager", the one title
   long enough to need them. The larger setting pushed it to three and broke
   the row again — same card, same cause. Tighter tracking at .82rem brings it
   back inside two, and the reserve is in em so it tracks any future change to
   this scale rather than having to be re-derived. */
.team-card__role {
  font-size: .82rem;
  letter-spacing: .06em;
  line-height: 1.5;
  /* No reserved lines: 78 lines the row up from the roles it actually has. */
}

/* ---- No chips, and the whole biography --------------------------------- */
/* Hidden rather than dropped from the markup, which is how this theme has
   unwrapped a card before (20.11): every credential stays in the page for
   search engines and still renders wherever else the field is used. */
.team-card__certs { display: none; }

.team-card__bio {
  /* The clamp and its reserved three lines go with the chips. The point of
     the card now is the whole sentence, so nothing truncates it — and with
     no chips beneath, a longer biography has the room it needs. */
  -webkit-line-clamp: none;
  min-height: 0;
  display: block;
  overflow: visible;
  line-height: 1.75;
  margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   48. The parent quotes go dark, each in its own colour

   The three cards cycle terracotta, sage and soft gold, and they keep doing
   exactly that — this only takes each tint down to the deep end of the same
   hue rather than swapping any card onto a colour it was not already wearing.
   A pale wash on cream was barely a card at all; at this depth the three read
   as three objects on the page and the cycle is finally legible as a cycle.

   Grounds, and cream (#EFEEE4) measured on each:
     t1  #5E2E0D  deep terracotta   9.4:1
     t2  #2B4A48  --sage-ink, already in the palette   8.1:1
     t3  #5C4310  deep amber        7.8:1
   The attribution line is the smallest text in the card and still clears
   6.5:1 at 78% cream, so every line here is past AA rather than near it.

   The avatars stay in their card's hue but move to the light end of it, so
   each initial lifts off its ground instead of sinking into it. Only t1's had
   to change: a site-wide rule was painting every avatar in dark sage, which
   on deep terracotta was two dark colours fighting.
   -------------------------------------------------------------------------- */
.testi-card--t1 { background: #5E2E0D; border-color: rgba(239, 238, 228, .16); }
.testi-card--t2 { background: #2B4A48; border-color: rgba(239, 238, 228, .16); }
.testi-card--t3 { background: #5C4310; border-color: rgba(239, 238, 228, .16); }

/* The quote itself, the name, and the line under it. */
.testi-card p { color: #EFEEE4; }
.testi-card__author b { color: #FFFFFF; }
.testi-card__author small { color: rgba(239, 238, 228, .78); }

/* The opening mark was brand terracotta at 22% — invisible on these grounds.
   Cream at 28% keeps it the quiet flourish it was meant to be. */
.testi-card .quote-mark,
.testi-card--t2 .quote-mark,
.testi-card--t3 .quote-mark { color: #EFEEE4; opacity: .28; }

/* Avatars: each lighter than the card it sits on. */
.testi-card--t1 .testi-card__avatar {
  background: linear-gradient(140deg, #C9682A, #E39257);
  color: #fff;
}
.testi-card--t2 .testi-card__avatar {
  background: linear-gradient(140deg, #6FA3A1, var(--accent-sage));
  color: var(--ink);
}
.testi-card--t3 .testi-card__avatar {
  background: linear-gradient(140deg, var(--accent-sun), var(--accent-yellow));
  color: var(--ink);
}

/* A warm shadow reads as a smudge under a dark card on cream; depth comes
   from the colour itself now, and lifts a little further on hover. */
.testi-card { box-shadow: 0 6px 18px rgba(60, 30, 10, .10); }
.testi-card:hover { box-shadow: 0 18px 38px rgba(60, 30, 10, .20); }

@media (prefers-contrast: more) {
  .testi-card--t1,
  .testi-card--t2,
  .testi-card--t3 { border-color: rgba(239, 238, 228, .4); }
  .testi-card__author small { color: rgba(239, 238, 228, .92); }
}

/* --------------------------------------------------------------------------
   49. The whole site, a quarter larger

   One rule rather than a sweep through every component: the root goes from
   16px to 20px, and since this stylesheet sizes type in rem almost
   everywhere, every heading, label, chip and paragraph moves together by the
   same quarter. The clamped headings come with it, because raising the rem
   ends raises the ceiling those clamps were hitting.

   body carried a hard 17px, which is the one piece of type a root change
   cannot reach. Restated as 1.0625rem — the same 17px against the old root,
   and 21.25px against the new one — so the base text scales with the rest
   instead of being left behind at its old size.

   Two things deliberately do not move. Media queries are viewport-based, so
   every breakpoint and every column count is exactly where it was. And the
   spacing written in px stays put, which is what makes this read as larger
   type rather than as a site zoomed in: the type grows inside the layout
   rather than taking the layout with it.
   -------------------------------------------------------------------------- */
/* 121.586% rather than 125%: the quarter, less the two trims asked for
   afterwards — 1.5% and then 1.25% (1.25 x 0.985 x 0.9875). Rounded to 122%
   because a browser resolves the root to a whole pixel anyway — 19.5px
   against the 19.7px before this trim and the 20px the quarter first set,
   and 16px before any of it. The full chain is kept here rather than the
   rounded figure it lands on, so the next trim compounds from the real
   number instead of from 122. Everything downstream is rem, so this one
   number is the only place the site's type scale is set. */
html { font-size: 122%; }
/* 122% was set for a phone, where it reads well, and then applied to every
   width: on a laptop the same scale gives 20.7px body copy and a 25.8px card
   title, which is what makes a desktop window feel shouted at. The phone keeps
   122%; from 900px the root comes down 10%, and because every size downstream
   is rem, that one number takes the whole scale with it. */
@media (min-width: 900px) {
  html { font-size: 110%; }
}
body { font-size: 1.0625rem; }

/* --------------------------------------------------------------------------
   50. The quotes get the brand ground

   Terracotta behind them, which is the site's own band colour — the hero
   opens on it and the closing CTA ends on it, so the parents' quotes sitting
   between them on the same ground ties the page together rather than
   introducing a fourth surface.

   It also makes the three cards work. Deep terracotta, sage-ink and deep
   amber on cream were three dark blocks on a pale sheet; on terracotta they
   read as three depths of one warm family, which is what the cycle was for.

   The heading goes white, and the eyebrow takes the frosted treatment the
   other terracotta bands use. Only the section head is restated — the card
   interiors are handled in 48 and must not be caught by a blanket rule here,
   or the quotes would inherit the band's white over their own cream.
   -------------------------------------------------------------------------- */
.testimonials { background: var(--brand); }

.testimonials > .wrap > .section-head .section-title,
.testimonials > .wrap > .section-head h2 { color: #fff; }
.testimonials > .wrap > .section-head .section-sub,
.testimonials > .wrap > .section-head p { color: rgba(255, 255, 255, .9); }
.testimonials > .wrap > .section-head .eyebrow {
  background: rgba(255, 255, 255, .18);
  color: #fff;
}

/* The cards sit on a mid-tone now, so the hairline that separated them from
   cream is doing a different job: it lifts the dark block off the terracotta
   rather than containing it. */
.testimonials .testi-card { border-color: rgba(255, 255, 255, .14); }
.testimonials .testi-card { box-shadow: 0 8px 24px rgba(60, 24, 4, .22); }
.testimonials .testi-card:hover { box-shadow: 0 20px 44px rgba(60, 24, 4, .34); }

/* --------------------------------------------------------------------------
   51. Three across for the team on About

   The stylesheet already anticipated this: "Four columns for the four
   teachers on the About page. A fifth person starts a second row with three
   gaps, so add people in fours — or drop this to repeat(3, 1fr)." The team is
   eight now, and 49 made every card's type a quarter larger while the column
   stayed 258px wide, so a full biography in four columns is a very narrow
   measure.

   Three columns gives each card about 350px instead. The advisors row gains
   from it too — three people in a four-column grid always left one gap, and
   now it is a complete row.

   About only. For Schools shows four educators chosen to fill one row and is
   left alone.

   From 760px, not 1000px: the team is nine and the advisors three, so the
   two-column tablet grid left one card alone at the end of each group. Three
   across fills both groups at every width from a tablet up; a card is still
   about 210px at 760px, and 78's subgrid keeps the rows level when a role
   wraps. Between the rail and 760px the grid is two across, and a lone last
   card sits in the middle rather than under the left column.
   -------------------------------------------------------------------------- */
@media (min-width: 760px) {
  .mm-page-about .team-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (min-width: 640px) and (max-width: 759.98px) {
  .mm-page-about .team-grid > :last-child:nth-child(odd) {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc((100% - 24px) / 2);
  }
}

/* --------------------------------------------------------------------------
   52. The terracotta moves up one section

   50 put the brand ground behind the parent quotes. It goes back to the page
   ground here and the quotes keep their dark cards — three deep cards on
   cream, which is what was asked for.

   The terracotta moves to the section above them, "Learning that starts with
   curiosity", where it does the same job for the programs: white cards on the
   brand colour, and the band a visitor meets before the quotes rather than
   the quotes themselves. The page still runs terracotta at the top, terracotta
   in the middle and terracotta at the close, so the rhythm 50 was after is
   intact — one section earlier.
   -------------------------------------------------------------------------- */

/* Back to the page ground. The cards are untouched: 48 still owns them. */
.testimonials { background: var(--canvas-2); }
.testimonials > .wrap > .section-head .section-title,
.testimonials > .wrap > .section-head h2 { color: var(--ink); }
.testimonials > .wrap > .section-head .section-sub,
.testimonials > .wrap > .section-head p { color: var(--muted); }
.testimonials > .wrap > .section-head .eyebrow {
  background: var(--sage-tint);
  color: var(--sage-deep);
}
/* On cream the dark cards need no ring, and the shadow goes back to the warm
   one the rest of the page uses. */
.testimonials .testi-card { border-color: transparent; box-shadow: 0 8px 22px rgba(60, 30, 10, .14); }
.testimonials .testi-card:hover { box-shadow: 0 20px 44px rgba(60, 30, 10, .24); }

/* ---- The programs band takes it ---------------------------------------- */
#classes.section--tint,
#classes { background: var(--brand); }

#classes > .wrap > .section-head .section-title,
#classes > .wrap > .section-head h2 { color: #fff; }
#classes > .wrap > .section-head .section-sub,
#classes > .wrap > .section-head p { color: rgba(255, 255, 255, .9); }
/* A frosted pill with white type on it is two light values on a mid ground —
   the label came out barely there. Solid white with the dark brand on it is
   the pairing the photo heroes already use for this chip. */
#classes > .wrap > .section-head .eyebrow {
  background: rgba(255, 255, 255, .92);
  color: var(--brand-darker);
}
/* The cards keep their white and gain a shadow that reads on a mid ground. */
#classes .class-card { border-color: transparent; box-shadow: 0 10px 28px rgba(60, 24, 4, .20); }
#classes .class-card:hover { box-shadow: 0 22px 48px rgba(60, 24, 4, .30); }
/* Any link or note the band carries outside a card is on terracotta now. */
#classes > .wrap > .section-foot,
#classes > .wrap > .section-foot p { color: rgba(255, 255, 255, .9); }

/* --------------------------------------------------------------------------
   53. A ground for "Meet the educators"

   The band was on --sage-tint-2, a very pale COOL green. The portraits on it
   are warm — one on dark olive, one on white, two cut out onto brand orange —
   and there is no card between them and the ground, so the ground is doing
   the job a card would: it is what every one of those faces sits directly on.
   A cool sheet under warm cut-outs is the mismatch, and it shows worst on the
   two orange ones.

   --canvas, the brand cream. Warm, so the orange cut-outs belong to it; a
   half-step darker than the page ground, so the band still reads as a band;
   and the same ground the trust strip uses, which is this page's other row of
   faces on a surface. It also avoids the two colours that would be repeats —
   a fourth terracotta after the hero, the programs and the close, or a second
   sage after the safety band.
   -------------------------------------------------------------------------- */
#collaborations,
#collaborations.section--tint { background: var(--canvas); }

/* --------------------------------------------------------------------------
   54. The For Schools educators get out of the 1200px wrap

   Measured before changing anything: nothing is being clipped — each card's
   scrollHeight matches its height. The fault is the proportion. Four columns
   inside the 1104px content width give each card 258px, and with the full
   biography restored and the type a quarter up, that produced cards 258 wide
   by 738 tall: a ribbon of text seven lines deep in a column barely wider
   than a phone.

   The section breaks out of the shared wrap from 1200px up. It is the one
   band on this page whose job is reading rather than scanning, and it is the
   one that has four columns of prose to fit, so it gets the room the viewport
   actually has instead of the width every other section agreed on.

   94vw with a 1600px ceiling: about 300px a card on a 1456px laptop and
   about 360px on a 1920px screen, against 258px before. Below 1200px nothing
   changes — the wrap is already the whole screen there, and the grid has
   dropped to two columns and then to a rail, where the cards are wide
   already.
   -------------------------------------------------------------------------- */
@media (min-width: 1200px) {
  .mm-page-schools #collaborations > .wrap { max-width: min(1600px, 94vw); }
}

/* --------------------------------------------------------------------------
   55. The pattern is the brand's own artwork now

   pattern-symbols-light.svg was seventeen marks drawn by hand in this
   repository, in this file, out of about ten path commands each — a pencil
   that was a rhombus, a globe that was a circle with two arcs over it. It was
   the right shape of idea and the wrong way to get it: the brand hands out an
   element sheet, and the site was ignoring it in favour of a sketch.

   pattern-elements.svg is that sheet. Fifteen marks, lifted path for path
   from "website elements for mm.svg": set square, bulb, compass, paper plane,
   flask, globe, test tubes, magnifier, pencil, book, palette, puzzle,
   rosette, brain, scroll. Drawn rather than approximated — the globe has
   continents on it, the brain has folds, the palette has a brush across it.

   Nothing else changes. The tile is still 640px, still white on transparent,
   still inverted by section 28 for the light grounds, still dropped to 420px
   on phones by the rules above, and every opacity in this file is the one it
   already had. Keeping the old file's contract exactly is what made the swap
   one file and seven urls rather than a re-tune of eight bands.

   Two things were done to the artwork and nothing else. The sheet draws its
   marks in cream at 43%, which is a finished graphic rather than a tile, so
   they are white here and the ground sets the strength — invert(1) only lands
   on black from pure white, and section 28 depends on that. And the outlines
   came off a tracer at about 240 segments a mark, so they are refit to a
   0.8-unit tolerance, roughly 0.6px at the size a mark lands: 167KB of path
   data down to 38, with the largest deviation anywhere in the fifteen marks
   still under a pixel.

   The marks land about three times the size the drawn ones did, which is what
   makes them read as objects rather than as grain. The brand sheet draws them
   larger still.

   pattern-crane.svg is untouched. It is the mark from the logo rather than a
   drawing of a pencil, so it was never the thing that was wrong here, and it
   still carries the quote panel's single corner mark.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   56. The dark grounds

   Three, and no fourth. Each is a colour the palette already owns — deep clay
   is .testi-card--t1, the warm near-black is --ink, the deep teal is
   --sage-ink — so a dark band spends what is there rather than extending the
   brand.

   On the blue that was asked about: there is none. The guidelines list five
   hexes (#BC5A1B, #EB940E, #A5C3C2, #EFDC95, #EFEEE4) and a census of all 22
   pages of the PDF finds no pixel anywhere in hue 181-270 — the only cool
   swatch is the sage, at hue 180.0 exactly, which the deck itself calls
   turquoise. Section 1 already wrote the house position: "Shadows —
   warm-tinted, never blue-black". So deep teal below IS the answer to blue,
   and it is a colour this site already ships. A literal blue would be a
   second brand sitting next to #A5C3C2 on the same page.

   --brand is deliberately NOT on this list. At L=0.18 terracotta is a
   mid-tone, not a dark, and it cannot carry reversed body copy at any
   transparency: .93 white measures 4.14:1 on it, .90 is 3.98 and .86 is 3.80,
   against the 4.5 body text needs. A band that wants reversed copy wants one
   of the three below, where cream at 82% clears with room to spare.

   Measured, cream #EFEEE4 / white / cream at 82%:
     --dark-clay  #5E2E0D   9.62  11.21   7.00
     --dark-ink   #241A12  14.63  17.05  10.16
     --dark-teal  #2B4A48   8.28   9.65   6.17

   --brand-darker #843E11 is a surface here, not a ground: cream on it is a
   fine 6.73, but it sits 1.43 from deep clay and at band scale reads as the
   hero repeated.
   -------------------------------------------------------------------------- */
:root {
  /* Grounds — a section goes dark on one of these, never on a fourth */
  --dark-clay:    #5E2E0D;  /* Deep terracotta — the warm dark band */
  --dark-ink:     #241A12;  /* Warm near-black — the quiet dark band */
  --dark-teal:    #2B4A48;  /* Deep sage — the cool dark band, the brand's own */

  /* Raised surfaces — the card that sits on the ground above it */
  --dark-clay-2:  #843E11;  /* Card on deep terracotta (matches --brand-darker) */
  --dark-ink-2:   #33251A;  /* Card on warm near-black */
  --dark-teal-2:  #365B59;  /* Card on deep sage, the depth --dark-fg-soft clears */

  /* Type — one set, measured on all six surfaces above */
  --dark-fg:      #EFEEE4;                   /* Body copy on any dark surface */
  --dark-fg-head: #FFFFFF;                   /* Headings and names only */
  --dark-fg-soft: rgba(239, 238, 228, .82);  /* Captions, attribution, small print */

  /* One hairline for all six: a warm shadow does nothing on a dark ground */
  --dark-line:    rgba(239, 238, 228, .16);  /* Card edge, rule, divider */

  /* The CTA a dark ground flips to. Terracotta is banned on clay (2.47:1) and
     on teal (2.12:1) — the worst pair on the site and the one most likely to
     be reached for. Same flip 18.3 already makes in the hero. */
  --dark-cta-bg:  #EFDC95;  /* Soft gold button on any dark ground */
  --dark-cta-fg:  #241A12;  /* Ink on it, 12.46:1 */
}

/* --------------------------------------------------------------------------
   57. One band goes dark, on four pages

   The site already had a dark-band system and nobody had named it:
   .cta-strip__box, the closing ask, running straight into the terracotta
   footer as one rectangle since 36. Every page that renders that block closes
   on a full stop — home, About, the blog index, an article, a program page.
   Programs, For Schools, Contact and Trial do not render it at all, and that,
   rather than any need for new colour, is why those four read as flat.

   The proper fix for those four is a template one: give them the closing
   strip. What follows is the CSS-only half — on the pages where one section
   is a statement rather than a reference, that section takes a dark ground so
   the page has a weighted moment instead of running light from hero to
   footer.

   Density, which is the part that keeps this from becoming a dark site: one
   dark band per page, one dark ground per page, never two touching, and cream
   keeps about 60% of scroll height. This is a warm cream children's brand —
   the cream is the brand and the dark is punctuation. Programs gets nothing
   on purpose: its only content section is a twelve-card comparison grid, and
   taking that dark means taking the whole page dark to buy a beat, at the
   cost of scannability on the one surface where scanning is the task.

   No hero changes. 24 gives every inner page its own light ground so that
   moving between pages reads as moving between rooms, and the single dark
   hero — a program page, the one place money is asked for — is the contrast
   that makes that page the destination. Taking the rest dark would either
   flatten that wayfinding to one colour or need four grounds the palette does
   not have, and each one means unpicking the invert in 28, the ink cascade in
   25 and 28.1's lead-paragraph fixes.

   No pattern is added anywhere here. 19 gives the page one patterned moment
   and the hero has it; the For Schools band already carries one from 32 and
   only has its invert undone.

   Scoping. Two of these four bands carry no class of their own but
   .section--tint, and .section--tint is on seven bands across six pages —
   About's core values, For Schools' educators and catalog, Contact's venues,
   an article's related posts, a program page's outcomes and teacher band, and
   Trial's outcomes. A bare rule would blacken all seven. Every selector below
   is pinned to the body slug plus the one block the band actually contains.
   -------------------------------------------------------------------------- */

/* ---- 57.1 What a dark band is ---------------------------------------------
   The ground per band, and the reading colours they share. Cream rather than
   white for body copy: it is the ground the rest of the site is made of, and
   48 already settled it as the colour that reads on a deep card without the
   glare full white has at paragraph length. */
.mm-page-schools .steps-section--photo,
.mm-page-trial   .section--tint:has(.outcome-grid) {
  background: var(--mm-band);
  color: var(--dark-fg);
}
.mm-page-schools .steps-section--photo              { --mm-band: var(--dark-teal); }
.mm-page-trial   .section--tint:has(.outcome-grid)  { --mm-band: var(--dark-clay); }

/* ---- 57.2 The heading block -----------------------------------------------
   .section-title carries --ink from section 3 and would be ink on ink;
   .section-sub carries --muted, which is 2.98:1 on the ink ground and 1.96 on
   clay. Both are named back rather than left to inherit. */
.mm-page-schools .steps-section--photo .section-title,
.mm-page-trial   .section--tint:has(.outcome-grid) .section-title { color: var(--dark-fg-head); }

.mm-page-schools .steps-section--photo .section-sub,
.mm-page-trial   .section--tint:has(.outcome-grid) .section-sub { color: var(--dark-fg-soft); }

/* The eyebrow is a chip, so it is measured against its own fill rather than
   the band. Frosted white on a dark ground is two light values and comes out
   barely there, so it is near-solid with the dark brand on it — the pairing
   52 settled on for the same problem, and identical on all three grounds. The
   .82 white the steps band carries from 32 would put --brand-dark at 4.3:1 on
   teal, under AA. */
.mm-page-schools .steps-section--photo .eyebrow,
.mm-page-trial   .section--tint:has(.outcome-grid) .eyebrow {
  background: rgba(255, 255, 255, .92);
  color: var(--brand-darker);
}

/* ---- 57.3 The white cards on them -----------------------------------------
   All three card bands keep their cards white and their card contents in ink:
   a card is a different surface and the band's colours must not reach into
   it. Two things do have to change.

   The hairline. --line is a warm near-cream, which on a white card against a
   dark ground draws a visible light rim round every card. It goes
   transparent, the same call 52 made for the program cards on terracotta.

   The shadow. --shadow-xs is rgba(60,30,10,.05), a warm smudge that does
   nothing under a white card on a dark ground, so the hover lift had no
   feedback at all. The warm ground takes warm numbers; the cool ones take a
   neutral dark, because a warm shadow on teal or ink is a brown halo. */
.mm-page-schools .steps-section--photo .step {
  border-color: transparent;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .34);
}
.mm-page-trial .section--tint:has(.outcome-grid) .outcome-item {
  border-color: transparent;
  box-shadow: 0 10px 28px rgba(60, 24, 4, .20);
}

/* The lift is gated on a real pointer rather than left to the phone
   hover-kill at 4487 to undo. That list is (0,1,1) and these selectors are
   (0,3,1) and (0,4,1), so they would have beaten it — media queries add no
   specificity — and a tap on a venue or outcome card would have latched the
   raised shadow until something else was tapped. .step was never in the 4487
   list at all. Gating here is better than adding three more selectors to a
   neutraliser: the hover state is simply never declared where there is no
   cursor to produce it. */
@media (hover: hover) and (pointer: fine) {
  .mm-page-schools .steps-section--photo .step:hover {
    box-shadow: 0 22px 48px rgba(0, 0, 0, .46);
  }
  .mm-page-trial .section--tint:has(.outcome-grid) .outcome-item:hover {
    box-shadow: 0 22px 48px rgba(60, 24, 4, .30);
  }
}

/* Two headings inside those cards declare no colour of their own and would
   inherit the band's cream onto white — invisible, and the only outright
   unreadable thing this section could produce. .step h3 already carries --ink
   from 32 and is not listed. */
.mm-page-trial   .section--tint:has(.outcome-grid) .outcome-item h3 { color: var(--ink); }

/* ---- 57.4 Home keeps its cream -------------------------------------------
   The partner wall briefly took the ink ground here, on the argument that the
   bottom of the home page runs three light sections in a row and wants one
   weighted moment. The argument was sound and the result was not: a near-black
   rectangle holding a white panel of grey logos read as a slab dropped into a
   warm cream page, and it made the panel — which is a container, not a subject
   — the brightest object on the screen.

   So the home page has no dark band and the three-light run stands. If it is
   ever worth solving again, the lever is the panel rather than the section:
   the marks are already inside a surface of their own, and a tint on THAT is
   a smaller move than a ground behind it. Section 58 and section 63 own what
   is there now. The other three bands below are unaffected. */

/* ---- 57.5 For Schools: the onboarding band --------------------------------
   The one section on any inner page that already has the site's band shape —
   its own pattern layer, four opaque white cards, overflow hidden — sitting
   on cream doing none of it. It is a band in everything but colour, which is
   why it costs three declarations.

   Deep teal rather than terracotta because the page opens on sage #A5C3C2 and
   this is that hue at its deep end, which is the move 48 made for the parent
   quotes; it also avoids a second terracotta against the footer. For Schools
   is the page whose subject is a process, and the dark belongs on the
   process.

   28 inverts the tile to ink on every light ground. Teal is not one, so the
   invert comes off or the strokes turn black on a near-black ground and
   vanish. .08 is the alpha at which a white tile on these three grounds
   reproduces the luminance ratio the hero's .14 on terracotta gives — one
   number that matches the hero's perceived texture on all of them. */
.mm-page-schools .steps-section--photo::before {
  filter: none;
  opacity: .08;
}
@media (max-width: 760px) {
  .mm-page-schools .steps-section--photo::before { opacity: .09; }
}

/* ---- 57.6 Higher contrast -------------------------------------------------
   Same habit as 48: the quiet line comes up to near-full cream and the cards
   take a ring, so nothing on these grounds is near AA rather than past it. */
@media (prefers-contrast: more) {
  .mm-page-schools .steps-section--photo .section-sub,
  .mm-page-trial   .section--tint:has(.outcome-grid) .section-sub { color: rgba(239, 238, 228, .94); }

  /* The ring has to be dark, and it has to be a shadow.

     --dark-line is cream at 16%, which is right against a dark ground and
     wrong here: the border box paints over the card's OWN white background,
     not over the band, so 16% cream on #FFF composites to #FCFCFB — 1.03:1,
     which is nothing. And .step carries no border at all (3128, 8437 both
     declare background, radius, padding and shadow and no border), so a
     border-color on its own could never paint anything there.

     Ink at 45% against white is 3.1:1, and the shadow spread carries it for
     the card that has no border width to colour. */
  .mm-page-schools .steps-section--photo .step,
  .mm-page-trial   .section--tint:has(.outcome-grid) .outcome-item {
    border-color: rgba(36, 26, 18, .45);
    box-shadow: 0 0 0 1px rgba(36, 26, 18, .45), 0 10px 28px rgba(0, 0, 0, .40);
  }
}

/* ==========================================================================
   58. One rail, and the rows that were missing one

   The brief was "make the mobile site horizontally scrollable — only the
   things that feel long", and the judgement is entirely in the second half of
   that sentence. A rail hides content behind a gesture. On a row of venue
   cards that is the right trade; on four safety facts it costs a parent the
   reason they were reading.

   Measured on a 390px phone against about 770px of usable screen, and only
   rows over roughly one and a half screens move:

     About values      4 cards  ~1,872px  2.4 screens  -> rail
     home groups       4 cards  ~1,640px  2.3 screens  -> rail
     related programs  3 cards  ~1,770px  2.3 screens  -> rail
     blog teasers      3 cards  ~1,480px  2.1 screens  -> rail (a restore)
     parent quotes     3 cards    ~970px  1.4 screens  -> stays
     onboarding steps  4 steps   ~920px   1.2 screens  -> stays
     contact FAQ       9 rows    ~967px   1.3 screens  -> stays
     safety facts      4 rows    ~590px   0.8 screens  -> stays
     home educators    4 tiles   ~390px   0.6 screens  -> stays, two-up
     partner marks     7 marks   ~180px   0.3 screens  -> stays

   The rows that stay each have a reason beyond the number. The quotes were a
   one-at-a-time rotator until 1.5.32 and were deliberately changed to show
   three at once, because "almost nobody waited seven seconds" to meet the
   second and third — a phone rail is that rotator again with a swipe in place
   of a timer. The partner marks were a marquee until 20.10 stopped it,
   because "a logo wall that slides cannot be read and these are the marks a
   parent scans for". Safety is the reassurance copy a parent came for. The
   step sequences are sequential, and a rail on a numbered sequence hides the
   later steps, which on the booking band are the two sentences that answer
   the question stopping the booking.

   The Programs and Trial listings stay stacked and this section does not
   reach them: .rail--static .class-grid is (0,2,0) against the (0,1,0) of the
   shared block below, so specificity keeps them as they are without a word
   being added. That is correct — they sit under a live filter whose result
   count only means something against a list that visibly shortens, and a
   parent comparing age, venue, schedule and price needs the cards adjacent.

   WHAT THIS SECTION ADDS BEYOND 17

   17 is the good rail and this makes it the only one. Every scroller in the
   theme is mapped onto one declaration block by selector list, so nothing in
   PHP has to change for the convergence itself, and four things are fixed:

   1. overscroll-behavior, which no rail in this file has ever set. The inline
      axis is contained, so a swipe past the last card stops instead of
      triggering the browser's back gesture. The block axis is explicitly
      auto, and that is the half that matters more: it keeps VERTICAL scroll
      chaining to the page. The shorthand is never used here, because
      `overscroll-behavior: contain` sets both axes and traps the page under
      the reader's thumb — and no rail may ever carry touch-action: pan-x,
      which does the same thing worse.
   2. A count-based fallback to a stack, borrowed whole from the venue panel
      at 5809, this file's existing no-PHP no-JS count switch. Below three
      items a rail costs more than it saves: at two it hides half the content
      to save about 390px, and at one it is a card with dead space either
      side. The arrows need no rule — once the scroller is an ordinary grid,
      scrollWidth equals clientWidth and measure() hides them.
   3. .map-grid's gap goes 14px -> 16px. It was 17 re-typed by hand and the
      hand slipped, so a venue card was 2px narrower than the same card
      elsewhere; it was also missing from both the focus list and the
      reduced-motion list.
   4. Two rows that had lost their rail get it back. See 58.3 and 58.5.

   --rail-peek stays at 16px. 17's note at 4212 already concedes the sliver is
   a weak signal and hands the job to the arrows; raising it would be right,
   but the tokens are shared with every rail on the site and it must be
   changed once in the token block, never per rail. If it is ever raised, 22px
   is the value — a 304px card, 78% of the viewport.
   -------------------------------------------------------------------------- */

@media (max-width: 639.98px) {

  /* ---- 58.1 The canonical rail --------------------------------------------
     .rail__track is the canonical name for new markup. Everything after it is
     an existing scroller adopting the same rules without a markup change.

     .collab-grid is deliberately NOT in this list. It renders on the home
     page only, where 20.11 turned it into a two-up grid of four 186px
     portraits — 0.56 of a screen, which is not long by any reading, and a
     rail there would hide three faces to save nothing. 58.6 takes the dead
     rail's leftovers off it instead. */
  .rail__track,
  .grid-3,
  .class-grid,
  .blog-grid,
  .team-grid,
  .map-grid,
  .group-grid,
  .values-grid--media {
    --rail-peek: 16px;
    --rail-gap: 16px;
    --rail-pad: calc(var(--rail-gap) + var(--rail-peek));
    --rail-card: calc(100vw - var(--rail-pad) * 2);

    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: var(--rail-gap);

    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;

    /* The two-axis rule. Never the shorthand — see the note above. */
    overscroll-behavior-inline: contain;
    overscroll-behavior-block: auto;

    width: auto;
    min-width: 0;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--rail-pad);
    /* Room in the block axis for anything that lifts: overflow-x: auto makes
       this a scroll container in BOTH axes, so a transform would otherwise be
       clipped. It also keeps the content shorter than the box, so there is no
       vertical overflow inside a rail to trap in the first place. */
    padding-block: 10px 26px;
    scroll-padding-inline: 0;

    /* Load-bearing. Without it the off-screen cards inflate
       documentElement.scrollWidth and every mobile emulator renders the site
       as a narrow column with a white band down one side. 4165 has the
       measurements. */
    contain: paint;

    scrollbar-width: none;
  }

  .rail__track::-webkit-scrollbar,
  .grid-3::-webkit-scrollbar,
  .class-grid::-webkit-scrollbar,
  .blog-grid::-webkit-scrollbar,
  .team-grid::-webkit-scrollbar,
  .map-grid::-webkit-scrollbar,
  .group-grid::-webkit-scrollbar,
  .values-grid--media::-webkit-scrollbar { display: none; }

  /* flex: none is the point — the cards must not shrink to fit. The width is
     figured from 100vw rather than as a percentage of the rail, because the
     rail is full-bleed with its padding inside it and a percentage would
     resolve against the padded content box. */
  .rail__track > *,
  .grid-3 > *,
  .class-grid > *,
  .blog-grid > *,
  .team-grid > *,
  .map-grid > *,
  .group-grid > *,
  .values-grid--media > * {
    flex: 0 0 var(--rail-card);
    min-width: 0;
    scroll-snap-align: center;
    overflow-wrap: anywhere;
  }

  /* min-width: auto is the default on a flex item and means "never shrink
     below your longest unbreakable run". The group card is a nested flex
     column and its venue names run to "iForest - International Forest
     School"; left alone one of those pushes the card past its own basis and
     widens the page from that section down. Same treatment 17 gives the class
     and blog cards at 4245. */
  .group-card,
  .group-card__body,
  .group-card__name { min-width: 0; }

  /* overflow-wrap: anywhere above reaches .class-grid > *, and 4384 restates
     flex, width and scroll-snap-align for the static listings but not this —
     so it was landing unopposed on the Programs and Trial grids, which are
     stacked, not railed. `anywhere` also shrinks min-content, unlike
     break-word, so a program title or a school name could break mid-word in a
     column that has room for it. 4280 had confined this to .grid-3 > *,
     .team-grid > * and .testi-card for exactly that reason. Put back to
     break-word where the cards are not in a rail. */
  .rail--static .class-grid > * { overflow-wrap: break-word; }

  /* ---- 58.2 Fewer than three items, and it is not a rail ------------------
     The same shape as .venue-panel__list:has(> li:nth-child(6)) at 5809, read
     the other way round: no third child, no rail. The declarations are
     .rail--static's own (4384), so a rail that falls back and a rail that was
     never one land in exactly the same place.

     Specificity is (0,2,0), not the (0,3,0) it looks like: :not() and :has()
     each take the weight of their most specific ARGUMENT rather than adding
     one of their own, and `> :nth-child(3)` is a single pseudo-class. So this
     clears 17's (0,1,0) outright but only TIES .rail--static .class-grid at
     4384, and wins that on source order alone. It is worth knowing which of
     the two is holding the Programs and Trial listings stacked: it is 4384,
     which restates display, flex-wrap, gap, overflow, scroll-snap-type,
     margin-inline, padding-inline and contain at the same (0,2,0) but is
     reached first — so a listing with fewer than three children lands on this
     block's declarations instead, which differ only in padding-block and
     overscroll-behavior. 58.5's (0,3,0) is what genuinely outranks 4384.

     This is what keeps an article's two-card "More from the blog" and the
     About founder row, which is a single card, from becoming rails. */
  .rail__track:not(:has(> :nth-child(3))),
  .grid-3:not(:has(> :nth-child(3))),
  .class-grid:not(:has(> :nth-child(3))),
  .blog-grid:not(:has(> :nth-child(3))),
  .team-grid:not(:has(> :nth-child(3))),
  .map-grid:not(:has(> :nth-child(3))),
  .group-grid:not(:has(> :nth-child(3))),
  .values-grid--media:not(:has(> :nth-child(3))) {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    flex-wrap: wrap;
    overflow: visible;
    scroll-snap-type: none;
    margin-inline: 0;
    padding-inline: 0;
    padding-block: 0;
    contain: none;
    overscroll-behavior-inline: auto;
  }
  .rail__track:not(:has(> :nth-child(3))) > *,
  .grid-3:not(:has(> :nth-child(3))) > *,
  .class-grid:not(:has(> :nth-child(3))) > *,
  .blog-grid:not(:has(> :nth-child(3))) > *,
  .team-grid:not(:has(> :nth-child(3))) > *,
  .map-grid:not(:has(> :nth-child(3))) > *,
  .group-grid:not(:has(> :nth-child(3))) > *,
  .values-grid--media:not(:has(> :nth-child(3))) > * {
    flex: none;
    width: auto;
    scroll-snap-align: none;
  }

  /* ---- 58.3 The blog row gets its rail back -------------------------------
     .blog-grid has been in 17's rail list since 1.5.9 and 20.8 silently took
     it back: a plain .blog-grid { display: grid } at 6991, one class against
     one class, later in the file, and media queries add no specificity. Only
     display, gap and grid-template-columns were restated, so every other rail
     property survived — the row has been a one-column stack wearing the
     rail's negative margins, its 32px inner padding, contain: paint and 36px
     of dead vertical padding ever since. Cards 326px under a 350px heading,
     bled to the screen edges.

     58.1 is later still, so the row is a rail again on the home page, where
     it is three cards and 2.1 screens. On an article it is two, and 58.2
     stacks it properly rather than leaving it in the half-state above.

     This also repairs 20.11's identical mistake on .collab-grid — see 58.6. */

  /* ---- 58.4 The About values become a rail --------------------------------
     Four cards, each a 4:3 photograph over an icon, a heading and three lines
     — about 453px apiece and 1,872px stacked, two and a half screens for four
     sentences.

     There was a phone layout written for these at 6045, a 44px icon beside
     the text, and it has not run since 42: .value-card--media { display: flex }
     at 9270 is later at equal specificity, so the grid template at 6046 is
     never consulted and the values are four tall cards with nothing
     compacting them. Reviving that row was the alternative to a rail here;
     the rail is better, because the photograph is the point of the card and
     the row layout throws it away. */

  /* 6046 puts 16px of padding on .value-card, which un-bleeds the photograph
     from the card's own edge. 6235 already resets it for the media variant;
     restated so a rail card cannot inherit the inset if that rule moves. */
  .values-grid--media > .value-card--media { padding: 0; }

  /* ---- 58.5 The program page gets its related-programs rail back ----------
     single-mm_class.php reuses the Programs listing's wrapper and picks up
     .rail--static with it, and with that the rule at 4384 that undoes the
     rail entirely. The modifier is right where it came from — that listing is
     a hundred cards behind a live filter — and wrong here, where the band is
     three cards and about 1,770px stacked, two and a third screens between
     the FAQs and the closing panel.

     (0,3,0) against 4384's (0,2,0), so the rail comes back with no change to
     the listing itself. The tidier fix is to drop the modifier from the
     template, and it is the one to prefer: initRails() returns early on that
     class, so by CSS alone this band swipes but gets no arrows, which is the
     one way it still differs from every other rail on the site. */
  .siblings-band .rail--static .class-grid {
    --rail-peek: 16px;
    --rail-gap: 16px;
    --rail-pad: calc(var(--rail-gap) + var(--rail-peek));
    --rail-card: calc(100vw - var(--rail-pad) * 2);

    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: var(--rail-gap);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    overscroll-behavior-inline: contain;
    overscroll-behavior-block: auto;
    width: auto;
    min-width: 0;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--rail-pad);
    padding-block: 10px 26px;
    scroll-padding-inline: 0;
    contain: paint;
    scrollbar-width: none;
  }
  .siblings-band .rail--static .class-grid::-webkit-scrollbar { display: none; }
  .siblings-band .rail--static .class-grid > * {
    flex: 0 0 var(--rail-card);
    width: auto;
    min-width: 0;
    scroll-snap-align: center;
    overflow-wrap: anywhere;
  }

  /* And its own count fallback. The selector above is (0,3,0), so 58.2's
     (0,2,0) cannot reach inside it — without this, a program with only one or
     two siblings gets a full-bleed snap rail holding a single card, and
     .rail--static also suppresses the arrows (4384), so there is nothing to
     page with either. single-mm_class.php tops the row up towards three from
     the rest of the site, but only when three other programs exist. */
  .siblings-band .rail--static .class-grid:not(:has(> :nth-child(3))) {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    flex-wrap: wrap;
    overflow: visible;
    scroll-snap-type: none;
    margin-inline: 0;
    padding-inline: 0;
    padding-block: 0;
    contain: none;
    overscroll-behavior-inline: auto;
  }
  .siblings-band .rail--static .class-grid:not(:has(> :nth-child(3))) > * {
    flex: none;
    width: auto;
    scroll-snap-align: none;
  }

  /* ---- 58.6 The educator row is a grid, so it stops wearing a rail --------
     20.11 turned .collab-grid back into a two-up grid at 7091 and won the
     same way 20.8 beat the blog rail — but only the properties it restated.
     overflow-x, the negative margins, the 32px padding, contain and the 26px
     of block padding all survived from 17, so four 186px portrait tiles sit
     in a track 24px narrower than the heading above them with 36px of dead
     air under it.

     The two-up grid is right and stays. This only takes the leftovers off. */
  .collab-grid {
    overflow: visible;
    scroll-snap-type: none;
    margin-inline: 0;
    padding-inline: 0;
    padding-block: 0;
    contain: none;
  }
  .collab-grid > * { scroll-snap-align: none; }

  /* ---- 58.8 The one card that latches its hover on tap --------------------
     A lift is a pointer affordance; on touch :hover latches on tap and the
     card sits raised with no cursor to explain it, and inside a scroll
     container it is clipped by the top edge besides. The list at 4487
     neutralises exactly this on thirteen card types — .value-card among them,
     at 4494 — so the only one missing is .group-card, whose lift at 8316 was
     written after that list. */
  .group-card:hover {
    transform: none;
    box-shadow: var(--shadow-xs);
  }
}

/* ---- 58.9 Focus, at every width -------------------------------------------
   A rail is a scroll container and a keyboard user can land inside one. The
   list at 4338 left out .map-grid and knew nothing of the two new rails.

   Worth being honest about what this does: neither Chrome nor Safari puts a
   scroll container in the tab order while it has focusable descendants, and
   none of these carries tabindex="0", so in those browsers the outline never
   paints. The real keyboard path is tabbing the cards' own links, which
   scrolls each into view and works today — every card in every rail here is
   either a link itself or carries a stretched one. Firefox does focus these,
   which is who the rule is for. */
.rail__track:focus-visible,
.grid-3:focus-visible,
.class-grid:focus-visible,
.blog-grid:focus-visible,
.team-grid:focus-visible,
.map-grid:focus-visible,
.group-grid:focus-visible,
.values-grid--media:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 4px;
}

/* ---- 58.10 Reduced motion -------------------------------------------------
   Snap is motion the reader did not ask for: a swipe that would have ended
   between two cards is taken over and finished by the browser. The list at
   4348 left out .map-grid and the two new rails. The arrows are already
   handled — initRails().step() picks its behaviour from the same query. */
@media (prefers-reduced-motion: reduce) {
  .rail__track,
  .grid-3,
  .class-grid,
  .blog-grid,
  .team-grid,
  .map-grid,
  .group-grid,
  .values-grid--media,
  .siblings-band .rail--static .class-grid { scroll-snap-type: none; }

  /* The partners strip looked like the one marquee with no reduced-motion
     block of its own, and a stop-and-wrap was written here for it. It was
     wrong twice over. .partners__rail and .partners__track are in no template
     — 20.10 replaced the marquee with a wrapped grid and left its CSS behind
     at 749, so those two selectors have matched nothing for a long time. And
     the third line, .partners__row { flex-wrap: wrap }, said nothing to the
     grid container that row actually is. All three are gone rather than
     corrected.

     The strip briefly became a swipe rail and was taken back out; see the
     note at the head of 60 for what that cost and why the wrapped grid is
     what ships. There is nothing left here for this block to switch off. */
}


/* ==========================================================================
   60. Two rows that were never going to swipe, and one that is offered
       arrows it cannot use

   The rail pass stops here. Measured again on a 390px phone against 772px of
   usable screen — and it is 772, not the 700 assumed earlier: the sticky
   enquiry bar is body.has-sticky-cta, which the home page never sets, so the
   header is the only chrome to subtract. Everything 58 did not move:

     hero              whole section    ~845px   1.10 screens  -> stays
     trust marks       30 marks         ~190px   0.25 screens  -> stays, marquee
     partner marks     7 marks          ~294px   0.38 screens  -> stays, wrapped
     why points        3 reasons        ~352px   0.46 screens  -> stays
     safety facts      4 rows           ~553px   0.72 screens  -> stays
     home educators    4 tiles          ~446px   0.58 screens  -> stays, two-up
     closing CTA       1 point          ~510px   0.66 screens  -> stays
     footer            3 link columns  ~1,427px  1.85 screens  -> stays, two-up

   The partner marks are on that list twice over, because they were made a
   swipe rail and then taken back out. The rail was defensible on paper — with
   the 150px width cap removed the seven marks come to a 976px track, and the
   two wordmarks finally reached the 56px optical height 18.6 asks for — but
   the thing it actually produced was a panel showing two logos and a third
   sliced down the middle. A cut card is a fine "there is more" signal; a cut
   LOGO is a damaged logo, and these are other companies' marks. 20.10 said it
   first and said it correctly: a logo wall that slides cannot be read, and
   these are the marks a parent scans for. Seven marks wrapping to four short
   rows inside the panel is 294px, well under a screen, and every mark whole.

   Only the footer clears the screen-and-a-half gate, and the footer is the one
   row on the site that must never rail whatever it measures: it is an index,
   and the single thing an index has to do is let a reader see that what they
   want is not in it. A rail makes an absence unprovable. Every item in it is a
   tap target besides, and a tap target inside a horizontal scroller is a coin
   toss between a tap and a drag. The length is real, so 60.2 answers it in the
   axis the reader is already moving in.

   The trust marks are the row that could have gone either way, and the answer
   is a rule rather than a judgement: a row gets the automatic movement or the
   gesture, never both. .trust__track is animated with translateX; a scroller
   laid over it reads scrollLeft, which the animation never touches, so a drag
   fights the marquee and lets go back to wherever the 60s timeline has
   reached. There is no swipeable marquee to build there — only a marquee or a
   rail — and for 30 marks saying "there are 45+ of them" the moving wall is
   the thing that says it. It is a quarter of a screen, and a swipe there would
   land immediately before the programs rail: two gestures back to back, one of
   them for decoration.

   The safety facts and the why points stay for a reason beyond the number:
   their items are not alternatives, they are clauses. Four safety facts are
   cumulative — small classes AND vetted staff AND controlled pick-up AND clean
   rooms — and a parent who has seen one of four has not been answered, they
   have been sampled. Railing .safety__list would not even shorten it: the text
   column goes from 265px to 241px inside a 326px rail card, so every paragraph
   gains a line and the cards get taller.

   The rule the whole pass now rests on, for the next time this is asked: a row
   may swipe only when it is a set of peers the reader picks one of, and only
   when stacking it runs past about a screen and a half. If they never see item
   four, have they lost a choice or lost the point? A choice is a rail; the
   point is a stack. Rails are a budget, not a style — never two adjacent, and
   a row that already moves on its own has spent its gesture.
   -------------------------------------------------------------------------- */

@media (max-width: 639.98px) {

  /* ---- 60.1 The educator grid stops being offered arrows -------------------
     58.6 took the rail's leftovers off .collab-grid and the two-up grid is
     right: four tiles at 0.58 of a screen, and 20.11 already stripped the card
     to a photo, a name and a role, so there is no depth in it to reward a
     swipe. What 58.6 could not reach is the markup. The educators block still
     renders <div class="rail"><div class="collab-grid" data-rail-item="teacher">,
     a leftover from before 20.11, and initRails() returns early only on
     .rail--static — so two .rail-nav buttons and two ResizeObservers are still
     built on every home page load.

     They are invisible today by luck rather than by rule: 58.6's
     overflow: visible makes scrollWidth equal clientWidth, so measure() writes
     display:none on both. The moment anything makes that grid overflow by more
     than the 1px of slack — a fifth educator, a longer name, a web font
     landing between the observer firing and the reflow — two terracotta discs
     appear over a grid that does not scroll, and pressing them does nothing.

     (0,3,0) against .rail-nav's (0,1,0) at 4403, so it holds inside the same
     query, and it survives the script: measure() writes the empty string to
     turn a button back on, which clears the inline value and hands the
     decision back to this rule rather than overriding it.

     The tidier fix is dropping the .rail wrapper from the educators template —
     the same trade 58.5 states about .rail--static on the program page. This
     is the lock until then. */
  .rail:has(> .collab-grid) .rail-nav { display: none; }

  /* ---- 60.2 The footer link columns go two across -------------------------
     1,427px, and 960 of that is three lists of short links each holding a
     column to itself: Explore at 8 links, Programs at 7, Get in Touch at 3,
     every row 44px since the touch-target pass at 4716 and rightly so.

     Two columns of 165px take the first two lists side by side. Only one link
     in either is long enough to wrap — "School & Study-Abroad English", to two
     lines, on its ampersand — so the row costs the height of the taller list
     rather than the sum of both, and the footer comes back about 336px with
     nothing hidden and nothing behind a gesture: 1,427px to roughly 1,091px,
     1.4 screens instead of 1.9.

     The brand block spans both columns. Its blurb carries max-width: 300px and
     sets its own measure; in a 165px column the same sentence runs to six
     lines and the 46px logo sits in a third of the width it was drawn for.

     The column gap comes down to 20 from the 26 the row gap keeps: 26px
     between two 162px columns is a sixth of the width spent on air, and it is
     the vertical rhythm between the blocks that was doing the work.

     Scoped to .site-footer so it is (0,2,0) against .footer__grid's (0,1,0) at
     1842, and confined to this query so the 640px and 900px templates at 2906
     and 2990 are untouched. */
  .site-footer .footer__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 26px 20px;
  }
  .site-footer .footer__brand { grid-column: 1 / -1; }
  /* Get in Touch is the fourth block, so it landed in a half column on its
     own, where the email address and phone number broke mid-string. It takes
     the full row instead. */
  .site-footer .footer__col:last-child { grid-column: 1 / -1; }
}

/* Tablet: three columns and four blocks. Get in Touch wrapped to a second row
   that started below the long Programs list, leaving a tall empty gap under
   the brand. The two link lists span both rows, so it tucks in under the
   brand block instead. */
@media (min-width: 640px) and (max-width: 899.98px) {
  .site-footer .footer__col:nth-child(2),
  .site-footer .footer__col:nth-child(3) { grid-row: span 2; }
  .site-footer .footer__grid { grid-template-rows: auto 1fr; }
}

/* ==========================================================================
   61. The parent quotes swipe

   58's table measured this row at three cards and about 970px — 1.4 screens —
   and left it stacked, because 1.5.32 deliberately moved it AWAY from
   one-at-a-time on the grounds that "almost nobody waited seven seconds" to
   meet the second and third quotes. That call has been overruled: the quotes
   are a rail on phones. 58's table stands as written; this is the one row in
   it that moves, and this comment is where the disagreement is recorded.

   MEASURED, against the real metrics in the shipped Open Sans at the shipped
   root of 122% — 1rem is 19.52px, so the quote is 19.91px on a 31.86px line —
   for the three quotes the home page renders today:

     viewport  card  text col   t1     t2     t3     tallest card   rail
       320     256     214      6 ln   8 ln   8 ln      429px       465px
       375     311     269      5 ln   7 ln   7 ln      397px       433px
       390     326     284      5 ln   7 ln   7 ln      370px       406px
       430     366     324      4 ln   6 ln   6 ln      339px       375px

   The card adds 88px of fixed chrome to whatever the quote costs — 22px of
   padding twice, a 1px edge twice, the 26px quote mark and the 16px under the
   blockquote — plus an author row of 59px. Below 390px that row is 86px
   instead: "Parent · Summer Camp 2026" measures 219.6px against a 221px
   column at 390px, 1.4px of slack, and wraps to two lines the moment the
   screen is any narrower.

   So the tallest card on the commonest phone is 370px and the rail is 406px,
   half a screen. NO CLAMP IS NEEDED FOR THE COPY ON THE PAGE TODAY. The clamp
   at 61.3 is for the copy that is not on the page today: these are editor
   records, mm_quote is free text, and a card taller than the viewport cannot
   be swiped at all — the thumb scrolls the page instead of the rail. Eight
   lines is a CEILING, not a trim. The longest quote shipping is exactly eight
   lines at 320px and seven everywhere above it, so nothing truncates and no
   ellipsis is ever painted on the current content; a three-paragraph review
   lands at 402px instead of running off the bottom of the screen.

   WHY THIS IS NOT A LINE ADDED TO 58.1'S SELECTOR LIST

   Every other scroller in that list holds a card that is a flex column or a
   plain block. .testi-card is a GRID — three rows, one per element, a subgrid
   on desktop so the quote marks and the author blocks line up across the row
   (1152, and the note above it at 1140). 4728 already drops the subgrid here
   and restates the rows as auto auto auto, and that is the shape that breaks
   in a rail: a flex item at align-items: stretch is as tall as the tallest
   card, and a grid whose tracks are ALL auto splits the leftover height
   EQUALLY ACROSS ALL THREE under the initial align-content: normal. The short
   card would open a gap above its quote mark, another under its quote and
   another under its author row — 1140's complaint arriving by a different
   door. Nothing in 58 prevents it, so this row cannot simply join the list.

   auto 1fr auto at 61.2 is the fix, and it is knowingly the arrangement 1140
   rejected. That note is about three cards SIDE BY SIDE, where the eye
   compares one card's gap against its neighbour's and any difference reads as
   a fault. In a centre-snapped rail no two cards are ever on screen together.
   What matters instead is that the card keeps ONE SILHOUETTE as the reader
   swipes — same height, same author row on the same line above the same
   bottom edge — and the slack a short quote leaves lands between the quote
   and the attribution, where it reads as air under a short quote. 1140 is
   still correct at 640px and up and is untouched there.

   WHAT THIS DELIBERATELY DOES NOT REPEAT

   .testi-grid is ALREADY in 17's hidden-scrollbar list (4204), its
   focus-visible list (4340) and its reduced-motion list (4351). It has been in
   all three since 1.5.9, which removed the rail and left the three lists
   alone. 58 had to add .map-grid and its two new rails to those lists; this
   section adds nothing to any of them, because nothing is missing. Likewise
   the hover latch: .testi-card:hover is already neutralised at 4490, so there
   is no 58.8 to write here.
   -------------------------------------------------------------------------- */

@media (max-width: 639.98px) {

  /* ---- 61.1 The rail, exactly as 58.1 draws it ---------------------------
     One departure, and it is the point of the section: align-items: stretch
     reverses 4727's align-items: start. That rule was written for the stacked
     layout, where letting a short quote size its own card was the whole
     point; in a rail it makes the card's bottom edge jump under the reader's
     thumb. grid-template-rows: none from the same rule is inert on a flex
     container and is left where it is. */
  .testi-grid {
    --rail-peek: 16px;
    --rail-gap: 16px;
    --rail-pad: calc(var(--rail-gap) + var(--rail-peek));
    --rail-card: calc(100vw - var(--rail-pad) * 2);

    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: var(--rail-gap);

    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;

    /* The two-axis rule. Never the shorthand — see 58's note. */
    overscroll-behavior-inline: contain;
    overscroll-behavior-block: auto;

    width: auto;
    min-width: 0;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--rail-pad);
    padding-block: 10px 26px;
    scroll-padding-inline: 0;

    /* Load-bearing, 4165. */
    contain: paint;

    scrollbar-width: none;
  }
  /* ::-webkit-scrollbar is already handled at 4204. */

  .testi-grid > * {
    flex: 0 0 var(--rail-card);
    min-width: 0;
    scroll-snap-align: center;
    overflow-wrap: anywhere;
  }

  /* ---- 61.2 The card's own three rows ------------------------------------
     4728 already does the first half — the subgrid at 1157 only resolves
     against a grid parent and the parent is a flex container now. Restated in
     full so a rail card cannot inherit span 3 if that rule ever moves, which
     is the same insurance 58.4 takes at 6235. The second half is the middle
     track; see the note above for why 1fr is right here and wrong at 640 up. */
  .testi-card {
    grid-row: auto;
    grid-template-rows: auto 1fr auto;
  }

  /* ---- 61.3 A ceiling on the quote, not a trim ---------------------------
     Eight lines is 255px of quote and a 402px card. The longest quote on the
     page is eight lines at 320px and seven above it, so this paints no
     ellipsis on anything shipping today — it is here for the review an editor
     writes next year.

     The native ellipsis rather than a gradient fade: a fade needs one
     hard-coded gradient per tint, because the card's ground is #5E2E0D,
     #2B4A48 or #5C4310 and a fade to transparent over a dark ground goes grey
     in Safari. Three declarations that never need revisiting beat three
     gradients that break on the fourth tint.

     Qualified by blockquote so the selector says what it clamps: the author's
     name and the attribution are a <b> and a <small> and must never be caught
     by this. */
  .testi-card blockquote p {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 8;
    line-clamp: 8;
    overflow: hidden;
  }

  /* ---- 61.4 Fewer than three quotes, and it is not a rail ----------------
     mm_resolve_posts() caps at three but does not guarantee three: a site with
     two published testimonials renders two cards, and at two a rail hides half
     the content to save about 400px. 58.2's shape, its declarations, and
     .rail--static's originals at 4384, so a quote row that falls back and a
     rail that was never one land in the same place. (0,3,0), and last in the
     file besides. */
  .testi-grid:not(:has(> :nth-child(3))) {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    flex-wrap: wrap;
    overflow: visible;
    scroll-snap-type: none;
    margin-inline: 0;
    padding-inline: 0;
    padding-block: 0;
    contain: none;
    overscroll-behavior-inline: auto;
  }
  .testi-grid:not(:has(> :nth-child(3))) > * {
    flex: none;
    width: auto;
    scroll-snap-align: none;
  }

  /* ---- 61.5 The arrows, on the one rail whose cards are dark -------------
     17b drew the buttons terracotta on white with a white ring at 38%, and
     the sage pass at 5458 then took the colour off it — .rail-nav with no
     media query, same specificity, later in the file — so every rail on the
     site actually ships a sage-tint disc with a sage-deep chevron.

     That composite, measured on the card grounds 48 gives these quotes, at
     17b's opacity of .38:

       over #5E2E0D   disc #917762  chevron #584C37   2.00:1
       over #2B4A48   disc #718886  chevron #385D5B   1.93:1

     Invisible, and this is the only rail where it happens: every other one
     shows a white card or a photograph. The pair below is 56's own answer to
     a control on a dark ground — soft gold with ink on it, the same flip 18.5
     and the home hero already make — and it is NOT marigold, so 17b's reason
     for holding the buttons back from the primary CTA colour still holds.

       over #5E2E0D   disc #C6AB6F  chevron #342011   6.94:1
       over #2B4A48   disc #B8B380  chevron #262721   7.00:1

     .72 rather than .38 for the same reason and no further: the swipe is
     still the primary gesture. The white ring at 4424 is inherited and wanted
     — it is what separates the disc from the card it half sits on. :active is
     restated because 4451's .rail-nav:active is (0,2,0), a tie with the
     selector here, and would otherwise put terracotta back on press. */
  .testimonials .rail-nav {
    background: var(--dark-cta-bg);
    color: var(--dark-cta-fg);
    opacity: .72;
  }
  .testimonials .rail-nav:active {
    background: var(--dark-cta-bg);
    opacity: 1;
  }
}

/* ==========================================================================
   62. The phone: the hero's announcement, and the filter bar back at the top

   Two things asked for on a phone, and one of them reverses a decision this
   file argued at 1970.

   62.1 THE HERO ANNOUNCEMENT. The badge is a pill holding a second pill: a
   date chip and a sentence. At 390px the two together need about 363px
   against 350px of content width, so both wrapped — "Summer 2026" broke
   across two lines INSIDE the white chip, which turned the chip from a pill
   into a tall rounded box, and the outer capsule stretched to a lozenge with
   a lot of dead space in it. The chip breaking is the actual fault; the
   capsule looking wrong is what that fault produced.

   Three things fix it and the third is the one that matters. The chip never
   wraps, because a date is one token. The type comes down a step so the
   common announcement fits one line. And the outer radius goes from --r-pill
   to 22px, which at a one-line badge of about 40px tall still reads as a
   pill — the radius is more than half the height — and at two lines reads as
   a rounded box rather than a stretched capsule. That is what makes this
   robust: the announcement is an editor field, the seed default is the longer
   "Saturday School — enrollment is now open", and anything that only works
   because today's text is short would break the first time it is edited.

   62.2 THE FILTER BAR. 1988 makes it position: static on a phone and the note
   above it is right about why: three dropdowns going full width turns one row
   into three, measured at 274px, which with the header pins 351px — 46% of a
   375x760 viewport. Browsing tall cards through a half-height window costs
   more than the filters are worth once set.

   But that note also says what the trade would take: "Keeping it pinned would
   mean shrinking the controls to earn the space." So they are shrunk. The
   three drops go on one row instead of stacking, and the row pays for itself
   by dropping the leading icon — the label already says PROGRAM, AGE and
   SCHOOLS, and a 17px terracotta glyph in front of a word that repeats it is
   the most expendable 25px on the bar. At 390px that is 111px a drop, which
   holds the label and the caret with room, and the whole bar comes to about
   96px against 274px.

   Pinned, that is 96 + 72 = 168px, 22% of a 760px viewport rather than 46%.
   The 46px tap target is untouched — 1970's condition was that a 44px target
   must not be what pays, and it is not: the icon paid.

   What this buys is the thing the static bar lost. The count is live — it
   reads "12 programs available" and drops as filters narrow — and that number
   is the only feedback the filter gives. Static, it scrolls away the moment
   you start reading the results it describes.
   -------------------------------------------------------------------------- */

@media (max-width: 639.98px) {

  /* ---- 62.1 The hero announcement fits ------------------------------------ */
  .hero__badge {
    font-size: .78rem;
    gap: 8px;
    padding: 6px 14px 6px 6px;
    /* A pill at one line, a rounded box at two. --r-pill would be a lozenge
       the moment the sentence wraps, and the sentence is an editor field. */
    border-radius: 22px;
    max-width: 100%;
    /* Two even lines, not "Enrollment now" and a stranded "open". */
    text-wrap: balance;
  }
  .hero__badge .pill {
    /* The one real bug: a date is a single token and must never break. */
    white-space: nowrap;
    font-size: .7rem;
    padding: 4px 9px;
  }

  /* ---- 62.2 The filter bar is pinned, and compact enough to deserve it ---- */
  .filter-bar {
    position: sticky;
    top: var(--header-h);
  }
  .filter-bar__inner { padding-block: 10px 8px; gap: 8px; }

  /* One row of three rather than three rows of one. minmax(0, 1fr) rather
     than 1fr so a long label shrinks the track instead of overflowing it. */
  .filter-bar__drops {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
  }
  .filter-drop,
  .filter-drop__btn { width: 100%; }
  .filter-drop__btn {
    justify-content: center;
    gap: 4px;
    padding: 8px 8px;
    min-height: 46px;
  }
  /* The 25px that pays for the row. The label says what the icon says. */
  .filter-drop__ico { display: none; }
  .filter-drop__label {
    font-size: .66rem;
    letter-spacing: .03em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* With three tracks there is no room for label and value side by side, so a
     chosen value replaces the label rather than joining it — the drop still
     says what it is filtering by, in the words the reader picked. */
  .filter-drop.has-selection .filter-drop__label { display: none; }
  .filter-drop__value {
    font-size: .78rem;
    margin-left: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .filter-bar__meta { width: 100%; justify-content: space-between; margin-left: 0; }
  .filter-bar__count { font-size: .84rem; }
  .filter-bar__count b { font-size: .94rem; }

  /* The menu spans the bar rather than its own 111px track.

     Anchored with left/right and NOT with a transform, which is the whole
     point of writing it this way: .filter-drop.is-open runs the dropIn
     keyframe at 2104 with fill-mode both, and an animation's transform beats a
     declared one in the cascade — so a translateX centring here is wiped the
     instant the menu opens. Measured before this rule: the middle menu opened
     at x=195 and ran to x=545 in a 390px viewport, 155px off the right edge.

     .filter-drop goes static and .filter-bar__inner becomes the containing
     block, so `top: calc(100% + 8px)` from 2081 resolves against the whole bar
     rather than against one 111px drop, and left/right 0 span the content
     width. The dropIn translateY still plays, because nothing here touches
     transform. */
  .filter-bar__inner { position: relative; }
  .filter-drop { position: static; }
  .filter-drop__menu {
    left: 0;
    right: 0;
    width: auto;
    min-width: 0;
    max-width: none;
  }
  .filter-drop[data-filter="venue"] .filter-drop__menu { min-width: 0; }
}

/* ==========================================================================
   63. The partner wall, arranged

   20.7 replaced a six-and-one wrap with a grid and said why: "seven fit across
   a desktop, and narrower screens break to four-and-three or three-and-two
   rather than stranding one." On a phone that is not what happens. The track
   is auto-fit minmax(98px, 1fr) with an 18px gap inside a panel padded 20px,
   so at 390px there is room for two columns and seven marks come out
   2+2+2+1 — the seventh alone on a fourth row, against the left edge, which
   is precisely the stranding 20.7 set out to stop. It only ever avoided it at
   widths where three or four columns fit.

   Three things, and none of them change the mark sizes.

   The columns are pinned to two rather than left to auto-fit. At this width
   auto-fit already resolves to two, so nothing moves — but it moves for a
   reason now rather than by arithmetic that happens to land there, and a
   later change to the minimum cannot quietly make it three 95px columns, at
   which the two wordmarks would be 20px tall.

   The odd mark out spans both columns, so it centres under the pair above
   instead of hanging at the left margin. Written as :last-child:nth-child(odd)
   so it is the COUNT that triggers it rather than the number seven: at six
   partners nothing spans, at seven or nine the last one centres. The count is
   editor data and will not stay at seven.

   And the rows get a little more air than the columns, 22px against 18px.
   Marks of different aspect need vertical separation more than horizontal to
   read as a grid rather than as a texture: a 48px square beside a 150px
   wordmark already has generous white space at its sides and almost none
   above it.

   The section keeps the cream ground it has always had. See the note at 57.4
   for the dark band that was tried here and taken back out.
   -------------------------------------------------------------------------- */

@media (max-width: 639.98px) {
  .partners__row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 22px 18px;
    align-items: center;
  }
  /* A common row height so the wall reads as a matrix. The tallest mark is a
     48px square and the shortest a 35px wordmark; without this each row takes
     its height from whichever mark happens to land in it. */
  .partners__item { min-height: 52px; }
  .partners__item:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* ==========================================================================
   64. "Meet the educators" takes the safety band's sage

   Asked for directly: the same cool ground the safety band uses higher up the
   page. It is the only cool hue this brand has — the guidelines list five
   hexes and none of them is blue — and #A5C3C2 is what the page already means
   when it looks blue.

   This reverses 53, which took the band OFF a cool green for two stated
   reasons, and both are worth keeping on the record because one of them is
   still true and visible:

   First, 53 avoided "a second sage after the safety band". That is a real
   repeat — safety is sage, and now the band three sections below it is too —
   but the two are separated by the partner wall, they are different values of
   the idea (one is a full band of white cards, this is faces on a sheet), and
   it was a preference rather than a fault.

   Second, and this one is a genuine consequence: "the portraits on it are
   warm — one on dark olive, one on white, two cut out onto brand orange — and
   there is no card between them and the ground, so the ground is doing the job
   a card would." That is still the case. .section--tint .collab-card at 7094
   strips the card to nothing, so each face sits directly on this sheet, and
   two of the four are cut out onto #EB940E. Orange on sage is close to a
   complementary pair, so those two will read louder than the other two. The
   orange is baked into the image files and no rule here can reach it; if it
   grates, the fix is to re-cut those two photographs onto the sage rather
   than to change anything in this stylesheet.

   THE PART THAT IS NOT OPTIONAL

   --muted measures 3.04:1 on #A5C3C2, under the 4.5 body copy needs, and two
   things in this band use it: the section's supporting line and every
   educator's role. On cream they were fine at 4.9. Both go to --ink-soft,
   6.32:1, which is the same ink the safety band puts on this exact ground.
   The names are --ink already and clear at 9.08.

   Scoped to the home page. #collaborations also renders on About and For
   Schools, where the cards are .team-grid and carry biographies on their own
   surfaces, and neither was asked for.
   -------------------------------------------------------------------------- */

/* (0,3,0) against 53's (0,2,0) at 9758. */
.mm-page-home #collaborations,
.mm-page-home #collaborations.section--tint { background: var(--accent-sage); }

.mm-page-home #collaborations .section-sub,
.mm-page-home #collaborations .collab-card__role { color: var(--ink-soft); }

@media (prefers-contrast: more) {
  .mm-page-home #collaborations .section-sub,
  .mm-page-home #collaborations .collab-card__role { color: var(--ink); }
}

/* ==========================================================================
   65. The programme page: no photograph in the hero, no FAQ, dark outcomes

   Three things asked for on a course detail page.

   65.1 THE HERO LOSES ITS PHOTOGRAPH. 1.5.60 put one back deliberately —
   22.2 had dropped hero photographs site-wide because heroes were using them
   as ground and darkening them until they could not be seen, and that also
   hid .detail-hero__media, so a programme with a 600x800 picture rendered a
   band of flat terracotta with nothing beside the heading. The answer then was
   to bring the photograph back as an OBJECT on the ground rather than as the
   ground: unscrimmed, inside the white panel, above the tuition and the trial
   fee. That reasoning was about the empty half of the band, and the panel is
   what fixed it — the picture was never the part doing the work. The panel
   stays and keeps everything that states money and what is paid when; only
   the photograph goes, so the hero is the programme's name, its facts and its
   price, and nothing else.

   65.2 THE FAQ BAND GOES. Hidden rather than deleted from the template: the
   questions and answers are editor content on the record, and a rule that
   stops painting them leaves every word in the database and in the page
   source. Putting the band back is deleting one rule. Nothing else on the
   page refers to it, and the closing panel directly below it already carries
   the ask.

   65.3 THE OUTCOME BOXES GO DARK, the way the parent quotes did on the home
   page. They are the same device: a three-tint cycle, t1/t2/t3, cycling so a
   list of four or five reads as a set rather than as a stack. On the home page
   48 took that cycle to the deep end of each hue — #5E2E0D, #2B4A48, #5C4310 —
   and these boxes kept the pale end, #FCF4EC, #E4EDEC, #F9F3DC. Same three
   colours, three steps lighter. This brings them into line, using 48's exact
   values rather than new ones.

   Measured on each ground, white / cream #EFEEE4 / soft gold:

     t1 deep clay   #5E2E0D   11.21   9.62   8.19
     t2 deep teal   #2B4A48    9.65   8.28   7.05
     t3 deep amber  #5C4310    9.27   7.95   6.77

   Everything inside the box needs naming, because all of it was drawn against
   a pale tint. The heading is --ink and would be ink on near-black. The body
   inherits --ink-soft. And the numeral chip is --brand-dark on --brand-tint,
   a pale blob with a mid-warm number on it, which on these grounds is both
   the brightest thing in the box and the least readable: --muted-class values
   measure under 2:1 on all three. It takes the flip the rest of this site
   makes when a warm ground will not carry a terracotta object — soft gold
   with ink on it, 12.46:1 inside the chip and 6.77:1 at worst against the
   ground behind it.
   -------------------------------------------------------------------------- */

/* ---- 65.1 The hero photograph -------------------------------------------- */
.mm-view-mm_class .detail-hero .program-panel__photo { display: none; }

/* ---- 65.2 The FAQ band ----------------------------------------------------
   Back on (24 Sep 2026): the copy document (v2, section 4) writes three FAQs
   for every program page, so the rule that hid the band is gone. */

/* ---- 65.3 The outcome boxes ----------------------------------------------- */
.mm-view-mm_class .outcomes .outcome-list__item {
  color: var(--dark-fg);
  border-color: transparent;
}
.mm-view-mm_class .outcomes .outcome-list__item--t1 { background: var(--dark-clay); }
.mm-view-mm_class .outcomes .outcome-list__item--t2 { background: var(--dark-teal); }
.mm-view-mm_class .outcomes .outcome-list__item--t3 { background: #5C4310; }

.mm-view-mm_class .outcomes .outcome-list__item h3 { color: var(--dark-fg-head); }
.mm-view-mm_class .outcomes .outcome-list__body,
.mm-view-mm_class .outcomes .outcome-list__body p { color: var(--dark-fg); }

/* The numeral. Soft gold with ink on it — the flip 18.3 makes in the hero and
   35.9 makes on the closing band, for the same reason: a terracotta object on
   a warm dark ground is not there. */
.mm-view-mm_class .outcomes .outcome-list__num {
  background: var(--dark-cta-bg);
  color: var(--dark-cta-fg);
}

@media (prefers-contrast: more) {
  .mm-view-mm_class .outcomes .outcome-list__item {
    border-color: rgba(239, 238, 228, .4);
  }
}

/* ==========================================================================
   66. Two things off the About page

   66.1 THE VALUE CARDS LOSE THEIR NUMBERS. Each core-value card carries an
   01/02/03/04 chip on the photograph. A number earns its place when the order
   is the meaning — a sequence of steps, a ranking — and these four are not
   that: "Child at the center", "Joy in learning", "Measurable growth",
   "Safety & trust" are four simultaneous principles, in no order, and
   numbering them tells a reader there is a first and a fourth when there is
   not. The chip is also the one opaque object sitting on each photograph.

   Hidden rather than cut from the template, because the markup emits the
   number from the loop index in two places (values/render.php, the media and
   the plain variants) and the count follows whatever an editor adds.

   66.2 THE SAFETY BAND COMES OFF ABOUT. It renders on exactly two pages, home
   and About, with the same heading, the same supporting line and the same
   four facts — word for word, the same block. On the home page it belongs:
   it is what a parent checks before enquiring, it carries a photograph, and
   31 placed it deliberately in the calmest colour the brand has. On About it
   is a repeat, and it renders WITHOUT the photograph — .safety-band rather
   than .safety-band--photo — so its two-column grid keeps a column for media
   that never arrives and the four cards bunch into the left half with the
   right half left as bare pattern. That is the dead space in the band.

   So it goes from About rather than being redesigned. Nothing is lost: every
   word of it is still on the home page, one click away, where a parent
   actually asks the question. Replacing it with new copy would mean writing
   and approving a section that About does not currently need — About already
   runs the story, the values, the team and the closing ask.

   Hidden rather than removed from the page's block list, so the content stays
   in the database and putting it back is deleting one rule. */

/* ---- 66.1 ---------------------------------------------------------------- */
.mm-page-about .value-card__num { display: none; }

/* ---- 66.2 ---------------------------------------------------------------- */
.mm-page-about .safety-band { display: none; }

/* ==========================================================================
   67. Hero readability

   Reported as "readability is getting compromised in the hero sections", and
   it is measurable. The cause is not the text colour. It is that the pattern
   runs behind the words, and a flat-ground contrast figure does not describe
   what a reader actually sees: wherever a doodle stroke passes behind a
   letter, the local ground is not the band colour, it is the band colour with
   white painted over it at the tile's opacity.

   Measured that way — white text sitting ON a stroke, not beside one:

     home hero      --brand #BC5A1B   tile .14   3.62:1   FAIL
     programme hero --brand-dark      tile .20   3.88:1   FAIL

   Both under the 4.5 body copy needs, and both worse than the flat-ground
   numbers that made them look acceptable (4.14 and 5.26).

   The important part: on --brand, NO tile opacity fixes this. At .06 — far
   fainter than the pattern is worth having — the worst case is still 4.12.
   The ground itself is too light to carry reversed body copy. --brand is a
   mid-tone at L=0.18; that is the same finding 56 recorded when it refused to
   make terracotta one of the dark grounds.

   So two levers, and both are needed:

     ground  --brand -> --brand-dark   an existing brand token, one step down,
                                       and already the programme hero's ground
     tile    .14/.20 -> .12            the strength the worst case clears at

   Worst case after: 4.57:1, over a stroke, on both heroes. The flat ground is
   5.78. Nothing else about the band changes — same hue, same family, same
   pattern, same size.

   The reversed body copy also goes solid white. .hero__sub was 93% white and
   .hero__proof 90%, which cost 0.4 and 0.6 of a ratio point for no perceptible
   softening — a transparency that only ever subtracted. The headings were
   already #fff.

   THE LIGHT HEROES are almost all fine and are left alone: ink on a stroke
   measures 5.70 on marigold, 7.33 on sage, 10.33 on soft gold, 12+ on the two
   creams. The one exception is About's supporting line, which is --ink-soft
   and lands at 3.97 over a stroke on marigold, the darkest of the light
   grounds. It goes to --ink, 5.70.
   -------------------------------------------------------------------------- */

/* ---- 67.1 The home hero takes the darker terracotta ---------------------- */
.hero { background: var(--brand-dark); }
.hero::before { opacity: .12; }

/* ---- 67.2 The programme hero's tile comes down ---------------------------- */
.mm-view-mm_class .detail-hero::before,
.mm-view-mm_class .detail-hero--panel::before,
.mm-view-mm_class .page-hero::before { opacity: .12; }
@media (max-width: 760px) {
  .mm-view-mm_class .detail-hero--panel::before { opacity: .13; }
}

/* ---- 67.3 Reversed body copy is solid white ------------------------------- */
.hero__sub,
.hero__proof { color: #fff; }

/* ---- 67.4 About's supporting line on marigold ---------------------------- */
.mm-page-about .page-hero .section-sub,
.mm-page-about .page-hero p { color: var(--ink); }

/* ==========================================================================
   68. The Programs and For Schools heroes get their pattern back

   The band looked like a rendering fault: soft gold with the element tile
   visible only on the right-hand third, nothing at all across the left, and
   the strongest part of it hidden behind the white venue panel.

   It is a mask, applied twice — 7569 on .listing-hero--split / .page-hero--split
   and again at 7681 for .mm-page-classes and .mm-page-schools:

     linear-gradient(90deg, transparent 0%, transparent 46%, #000 74%, #000 100%)

   Fully erased for the left 46%, fading in to 74%, full only after that. Both
   were written when these heroes carried a photograph and the tile had to keep
   off the copy. The photographs went in 26 — .listing-hero > .photo-band is
   display: none — so the mask is now clearing a pattern away from a flat
   ground for a reason that no longer exists, and hiding the rest behind a
   panel.

   18.3 already settled this for the home hero, in as many words: "Full width,
   no mask: on a flat ground the pattern should run right across rather than
   fade out halfway, which is what made it look like a rendering fault." Both
   6689 and 6510 set mask-image: none on exactly these selectors to do that —
   and 7681 is later in the file at higher specificity, so it put the mask back
   and quietly reverted the fix.

   Readability is not a reason to keep it. These are light grounds and the tile
   is inverted to ink at .10, where ink body copy over a stroke measures 10.33:1
   on soft gold and 7.33:1 on sage — far past the 4.5 needed, and nothing like
   the reversed-white problem 67 had to solve on the terracotta bands.

   The centre-clearing mask at 7563 is left alone. That one is on the centred
   heroes, it clears the middle rather than a whole half, and it is doing the
   job it was written for.
   -------------------------------------------------------------------------- */

.mm-page-classes .listing-hero::before,
.mm-page-schools .listing-hero::before,
.mm-page-classes .page-hero::before,
.mm-page-schools .page-hero::before,
.listing-hero--split::before,
.page-hero--split::before {
  -webkit-mask-image: none;
  mask-image: none;
}

/* ==========================================================================
   69. The programme hero: the copy on white, and the photograph back

   Two corrections to 65.

   65.1 hid .program-panel__photo. It comes back — the course's own picture is
   the one thing on this page that says what the session looks like, and the
   panel has room for it above the tuition.

   And the copy goes on a white panel of its own. It was white type straight
   on the terracotta band, which is the pairing 67 had to rescue with a darker
   ground and a fainter tile; on a white card the same words are ink on white
   at 14.6:1 and nothing about the band has to carry them. It also gives the
   hero two objects of the same family — the copy card on the left, the tuition
   card on the right — instead of one floating panel beside loose type.

   Everything inside it was drawn to be reversed out of terracotta, so each
   piece is named back to its ink-on-white value. The tag pills go from frosted
   white to the brand tint; the secondary button, which was a frosted rule on a
   dark ground and would be invisible on white, takes a terracotta outline.
   The primary stays soft gold with ink on it: 12.46:1, and it is the same
   button the band used, so the page does not gain a new CTA colour.
   -------------------------------------------------------------------------- */

/* The photograph, back. */
.mm-view-mm_class .detail-hero .program-panel__photo { display: block; }

/* The copy takes a card. */
.mm-view-mm_class .detail-hero__copy {
  background: var(--surface);
  border-radius: var(--r-xl);
  padding: clamp(22px, 2.6vw, 36px);
  box-shadow: var(--shadow-md);
  color: var(--ink-soft);
}

.mm-view-mm_class .detail-hero__copy h1 { color: var(--ink); }
.mm-view-mm_class .detail-hero__lead { color: var(--ink-soft); }

/* Pills were frosted white on the band. */
.mm-view-mm_class .detail-hero__tags > * {
  background: var(--brand-tint);
  color: var(--brand-dark);
  border-color: transparent;
}
.mm-view-mm_class .detail-hero__tags svg { color: var(--brand); }

/* The secondary action was a white rule on a dark ground; on white it needs a
   real edge. The primary keeps the soft gold the band already gave it. */
.mm-view-mm_class .detail-hero__copy .btn--secondary {
  background: transparent;
  border: 1.5px solid var(--brand);
  color: var(--brand-dark);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.mm-view-mm_class .detail-hero__copy .btn--secondary:hover {
  background: var(--brand-tint-2);
  border-color: var(--brand-dark);
}

@media (max-width: 639.98px) {
  .mm-view-mm_class .detail-hero__copy { padding: 20px 18px; }
}

/* ==========================================================================
   70. The dark tint cycle, put back on brand hues

   Asked for: the three dark cards read as brown, teal and olive rather than
   as Mirai Minds. Measured by hue against the five official colours
   (terracotta 23.5 deg, marigold 36.4, sage 178):

     t1  #5E2E0D   hue 24.4   sat 76%   L 21%    terracotta, hue exact
     t2  #2B4A48   hue 176.1  sat 27%   L 23%    sage, hue exact
     t3  #5C4310   hue 40.3   sat 70%   L 21%    marigold, drifted

   So two of the three were already right and only read wrong because any warm
   orange this dark gets called brown in English. t3 is the real fault: the
   hue is four degrees off marigold, the saturation is eighteen points down and
   the lightness is halved — and yellow has no dark reading the way orange
   does, so what comes out is olive. It was the one card that had stopped
   being a brand colour at all.

   t1 takes --brand-darker #843E11, already a token and already the exact
   terracotta hue, so the deep card and the closing panel are the same colour
   rather than two browns a few degrees apart. t3 takes #6E4A10, hue 37, which
   is marigold's own hue at card depth. t2 is untouched.

   Cream #EFEEE4 and white stay well clear on both:
     #843E11   cream 6.73:1   white 7.84:1
     #6E4A10   cream 6.80:1   white 7.92:1

   Applied to both places the cycle runs — the parent quotes on the home page
   (48) and the programme page's outcome boxes (65) — so the two stay one
   device rather than drifting apart again.
   -------------------------------------------------------------------------- */

.testi-card--t1,
.mm-view-mm_class .outcomes .outcome-list__item--t1 { background: var(--brand-darker); }

.testi-card--t3,
.mm-view-mm_class .outcomes .outcome-list__item--t3 { background: #6E4A10; }

/* ==========================================================================
   71. Only the five colours in the guidelines

   Instruction: the darkened versions are not to be used at all — the site
   uses the colours the brand guidelines name, and nothing derived from them.
   That retires --brand-dark, --brand-darker, --dark-clay, --dark-teal, the
   #6E4A10 of 70 and the #5C4310 before it, as GROUNDS. Sections 56, 57, 65,
   67 and 70 are all wound back to the five hexes here.

     #BC5A1B terracotta   #EB940E marigold   #A5C3C2 sage
     #EFDC95 soft gold    #EFEEE4 cream

   Which text each one takes is not a choice, it is arithmetic:

     terracotta  white 4.55   ink 3.75   -> white, and only white
     marigold    white 2.39   ink 7.13   -> ink
     sage        white 1.88   ink 9.08   -> ink
     soft gold   white 1.37   ink 12.46  -> ink
     cream       white 1.17   ink 14.63  -> ink

   So terracotta is the only one of the five that can carry reversed copy, and
   it carries it with nothing to spare: 4.55 against the 4.5 body text needs.
   That single number governs the rest of this section.

   THE PATTERN COMES OFF THE WORDS. 67 measured the real fault — the tile runs
   behind the copy, so where a stroke passes behind a letter the local ground
   is terracotta with white over it, and white-on-white-over-terracotta is
   3.62 at the shipped .14. 67 solved it by darkening the ground, which is no
   longer available. The only other lever is to stop painting the tile where
   the words are, so the copy sits on flat #BC5A1B at the full 4.55.

   On the home hero the copy is the left column and the photograph the right,
   so a gradient that is clear to 44% and full from 72% keeps every word on
   flat ground and leaves the tile across the picture side, at full strength
   rather than the reduced .12 the darker ground needed. This is the same
   device 68 removed from the Programs hero — correctly, because there the
   band is soft gold with INK copy, which measures 10.33 straight through a
   stroke and needs no help. A mask is right where the copy is reversed out
   and wrong where it is not.

   THE TINT CYCLE stops being a dark cycle and becomes the brand's own three
   primaries: terracotta with white on it, sage with ink, marigold with ink.
   Same rotation, same job, no invented shades.
   -------------------------------------------------------------------------- */

/* ---- 71.1 The heroes go back to the brand colour ------------------------- */
.hero { background: var(--brand); }
.hero::before { opacity: .14; }

/* Clear of the copy column, full over the picture. */
@media (min-width: 900px) {
  .hero::before {
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 44%, #000 72%, #000 100%);
    mask-image: linear-gradient(90deg, transparent 0%, transparent 44%, #000 72%, #000 100%);
  }
}
/* One column on a phone, so there is no picture side to keep it on: the tile
   comes right down instead, which is the only way white clears 4.5 there. */
@media (max-width: 899.98px) {
  .hero::before { opacity: .05; }
}

.mm-view-mm_class .detail-hero,
.mm-view-mm_class .detail-hero--panel { background: var(--brand); }

/* ---- 71.2 The tint cycle, on the three primaries ------------------------- */
.testi-card--t1,
.mm-view-mm_class .outcomes .outcome-list__item--t1 { background: var(--brand); }
.testi-card--t2,
.mm-view-mm_class .outcomes .outcome-list__item--t2 { background: var(--accent-sage); }
.testi-card--t3,
.mm-view-mm_class .outcomes .outcome-list__item--t3 { background: var(--accent-sun); }

/* Terracotta keeps white. The other two take ink — white on sage is 1.88 and
   on marigold 2.39, which is not a near miss, it is unreadable. */
.testi-card--t1,
.testi-card--t1 blockquote p,
.mm-view-mm_class .outcomes .outcome-list__item--t1,
.mm-view-mm_class .outcomes .outcome-list__item--t1 .outcome-list__body { color: #fff; }
.testi-card--t1 .testi-card__author b,
.mm-view-mm_class .outcomes .outcome-list__item--t1 h3 { color: #fff; }
.testi-card--t1 .testi-card__author small { color: rgba(255, 255, 255, .88); }

.testi-card--t2, .testi-card--t3,
.testi-card--t2 blockquote p, .testi-card--t3 blockquote p,
.mm-view-mm_class .outcomes .outcome-list__item--t2,
.mm-view-mm_class .outcomes .outcome-list__item--t3,
.mm-view-mm_class .outcomes .outcome-list__item--t2 .outcome-list__body,
.mm-view-mm_class .outcomes .outcome-list__item--t3 .outcome-list__body { color: var(--ink-soft); }

.testi-card--t2 .testi-card__author b,
.testi-card--t3 .testi-card__author b,
.mm-view-mm_class .outcomes .outcome-list__item--t2 h3,
.mm-view-mm_class .outcomes .outcome-list__item--t3 h3 { color: var(--ink); }
.testi-card--t2 .testi-card__author small,
.testi-card--t3 .testi-card__author small { color: var(--ink-soft); }

/* The quote mark was cream at 28% for a dark card. */
.testi-card--t2 .quote-mark,
.testi-card--t3 .quote-mark { color: rgba(36, 26, 18, .26); }

/* The numeral chip: ink on cream reads on all three without a fourth colour. */
.mm-view-mm_class .outcomes .outcome-list__num {
  background: var(--canvas);
  color: var(--ink);
}

/* ---- 71.3 The two remaining bands --------------------------------------- */
/* For Schools' onboarding was deep teal; sage is the colour it was a shade
   of. Its pattern was white-not-inverted for a dark ground and goes back to
   ink, which is what every light ground in 28 uses. */
.mm-page-schools .steps-section--photo { --mm-band: var(--accent-sage); color: var(--ink-soft); }
.mm-page-schools .steps-section--photo .section-title { color: var(--ink); }
.mm-page-schools .steps-section--photo .section-sub { color: var(--ink-soft); }
.mm-page-schools .steps-section--photo::before { filter: invert(1); opacity: .10; }
.mm-page-schools .steps-section--photo .eyebrow {
  background: rgba(255, 255, 255, .72);
  color: var(--brand-dark);
}

/* Trial's "what happens" band was deep clay; soft gold is the brand's own
   quiet ground and takes ink at 12.46. */
.mm-page-trial .section--tint:has(.outcome-grid) { --mm-band: var(--accent-yellow); color: var(--ink-soft); }
.mm-page-trial .section--tint:has(.outcome-grid) .section-title { color: var(--ink); }
.mm-page-trial .section--tint:has(.outcome-grid) .section-sub { color: var(--ink-soft); }
.mm-page-trial .section--tint:has(.outcome-grid) .outcome-item {
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
}
.mm-page-trial .section--tint:has(.outcome-grid) .eyebrow {
  background: rgba(255, 255, 255, .72);
  color: var(--brand-dark);
}

/* ==========================================================================
   72. Two layout faults

   72.1 THE PROGRAMME HERO'S COPY CARD — a regression from 69, and a bad one:
   the lead paragraph and the two tag pills went invisible. 69 put the copy on
   a white card and named h1 and .detail-hero__lead back to ink, but the rules
   that paint this hero's text white are more specific than the ones it wrote,
   so they kept winning and the copy stayed white on white. What showed was a
   title, a gap where the description should be, and two empty outlines where
   the age and venue pills should be.

   Named again at (0,4,0), scoped through .detail-hero__copy so nothing
   outside the card is touched, and stated for every text node in it rather
   than for the two elements that happened to be noticed — a card that turns
   its own contents invisible is the one failure mode worth over-specifying
   against.

   72.2 THE SAFETY ROWS. The icon is vertically centred against the whole row,
   so on a card with a heading and three lines of copy it floats level with
   the second line, beside nothing. 1.5.24 centred these markers deliberately
   and was right about the case it fixed — a 34px tile against a 20px label
   rides high when top-aligned — but that argument holds for a row that is one
   or two short lines, not for one carrying a heading and a paragraph. The
   comment at the time said as much: rows whose body can run to a paragraph
   are left top-aligned on purpose. This row grew a paragraph and never got
   the exception.

   Top-aligned, with a small nudge down so the 36px tile sits on the heading's
   centre line rather than on its cap height.
   -------------------------------------------------------------------------- */

/* ---- 72.1 ---------------------------------------------------------------- */
.mm-view-mm_class .detail-hero .detail-hero__copy,
.mm-view-mm_class .detail-hero .detail-hero__copy p,
.mm-view-mm_class .detail-hero .detail-hero__copy li,
.mm-view-mm_class .detail-hero .detail-hero__copy .detail-hero__lead { color: var(--ink-soft); }

.mm-view-mm_class .detail-hero .detail-hero__copy h1,
.mm-view-mm_class .detail-hero .detail-hero__copy strong,
.mm-view-mm_class .detail-hero .detail-hero__copy b { color: var(--ink); }

.mm-view-mm_class .detail-hero .detail-hero__copy .detail-hero__tags > * {
  background: var(--brand-tint);
  color: var(--brand-dark);
  border-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.mm-view-mm_class .detail-hero .detail-hero__copy .detail-hero__tags svg { color: var(--brand); }

/* ---- 72.2 ---------------------------------------------------------------- */
.safety__row { align-items: flex-start; }
.safety__row .ico { margin-top: 2px; }

/* ==========================================================================
   73. The safety photograph squares up with its section

   Measured: .wrap.safety is a two-column grid, 649px of copy and 403px of
   picture, set to align-items: center. The copy column runs 506px — eyebrow,
   heading, supporting line and four cards — and the photograph is 362px, so
   centring left it floating with about 72px of band above it and 72px below,
   lining up with neither the heading it sits beside nor the cards it sits
   above. It read as a picture dropped into the band rather than as half of it.

   Stretched instead, so the photograph is exactly the height of the column
   next to it and the band has one top edge and one bottom edge across its
   whole width. The image was already object-fit: cover, so it crops to the
   new height rather than distorting; at 403 x 506 it is close to 4:5, which
   is a kinder frame for a room full of people than the 4:3 it had.

   align-items only — the column widths, the gap and everything inside both
   columns are left exactly as they were.
   -------------------------------------------------------------------------- */

.safety-band .wrap.safety { align-items: stretch; }

.safety-band .safety__media { height: 100%; }
/* The frame between the two kept its 4:3.6 ratio, so the column stretched
   and the photograph did not: 362px of picture in a 508px column. */
.safety-band .safety__frame { height: 100%; aspect-ratio: auto; }
.safety-band .safety__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* One column below the split, where stretching would make the photograph as
   tall as the whole stack of cards. The frame sets the shape (a landscape
   3:2, so a full-width tablet photo is not 620px tall) and the image fills
   it: height: auto here let a 4:3 photo sit short inside the frame, leaving
   an empty band under the picture. */
@media (max-width: 899.98px) {
  .safety-band .wrap.safety { align-items: center; }
  .safety-band .safety__media { height: auto; }
  .safety-band .safety__frame { height: auto; aspect-ratio: 3 / 2; }
  .safety-band .safety__media img { height: 100%; }
}

/* ==========================================================================
   74. The partner cards drop their label

   Every card in the partner-schools slider carried the same pill: "Partner
   school - Japan", three times across a row and identically on every card in
   the set. A label that never varies is not telling the reader anything the
   section heading has not already said, and here it was the only coloured
   object on an otherwise white card, so it drew the eye away from the mark
   and the name, which are the two things the card exists to show.

   Hidden rather than removed from the template: the string is built from the
   record's own type and country, so it stays available to anything else that
   renders these cards, and putting it back is deleting one rule.
   -------------------------------------------------------------------------- */

.case-card__tag { display: none; }

/* ---------------------------------------------------------------------
   The booking form on the program page.

   It replaces the two standalone booking pages, so it carries their
   weight: the .booking two-column shell, the summary aside with the
   photograph, the facts, the cost and the numbered steps, and the form
   grouped into numbered steps of its own rather than one long column of
   inputs. Enrolling leads \u2014 it is what the program is for \u2014 and the
   trial is offered beside it as the way in.
   --------------------------------------------------------------------- */

.booking-band {
  background: var(--canvas);
  border-top: 1px solid var(--line);
}

.booking-band__head {
  max-width: 640px;
  margin: 0 auto clamp(28px, 4vw, 44px);
  text-align: center;
}
.booking-band__head h2 { margin: 6px 0 10px; }
.booking-band__head p  { color: var(--muted); }

/* The form column, grouped into steps -------------------------------- */

.booking-step {
  border: 0;
  margin: 0 0 clamp(24px, 3vw, 34px);
  padding: 0;
  min-width: 0;
}
.booking-step:last-of-type { margin-bottom: 26px; }

.booking-step__title {
  display: flex;
  /* baseline, not center: when the title wraps on a phone the number stays
     on its first line instead of floating between the two. */
  align-items: baseline;
  gap: 12px;
  float: none;
  width: 100%;
  padding: 0;
  margin-bottom: 16px;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.05rem;
}

.booking-step__n {
  flex-shrink: 0;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-family: var(--font-head); font-weight: 700; font-size: .8rem;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Two fields on one line where they are a natural pair, so the form is
   not a single tall column. */
.field-pair {
  display: grid;
  gap: 0 18px;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 560px) {
  .field-pair { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* The choice ---------------------------------------------------------- */

.booking-form__options {
  display: grid;
  gap: 12px;
  grid-template-columns: minmax(0, 1fr);
}

.pick {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  margin: 0;
  padding: 16px 18px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg);
  background: #fff;
  cursor: pointer;
  transition: border-color var(--fast), box-shadow var(--fast);
}
.pick:hover { border-color: var(--brand); }

.pick:has(input:checked) {
  border-color: var(--brand);
  box-shadow: 0 0 0 1.5px var(--brand) inset;
}
.pick:has(input:focus-visible) {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.pick__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pick__body b {
  font-family: var(--font-head);
  font-weight: 700;
  line-height: 1.25;
}
.pick__body small { color: var(--muted); font-size: .85rem; line-height: 1.35; }

/* Radios, matched to the checkbox treatment already in the sheet. */
.pick input[type="radio"],
.check input[type="radio"] {
  flex-shrink: 0;
  appearance: none; -webkit-appearance: none;
  width: 22px; height: 22px; margin: 1px 0 0;
  border: 1.5px solid var(--line);
  border-radius: 50%;
  background: var(--canvas);
  cursor: pointer;
  display: grid;
  place-content: center;
  transition: background var(--fast), border-color var(--fast);
}
.pick input[type="radio"]::after,
.check input[type="radio"]::after {
  content: "";
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #fff;
  transform: scale(0);
  transition: transform var(--fast);
}
.pick input[type="radio"]:checked,
.check input[type="radio"]:checked {
  background: var(--brand);
  border-color: var(--brand);
}
.pick input[type="radio"]:checked::after,
.check input[type="radio"]:checked::after { transform: scale(1); }

/* The closing reassurance line. */
.booking-form__note {
  margin-top: 14px;
  text-align: center;
  color: var(--muted);
  font-size: .85rem;
}

/* A swapped-out panel leaves no gap behind it. */
#book [data-booking-panel][hidden],
#book [data-booking-swap][hidden] { display: none; }

/* Compact pass on the booking band: it sits near the foot of a long page,
   so it should read as a short, finishable task rather than a second page
   of its own. Everything below only tightens spacing — no size, colour or
   layout decision above is changed. */

.booking-band { padding-block: clamp(34px, 5vw, 56px); }

.booking-band__head { margin-bottom: clamp(18px, 2.5vw, 26px); }
.booking-band__head h2 { margin: 0 0 6px; }
.booking-band__head p  { font-size: .95rem; margin: 0; }

.booking-band .booking { gap: 20px; }

.booking-band .booking__summary { padding: clamp(16px, 2vw, 22px); }
.booking-band .booking__photo   { margin-bottom: 14px; }
/* One column below 900px: at tablet width the 16/10 photo stood 350px tall
   above the form. Held to a band, as it already is on a phone (180px). */
@media (min-width: 640px) and (max-width: 899.98px) {
  .booking-band .booking__photo { max-height: 15rem; }
}
.booking-band .booking__title   { margin-bottom: 12px; }
.booking-band .booking__facts   { gap: 8px; margin-bottom: 14px; }
.booking-band .booking__steps-title { margin: 16px 0 10px; }
.booking-band .booking__steps li { margin-bottom: 8px; }
.booking-band .booking__steps li:last-child { margin-bottom: 0; }

.booking-band .form-card { padding: clamp(20px, 2.6vw, 30px); }

.booking-step { margin-bottom: 22px; }
.booking-step:last-of-type { margin-bottom: 18px; }
.booking-step__title { margin-bottom: 12px; font-size: 1rem; }
.booking-step__n { width: 23px; height: 23px; font-size: .75rem; }

.booking-form .field { margin-bottom: 14px; }
.booking-form .field:last-child { margin-bottom: 0; }
/* ...except the second field of a pair with more of the step shown after
   it: on a phone the pair stacks, and without this the next label sat flush
   on the input above it. [hidden] siblings are the enrollment/trial panels
   the choice swaps out, so they do not count. Side by side it changes
   nothing (the row is as tall as its first field, which has the margin). */
.booking-form .field-pair:has(~ :not([hidden])) > .field:last-child { margin-bottom: 14px; }
.booking-form .field__help { margin-top: 4px; font-size: .8rem; }
.booking-form .check { padding: 8px 0; }

.booking-form__options { gap: 10px; }
.pick { padding: 13px 15px; gap: 11px; }
.pick__body small { font-size: .82rem; }

.booking-form__note { margin-top: 10px; font-size: .8rem; }

/* ---------------------------------------------------------------------
   Partner and trust marks: colour by default, quiet only where hovering
   is possible.

   The greyed-at-rest treatment exists so the logo strip does not shout
   next to the cards above it, and it relies on hover to give a visitor
   the colour back. That bargain only holds with a mouse. It had been
   attached to width instead (760px and 900px), so phones and tablets got
   full colour while laptops got grey — the same section reading as two
   different designs, and on touch no way to reach the colour at all.

   hover: hover keeps it off every touch device whatever its width, and
   the 1024px floor keeps it off small laptops in tablet mode.
   --------------------------------------------------------------------- */

@media (hover: hover) and (min-width: 1024px) {
  .logo-carousel__item img {
    filter: grayscale(1);
    opacity: .62;
  }
  .logo-carousel__item img:hover {
    filter: grayscale(0);
    opacity: 1;
    transform: scale(1.06);
  }

  .partners__mark img {
    filter: grayscale(1);
    opacity: .75;
  }
  .partners__item:hover .partners__mark img {
    filter: none;
    opacity: 1;
  }
}

/* ---------------------------------------------------------------------
   The programme row on the home page.

   Four across only once there is room for four. This card carries a
   photo, a two-line title and three fact rows, and below about 300px
   wide it goes tall and thin and the venue and schedule start wrapping
   again. So the count follows the available width rather than being
   fixed at four: two on a small tablet, three on a laptop, four only on
   a wide screen. On a phone the cards stack (see rail--static on the
   block) instead of becoming a one-card swipe rail.
   --------------------------------------------------------------------- */

#homeClassGrid { gap: 18px; }

@media (min-width: 640px) {
  #homeClassGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  #homeClassGrid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
}
@media (min-width: 1280px) {
  #homeClassGrid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 22px; }
}

/* Only the four-across case needs the type stepped down; at three the
   card is the same width as it is on the Programs listing and should
   read identically. */
@media (min-width: 1280px) {
  #homeClassGrid .class-card__body { padding: 16px 16px 18px; }
  #homeClassGrid .class-card__title { font-size: 1.08rem; line-height: 1.25; }
  #homeClassGrid .class-card__meta,
  #homeClassGrid .class-card__when { font-size: .82rem; }
  #homeClassGrid .class-card__foot { padding-top: 14px; }
  #homeClassGrid .class-card__price { font-size: .95rem; }
}

#homeClassGrid .class-card { height: 100%; }
#homeClassGrid .class-card__body { flex: 1 1 auto; display: flex; flex-direction: column; }
#homeClassGrid .class-card__foot { margin-top: auto; }

/* ---------------------------------------------------------------------
   Programme card: marker alignment and the row under it.

   A marker is centred against its label, which is right for one line and
   wrong the moment the label wraps: on a phone the venue and the schedule
   both run to two lines and the pin floated into the gap between them.
   These rows can wrap, so their markers sit on the first line.
   --------------------------------------------------------------------- */

.class-card__meta span,
.class-card__when {
  align-items: flex-start;
}
.class-card__meta svg,
.class-card__when svg {
  flex-shrink: 0;
  margin-top: .15em;
}

/* The schedule is a sentence, so it gets the same treatment as the meta
   rows rather than being centred on its own line. */
.class-card__when {
  display: flex;
  gap: 6px;
  line-height: 1.4;
}

/* Air between the last card and the link that follows the row, which were
   almost touching on a phone. */
.section-more { margin-top: 26px; text-align: center; }

/* ---------------------------------------------------------------------
   Programme cards: one height, everywhere.

   The photo already has a fixed ratio and the title already clamps to two
   lines, so the parts that still moved were: a title running to one line
   instead of two, a venue or schedule wrapping, and a programme with no
   schedule at all. Each of those shortened one card in a row of four.

   Rather than let the grid sort it out per row, every row inside the body
   now holds its space whether or not it has content, and the card fills
   its track. A card is then the same height as its neighbours regardless
   of what the record happens to carry.
   --------------------------------------------------------------------- */

.class-grid { align-items: stretch; }

.class-card {
  height: 100%;
}

.class-card__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

/* The second title line is no longer held open. Most titles are one line, so
   the reservation was a blank line under nearly every card. The price and the
   buttons still land level across a row because the foot is pushed down with
   margin-top: auto, which is what actually kept them aligned; the reservation
   only lined up the rows in between. */

/* The facts rows keep their slot when a record has no venue or no
   schedule, so the price line lands in the same place on every card. */
.class-card__meta {
  min-height: 1.4em;
}
.class-card__when {
  min-height: 1.4em;
  margin-bottom: 10px;
}

/* A phone lists the cards one under another, so there is no neighbour to
   line up with, and the reserved second title line was only a blank gap
   under a one-line title. The program page's "More programs" row stays a
   side-by-side swipe rail on a phone, so it keeps the reservation. */
@media (max-width: 639.98px) {
  .rail--static .class-grid:not(.siblings-band *) > .class-card h3 { min-height: 0; }
}

/* On a phone the cards are a swipe rail; rail items do not stretch on
   their own, so they are told to. */
.rail .class-grid > .class-card,
.rail > .class-grid > .class-card { height: 100%; }

/* ==========================================================================
   75. Each page hero draws its own marks (MIRAI-82)

   Every hero drew all fifteen marks of pattern-elements.svg, so Programs,
   For Schools, About, Contact and the blog opened on the same band. 19 took
   the pattern off everything but the hero to stop it reading as wallpaper;
   this finishes the job across pages rather than within one.

   Each of those five takes nine marks chosen for the page, built by
   tools/build-hero-patterns.py into pattern-{page}.svg. Only the image
   changes. Those tiles keep the source's contract: 640px, white on
   transparent, every mark at its source scale and rotation. So the sizes,
   the 420px phone tile, the opacities, the masks and section 28's invert
   above all still apply as written. The home page, the programme pages and
   the 404 keep the full sheet.

   The Japanese twin of a page is "{slug}-ja" (tools/seed-ja.php), so each
   rule names both.
   ========================================================================== */
/* The home page had no set of its own, so the first band a visitor ever sees
   was the generic fifteen-mark sheet every other hero falls back to - the one
   page that should look most like itself looked most like everywhere else. */
.mm-page-home .hero::before,
.mm-page-home-ja .hero::before { background-image: url(../images/pattern-home.svg); }
.mm-page-classes :is(.page-hero, .listing-hero)::before,
.mm-page-classes-ja :is(.page-hero, .listing-hero)::before { background-image: url(../images/pattern-programs.svg); }
.mm-page-schools :is(.page-hero, .listing-hero)::before,
.mm-page-schools-ja :is(.page-hero, .listing-hero)::before { background-image: url(../images/pattern-schools.svg); }
.mm-page-about .page-hero::before,
.mm-page-about-ja .page-hero::before { background-image: url(../images/pattern-about.svg); }
.mm-page-contact .page-hero::before,
.mm-page-contact-ja .page-hero::before { background-image: url(../images/pattern-contact.svg); }
.mm-page-blog .page-hero::before,
.mm-page-blog-ja .page-hero::before { background-image: url(../images/pattern-blog.svg); }

/* ==========================================================================
   76. Copy doc v2 (24 Sep 2026)

   Structure the copy document asks for by name: the home proof row, the
   programs grouped by age, stars on the quotes, the program page's reviews
   and interest form, the For Schools additions and the footer newsletter.
   Nothing here restyles a section the document did not touch.
   ========================================================================== */

/* ---- 76.1 Home proof row (doc 1.4, 1.5) ---------------------------------- */
.trust--facts .trust__facts {
  list-style: none;
  margin: 0 0 clamp(18px, 2.4vw, 26px);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(12px, 2vw, 24px);
}
.trust__fact {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-inline-start: 14px;
  border-inline-start: 2px solid var(--line);
}
.trust__fact b {
  font-family: var(--font-head);
  font-size: 1.05rem;
  line-height: 1.25;
  color: var(--ink);
}
.trust__fact b.stars { color: var(--accent-sun); letter-spacing: .08em; }
.trust__fact small { font-size: .86rem; color: var(--muted); }
.trust__heading {
  margin: 0 0 12px;
  font-size: .9rem;
  font-weight: 600;
  color: var(--muted);
}
@media (max-width: 899.98px) {
  .trust--facts .trust__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .trust--facts .trust__fact:last-child { grid-column: 1 / -1; }
}

/* ---- 76.2 Home programs grouped by age (doc 1.7) --------------------------- */
.age-tabs__list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0 0 clamp(22px, 3vw, 32px);
}
.age-tabs__list[hidden] { display: none; }
.age-tabs__tab {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: 44px;
  padding: 10px 22px;
  border: 1.5px solid rgba(255, 255, 255, .55);
  border-radius: 999px;
  background: transparent;
  color: #fff;
  font: inherit;
  cursor: pointer;
  transition: background var(--base) var(--ease), color var(--base) var(--ease);
}
.age-tabs__tab b { font-family: var(--font-head); font-size: 1rem; }
.age-tabs__tab small { font-size: .8rem; opacity: .9; }
.age-tabs__tab:hover { background: rgba(255, 255, 255, .12); }
.age-tabs__tab[aria-selected="true"] { background: #fff; border-color: #fff; color: var(--brand-dark); }
.age-tabs__tab:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

/* Without the script every group shows under its own heading; with it the
   tabs say which group is showing. */
.age-tabs__heading { margin: 0 0 16px; color: #fff; font-size: 1.25rem; }
.age-tabs__heading small { font-size: .85rem; font-weight: 400; opacity: .9; }
.age-tabs.is-tabbed .age-tabs__heading { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.age-tabs__panel + .age-tabs__panel { margin-top: clamp(28px, 4vw, 40px); }
.age-tabs.is-tabbed .age-tabs__panel + .age-tabs__panel { margin-top: 0; }

/* The three bands were separate lozenges, each sized to its own label, so
   "Primary" came out half the width of "Middle & High School" and the row read
   as three unrelated blobs rather than one control with one of them chosen.
   From tablet up they sit in a single track as equal segments - grid columns
   of 1fr inside a fit-content track all take the width of the widest - and the
   chosen one is the only filled thing in it. The track carries the edge, so a
   segment needs no border of its own. Below 768px there is no room for three
   equal segments - "Middle & High School" breaks in two and takes all three
   with it - so they stay separate pills there and keep their own edge. */
.age-tabs__tab {
  background: rgba(255, 255, 255, .10);
  border-color: rgba(255, 255, 255, .72);
  gap: 3px;
  padding: 11px 22px;
}
.age-tabs__tab:hover { background: rgba(255, 255, 255, .22); }
.age-tabs__tab small { opacity: 1; color: rgba(255, 255, 255, .88); }
.age-tabs__tab[aria-selected="true"] small { color: var(--brand-dark); opacity: .82; }

/* The groups hold 4, 7 and 2 programs. On the shared three-column grid that
   was a lone card under a full row (4) and a half-empty row (2). A centred
   wrapping row instead: four across a desktop, and any short last row sits
   in the middle rather than hanging off the left edge. */
@media (min-width: 760px) {
  .age-tabs__panel .class-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; }
  /* height: auto, so the row's stretch gives every card one height (the
     card's own height: 100% is for a grid cell and blocks stretch here). */
  .age-tabs__panel .class-grid > .class-card { flex: 0 0 calc((100% - 16px) / 2); min-width: 0; height: auto; }
}
@media (min-width: 1000px) {
  .age-tabs__panel .class-grid { gap: 24px; }
  .age-tabs__panel .class-grid > .class-card { flex-basis: calc((100% - 48px) / 3); }
}
/* Four across from 1180px, not 1280. The Early Years band holds four
   programmes, so at three columns the fourth sat alone on a row of its own,
   and a 1280px threshold dropped every laptop window narrower than that into
   it. 1180px is the measured point where four cards still leave the action
   pill wide enough for its longest label on one line; at 1100px it breaks. */
@media (min-width: 1180px) {
  .age-tabs__panel .class-grid > .class-card { flex-basis: calc((100% - 72px) / 4); }
}

/* Phone: the three tabs wrap two-and-one, and each row fills the width, so
   the pills are not three different widths stacked down the middle. */
@media (max-width: 639.98px) {
  .age-tabs__list { gap: 8px; }
  .age-tabs__tab { flex: 1 1 auto; padding: 10px 14px; }
}

@media (min-width: 768px) {
  .age-tabs__list {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    width: fit-content;
    max-width: 100%;
    margin: 0 auto clamp(22px, 3vw, 32px);
    gap: 4px;
    padding: 5px;
    background: rgba(255, 255, 255, .14);
    border: 1px solid rgba(255, 255, 255, .30);
    border-radius: 999px;
  }
  .age-tabs__tab {
    background: transparent;
    border-color: transparent;
  }
  .age-tabs__tab:hover { background: rgba(255, 255, 255, .16); }
  .age-tabs__tab[aria-selected="true"]:hover { background: #fff; }
  .age-tabs__tab[aria-selected="true"] { box-shadow: 0 1px 3px rgba(0, 0, 0, .14); }
}
/* Between tablet portrait and desktop a track sized to its own content left
   each segment about 185px, which broke "Middle & High School" over two lines
   and took all three to 114px tall. The track takes the row instead, so the
   segments are wide enough for the label to stay on one line. */
@media (min-width: 768px) and (max-width: 899.98px) {
  .age-tabs__list { width: 100%; }
  .age-tabs__tab { padding-inline: 14px; }
  .age-tabs__tab b { font-size: .92rem; }
}

/* The one thing to do on a programme card was a line of text set at the body
   size, sitting under a rule: it read as a caption, not as the action. A pill
   across the card's width makes it the control it already was - the whole card
   has been clickable all along through .class-card__link::after, so this
   changes what the card LOOKS like it does, not what it does. Filling on card
   hover ties the pill to the card the pointer is actually over. */
.class-card--age .class-card__foot {
  display: block;
  padding-top: 16px;
}
.class-card--age .class-card__link {
  display: flex;
  justify-content: center;
  width: 100%;
  min-height: 46px;
  padding: 10px 18px;
  border: 1.5px solid var(--brand);
  border-radius: 999px;
  background: transparent;
  color: var(--brand-dark);
  text-align: center;
  transition: background var(--base) var(--ease), color var(--base) var(--ease), border-color var(--base) var(--ease);
}
.class-card--age .class-card__link:hover,
.class-card--age:hover .class-card__link,
.class-card--age:focus-within .class-card__link {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
@media (prefers-reduced-motion: reduce) {
  .class-card--age .class-card__link { transition: none; }
}
/* Four across from 1280px leaves the pill 208px, six short of what the longest
   label needs, so "Ask about this class" broke over two lines beside three
   neighbours that kept one. Less air in the pill and a slightly smaller label
   buy the room back rather than letting one card stand taller than its row. */
/* Matches where the band goes four across (1180px). At four columns the pill
   is about 257px of card, six short of what "Ask about this class" needs, so
   without this it breaks over two lines beside three neighbours that keep
   one. */
@media (min-width: 1180px) {
  .class-card--age .class-card__link {
    padding-inline: 12px;
    font-size: .88rem;
    gap: 6px;
  }
}

/* The head of this band was carrying a lot of air: a pill, a heading, two
   lines of copy and then 52px before the age control. With the pill gone the
   heading opens the band, and the gaps close up so the programmes themselves
   start higher up the screen. */
#classes .section-head { margin-bottom: clamp(20px, 2.6vw, 30px); }
#classes .section-title { margin-bottom: 8px; }

.class-card--age .class-card__meta { display: block; margin-bottom: 8px; color: var(--muted); font-size: .86rem; }
.class-card--age .class-card__meta a { color: var(--brand-dark); text-decoration: underline; text-underline-offset: 2px; }
.class-card--age .class-card__line { margin-bottom: 16px; }
.class-card--age h3 { min-height: 0; }
/* The title link was a 31px tap target on a phone. The padding takes it past
   44px and the matching negative margin keeps the card's spacing as it was. */
.class-card--age .class-card__title a { display: inline-block; padding-block: 9px; margin-block: -9px; }

/* ---- 76.3 Stars on the quotes, and the parent's photo (doc 1.8) ----------- */
.testi-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.testi-card__stars { font-size: .95rem; line-height: 1; letter-spacing: .08em; color: var(--accent-sun); }
.testi-card--t3 .testi-card__stars { color: var(--ink); }
.testi-card__avatar--photo { padding: 0; overflow: hidden; background: none; }
.testi-card__avatar--photo img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }

/* ---- 76.4 Meet the educators: photo, name, subject, one line (doc 1.11) ---
   20.11 hid the bio to keep the row to faces and names; the document asks
   for the one line back. The "does" line stays hidden. */
.collab-card:not(.collab-card--school) .collab-card__bio {
  display: block;
  margin: 8px auto 0;
  max-width: 26ch;
  text-align: center;
  font-size: .88rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* ---- 76.5 Programs page (doc 2) ------------------------------------------ */
.btn--sm { min-height: 40px; padding: 8px 16px; font-size: .88rem; gap: 6px; }

/* The card's six rows, then the two buttons side by side under the price.
   The rows keep their slot when empty so the price and buttons line up
   across a row of cards. */
.class-card__body .class-card__status { min-height: 1.4em; margin: 0 0 8px; font-size: .84rem; font-weight: 600; line-height: 1.4; color: var(--sage-deep); }
.class-card__body p.class-card__line { margin-bottom: 12px; }
.class-card__foot { flex-direction: column; align-items: stretch; gap: 12px; }
/* Two equal buttons while both labels fit on one line each; when they do
   not, each takes the full width rather than breaking into a two-line pill
   ("See / program" in a 3-up row at 1024px, "Register / interest" on every
   width). The wrap is decided by the labels themselves, so a longer
   translation wraps too. 44px keeps them a finger's width on a phone. */
.class-card { container: class-card / inline-size; }
.class-card__actions { display: flex; flex-wrap: wrap; gap: 8px; }
.class-card__actions .btn { flex: 1 1 0; min-width: fit-content; min-height: 44px; padding-inline: 10px; white-space: normal; text-align: center; line-height: 1.2; }
/* In a row of cards the neighbours must agree, or the price and buttons
   stop lining up across the row. So from two columns up, a card too narrow
   for the longest pair (Register interest + Ask a question) stacks its
   buttons whatever it carries. On a phone the list is one column and each
   card decides for itself. */
@media (min-width: 640px) {
  @container class-card (inline-size < 17.5rem) {
    .class-card__actions .btn { flex-basis: 100%; }
  }
}

/* How to choose: the area over the title. */
.feature-card__kicker {
  display: block;
  margin-bottom: 4px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand-dark);
}
.feature-card__more { margin-top: 8px; font-size: .9rem; }

/* The areas line under the Programs lead. */
.split-hero__note { margin-top: 10px; font-weight: 600; letter-spacing: .02em; }
.listing-hero--photo .split-hero__note { color: #fff; }

/* ---- 76.6 Program page (doc 3) ------------------------------------------- */
/* The line under the hero buttons. */
.detail-hero__note { margin: 14px 0 0; font-size: .95rem; color: var(--muted); max-width: 52ch; }

/* The teacher: the photo slot keeps its square with no photo set (the
   placeholder of 14g paints it), and the button sits under the line. */
.people-band .teacher-card__photo { aspect-ratio: 1; }
.people-band .teacher-card__photo img { width: 100%; height: 100%; object-fit: cover; }
.people-band .teacher-card--general { grid-template-columns: 1fr !important; max-width: 760px; margin-inline: auto; text-align: center; }
.people-band .teacher-card--general p { margin-inline: auto; }

/* What parents say about this program. */
.reviews-grid { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.review-card {
  margin: 0;
  padding: clamp(22px, 3vw, 30px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.review-card__stars { color: var(--accent-sun); letter-spacing: .08em; }
.review-card blockquote { margin: 0; }
.review-card blockquote p { margin: 0; color: var(--ink-soft); font-size: 1.02rem; line-height: 1.7; }
.review-card figcaption { margin-top: auto; display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }
.review-card figcaption b { font-family: var(--font-head); color: var(--ink); }
.review-card figcaption span { color: var(--muted); font-size: .88rem; }

/* Register interest: one column, the width of the booking form's card. */
.interest-form { max-width: 640px; margin-inline: auto; }
/* The email field is the last child of its pair, so `.booking-form
   .field:last-child` took its bottom margin. Side by side that is hidden by
   the name field's margin; stacked on a phone it put the phone label flush
   against the email input. Both fields in the pair keep the form's spacing. */
.interest-form .field-pair > .field { margin-bottom: 14px; }

/* ---- 76.7 Program skills carry their icon (doc 4) ------------------------- */
.outcome-list__num--ico svg { width: 18px; height: 18px; }

/* ---- 76.8 For Schools (doc 6) --------------------------------------------- */
/* The two buttons never fit side by side in the copy column, so they stack,
   and a grid makes the stack one width: as a wrapping flex row each button
   kept its own length, the second visibly wider than the first. On a phone
   both take the full column. */
.split-hero__actions { display: grid; justify-content: start; gap: 12px; margin-top: 22px; }
.split-hero__actions .btn { justify-content: center; }
@media (max-width: 599.98px) {
  .split-hero__actions { justify-content: stretch; }
}
.split-hero__stats {
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}
.split-hero__stats li { display: flex; flex-direction: column; gap: 2px; }
.split-hero__stats b { font-family: var(--font-head); font-size: clamp(1.4rem, 2.4vw, 1.9rem); line-height: 1.1; }
.split-hero__stats small { font-size: .84rem; opacity: .9; }
@media (max-width: 599.98px) {
  .split-hero__stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- 76.9 About team card lines (doc 7.4, 7.5) ----------------------------- */
.team-card__points { order: 3; margin: 0; padding: 0; list-style: none; color: var(--ink-soft); font-size: .92rem; line-height: 1.5; }
.team-card__points li + li { margin-top: 4px; }

/* ---- 76.10 Contact FAQ in two tabs, and the programs line (doc 8) ---------- */
.faq-tabs__list { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 0 0 clamp(20px, 3vw, 28px); }
.faq-tabs__list[hidden] { display: none; }
.faq-tabs__tab {
  min-height: 44px;
  padding: 10px 22px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: 600 .95rem var(--font-head);
  cursor: pointer;
}
.faq-tabs__tab[aria-selected="true"] { background: var(--brand); border-color: var(--brand); color: #fff; }
.faq-tabs__tab:focus-visible { outline: 3px solid var(--brand); outline-offset: 3px; }
.faq-tabs__heading { margin: 0 0 14px; font-size: 1.2rem; text-align: center; }
.is-faq-tabbed .faq-tabs__heading { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.faq-tabs__panel + .faq-tabs__panel { margin-top: 28px; }
.is-faq-tabbed .faq-tabs__panel + .faq-tabs__panel { margin-top: 0; }
.map-box__detail { margin-top: 4px; font-size: .88rem; color: var(--ink-soft); }

/* ---- 76.11 Footer newsletter (doc 9.2) ------------------------------------ */
.footer__newsletter {
  display: grid;
  gap: 10px 24px;
  grid-template-columns: 1fr;
  align-items: center;
  padding-bottom: clamp(24px, 3vw, 36px);
  margin-bottom: clamp(24px, 3vw, 36px);
  border-bottom: 1px solid rgba(255, 255, 255, .14);
}
.footer__newsletter h3 { margin: 0; color: #fff; font-size: clamp(1.15rem, 2vw, 1.35rem); }
.footer__newsletter-form { display: flex; flex-wrap: wrap; gap: 10px; }
.footer__newsletter-form input {
  flex: 1 1 220px;
  min-height: 48px;
  padding: 10px 18px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .3);
  background: rgba(255, 255, 255, .08);
  color: #fff;
  font: inherit;
}
.footer__newsletter-form input::placeholder { color: rgba(255, 255, 255, .7); }
.footer__newsletter-note, .footer__newsletter-done { margin: 0; font-size: .85rem; color: rgba(255, 255, 255, .75); }
@media (min-width: 900px) {
  .footer__newsletter { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); }
  .footer__newsletter-note { grid-column: 2; }
}
.footer__newsletter-form .btn { --btn-bg: #fff; --btn-fg: var(--brand-dark); box-shadow: none; }
.footer__newsletter-form .btn:hover { background: var(--brand-tint); }

/* ==========================================================================
   77. The school marks under the hero, at a width they can be read (MIRAI-86)

   The marks were still unreadable at 104px, and the ring was the reason. A
   circle hands every mark the same square to live in, about 68px across once
   the padding and border are taken out, and most of these marks are not
   square. A wordmark fills the width and gets a third of the height: Ryozan
   Park drew at 68x20, ASIJ at 68x31, TIK and Tokyo YMCA at about 68x33, Global
   Indian at 68x36, and the name set in each of them came out a few pixels
   tall.

   The ring becomes a pill: the same 2px line on the same white, the radius
   the buttons and the hero badge already use, and the same 104px height, so
   the band does not grow. What changes is the width. The drawing gets about
   148x76 instead of 68x68, so a wordmark roughly doubles and a square crest
   gains a little. Every tile is still the same size, so the row still reads
   as one row of equal objects.

   The height limit is written in pixels. max-height: 100% never resolved
   here, because the wrapper span is a grid item whose row has no definite
   height; the ring hid that, since in a square the width always ran out
   first. United School of Tokyo, the one tall mark, was already drawing
   102px high in a ring meant to hold 68. The limit is the tile's height less
   its padding and its 2px border top and bottom.

   Width and height stay auto, which never draws a mark larger than its own
   file: a small source stays at its own size rather than being blown up
   soft.
   ========================================================================== */
.trust__track .logos .trust__logo {
  width: 200px;
  height: 104px;
  padding: 12px 24px;
  border-radius: 999px;
}
.trust__track .logos .trust__logo img { max-height: 76px; }

@media (max-width: 639.98px) {
  /* The phone size from 39, widened the same way: about 110x54 for the
     drawing against 50x50 in the ring. */
  .trust__track .logos .trust__logo { width: 150px; height: 78px; padding: 10px 18px; }
  .trust__track .logos .trust__logo img { max-height: 54px; }
}
/* ---- 77. Long parent quotes fold to a few lines ---------------------------
   The quotes are printed as the parents wrote them, and some run to a
   paragraph. initQuoteClamp() in site-effects.js adds .is-clamped only when
   the quote overflows these lines, and a "Read more" button that opens it
   in place; without JavaScript the quote reads in full. Seven lines keeps
   the row of three level with the short quotes beside it. The button takes
   the card's text colour (white on terracotta, ink on sage and gold), bold
   and underlined so it reads as an action rather than more of the quote. */
.testi-card__quote.is-clamped p {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 7;
  line-clamp: 7;
  overflow: hidden;
}
.testi-card__more {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  font-size: .92rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.testi-card__more:hover { text-decoration-thickness: 2px; }
.testi-card__more:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; border-radius: 4px; }

/* ---- 78. The testimonials carousel (MIRAI-93) -----------------------------
   Copy doc 1.8 asks for a carousel, and new quotes (principals, children)
   join it in the same format, so the row has to read the same with one
   quote, three or fifteen. The cards are untouched; this is the container.

   The track is .testi-grid itself, still a grid, so a card keeps its three-
   row subgrid and every quote and author row still lines up across what is
   in view. From 640px it is one row that flows sideways: two cards across,
   three from 760px, the counts and gaps the grid had in 16, and a scroll-
   snap scroller for the rest. Below 640px the phone rail in 61 is left as it
   was: one centred card with a peek of the next either side.

   justify-content: safe center is what makes one or two quotes on a desktop
   a centred card or pair rather than a left-hand column with a blank beside
   it; `safe` drops back to start the moment the row overflows, so the first
   card can never be pushed out of reach. A browser without `safe` ignores
   the declaration and starts the row at the left, which is what it did.

   The track bleeds --testi-bleed past .wrap's content box on each side, and
   takes the same back as padding. overflow-x clips whatever reaches past the
   scroller's edge, and without the bleed that would be the hover lift and the
   shadow of every end card. The bleed is less than the smallest gutter (20px),
   so the scroller never reaches the edge of the page. scroll-padding matches
   it, so a snapped card lines up with the section heading as before. */
.testi-carousel__nav[hidden] { display: none; }

@media (min-width: 640px) {
  .testi-carousel {
    --testi-per-view: 2;
    --testi-gap: 24px;
    --testi-bleed: 16px;
  }
  .testi-grid--carousel {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: calc((100% - (var(--testi-per-view) - 1) * var(--testi-gap)) / var(--testi-per-view));
    gap: var(--testi-gap);
    justify-content: safe center;

    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--testi-bleed);
    scrollbar-width: none;

    margin: -12px calc(var(--testi-bleed) * -1) -30px;
    padding: 12px var(--testi-bleed) 30px;

    /* The card just out of view still casts its shadow into the bleed, and
       the scroller's edge cut it off in a hard vertical line beside the
       first card. Fading the bleed takes the line away; the cards in view
       sit wholly inside the opaque part. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--testi-bleed), #000 calc(100% - var(--testi-bleed)), transparent);
    mask-image: linear-gradient(to right, transparent, #000 var(--testi-bleed), #000 calc(100% - var(--testi-bleed)), transparent);
  }
  .testi-grid--carousel > * { scroll-snap-align: start; }
}
@media (min-width: 760px) {
  .testi-carousel { --testi-per-view: 3; --testi-gap: 16px; }
}
@media (min-width: 900px) {
  .testi-carousel { --testi-gap: 24px; }
}
.testi-grid--carousel::-webkit-scrollbar { display: none; }

/* 17's reduced-motion rule turns snapping off on every rail; this section's
   own scroll-snap-type above would otherwise put it back from 640px. */
@media (prefers-reduced-motion: reduce) {
  .testi-grid--carousel { scroll-snap-type: none; }
}

/* ---- 78.1 The controls: previous, the dots, next --------------------------
   Under the row at every width rather than over the cards: on a phone the
   rail's own buttons sat on the quote, and on a desktop a button beside the
   row would need gutter the wrap does not have at 640 to 1100px. White discs
   with a terracotta chevron, the For Schools slider's buttons (4899) and its
   sage hover (5470), so the site has one kind of arrow.

   The dots are sage, the old quote panel's colours (5466), one per stop, and
   the current one stretches to a bar so it reads as "here" without relying
   on colour alone. When there are more than fit on one line between the
   arrows (fifteen quotes on a 320px phone), the script swaps them for a
   "3 / 15" count in the same place rather than wrap them onto a second row.

   An arrow at the end of the row is dimmed, not removed or disabled: it keeps
   its place so the dots do not shift, and it keeps keyboard focus, so a
   reader pressing Next through to the last quote is not dropped to the top
   of the page. */
.testi-carousel__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  margin-top: 20px;
}
.testi-carousel__btn {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  padding: 0;
  border-radius: 50%;
  background: #fff;
  color: var(--brand-dark);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
.testi-carousel__btn svg { width: 20px; height: 20px; }
.testi-carousel__btn:hover { background: var(--sage-deep); border-color: var(--sage-deep); color: #fff; }
.testi-carousel__btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.testi-carousel__btn[aria-disabled="true"] {
  opacity: .4;
  cursor: default;
  box-shadow: none;
  background: #fff;
  border-color: var(--line);
  color: var(--brand-dark);
}
.testi-carousel__dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 0;
  overflow: hidden;
}
.testi-carousel__dots.is-count {
  min-width: 4.5em;
  color: var(--sage-deep);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: .95rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.testi-carousel__dot {
  flex-shrink: 0;
  width: 8px; height: 8px;
  border-radius: var(--r-pill);
  background: rgba(63, 102, 100, .3);
  transition: width var(--fast) var(--ease), background var(--fast) var(--ease);
}
.testi-carousel__dot.is-active { width: 22px; background: var(--sage-deep); }

@media (max-width: 639.98px) {
  .testi-carousel__nav { gap: 14px; margin-top: 0; }
}

/* --------------------------------------------------------------------------
   78. The team card's text sits under the role, flush with the name

   The About team cards showed three faults, and each had its own cause.
   A gap of about 48px between the role and the lines below it: 40 and 47
   held three lines open under every role for "Business Development
   Manager", a title nobody on the team has any more, so a one-line role
   carried two empty lines. The lines were indented about 20px: the list kept
   the 1.1em of padding a bullet needs, but the base reset hides the bullets,
   so the padding was all that showed. And extra space under the last line:
   the list kept a 16px margin below it, on top of the card's own padding.

   The reserve existed so the text starts level across a row, and that still
   matters: at two columns a role such as "Finance & Administration" can wrap.
   So the row now lines up from what it actually contains. Each card becomes
   four rows of the grid it sits in (photo, name, role, text) through
   subgrid, and each row of the grid is as tall as the tallest name or role
   in that row of cards. A role that wraps pushes its neighbours' text down
   with it, and when every role is one line, no space is held open.

   Only where the cards are a grid. On phones they are a rail of one card at
   a time, with no row to line up with, and the flex column from 40 stays.
   A browser without subgrid keeps the flex column too: the text starts
   straight under each card's own role.
   -------------------------------------------------------------------------- */
@media (min-width: 640px) {
  @supports (grid-template-rows: subgrid) {
    .team-card {
      display: grid;
      grid-row: span 4;
      grid-template-rows: subgrid;
      row-gap: 0;
    }
    .team-card__body {
      display: grid;
      grid-row: span 3;
      grid-template-rows: subgrid;
      row-gap: 0;
      align-content: start;
    }
  }
}

/* ---- The programme card's type scale ------------------------------------
   The card had no hierarchy between its name and its fee. The two fact rows
   sat at different sizes although they are the same kind of information one
   under the other - the ages and venue at .82rem, the schedule at .95rem
   because nothing set it and it inherited the body size - and the teaser
   matched the schedule exactly, so a sentence and a data row carried the same
   weight. Everything between the title and the price was also one colour.

   Four steps, with size and tone both doing work:
     the name     1.32rem  700  ink        what it is
     the facts     .82rem  400  muted      ages, place, schedule - scanned
     the sentence  .9rem   400  ink-soft   what the child does - read
     the fee      1.06rem  700  ink        the decision                    */
.class-card .class-card__meta,
.class-card .class-card__when {
  font-size: .82rem;
  line-height: 1.45;
  color: var(--muted);
}
.class-card p.class-card__line {
  font-size: .9rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* The trial note in the booking panel (copy doc 5.2 and 5.5). It reads as
   reassurance beside the fee, so it sets quieter than the panel's own facts
   and keeps a rule above it to separate it from the figure. */
.booking__trial-note {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.booking__trial-note p {
  margin: 0 0 8px;
  font-size: .88rem;
  line-height: 1.5;
  color: var(--ink-soft);
}
.booking__trial-note p:last-child { margin-bottom: 0; }
.booking__trial-note[hidden] { display: none; }

/* ---- Bands alternate, however many an editor adds -----------------------
   The design alternates the page's cream ground with white bands, which works
   while the sections alternate. For Schools runs two white ones back to back
   (the formats and the pricing ask) directly under a third, so a long stretch
   of that page is one unbroken white field - which is what reads as bland.

   A white band that directly follows another takes the cream ground back, so
   the alternation holds whatever order the sections end up in. Positional
   rather than a class on each section, so nothing needs re-tagging when a
   section is added, moved or removed.

   Contact is untouched: its bands already alternate. */
.section--tint + .section--tint { background: var(--canvas-2); }
.section--tint + .section--tint + .section--tint { background: var(--canvas); }
.section--tint + .section--tint + .section--tint + .section--tint { background: var(--canvas-2); }

/* The catalog's padding was collapsed into the band above it precisely
   because the two shared a ground. They no longer do, so it needs its own
   top padding back or its card sits on the seam between two colours. */
.section--tint + .section--tint:has(> .wrap > .catalog) { padding-top: var(--band); }

/* ---- The hero's primary button keeps a white label ----------------------
   Measured on For Schools: ink on terracotta, 2.61:1, where a label needs
   4.5:1 - and the same at rest as on hover, so the button was hard to read
   before the pointer ever reached it. --btn-fg was #fff the whole time;
   something downstream set `color` directly and won, so this says it at the
   end of the sheet where nothing follows.

   Every hero band, because they all put the same terracotta button on a
   coloured ground. */
.page-hero .btn--primary,
.page-hero .btn--primary:hover,
.page-hero .page-hero__actions .btn--primary,
.page-hero .page-hero__actions .btn--primary:hover,
.listing-hero .btn--primary,
.listing-hero .btn--primary:hover,
.detail-hero .btn--primary,
.detail-hero .btn--primary:hover { color: #fff; }

/* The buttons that sit on the soft gold keep their ink. White on gold is
   1.37:1 - the pair that actually fails - and these flip their ground rather
   than their label, so they were already right. The home hero's button is
   gold at rest, not only on hover. */
.hero .btn--primary,
.hero .btn--primary:hover,
.detail-hero--panel .btn--primary,
.detail-hero--panel .btn--primary:hover,
.mm-view-mm_class .detail-hero--panel .btn--primary,
.mm-view-mm_class .detail-hero--panel .btn--primary * { color: var(--ink); }

/* ---- The hero's copy sits on its own surface ----------------------------
   Every inner-page hero set its heading and copy straight onto the band, with
   the page's marks showing through behind the words. The programme page had
   already solved this - .detail-hero__copy is a white card on the terracotta
   band - and this is the same treatment on the rest of them, so a heading
   never has a compass drawn through it.

   The home page is excluded: its hero is the photograph and the copy beside
   it, and a panel there would box in the one band that should feel open. */
.page-hero--split .split-hero__copy,
.listing-hero .split-hero__copy,
.page-hero .page-hero__inner {
  background: var(--surface);
  border-radius: var(--r-xl);
  padding: clamp(22px, 2.6vw, 36px);
  box-shadow: var(--shadow-md);
}
/* A centred hero's panel should not run the full measure of the band. */
.page-hero .page-hero__inner { max-width: 720px; margin-inline: auto; }

@media (max-width: 639.98px) {
  .page-hero--split .split-hero__copy,
  .listing-hero .split-hero__copy,
  .page-hero .page-hero__inner { padding: 20px 18px; }
}
