/* ==========================================================================
   The Oxfordian School — block-theme compatibility layer
   --------------------------------------------------------------------------
   The design system in theme.css was written against hand-authored markup:
   plain <section>, <div class="ox-container">, <div class="ox-grid">. In a
   block theme the same DOM is produced by core Group / Columns / Image blocks,
   which arrive with wp-block-* classes and core layout CSS attached.

   This file exists to make those two things equivalent. It does NOT introduce
   any new design; every rule here either neutralises a core default that the
   original markup never had, or restores a core default that the original
   markup relied on. If a rule here changes how the site looks, it is a bug.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Block gap
   --------------------------------------------------------------------------
   theme.json sets styles.spacing.blockGap to 0px, so core's layout rules
   (:where(.is-layout-flow) > * { margin-block-start: … }) resolve to zero and
   the theme's own element margins — p, h1–h6 — win on specificity, exactly as
   they did in the classic templates.

   That leaves core blocks that are NOT part of the design system without any
   spacing at all, which would make the block editor unusable for new content.
   Restore a sensible gap for those, matched on "carries no ox- class" so the
   design system's own grids and stacks are never touched. The :not() selectors
   keep these above the 0-specificity core rules regardless of load order.
   -------------------------------------------------------------------------- */

.wp-block-columns:not([class*="ox-"]) {
  gap: var(--space-lg);
}

.wp-block-group.is-layout-flex:not([class*="ox-"]),
.wp-block-group.is-layout-grid:not([class*="ox-"]),
.wp-block-buttons.is-layout-flex:not([class*="ox-"]),
.wp-block-social-links:not([class*="ox-"]) {
  gap: var(--space-sm);
}

/* Flow spacing for editor-authored content inside the prose wrappers. The
   theme already gives p and h1–h6 their margins; this only covers the block
   wrappers (figures, embeds, nested groups) that have none of their own. */
.ox-prose > .wp-block-group,
.ox-prose > .wp-block-columns,
.ox-prose > .wp-block-image,
.ox-prose > .wp-block-embed,
.ox-prose > .wp-block-table,
.ox-prose > .wp-block-quote,
.ox-prose > .wp-block-gallery,
.ox-prose > .wp-block-buttons,
.ox-prose > .wp-block-separator,
.ox-prose > .wp-block-details,
.ox-home-content > .wp-block-group,
.ox-home-content > .wp-block-columns,
.ox-home-content > .wp-block-image {
  margin-block: var(--space-md);
}

.ox-prose > *:first-child,
.ox-home-content > *:first-child { margin-block-start: 0; }
.ox-prose > *:last-child,
.ox-home-content > *:last-child { margin-block-end: 0; }

/* --------------------------------------------------------------------------
   2. Group blocks standing in for the layout primitives
   --------------------------------------------------------------------------
   A Group block always carries a layout class. Where that group is one of the
   design system's primitives, the primitive's own display must win — .ox-grid
   is a grid, .ox-split is a grid, .ox-stack is a flex column — so cancel the
   core layout display that would otherwise fight it.
   -------------------------------------------------------------------------- */

.ox-grid.is-layout-flow,
.ox-grid.is-layout-constrained,
.ox-grid.is-layout-flex,
.ox-split.is-layout-flow,
.ox-split.is-layout-constrained,
.ox-split.is-layout-flex,
.ox-numbers-grid.is-layout-flow,
.ox-numbers-grid.is-layout-flex,
.ox-service-grid.is-layout-flow,
.ox-service-grid.is-layout-flex,
.ox-experience-grid.is-layout-flow,
.ox-experience-grid.is-layout-flex {
  display: grid;
}

.ox-stack.is-layout-flow,
.ox-stack.is-layout-constrained,
.ox-stack.is-layout-grid,
.ox-row.is-layout-flow,
.ox-row.is-layout-grid,
.ox-cta__actions.is-layout-flow,
.ox-cta__actions.is-layout-grid,
.ox-hero__actions.is-layout-flow,
.ox-hero__actions.is-layout-grid {
  display: flex;
}

/* Columns blocks used as design-system grids: the .wp-block-column child is a
   flex item by default, which is inert under grid, but its flex-basis:0 is not
   — clear it so grid tracks size the way the original divs did. */
[class*="ox-grid"] > .wp-block-column,
.ox-split > .wp-block-column,
.ox-experience-grid > .wp-block-column,
.ox-service-grid > .wp-block-column {
  flex-basis: auto;
  min-width: 0;
}

/* Constrained groups inside the design system must not re-apply a max-width;
   .ox-container and .ox-measure already own horizontal measure. */
.ox-container.is-layout-constrained > *,
.ox-section.is-layout-constrained > * { max-width: none; }
.ox-container.is-layout-constrained > .alignwide,
.ox-container.is-layout-constrained > .alignfull { max-width: none; margin-inline: 0; }

/* --------------------------------------------------------------------------
   3. Full-bleed sections
   --------------------------------------------------------------------------
   The classic templates emitted <section> straight into the document flow, so
   every band ran the full viewport width and .ox-container did the centring.
   alignfull reproduces that; make sure nothing re-introduces a gutter.
   -------------------------------------------------------------------------- */

.wp-site-blocks > .alignfull,
.wp-site-blocks .ox-section.alignfull,
.wp-site-blocks .ox-hero.alignfull,
.wp-site-blocks .ox-cta.alignfull {
  margin-inline: 0;
  max-width: none;
  width: 100%;
}

/* wp-site-blocks is the new outermost wrapper. body used to be it. */
.wp-site-blocks { overflow-x: clip; }

/* --------------------------------------------------------------------------
   4. Image blocks standing in for plain <img>
   --------------------------------------------------------------------------
   Core Image renders <figure class="wp-block-image"><img>. Every image rule in
   theme.css is a descendant selector (.ox-hero__media img, .ox-card__media img)
   so it still matches through the figure — the only thing needed is for the
   figure itself to be transparent to layout, which it is not by default.
   -------------------------------------------------------------------------- */

.ox-hero__media > .wp-block-image,
.ox-card__media > .wp-block-image,
.ox-experience-card__media > .wp-block-image,
.ox-map-embed > .wp-block-image,
.ox-feature-card > .wp-block-image {
  margin: 0;
  display: block;
}

.wp-block-image figcaption { color: var(--ox-body); font-size: 14px; }

/* --------------------------------------------------------------------------
   4b. Utilities standing in for the classic templates' inline styles
   --------------------------------------------------------------------------
   The classic templates carried a handful of one-off inline styles, e.g.
   style="margin-top:var(--space-lg)". Block markup cannot hold arbitrary CSS,
   so each of those declarations gets a class here with exactly the same
   declaration — nothing more. Applied via a block's Additional CSS class.
   -------------------------------------------------------------------------- */

.ox-u-mt-md { margin-top: var(--space-md); }
.ox-u-mt-lg { margin-top: var(--space-lg); }
.ox-u-mt-xl { margin-top: var(--space-xl); }
.ox-u-gap-md { gap: var(--space-md); }
.ox-u-gap-sm { gap: var(--space-sm); }
.ox-u-measure-none { max-width: none; }
.ox-u-measure-600 { max-width: 600px; }
.ox-u-pull-up { margin-top: calc(var(--space-lg) * -1); position: relative; z-index: 1; }
.ox-u-min-300 { min-height: 300px; }
.ox-u-light { color: #fff; }
.ox-u-bg-white { background: var(--ox-card); }
.ox-u-mt-auto { margin-top: auto; }
.ox-u-mb-md { margin-bottom: var(--space-md); }

/* The accreditation wordmark tiles and the IB status notice on Our School. */
.ox-u-logo-card {
  padding: var(--space-md);
  align-items: center;
  text-align: center;
}
.ox-u-notice {
  font-size: 14px;
  background: var(--ox-navy-subtle);
  padding: var(--space-sm) 14px;
  border-radius: var(--radius-btn);
}
.ox-u-order-2 { order: 2; }

/* The two uppercase micro-labels and the fine print under the campus plans. */
.ox-u-minilabel {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  margin-bottom: var(--space-xs);
}
.ox-u-fineprint {
  font-size: 13px;
  color: var(--ox-body);
  margin-top: var(--space-xs);
}

/* --------------------------------------------------------------------------
   5. Core element defaults the design system overrides
   -------------------------------------------------------------------------- */

/* Heading blocks carry .wp-block-heading, which is specific enough to beat the
   bare h1–h6 rules in theme.css. Restate the tracking here or block headings
   lose it — and with Montserrat the tracking is not cosmetic: the round forms
   open gaps at display sizes that the negative correction closes. */
h1.wp-block-heading { letter-spacing: var(--track-display); }
h2.wp-block-heading { letter-spacing: var(--track-lg); }
h3.wp-block-heading { letter-spacing: -0.02em; }
h4.wp-block-heading,
h5.wp-block-heading { letter-spacing: var(--track-md); }
h6.wp-block-heading { letter-spacing: var(--track-body); }

/* Core's button element styling must not leak into .ox-btn. */
.ox-btn.wp-element-button,
.ox-btn.wp-block-button__link {
  background: unset;
  border-radius: var(--radius-btn);
  padding: 15px 26px;
  font-size: 15px;
}
.ox-btn.wp-element-button.ox-btn--primary { background: var(--ox-red); color: #fff; }
.ox-btn.wp-element-button.ox-btn--navy { background: var(--ox-navy); color: #fff; }
.ox-btn.wp-element-button.ox-btn--white { background: #fff; color: var(--ox-fg-strong); }
.ox-btn.wp-element-button.ox-btn--ghost { background: transparent; color: var(--ox-fg-strong); }
.ox-btn.wp-element-button.ox-btn--ghost-light { background: rgba(255, 255, 255, 0.08); color: #fff; }

/* The paginated post-content / query pagination reuse the theme's pager. */
.wp-block-query-pagination { display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: center; }

/* Sticky header: the header part wrapper must not clip the mega dropdowns. */
.wp-site-blocks > header.ox-header,
.wp-site-blocks > .ox-header { overflow: visible; }


/* ======================================================================
   Accreditation flip cards + clickable campus map            (added 2026-08)
   One unified row of equal-width cards, centred at every breakpoint.
   Faces do NOT clip: the number pill overhangs the top edge by design.
   ====================================================================== */
.ox-cert-cards {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
  padding-top: 14px;          /* room for the overhanging number pill */
}
.ox-cert-card {
  flex: 1 1 210px;
  min-width: 180px;
  max-width: 250px;
  height: 268px;
  perspective: 1100px;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ox-cert-card__inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.ox-cert-card.is-flipped .ox-cert-card__inner { transform: rotateY(180deg); }
.ox-cert-card__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: calc(var(--space-md) + 2px) var(--space-sm) var(--space-sm);
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  overflow: visible;          /* number pill must not be clipped */
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.ox-cert-card:hover .ox-cert-card__face,
.ox-cert-card:focus-visible .ox-cert-card__face {
  border-color: var(--ox-fg-brand);
  box-shadow: 0 14px 32px var(--ox-navy-subtle);
}
.ox-cert-card__face--back {
  transform: rotateY(180deg);
  background: var(--ox-navy);
  border-color: var(--ox-fg-strong);
}
.ox-cert-card__num {
  position: absolute;
  top: -11px;
  left: var(--space-sm);
  min-width: 30px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--oxford-royal);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: center;
  color: #fff;
  z-index: 2;
}
.ox-cert-card__name {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: var(--track-md);
  color: var(--ox-fg-strong);
  text-wrap: balance;
}
.ox-cert-card__note {
  font-size: 12px;
  line-height: 1.45;
  color: var(--ox-body);
  margin: 0;
}
.ox-cert-card__face--back .ox-cert-card__name { color: #fff; }
.ox-cert-card__face--back .ox-cert-card__note { color: var(--ox-nude-300); }
.ox-cert-card__cue {
  margin-top: auto;
  padding-top: 8px;
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ox-fg-brand);
}
.ox-cert-card__face--back .ox-cert-card__cue { color: #fff; opacity: 0.7; }

/* Below the single-row threshold, wrap but keep every row centred. */
@media (max-width: 1080px) {
  .ox-cert-cards { flex-wrap: wrap; }
  .ox-cert-card { flex: 1 1 200px; max-width: 250px; height: 262px; }
}
@media (max-width: 780px) {
  .ox-cert-card { flex: 1 1 195px; min-width: 170px; height: 252px; }
}
@media (max-width: 560px) {
  .ox-cert-card { flex: 1 1 100%; max-width: 380px; height: 220px; }
  .ox-cert-card__name { font-size: 16px; }
  .ox-cert-card__note { font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) {
  .ox-cert-card__inner { transition: opacity 0.2s linear; transform: none !important; }
  .ox-cert-card__face { transition: opacity 0.2s linear; }
  .ox-cert-card__face--back { transform: none; opacity: 0; pointer-events: none; }
  .ox-cert-card.is-flipped .ox-cert-card__face--front { opacity: 0; }
  .ox-cert-card.is-flipped .ox-cert-card__face--back { opacity: 1; pointer-events: auto; }
}

/* Clickable map */
.ox-map-embed .ox-map-embed__link { position: absolute; inset: 0; display: block; line-height: 0; }
.ox-map-embed .ox-map-embed__link img { width: 100%; height: 100%; object-fit: cover; }
.ox-map-embed__cue {
  position: absolute;
  left: var(--space-sm);
  bottom: var(--space-sm);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border-radius: 999px;
  background: var(--ox-card);
  border: 1px solid var(--ox-navy-line);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ox-fg-strong);
  line-height: 1.2;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.ox-map-embed__link:hover .ox-map-embed__cue,
.ox-map-embed__link:focus-visible .ox-map-embed__cue {
  background: var(--oxford-royal);
  border-color: var(--ox-fg-brand);
  color: #fff;
}

/* How We Teach: let the framework intro run the full section width
   instead of sitting in a narrow left-aligned column above the cards. */
.ox-section-header:has(+ .ox-approach-grid) { max-width: none; }
.ox-section-header:has(+ .ox-approach-grid) p { max-width: none; }


.ox-dept-support__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
.ox-dept-support__row .ox-chip {
  flex: 1 1 330px;
  max-width: 362px;
  min-height: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-sm);
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ox-fg-strong);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.ox-dept-support__row .ox-chip:hover {
  border-color: var(--ox-fg-brand);
  box-shadow: 0 14px 32px var(--ox-navy-subtle);
}
@media (max-width: 560px) {
  .ox-dept-support__row .ox-chip { flex: 1 1 100%; max-width: 380px; }
}

/* Curriculum dropdowns — one collapsible row per year group, full width. */
.ox-curriculum-grid--accordion {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: var(--space-md);
}
.ox-curriculum-grid--accordion .ox-curriculum-card { padding: 0; overflow: hidden; }
.ox-curriculum-grid--accordion .ox-curriculum-card__group { margin: 0; }
.ox-curriculum-card__toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: var(--space-sm) var(--space-md);
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ox-curriculum-card__toggle:hover .ox-curriculum-card__title { color: var(--ox-fg-brand); }
.ox-curriculum-card__title {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ox-fg-strong);
  transition: color 0.15s ease;
}
.ox-curriculum-card__chev {
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--oxford-royal);
  border-bottom: 2px solid var(--oxford-royal);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.22s ease;
}
.ox-curriculum-card.is-open .ox-curriculum-card__chev {
  transform: rotate(-135deg) translate(-2px, -2px);
}
.ox-curriculum-card__panel { display: none; padding: 0 var(--space-md) var(--space-md); }
.ox-curriculum-card__panel.is-open { display: block; }
@media (prefers-reduced-motion: reduce) {
  .ox-curriculum-card__chev { transition: none; }
}

/* ── Leadership bands ───────────────────────────────────────────────────
   Head of School on navy, campus directors on a red-topped band, and the
   institutional team in a band that shows four at a time with arrows.     */
.ox-band {
  grid-column: 1 / -1;
  border-radius: var(--radius-card);
  padding: var(--space-md);
  margin: var(--space-xs) 0 var(--space-md);
}
.ox-band__label {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin: 0 0 var(--space-sm);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.ox-band__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-sm);
  align-items: start;
}

/* 1 — Head of School */
.ox-band--head { background: var(--ox-navy); border: 1px solid var(--ox-navy); }
.ox-band--head .ox-band__label { color: #fff; }
.ox-band--head .ox-band__label::after {
  content: ""; flex: 1; height: 1px; background: rgba(255,255,255,0.28);
}
.ox-band--head .ox-leader-card {
  background: var(--ox-card);
  border: 1px solid var(--ox-navy);
  border-radius: var(--radius-card);
  overflow: hidden;
  margin: 0;
}
.ox-band--head .ox-leader-card__role { color: var(--ox-fg-brand); }

/* 2 — Campus directors */
.ox-band--directors {
  background: var(--ox-nude-300);
  border: 1px solid var(--ox-navy-line);
  border-top: 3px solid var(--oxford-royal);
}
.ox-band--directors .ox-band__label { color: var(--ox-fg-brand); }
.ox-band--directors .ox-leader-card__role { color: var(--ox-fg-brand); }

/* 3 — Institutional leadership, four per view */
.ox-band--institutional {
  background: var(--ox-nude-200);
  border: 1px solid var(--ox-navy-line);
  border-top: 3px solid var(--ox-navy);
}
.ox-band--institutional .ox-band__label { color: var(--ox-fg-strong); }
.ox-band__nav { margin-left: auto; display: flex; gap: 6px; }
.ox-band__arrow {
  width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--ox-navy-line);
  border-radius: 50%;
  background: var(--ox-card);
  cursor: pointer;
  position: relative;
  transition: border-color .15s ease, opacity .15s ease, background .15s ease;
}
.ox-band__arrow::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 7px; height: 7px;
  border-right: 2px solid var(--ox-navy);
  border-bottom: 2px solid var(--ox-navy);
}
.ox-band__arrow--next::before { transform: translate(-65%, -50%) rotate(-45deg); }
.ox-band__arrow--prev::before { transform: translate(-35%, -50%) rotate(135deg); }
.ox-band__arrow:hover:not(:disabled) { border-color: var(--ox-fg-brand); background: var(--ox-nude-100); }
.ox-band__arrow:hover:not(:disabled)::before { border-color: var(--ox-fg-brand); }
.ox-band__arrow:disabled { opacity: .3; cursor: default; }

.ox-band__viewport {
  display: grid;
  grid-auto-flow: column;
  /* Four per view unless a band asks for another number. */
  grid-auto-columns: calc(
    (100% - (var(--ox-band-cols, 4) - 1) * var(--space-sm)) / var(--ox-band-cols, 4)
  );
  gap: var(--space-sm);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 2px;
}
.ox-band__viewport::-webkit-scrollbar { display: none; }
/* Any child, not only a leader card: the band takes whatever a page puts in it. */
.ox-band__viewport > * { scroll-snap-align: start; }

/* Shared card treatment inside every band */
.ox-band .ox-leader-card,
.ox-band .ox-leader-card--wide {
  display: block;
  margin: 0;
  background: var(--ox-card);
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.ox-band .ox-leader-card__photo { width: 100%; aspect-ratio: 4 / 5; }
.ox-band .ox-leader-card__photo img { width: 100%; height: 100%; object-fit: cover; }
.ox-band .ox-leader-card__body { padding: var(--space-sm); }
.ox-band .is-director-title {
  color: var(--ox-fg-brand);
  font-weight: 700;
  font-family: var(--font-display);
  letter-spacing: 0.01em;
}

@media (max-width: 900px) {
  .ox-band__viewport { grid-auto-columns: calc((100% - var(--space-sm)) / 2); }
}
@media (max-width: 560px) {
  .ox-band__viewport { grid-auto-columns: 100%; }
  .ox-band__row { grid-template-columns: 1fr; }
}

/* Collapsible bios keep the detail without the page length. */
.ox-leader-card__bio.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ox-leader-card__more {
  margin-top: 6px; padding: 0;
  background: none; border: 0; cursor: pointer;
  font-family: var(--font-display);
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ox-fg-brand);
}
.ox-leader-card__more:hover { text-decoration: underline; }

/* ── Head of School: compact, horizontal ────────────────────────────── */
.ox-band--head .ox-leader-card,
.ox-band--head .ox-leader-card--wide {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr);
  align-items: stretch;
  max-width: 620px;
}
.ox-band--head .ox-leader-card__photo { width: 148px; aspect-ratio: 3 / 4; height: 100%; }
.ox-band--head .ox-leader-card__body { padding: var(--space-sm) var(--space-md); align-self: center; }
/* Narrow tablets: tighten the gutter so the text column keeps its measure. */
@media (max-width: 860px) {
  .ox-band--head .ox-leader-card,
  .ox-band--head .ox-leader-card--wide {
    grid-template-columns: 124px minmax(0, 1fr);
    gap: 16px;
    padding: 16px;
    max-width: none;
  }
  .ox-band--head .ox-leader-card__photo { width: 124px; }
  .ox-band--head .ox-leader-card__body { padding: 0; }
}
/* Phones: a 305px band cannot hold two columns — stack them. */
@media (max-width: 560px) {
  .ox-band--head .ox-leader-card,
  .ox-band--head .ox-leader-card--wide {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
    padding: 16px;
  }
  .ox-band--head .ox-leader-card__photo {
    width: 100%;
    max-width: 190px;
    aspect-ratio: 3 / 4;
    height: auto;
  }
}

/* ── Six departments: compact accordion stack ───────────────────────── */
.ox-dept-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: var(--space-md);
}
.ox-dept-row {
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  overflow: hidden;
  transition: border-color .18s ease;
}
.ox-dept-row:hover { border-color: var(--ox-fg-brand); }
.ox-dept-row__toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px var(--space-sm);
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.ox-dept-row__num {
  flex: none;
  min-width: 26px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--oxford-royal);
  color: #fff;
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .08em;
  text-align: center;
}
.ox-dept-row__name {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: var(--track-md);
  color: var(--ox-fg-strong);
}
.ox-dept-row__chev {
  flex: none;
  margin-left: auto;
  width: 8px; height: 8px;
  border-right: 2px solid var(--oxford-royal);
  border-bottom: 2px solid var(--oxford-royal);
  transform: rotate(45deg) translate(-2px,-2px);
  transition: transform .2s ease;
}
.ox-dept-row.is-open .ox-dept-row__chev { transform: rotate(-135deg) translate(-2px,-2px); }
.ox-dept-row__body {
  display: none;
  margin: 0;
  padding: 0 var(--space-sm) var(--space-sm) 50px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ox-body);
}
.ox-dept-row.is-open .ox-dept-row__body { display: block; }

/* ── Support areas: detail tucked behind the block ──────────────────── */
.ox-chip--has-detail { cursor: pointer; position: relative; padding-right: 30px !important; }
.ox-chip--has-detail::after {
  content: "";
  position: absolute; right: 14px; top: 50%;
  width: 7px; height: 7px;
  border-right: 2px solid var(--oxford-royal);
  border-bottom: 2px solid var(--oxford-royal);
  transform: translateY(-70%) rotate(45deg);
  transition: transform .2s ease;
}
.ox-chip--has-detail.is-open::after { transform: translateY(-30%) rotate(-135deg); }
.ox-support-detail {
  display: none;
  margin: var(--space-sm) auto 0;
  max-width: 760px;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--ox-navy-line);
  border-left: 3px solid var(--oxford-royal);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ox-body);
}
.ox-support-detail.is-open { display: block; }
@media (prefers-reduced-motion: reduce) {
  .ox-dept-row__chev, .ox-chip--has-detail::after { transition: none; }
}

/* The band heading already names the group — drop the per-card repeat. */
.ox-band .ox-leader-card__role { display: none; }

/* ── Story photo galleries ──────────────────────────────────────────────
   A contact sheet beneath the article, full container width rather than
   squeezed into the text column. Grid (not the core flex) so the count per
   row is predictable at every breakpoint. 3/2 matches the native ratio of
   almost every source photo, so the cover crop is effectively lossless. */
.ox-story-gallery-wrap { margin-top: var(--space-lg); }
.ox-story-gallery.wp-block-gallery {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}
.ox-story-gallery.wp-block-gallery > figure {
  width: auto !important;
  margin: 0 !important;
  flex: none !important;
}
.ox-story-gallery.wp-block-gallery > figure img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 8px;
}
@media (max-width: 900px) {
  .ox-story-gallery.wp-block-gallery { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .ox-story-gallery.wp-block-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
}

/* ── Certifications: accreditation column runs the full height of the blurb ──
   The split aligns its columns to the start, so the cards stopped ~159px short
   of the text and image beside them. Stretch the column, then lay the cards out
   on a grid whose rows share the leftover height instead of a fixed card height. */
.ox-cert-steps {
  align-self: stretch;
  display: flex;
  flex-direction: column;
}
.ox-cert-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  grid-auto-rows: 1fr;
  align-content: stretch;
  flex: 1 1 auto;
  min-height: 0;
}
.ox-cert-card,
.ox-cert-cards .ox-cert-card {
  height: auto;
  min-height: 220px;
  max-width: none;
  min-width: 0;
  flex: none;
}
@media (max-width: 1080px) {
  .ox-cert-card { height: auto; max-width: none; }
}
@media (max-width: 780px) {
  .ox-cert-card { height: auto; }
}
@media (max-width: 560px) {
  /* Centre the grid, never the item: auto margins on a grid child whose faces
     are absolutely positioned collapse it to zero width. */
  .ox-cert-cards {
    grid-template-columns: minmax(0, 380px);
    justify-content: center;
    max-width: none;
    margin-inline: 0;
  }
  .ox-cert-cards .ox-cert-card {
    height: auto;
    min-height: 210px;
    max-width: none;
    margin-inline: 0;
  }
}

/* ── Head of School: card fills its band, portrait matches every other one ──
   Other leader photos are 222×277 (4/5). Ryder's was 148×238 (3/4) in a 620px
   card inside a 1093px band. */
.ox-band--head .ox-leader-card,
.ox-band--head .ox-leader-card--wide {
  grid-template-columns: 222px minmax(0, 1fr);
  max-width: none;
  width: 100%;
}
.ox-band--head .ox-leader-card__photo {
  width: 222px;
  aspect-ratio: 4 / 5;
  height: auto;
  align-self: start;
}
@media (max-width: 860px) {
  .ox-band--head .ox-leader-card,
  .ox-band--head .ox-leader-card--wide { grid-template-columns: 180px minmax(0, 1fr); }
  .ox-band--head .ox-leader-card__photo { width: 180px; }
}
@media (max-width: 560px) {
  .ox-band--head .ox-leader-card,
  .ox-band--head .ox-leader-card--wide { grid-template-columns: minmax(0, 1fr); }
  .ox-band--head .ox-leader-card__photo { width: 100%; max-width: 222px; }
}

/* ── Support teams: equal chips, however many there are ─────────────────
   This was written as a fixed four-column grid back when there were four
   chips. One has since been removed, and the empty fourth track was pulling
   the remaining three 148px left of centre. auto-fit drops the unused track,
   so three or four both fill the row and stay centred. */
.ox-row.ox-dept-support__row,
.ox-row.ox-dept-support__row.is-layout-flow {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}
.ox-dept-support__row .ox-chip {
  width: auto;
  margin: 0;
  flex: 1 1 240px;
  max-width: 360px;
}

/* ── Hambretón as the right column of Love Foundation ───────────────────
   It used to be a full-width navy section nested inside the split's image
   slot, which is why it rendered boxed. As a card it can sit beside the
   Love Foundation copy and keep the navy treatment. */
.ox-impact-card {
  background: var(--ox-navy);
  border: 1px solid var(--ox-navy);
  border-radius: var(--radius-card);
  padding: var(--space-md);
  align-self: start;
}
.ox-impact-card,
.ox-impact-card p,
.ox-impact-card h2,
.ox-impact-card h3 { color: #fff; }
.ox-impact-card h2 {
  font-size: clamp(1.1rem, 1.9vw, 1.5rem);
  line-height: 1.2;
  text-wrap: balance;
}
.ox-impact-card p { font-size: 14px; line-height: 1.55; }
/* The editor leaves empty paragraphs as spacers; collapse them in the card. */
.ox-impact-card p:empty { margin: 0; min-height: var(--space-xs); }
@media (max-width: 900px) {
  .ox-impact-card { padding: var(--space-sm); }
}

/* ── Community Connection: flatter blocks, columns matched in height ────
   Scoped to the page so other pages keep their card treatments. */

/* Images fill their column rather than setting the row height. */
.page-id-424 #act-locally .ox-split,
.page-id-424 #love-foundation-mexico .ox-split,
.page-id-424 #live-green .ox-split { align-items: stretch; }

.page-id-424 #act-locally .ox-hero__media,
.page-id-424 #act-locally .ox-hero__media .wp-block-image { height: 100%; margin: 0; }
.page-id-424 #act-locally .ox-hero__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-card);
}

/* Love Foundation: the photo takes whatever is left after the copy, so the
   column lands level with the Hambretón panel beside it. */
.page-id-424 #love-foundation-mexico .ox-stack {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.page-id-424 #love-foundation-mexico .ox-stack > .wp-block-image {
  /* basis 0 so the photo's natural height does not set the row — it takes
     what is left beside the Hambretón panel instead. */
  flex: 1 1 0;
  min-height: 240px;
  margin: var(--space-sm) 0 0;
}
.page-id-424 #love-foundation-mexico .ox-stack > .wp-block-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-card);
}
.page-id-424 #hambreton.ox-impact-card { align-self: stretch; }

/* Live Green: copy left, the two photos stacked and sharing the same height. */
.ox-media-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  height: 100%;
  min-height: 0;
}
.ox-media-stack .wp-block-image { margin: 0; flex: 1 1 0; min-height: 0; }
.ox-media-stack img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-card);
}

/* Checklists render as plain blocks everywhere, not boxed cards.
   Deliberately not applied to the accreditation flip cards, leader cards or
   story cards — those are components whose card form carries meaning. */
.ox-checklist {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
}

@media (max-width: 900px) {
  .page-id-424 #act-locally .ox-hero__media img { height: auto; max-height: 420px; }
  .ox-media-stack { height: auto; }
  .ox-media-stack .wp-block-image { flex: none; }
  .ox-media-stack img { height: auto; }
  .page-id-424 #love-foundation-mexico .ox-stack > .wp-block-image { flex: none; }
  .page-id-424 #love-foundation-mexico .ox-stack > .wp-block-image img { height: auto; }
}

/* ── Story date, under the hero title ──────────────────────────────────── */
.ox-story-date {
  margin: 10px 0 0;
  font-size: 13px;
  letter-spacing: var(--track-md);
  text-transform: uppercase;
  color: rgba(255, 255, 255, .72);
}
.ox-story-date time { font-variant-numeric: tabular-nums; }

/* ── Video embed (TEDx) ────────────────────────────────────────────────── */
.ox-video-embed {
  margin: var(--space-lg) 0 0;
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: #000;
  box-shadow: 0 18px 48px -20px rgba(0, 0, 0, .6);
}
.ox-video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* Sub-headings inside the Community Connection & Sustainability columns. */
.ox-subtitle {
  margin: 4px 0 10px;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: var(--track-md);
  color: var(--ox-fg-brand);
  text-transform: none;
}
.ox-service .ox-stack > h3 { margin-top: 0; }
.ox-service .ox-stack + .ox-stack { margin-top: 0; }
.ox-inline-link a {
  font-weight: 600;
  color: var(--ox-fg-brand);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}
.ox-inline-link a:hover { opacity: .8; }

/* ── Disclosure: title visible, description behind a toggle ─────────────
   Used for Love Foundation / Live Green on Oxfordian Life. */
.ox-disclosure-set {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}
.ox-disclosure {
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  overflow: hidden;
}
.ox-disclosure__toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.ox-disclosure__head { flex: 1 1 auto; min-width: 0; }
.ox-disclosure__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.9vw, 1.35rem);
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.2;
  color: var(--ox-fg-strong);
}
.ox-disclosure__sub {
  margin: 6px 0 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: var(--track-md);
  color: var(--ox-fg-brand);
}
.ox-disclosure__chev {
  flex: none;
  width: 10px;
  height: 10px;
  border-right: 2px solid var(--oxford-royal);
  border-bottom: 2px solid var(--oxford-royal);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform .25s ease;
}
.ox-disclosure.is-open .ox-disclosure__chev {
  transform: rotate(-135deg) translate(-3px, -3px);
}
.ox-disclosure__panel {
  display: none;
  padding: 0 var(--space-md) var(--space-md);
}
.ox-disclosure.is-open .ox-disclosure__panel { display: block; }
.ox-disclosure__panel > * + * { margin-top: var(--space-md); }
.ox-disclosure__panel p { line-height: 1.65; }
.ox-disclosure__panel li + li { margin-top: 10px; }
.ox-disclosure__panel li { line-height: 1.6; }
.ox-disclosure__panel ul { padding-left: 1.15em; }
@media (max-width: 560px) {
  .ox-disclosure__toggle { padding: var(--space-sm); }
  .ox-disclosure__panel { padding: 0 var(--space-sm) var(--space-sm); }
}

/* ── Plain columns (replacing card grids) ──────────────────────────────── */
.ox-plain-columns { gap: var(--space-lg); margin-top: var(--space-lg); }
.ox-plain-columns .wp-block-column > h3 {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.7vw, 1.2rem);
  font-weight: 700;
  line-height: 1.25;
  color: var(--ox-fg-strong);
}
.ox-plain-columns .wp-block-column > h3::before {
  content: "";
  display: block;
  width: 34px;
  height: 3px;
  background: var(--oxford-royal);
  margin-bottom: 12px;
  border-radius: 2px;
}
.ox-plain-columns .wp-block-column p { margin: 0; line-height: 1.6; color: var(--ox-body); }
@media (max-width: 780px) {
  .ox-plain-columns { gap: var(--space-md); }
}

/* Rounded media, consistent with the rest of the site. */
.wp-block-image.ox-rounded img { border-radius: var(--radius-card); display: block; }

/* Core Columns stacks everything below 782px, which leaves three short text
   columns as three full-width blocks on a tablet. Two across reads far better;
   single column only on phones. Core uses !important here, so we must too. */
@media (min-width: 601px) and (max-width: 900px) {
  .ox-plain-columns.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column {
    flex-basis: calc(50% - var(--space-md) / 2) !important;
    flex-grow: 0;
  }
}
@media (max-width: 600px) {
  .ox-plain-columns.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column {
    flex-basis: 100% !important;
  }
}

/* ── Contact page jump navigation ──────────────────────────────────────── */
.ox-jumpnav__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  justify-content: center;
}
.ox-jumpnav__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 10px 22px;
  border: 1px solid var(--ox-navy-line);
  border-radius: 999px;
  background: var(--ox-card);
  color: var(--ox-fg-strong);
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: var(--track-md);
  text-decoration: none;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.ox-jumpnav__btn:hover,
.ox-jumpnav__btn:focus-visible {
  background: var(--oxford-royal);
  border-color: var(--ox-fg-brand);
  color: #fff;
}
@media (max-width: 560px) {
  .ox-jumpnav__row { flex-direction: column; }
  .ox-jumpnav__btn { width: 100%; }
}

/* Any anchored section must clear the sticky header when jumped to.
   Hard-coding ids missed every level, campus and extracurricular anchor. */
section[id],
.ox-section[id],
[id][class*="ox-"] { scroll-margin-top: 96px; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ── General message form (Contact) ────────────────────────────────────── */
.ox-message-form { margin-top: var(--space-lg); max-width: 760px; }
.ox-message-form__hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.ox-message-form__req { font-size: 13px; color: var(--ox-body); margin: 0 0 var(--space-md); }
.ox-message-form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); }
.ox-field { margin: 0 0 var(--space-md); }
.ox-field label {
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ox-fg-strong);
}
.ox-field__req { color: var(--ox-fg-brand); }
.ox-field input,
.ox-field textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--ox-navy-line);
  border-radius: 10px;
  background: var(--ox-card);
  font: inherit;
  font-size: 16px;
  color: var(--ox-fg-strong);
}
.ox-field textarea { resize: vertical; min-height: 150px; }
.ox-field input:focus-visible,
.ox-field textarea:focus-visible {
  outline: 2px solid var(--ox-focus-ring);
  outline-offset: 1px;
  border-color: var(--ox-fg-brand);
}
.ox-message-form__actions { margin: 0; }
.ox-message-form__status { margin: var(--space-sm) 0 0; font-size: 14px; min-height: 1.4em; }
.ox-message-form__status.is-ok { color: #1c6b4f; font-weight: 600; }
.ox-message-form__status.is-error { color: var(--ox-fg-brand); font-weight: 600; }
@media (max-width: 560px) {
  .ox-message-form__grid { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* Auto-linked contact details keep the surrounding type, gain an underline. */
.ox-auto-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--ox-fg-brand);
}
.ox-auto-link:hover,
.ox-auto-link:focus-visible { color: var(--ox-fg-brand); }

/* Enrichment offerings read as a clean list of subjects; the stock photos
   added visual noise without adding information. */
.ox-enrichment-card__img { display: none; }
.ox-enrichment-card {
  display: flex;
  align-items: center;
  min-height: 0;
  padding: var(--space-md);
}
.ox-enrichment-card__title { margin: 0; }


/* ---- Centred introductory paragraph beneath the home hero (3.91.0) ---- */
.ox-page-intro { padding-block: var(--space-lg); }
.ox-page-intro .ox-container { display: flex; justify-content: center; }
.ox-page-intro p { max-width: 62ch; }

/* ---- Love Foundation project grid (3.91.0) ---- */
.ox-project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--space-md);
  margin-top: var(--space-md);
}
.ox-project-grid .ox-plain-columns__item > h3 { margin: 0 0 6px; color: var(--ox-fg-strong); }
.ox-project-grid .ox-plain-columns__item > h3::before {
  content: ""; display: block; width: 26px; height: 3px;
  background: var(--oxford-royal); margin-bottom: 10px; border-radius: 2px;
}
.ox-project-grid .ox-plain-columns__item > p { margin: 0; color: var(--ox-body); }
@media (max-width: 560px) { .ox-project-grid { grid-template-columns: minmax(0, 1fr); } }


/* ================= After School: Scheduling & Enrollment (3.92.0) ================= */

/* ---- Commitment letter ---- */
.ox-terms {
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  padding: var(--space-md);
}
.ox-terms__head {
  display: flex; flex-wrap: wrap; gap: var(--space-sm);
  align-items: center; justify-content: space-between;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--ox-navy-line);
}
.ox-terms__title {
  margin: 0; font-family: var(--font-display); font-weight: 700;
  color: var(--ox-fg-strong); letter-spacing: var(--track-md); max-width: 48ch;
}
.ox-terms__toggle { display: flex; gap: 4px; flex: none; }
.ox-terms__lang {
  min-height: 34px; padding: 6px 14px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--ox-navy-line); background: transparent;
  color: var(--ox-body); font-family: var(--font-display);
  font-size: 0.8125rem; font-weight: 600;
}
.ox-terms__lang.is-active { background: var(--ox-navy); border-color: var(--ox-fg-strong); color: #fff; }
.ox-terms__lang:focus-visible { outline: 2px solid var(--ox-focus-ring); outline-offset: 2px; }
.ox-terms__intro, .ox-terms__declaration { color: var(--ox-body); margin: var(--space-sm) 0 0; }
.ox-terms__list { margin: var(--space-sm) 0 0; padding-left: 1.25rem; color: var(--ox-body); }
.ox-terms__list li { margin-bottom: 10px; line-height: 1.55; }
.ox-terms__note {
  margin: var(--space-md) 0 0; padding-top: var(--space-sm);
  border-top: 1px solid var(--ox-navy-line);
  font-size: 0.875rem; color: var(--ox-body);
}
.ox-terms__note a { color: var(--ox-fg-brand); font-weight: 600; }

/* Inside the form the letter is a scroll box so the fields stay reachable. */
.ox-enroll__terms-body {
  max-height: 320px; overflow-y: auto;
  border-radius: var(--radius-card);
  background: var(--ox-nude-50);
  padding: 4px;
}
.ox-enroll__terms-body .ox-terms { border: 0; background: transparent; }
.ox-enroll__terms-body:focus-visible { outline: 2px solid var(--ox-focus-ring); outline-offset: 2px; }

/* ---- Enrollment form ---- */
.ox-enroll {
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
  padding: var(--space-md);
  display: flex; flex-direction: column; gap: var(--space-md);
}
.ox-enroll__grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-sm) var(--space-md);
}
.ox-field { margin: 0; display: flex; flex-direction: column; gap: 6px; }
.ox-field--wide { grid-column: 1 / -1; }
.ox-field > label {
  font-family: var(--font-display); font-weight: 600;
  font-size: 0.875rem; color: var(--ox-fg-strong);
}
.ox-field > label span { color: var(--ox-fg-brand); }
.ox-field input, .ox-field select, .ox-field textarea {
  width: 100%; min-height: 46px; padding: 10px 14px;
  border: 1px solid var(--ox-navy-line); border-radius: 10px;
  background: #fff; color: var(--ox-fg-strong); font: inherit;
}
.ox-field textarea { min-height: 88px; resize: vertical; }
.ox-field input:focus-visible, .ox-field select:focus-visible, .ox-field textarea:focus-visible {
  outline: 2px solid var(--ox-focus-ring); outline-offset: 1px; border-color: transparent;
}

/* A hint sits between its label and its input, never after the input: .ox-field
   uses justify-content: space-between so the last child is pinned to the bottom
   and inputs share a baseline across a row. A hint placed after the input would
   take that bottom position and lift the input out of line with its neighbours.
   The auto margin keeps the hint with its label and leaves the free space below. */
.ox-field__hint {
  margin-bottom: auto;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 400;
  color: var(--ox-body);
}

.ox-field--check {
  flex-direction: row; align-items: flex-start; gap: 10px; margin-top: var(--space-sm);
}
.ox-field--check input { width: 20px; min-height: 20px; height: 20px; flex: none; margin-top: 2px; }
.ox-field--check label { font-weight: 600; color: var(--ox-fg-strong); }

.ox-enroll__terms { display: flex; flex-direction: column; gap: var(--space-sm); }
.ox-enroll__terms-lead { margin: 0; color: var(--ox-body); font-weight: 600; }

.ox-enroll__actions { margin: 0; }
.ox-enroll__actions .ox-btn[disabled] { opacity: .5; cursor: not-allowed; }
.ox-enroll__status { margin: 0; font-weight: 600; min-height: 1.2em; }
.ox-enroll__status.is-ok { color: #1a7f4b; }
.ox-enroll__status.is-error { color: var(--ox-fg-brand); }
.ox-enroll__status.is-pending { color: var(--ox-body); }

/* Honeypot: present for bots, gone for everyone else. */
.ox-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

@media (max-width: 700px) {
  .ox-enroll__grid { grid-template-columns: minmax(0, 1fr); }
  .ox-terms__head { flex-direction: column; align-items: flex-start; }
}

/* The Scheduling & Enrollment entry reads as a child of Extracurriculars. */
.ox-dropdown__link--sub { padding-left: 30px; position: relative; }
.ox-dropdown__link--sub::before {
  content: ""; position: absolute; left: 16px; top: 50%;
  width: 6px; height: 1px; background: var(--ox-navy-line);
}
.ox-mobile-nav__sublink { padding-left: 18px !important; opacity: .92; }

/* Collapsed Carta Compromiso: the full text lives in the enrollment form above. */
.ox-terms-details {
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  background: var(--ox-card);
}
.ox-terms-details__summary {
  cursor: pointer; list-style: none;
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-display); font-weight: 600;
  color: var(--ox-fg-strong); display: flex; align-items: center; gap: 10px;
  min-height: 46px;
}
.ox-terms-details__summary::-webkit-details-marker { display: none; }
.ox-terms-details__summary::before {
  content: "+"; font-size: 1.1rem; line-height: 1; color: var(--ox-fg-brand); flex: none;
}
.ox-terms-details[open] .ox-terms-details__summary::before { content: "\2013"; }
.ox-terms-details__summary:focus-visible { outline: 2px solid var(--ox-focus-ring); outline-offset: -2px; }
.ox-terms-details__body { padding: 0 var(--space-sm) var(--space-sm); }
.ox-terms-details__body .ox-terms { border: 0; }

/* Inline PDF link beside the Carta Compromiso mention in the enrollment form. */
.ox-pdf-link {
  color: var(--ox-fg-brand); font-weight: 600; white-space: nowrap;
  text-decoration: underline; text-underline-offset: 2px;
}
.ox-pdf-link::after { content: " \2193"; text-decoration: none; }
.ox-pdf-link:focus-visible { outline: 2px solid var(--ox-focus-ring); outline-offset: 2px; }

/* ---- Home: university agreements strip (3.95.0) ---- */
.ox-uni-strip__row {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 10px; margin-top: var(--space-md);
}
.ox-uni-strip__row .ox-chip { white-space: nowrap; }
.ox-uni-strip__cta { justify-content: center; display: flex; margin-top: var(--space-md); }
@media (max-width: 560px) {
  .ox-uni-strip__row { gap: 8px; }
  .ox-uni-strip__row .ox-chip { white-space: normal; }
}


/* ==== Campus + grade level pages: one section-header treatment (3.102.0) ==== */

/* The eyebrow above each section title. Three different renderings had crept in:
   `.ox-section-header p` (0,1,1) outweighs `.ox-badge` (0,1,0) and was repainting
   the badge as 17px body grey, while a badge inside a flex-column .ox-stack
   stretched to the full column width. Scoping to the campus body class wins
   both, so every section on every campus gets the same eyebrow. */
.single-ox_campus .ox-badge,
.single-ox_level .ox-badge {
  display: inline-block;
  width: fit-content;
  align-self: start;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.45;
  letter-spacing: 1px;
  text-transform: uppercase;
  /* The darkened red on the red wash, like every other badge. This rule had
     kept var(--ox-fg-brand) on the royal wash — the wash from an earlier
     palette, the red from this one — and the pair measured 3.93 on the level
     pages and 4.01 on the campuses. */
  color: #C40B21;
  background: var(--ox-red-subtle);
  padding: 5px 12px;
  border-radius: var(--radius-badge);
  margin: 0 0 var(--space-sm);
}

/* Section titles. Campus pages had drifted to weight 600 while every other
   section title on the site sits at 800, which is what made them read as thin
   and slightly off. Both page types now share one treatment: the heavier weight,
   at a size that still stays below the page hero. Montserrat 800 is a real face
   here (served from the Font Library), not a synthesised bold. */
.single-ox_campus .ox-section-header h2,
.single-ox_campus .ox-section-header h3,
.single-ox_campus .ox-badge + h2,
.single-ox_campus .ox-badge + h3,
.single-ox_level .ox-section-header h2,
.single-ox_level .ox-badge + h2 {
  font-family: var(--font-display);
  color: var(--ox-fg-strong);
  letter-spacing: -0.02em;
  font-weight: 800;
  font-size: clamp(1.5rem, 1.2rem + 1.35vw, 2.1rem);
  line-height: 1.18;
  text-wrap: balance;
  margin: 0;
}
.single-ox_campus .ox-section-header + *,
.single-ox_level .ox-section-header + * { margin-top: var(--space-md); }

/* Sub-headings inside a level section (Academic Approach, 1st & 2nd, …) sit a
   clear step below the section title rather than competing with it. */
.single-ox_level .ox-section-header ~ * h3,
.single-ox_level .ox-approach-card h3,
.single-ox_level .ox-plain-columns__item h3 {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--ox-fg-strong);
  letter-spacing: -0.01em;
}

/* One director's-message treatment on every campus, spanning the container. */
.ox-campus-quote {
  max-width: none;
  width: 100%;
  margin: var(--space-md) 0 0;
  border: 0;
  padding: 0;
}
.ox-campus-quote p { margin: 0; }
.ox-campus-quote__text {
  background: var(--ox-navy-subtle);
  color: var(--ox-fg-strong);
  border-left: 3px solid var(--oxford-royal);
  border-radius: var(--radius-card);
  padding: clamp(18px, 2.4vw, 32px) clamp(20px, 3.2vw, 48px);
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.125rem);
  line-height: 1.6;
  text-align: center;
  text-wrap: pretty;
}
.ox-campus-quote__by {
  margin-top: var(--space-sm) !important;
  text-align: center;
  font-size: 0.8125rem;
  color: var(--ox-body);
  letter-spacing: 0.02em;
}
.ox-campus-quote__by strong { color: var(--ox-fg-strong); }

@media (max-width: 560px) {
  .ox-campus-quote__text { text-align: left; }
  .single-ox_campus .ox-badge,
  .single-ox_level .ox-badge { font-size: 11px; letter-spacing: 0.9px; }
}

/* Photo captions on campus pages. Quiet, centred under the frame, and narrow
   enough that a one-line memory does not stretch the full container width. */
.single-ox_campus figure.wp-block-image figcaption,
.single-ox_campus .wp-element-caption,
.single-ox_level figure.wp-block-image figcaption,
.single-ox_level .wp-element-caption {
  margin: var(--space-sm) auto 0;
  max-width: 56ch;
  text-align: center;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ox-body);
  letter-spacing: 0.01em;
  text-wrap: pretty;
}
.single-ox_campus .ox-campus-figure { margin-bottom: 0; }
@media (max-width: 560px) {
  .single-ox_campus figure.wp-block-image figcaption,
  .single-ox_campus .wp-element-caption,
  .single-ox_level figure.wp-block-image figcaption,
  .single-ox_level .wp-element-caption { font-size: 0.78rem; text-align: left; }
}

/* Oxford Language School — "A Clear Path Forward". The lede and the summary line
   under the levels are centred, but the header block and the level row were still
   packed to the left, so the section read as half-centred. :has() scopes this to
   the one section that carries the levels row. */
.ox-section:has(.ox-ols-levels) .ox-section-header {
  max-width: 68ch;
  margin-inline: auto;
  text-align: center;
}
.ox-section:has(.ox-ols-levels) .ox-ols-levels .is-layout-flex {
  justify-content: center;
  flex-wrap: wrap;
}
/* The seven cards were pinned at min-width 175px, which needs 1297px — more than
   the container gives at 1440, so they either overflowed or broke to a second row
   of one. Letting them flex keeps the set on a single centred row on desktop and
   still wraps cleanly on smaller screens. */
.ox-section:has(.ox-ols-levels) .ox-ols-level {
  min-width: 0;
  flex: 1 1 150px;
  max-width: 175px;
}
@media (max-width: 560px) {
  /* 175px leaves room for only one card per row on a phone; releasing the cap
     lets two sit side by side instead of a seven-high stack. */
  .ox-section:has(.ox-ols-levels) .ox-ols-level { max-width: none; flex: 1 1 40%; }
}


/* ── Grids holding fewer items than they have tracks (3.103.0) ──────────
   A fixed-column grid with an empty track leaves its row hanging to the left
   of its own container: two photos in a three-column gallery sat 210px off
   centre, a lone campus photo 430px off. Matching the track count to what is
   actually in the grid keeps the row centred whatever the count.
   Bounded above 560px so theme.css still owns the single-column phone
   layout, but the tablet band is covered too. */
@media (min-width: 561px) {
  /* .ox-grid.is-layout-flow re-declares display:grid at (0,2,0), so these have
     to match that weight to win. */
  .ox-gallery,
  .ox-campus-gallery,
  .ox-grid.ox-campus-gallery,
  .ox-campus-gallery.is-layout-flow {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
  }
  .ox-gallery > *,
  .ox-campus-gallery > * {
    flex: 1 1 240px;
    max-width: 420px;
    margin: 0;
  }
  /* A single photo should not stretch the full band. */
  .ox-gallery:has(> :only-child) > *,
  .ox-campus-gallery:has(> :only-child) > * { max-width: 560px; }
}

/* ── Our School: dynamic blocks converted to core blocks (3.107.0) ──────
   The certification card lines and the section badges used to render as
   <span>/<strong>, which carry no default margin. They are core paragraphs
   now so the Site Editor can reach the text; these keep the spacing exactly
   where it was rather than picking up the theme's paragraph margins. */
.ox-cert-card__num,
.ox-cert-card__name,
.ox-cert-card__cue,
.ox-cert-card__note { display: block; margin-block: 0; }
.ox-cert-card__cue { margin-top: auto; }
/* `.ox-section-header p` (0,1,1) outweighs `.ox-badge` (0,1,0), so a badge that
   is now a paragraph gets repainted as 17px body grey. Same trap as the campus
   pages; `p.ox-badge` ties on weight and wins on order. */
p.ox-badge {
  margin-top: 0;
  margin-bottom: var(--space-sm);
  font-size: 11px;
  color: var(--ox-fg-brand);
}

/* ==== Home: dynamic blocks converted to core blocks (3.109.0) ==== */
/* Every one of these rendered as a <span>. As a paragraph each picks up the
   default margin-block, and the flex or grid parent it sits in turns that
   into visible drift rather than collapsing it away. */
p.ox-chip,
p.ox-pillar-row__num,
p.ox-bullet__num,
p.ox-hero__media-badge { margin: 0; }

/* The two numbered counters live inside blocks that style their paragraphs:
   `.ox-pillar-row p` and `.ox-bullet p` (0,1,1) both outweigh the counter's
   own class (0,1,0) and would repaint it as 15px body copy. */
p.ox-pillar-row__num { font-size: 18px; color: #fff; }
p.ox-bullet__num { font-size: 14px; }

/* `p.ox-badge` above pins the crimson; the closing CTA's badge sits on navy
   and has to keep the white the --light variant gives it. */
p.ox-badge--light { color: #fff; }

/* ==== Campus cards: admissions + contact (3.111.0) ====
   Five campuses in a fixed-track grid never divided evenly: five tracks gave
   cramped cards on desktop, and every narrower breakpoint (4, 3, then 2) left
   a short final row hard against the left edge — 473px off centre on contact
   at desktop, 185–192px off on both pages at tablet. A grid centres its
   tracks, never the orphan row; flex centres every row including the last,
   and sizes the cards from a comfortable minimum instead of a column count. */
.ox-grid.ox-campus-contacts,
.ox-grid.ox-contact-cards,
.ox-section .ox-grid--4.ox-campus-contacts,
.ox-section .ox-grid--5.ox-campus-contacts,
.ox-section .ox-grid--5.ox-contact-cards {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg);
}

.ox-campus-contacts > .ox-icon-card,
.ox-contact-cards > .ox-icon-card {
  flex: 1 1 300px;
  /* Without a floor, flex-shrink squeezes a fourth card onto the first row on
     one grid and not the other, so the contact page wrapped 4+1 in one place
     and 3+2 in the other. The floor makes five campuses wrap the same way
     wherever they appear. */
  min-width: 280px;
  max-width: 360px;
  margin: 0;
}

/* Prominence: the cards are the point of these two sections, so give them a
   little lift off the page rather than sitting flat inside a hairline box. */
.ox-campus-contacts > .ox-icon-card,
.ox-contact-cards > .ox-icon-card {
  box-shadow: 0 1px 2px rgba(13, 16, 69, 0.04), 0 8px 24px rgba(13, 16, 69, 0.06);
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.ox-campus-contacts > .ox-icon-card:hover,
.ox-contact-cards > .ox-icon-card:hover,
.ox-campus-contacts > .ox-icon-card:focus-within,
.ox-contact-cards > .ox-icon-card:focus-within {
  transform: translateY(-2px);
  border-color: rgba(232, 23, 44, 0.32);
  box-shadow: 0 2px 4px rgba(13, 16, 69, 0.05), 0 14px 34px rgba(13, 16, 69, 0.10);
}
/* The campus-contacts block names its cards with an h4, the contact page's
   own cards with an h3. Both are the card title, so both take the size. */
.ox-campus-contacts > .ox-icon-card :is(h3, h4),
.ox-contact-cards > .ox-icon-card :is(h3, h4) {
  font-size: 22px;
  line-height: 1.25;
}

@media (prefers-reduced-motion: reduce) {
  .ox-campus-contacts > .ox-icon-card,
  .ox-contact-cards > .ox-icon-card { transition: none; }
  .ox-campus-contacts > .ox-icon-card:hover,
  .ox-contact-cards > .ox-icon-card:hover { transform: none; }
}

/* Below the two-up point one card per row already fills the width, so let it
   run edge to edge rather than sitting narrow in the middle. */
@media (max-width: 620px) {
  .ox-campus-contacts > .ox-icon-card,
  .ox-contact-cards > .ox-icon-card { flex-basis: 100%; max-width: none; }
}

/* The three-step "how it works" rows have the same orphan problem one
   breakpoint down: three steps in a two-column grid leaves the third hard
   left. Flex keeps the desktop sizing identical (the basis grows to the same
   ~373px the three tracks gave) and centres the short row below it. */


/* ==== Nested menu items (3.115.0) ====
   A third level in the menu renders inside the same dropdown, indented under
   the item it belongs to, with a short rule standing in for the tree line. */
.ox-dropdown__link--nested {
  padding-left: calc(var(--space-md) + 14px);
  position: relative;
  font-size: 14px;
}
.ox-dropdown__link--nested::before {
  content: "";
  position: absolute;
  left: var(--space-md);
  top: 50%;
  width: 8px;
  height: 1px;
  background: var(--ox-navy-line);
}
.ox-mobile-nav__nested {
  padding-left: var(--space-md);
  opacity: 0.85;
  font-size: 14px;
}

/* The After School contacts sit in a section the theme gives a tighter gap,
   written when four contacts fitted one line. There are five now, so it wrapped
   4+1 while the admissions cards above it wrapped 3+2. Matching the gap makes
   both grids on the contact page break the same way. */
#after-school .ox-grid--4.ox-campus-contacts { gap: var(--space-lg); }

/* ==== Approach cards: equal boxes (3.116.0) ====
   `.ox-approach-grid` is a grid, but its saved class is `is-layout-flow`, so
   WordPress's flow-gap rule was putting a 16px top margin on every card except
   the first. Each card still stretched to the row, so the first card came out
   16px taller and 16px higher than its neighbours — visible on "How We Teach"
   and on "A Complete Bilingual Development System" at every campus. */
/* superseded by the gap-owns-spacing rule below */

/* Cards stretch to the tallest in their row, so a row reads as one band. A
   min-height set per card in the block's Dimensions panel still wins. */
.ox-approach-grid { align-items: stretch; }

/* ==== Campus sections: warm/white rhythm (3.118.0) ====
   The tinted band was `--ox-surface` (#f0f4f7), a cool blue-grey that reads as
   grey rather than as part of the brand. The nude ramp is the warm equivalent
   and is already in the palette — one campus section was using it, which is
   the tone that prompted this. Campus pages now alternate that warm tint with
   the plain white ground. */
.single-ox_campus .ox-section--surface { background: var(--oxford-white); }

/* Button rows had the same fault as the approach cards: `.ox-row` is a flex
   row but its saved class is `is-layout-flow`, so WordPress's flow-gap rule
   put a 16px top margin on every child after the first. With the row centring
   its items that showed as the two buttons sitting 8px out of line. */
/* superseded by the gap-owns-spacing rule below */

/* ==== One fix for the whole family (3.120.0) ====
   Every card container in the theme is laid out by its own class — grid or
   flex — while its saved WordPress class stays `is-layout-flow`. WP's flow-gap
   rule then adds a top margin to every child after the first, so the first
   card in each row came out taller and higher than its neighbours. It showed
   up on the approach cards, the button rows, and the Achievements cards.
   Card children are positioned by the container, never by their own margins. */
/* superseded by the gap-owns-spacing rule below */

/* Achievements and certification cards fill their row, so a row reads as one
   band. A per-card min-height from the Dimensions panel still wins. */
.ox-grid, .ox-cert-cards { align-items: stretch; }


/* ==== One rule: gap owns the spacing (3.122.0) ====
   Every container below is laid out by the theme as grid or flex WITH a gap,
   but its saved WordPress class is `is-layout-flow`. WP's flow rule then adds
   a top margin to each child after the first, so the first card in a row came
   out taller and higher than its neighbours. It surfaced four separate times —
   approach cards, button rows, Achievements cards, project grid — so this
   replaces the piecemeal fixes. Where a container declares `gap`, the gap is
   the spacing and child margins are redundant by definition. */
.ox-approach-card > *,
.ox-approach-grid > *,
.ox-as-hero > *,
.ox-bullet > *,
.ox-campus-contacts > *,
.ox-campus-gallery > *,
.ox-campus-list > *,
.ox-campus-row > *,
.ox-cert-cards > *,
.ox-cert-step > *,
.ox-cert-steps > *,
.ox-checklist > *,
.ox-contact-card > *,
.ox-contact-cards > *,
.ox-contact-line > *,
.ox-contact-list > *,
.ox-cta-box > *,
.ox-cta-option > *,
.ox-curriculum-grid > *,
.ox-curriculum-grid--accordion > *,
.ox-curriculum-note > *,
.ox-dept-stack > *,
.ox-disclosure-set > *,
.ox-enrichment-grid > *,
.ox-enroll > *,
.ox-field > *,
.ox-filter > *,
.ox-form > *,
.ox-gallery > *,
.ox-grid > *,
.ox-icon-card > *,
.ox-inline-links > *,
.ox-journey-strip > *,
.ox-leader-card--wide > *,
.ox-leader-grid > *,
.ox-media-stack > *,
.ox-nav > *,
.ox-numbers-grid > *,
.ox-numbers-stat > *,
.ox-ols-levels > *,
.ox-pagination > *,
.ox-pillar-card > *,
.ox-pillar-list > *,
.ox-pillar-row > *,
.ox-plans > *,
.ox-program-card > *,
.ox-program-grid > *,
.ox-project-grid > *,
.ox-role-card > *,
.ox-row > *,
.ox-schedule > *,
.ox-scroller > *,
.ox-service-card > *,
.ox-service-grid > *,
.ox-split > *,
.ox-stack > *,
.ox-stat > *,
.ox-stat-grid > *,
.ox-step > *,
.ox-story-timeline > *,
.ox-timeline--compact > *,
.ox-trust-bar > *,
.ox-upgrade-note > *,
.ox-util > *,
.ox-voices-split > * {
  margin-block: 0;
}

/* ==== Academic calendar: month grids (3.123.0) ====
   The agreed design is a calendar view, not a list of dates: twelve month
   grids with the marked days coloured by what kind of date they are, and the
   month's entries listed underneath so the grid stays readable at a glance. */
.ox-cal {
  --cal-academic: var(--ox-royal);
  --cal-event: var(--oxford-royal);
  --cal-holiday: var(--ox-nude-base);
  --cal-photo: var(--ox-navy-400);
}

.ox-cal__legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-lg);
}
.ox-cal__key {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ox-body);
}
.ox-cal__key i {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--cal-academic);
}
.ox-cal__key[data-cat="event"] i { background: var(--cal-event); }
.ox-cal__key[data-cat="holiday"] i { background: var(--cal-holiday); }
.ox-cal__key[data-cat="photo"] i { background: var(--cal-photo); }
.ox-cal__key b {
  font-family: var(--font-display);
  color: var(--ox-fg-strong);
  font-variant-numeric: tabular-nums;
}
.ox-cal__key.is-total { color: var(--ox-fg-strong); }

.ox-cal__months {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-lg);
}
.ox-cal__month {
  background: var(--ox-card);
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card);
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
}
.ox-cal__name {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
  color: var(--ox-fg-strong);
  margin: 0 0 var(--space-sm);
  letter-spacing: -0.01em;
}
.ox-cal__name span { color: var(--ox-body); font-weight: 600; }

.ox-cal__dow,
.ox-cal__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}
.ox-cal__dow {
  margin-bottom: 4px;
}
.ox-cal__dow span {
  text-align: center;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ox-body);
  opacity: 0.65;
}
.ox-cal__day {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ox-fg-strong);
  border-radius: 5px;
}
.ox-cal__day.is-blank { visibility: hidden; }
/* A shaded run is a holiday stretch, exactly as the printed calendar shades it. */
.ox-cal__day.is-break { background: var(--oxford-white); }
/* Marked days sit at half strength so the grid reads as a tint behind the
   number rather than a block of solid colour. The date stays navy, which is
   legible on every one of the four tints — white would not be. */
.ox-cal__day.is-marked {
  font-weight: 700;
  color: var(--ox-fg-strong);
  /* Plain tint first for browsers without color-mix, then the real 50%. */
  background: var(--ox-royal-100, #e8eaf6);
  background: color-mix(in srgb, var(--cal-academic) 50%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cal-academic) 30%, transparent);
}
.ox-cal__day.is-marked[data-cat="event"] {
  background: color-mix(in srgb, var(--cal-event) 50%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cal-event) 30%, transparent);
}
.ox-cal__day.is-marked[data-cat="holiday"] {
  background: color-mix(in srgb, var(--cal-holiday) 50%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cal-holiday) 30%, transparent);
}
.ox-cal__day.is-marked[data-cat="photo"] {
  background: color-mix(in srgb, var(--cal-photo) 50%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cal-photo) 30%, transparent);
}
/* Two kinds of date on one day: a corner flag rather than a second colour. */
.ox-cal__day.is-multi { position: relative; }
.ox-cal__day.is-multi::after {
  content: "";
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ox-navy);
  opacity: 0.55;
}

/* The month's entries are folded away behind a full-width disclosure under
   the grid — <details>/<summary>, so it works without JavaScript and keeps
   keyboard and screen-reader behaviour for free. */
.ox-cal__more {
  margin-top: var(--space-sm);
  border-top: 1px solid var(--ox-navy-line);
  padding-top: var(--space-sm);
}
.ox-cal__toggle {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* Border-box so the toggle is exactly as wide as the month grid above it. */
  box-sizing: border-box;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-badge);
  background: var(--ox-nude-200);
  font-family: var(--font-body);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ox-fg-strong);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.ox-cal__toggle::-webkit-details-marker { display: none; }
.ox-cal__toggle:hover { background: var(--oxford-white); border-color: var(--ox-fg-brand); }
.ox-cal__toggle:focus-visible { outline: 2px solid var(--ox-focus-ring); outline-offset: 2px; }
.ox-cal__toggle .ox-cal__n {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--ox-body);
  font-weight: 600;
}
/* Caret drawn in CSS so there is no icon to load or transcribe. */
.ox-cal__toggle .ox-cal__caret {
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--ox-navy);
  border-bottom: 2px solid var(--ox-navy);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.18s ease;
  flex: none;
}
.ox-cal__more[open] .ox-cal__caret { transform: rotate(-135deg) translate(-2px, -2px); }
.ox-cal__more[open] .ox-cal__toggle { background: var(--oxford-white); }

.ox-cal__none {
  margin-top: var(--space-sm);
  border-top: 1px solid var(--ox-navy-line);
  padding-top: var(--space-md);
  font-size: 12px;
  color: var(--ox-body);
  opacity: 0.6;
  font-style: italic;
  text-align: center;
}

.ox-cal__list {
  list-style: none;
  margin: var(--space-sm) 0 0;
  padding: 0;
  display: grid;
  gap: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .ox-cal__toggle, .ox-cal__toggle .ox-cal__caret { transition: none; }
}
/* Each entry is a date chip, a colour rule for its category, then the name —
   so the eye can run down the dates or scan the colours without re-reading. */
.ox-cal__list li {
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 10px;
  padding: 6px 0 6px 10px;
  border-left: 3px solid var(--cal-academic);
  border-radius: 2px;
  font-size: 12.5px;
  line-height: 1.45;
  align-items: start;
  transition: background 0.15s ease;
}
.ox-cal__list li:hover { background: var(--ox-nude-200); }
.ox-cal__list li[data-cat="event"] { border-left-color: var(--cal-event); }
.ox-cal__list li[data-cat="holiday"] { border-left-color: var(--cal-holiday); }
.ox-cal__list li[data-cat="photo"] { border-left-color: var(--cal-photo); }

.ox-cal__list li b {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 700;
  color: var(--ox-fg-strong);
  text-align: center;
  padding: 2px 0;
  border-radius: 999px;
  background: var(--ox-nude-300);
}
.ox-cal__list li[data-cat="event"] b { background: color-mix(in srgb, var(--cal-event) 16%, transparent); }
.ox-cal__list li[data-cat="holiday"] b { background: color-mix(in srgb, var(--cal-holiday) 26%, transparent); }
.ox-cal__list li[data-cat="photo"] b { background: color-mix(in srgb, var(--cal-photo) 16%, transparent); }

.ox-cal__t {
  color: var(--ox-fg-strong);
  font-weight: 700;
  display: block;
  text-wrap: balance;
}
.ox-cal__s {
  color: var(--ox-body);
  display: block;
  font-size: 11.5px;
  margin-top: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .ox-cal__list li { transition: none; }
}
.ox-cal__empty { color: var(--ox-body); opacity: 0.6; font-style: italic; }

@media (max-width: 900px) {
  .ox-cal__months { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .ox-cal__months { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
  .ox-cal__day { font-size: 13px; }
}

/* ==== Centred section headings (3.127.0) ====
   Global Styles emits `:root :where(.wp-block-heading) { text-align: left }`.
   That is a direct rule on the heading, so it beats the `text-align: center`
   the heading would otherwise inherit from a centred wrapper — the badge and
   lede centred correctly while the h2 between them stayed left. Only the
   theme's explicit centring classes need to win it back; every other heading
   on the site is meant to be left. */
.ox-section-header--center :is(h1, h2, h3, h4, h5, h6),
.ox-center :is(h1, h2, h3, h4, h5, h6),
.ox-cta :is(h1, h2, h3, h4, h5, h6),
.ox-video-hero :is(h1, h2, h3, h4, h5, h6) {
  text-align: center;
}

/* ---------------------------------------------------------------------------
   Header navigation: keep every top-level name on one line.

   Seven items at 17px with a clamp(24px, 2.4vw, 40px) gap need about 1150px.
   The nav only has that from roughly 1300px up, so below it labels wrapped
   inside their own items - "Oxfordian Language School" going to two lines took
   the bar from 76px to 104px and pushed the whole header down.

   The type and the gap now scale with the viewport instead of stepping, and
   white-space stops a label breaking even when it is tight. At the 961px point
   where the mobile toggle takes over, the row needs about 780px of the 794px
   available, so there is room for the current seven and not much more: adding
   an eighth item, or a name longer than "Oxfordian Language School", will need
   a rethink rather than another size reduction.
   --------------------------------------------------------------------------- */
.ox-nav { gap: clamp(12px, 1.5vw, 30px); }

.ox-nav__link {
  font-size: clamp(12.5px, 1.1vw, 16px);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Admission steps: a row that pages sideways.

   Built first as a vertical rail, on the reasoning that seven steps never
   divided evenly into a grid. Andrea asked for a row with arrows instead
   (13 Sep 2026), which is what the site already does with every other card
   row: the cards hold their size and the reader flips through them, so the
   count never has to divide by anything.

   All three widths are written here rather than left to the generic card row
   rules, because `.ox-grid:has(> .ox-step)` is (0,2,0) and outranks the
   `.ox-grid--3` band rules it would otherwise inherit. theme.js adds
   `.ox-flip-row` above 961px, which is what supplies the arrow bar.
   --------------------------------------------------------------------------- */
.ox-grid:has(> .ox-step) {
  display: grid;
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: minmax(var(--ox-flip-card, 340px), 1fr);
  gap: var(--space-md);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  /* Room for the lift and shadow a card takes on hover. */
  padding-bottom: 6px;
}
.ox-grid:has(> .ox-step)::-webkit-scrollbar { display: none; }

/* A short row does not need a card floor. Three steps at 340px each overflow a
   1002px container by 66px and clip the third, which is the Language School
   row; four or more is where the row genuinely has to page, and only then does
   the card size have to hold. Below 621px the floor always applies, because
   three cards abreast of a phone is not a row, it is a squeeze. */
@media (min-width: 621px) {
  .ox-grid:has(> .ox-step):not(:has(> :nth-child(4))) {
    grid-auto-columns: minmax(0, 1fr);
  }
}

.ox-grid:has(> .ox-step) > .ox-step {
  display: block;
  scroll-snap-align: start;
  margin: 0;
  padding: var(--space-lg);
  background: var(--oxford-white);
  border: 1px solid var(--oxford-hairline);
  border-top: 4px solid var(--ox-accent, var(--oxford-navy));
  border-radius: var(--oxford-radius-md);
  box-shadow: var(--oxford-shadow);
}

/* The three school colours in turn, so a row of steps reads as a set of
   distinct moments rather than one long block, exactly as the approach cards
   do. The cycle is written in threes so it carries past six. */
.ox-grid:has(> .ox-step) > .ox-step:nth-child(3n+1) { --ox-accent: var(--oxford-navy, #0d1045); }
.ox-grid:has(> .ox-step) > .ox-step:nth-child(3n+2) { --ox-accent: var(--ox-red-600, #b00d1e); }
.ox-grid:has(> .ox-step) > .ox-step:nth-child(3n+3) { --ox-accent: var(--oxford-royal, #1a227f); }

.ox-grid:has(> .ox-step) > .ox-step .ox-step__badge {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  font-size: 15px;
  margin-bottom: var(--space-sm);
}

.ox-grid:has(> .ox-step) > .ox-step :is(h3, h4) { font-size: 17px; margin: 0 0 6px; }
.ox-grid:has(> .ox-step) > .ox-step p { font-size: 15px; }

/* The step that carries the documents. A real list, because nine things a
   family has to gather are items to tick off, not a sentence to parse.

   Every card in the row is as tall as the tallest, so nine items down one
   column set the whole row to 449px and left 240px of nothing in the short
   steps. Two columns for a long list brings the row to 307px, which is close
   to what the other six need on their own. */
.ox-step__list {
  margin: 0;
  padding-left: 16px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ox-body);
}
.ox-step__list li { margin: 0 0 3px; break-inside: avoid; }
.ox-step__list li:last-child { margin-bottom: 0; }

/* Six items is where one column starts costing the row more than the second
   column costs the measure. A three item list stays a single column. */
@media (min-width: 621px) {
  .ox-step__list:has(li:nth-child(6)) { columns: 2; column-gap: 16px; }
}

/* On a phone the row stays a row, swiped rather than paged, so the two views
   are the same design. The card is sized off the viewport so the next one is
   always visibly half in frame and the gesture is discoverable. */
@media (max-width: 620px) {
  .ox-grid:has(> .ox-step) {
    --ox-flip-card: 76vw;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }
}

/* ---------------------------------------------------------------------------
   Enquiry form: red boxes that line up.

   Two fields per row, but "Student age / expected grade" and the phone label
   wrap to a second line while their neighbours do not, so the inputs beside
   them started 23px lower. Pinning the input to the bottom of its field makes
   every box in a row share a baseline whatever the label does, and a single
   height across input, select and date stops the three control types from
   rendering at 50, 46 and 52px.
   --------------------------------------------------------------------------- */
.ox-field { justify-content: space-between; }

.ox-field input,
.ox-field select,
.ox-field textarea {
  /* Red at rest fought the page - the theme spends red on CTAs and badges
     only. Navy at 45% is the same weight of line without the shout, and red
     stays for focus so the active field still announces itself. */
  border: 1.5px solid rgba(13, 16, 69, 0.45);
  box-sizing: border-box;
}

.ox-field input:not([type="checkbox"]):not([type="radio"]),
.ox-field select {
  height: 50px;
  padding: 10px 14px;
}

.ox-field textarea { min-height: 100px; }

.ox-field input:focus,
.ox-field select:focus,
.ox-field textarea:focus {
  border-color: var(--ox-fg-brand);
  box-shadow: 0 0 0 3px var(--ox-royal-subtle);
}

/* ---------------------------------------------------------------------------
   Campus contact cards: one line per detail, and links that look like links.

   At 15px every address ran to exactly the card's width and wrapped onto a
   second line, which broke the alignment between cards. 13px fits the longest
   of them - os.admisiones@oxfordreynosa.edu.mx - with room to spare. The phone
   and email were already real tel: and mailto: links, but inherited the body
   colour with no underline, so nothing said they were tappable.
   --------------------------------------------------------------------------- */
/* .ox-icon-card p sets 15px at (0,1,1), so a bare .ox-contact-line at (0,1,0)
   loses to it - scoping to the list wins on specificity without !important. */
.ox-contact-list .ox-contact-line { font-size: 13px; }

/* overflow-wrap stays as the theme set it: at 13px the longest current address
   fits on one line, but a longer one must still break rather than push out of
   the card, which is what that property is guarding against. */
.ox-contact-list .ox-contact-line a {
  text-decoration: underline;
  text-decoration-color: rgba(232, 23, 44, 0.35);
  text-underline-offset: 3px;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

.ox-contact-list .ox-contact-line a:hover,
.ox-contact-list .ox-contact-line a:focus-visible {
  color: var(--ox-fg-brand);
  text-decoration-color: var(--ox-fg-brand);
}

/* Below the desktop layout the campus cards keep their 40px padding, which on
   a 375px screen leaves only 194px for a 34-character address that needs 253.
   Reclaiming the padding and tightening the icon gap gives the address room to
   stay on one line down to tablet width. */
@media (max-width: 900px) {
  .ox-campus-contacts > .ox-icon-card,
  .ox-contact-cards > .ox-icon-card { padding: var(--space-md); }

  .ox-contact-list .ox-contact-line { gap: 8px; }
}

/* On the narrowest phones there is no honest way to fit that address on one
   line - it would need 11px type - so it wraps, and overflow-wrap keeps the
   break inside the card. */
@media (max-width: 430px) {
  .ox-contact-list .ox-contact-line { font-size: 12px; }
}


/* ---------------------------------------------------------------------------
   Nav: proportions after the type change.

   The link font now scales from 15.8px down to 12.5px, but the caret stayed a
   fixed 14px and the vertical padding a fixed 24px, so at the small end the
   arrow was oversized against its label and the bar stayed taller than the
   type needed. Both now scale with the text.
   --------------------------------------------------------------------------- */
.ox-nav__link { padding: clamp(18px, 1.5vw, 24px) 0; }

.ox-nav__caret svg {
  width: 0.85em;
  height: 0.85em;
}

/* ---------------------------------------------------------------------------
   Dropdowns: more glass.

   Kept inside the same @supports guard the theme uses - without a blur behind
   it, translucent panel text over a photograph is unreadable, so browsers that
   cannot blur keep the solid fill. The extra blur compensates for the lower
   opacity and holds the navy labels legible.
   --------------------------------------------------------------------------- */
@supports ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .ox-dropdown {
    background: rgba(255, 253, 249, 0.62);
    -webkit-backdrop-filter: blur(24px) saturate(1.5);
    backdrop-filter: blur(24px) saturate(1.5);
  }
}

/* ---------------------------------------------------------------------------
   Menus: no markers, no caret, more glass.

   1. The nested (third level) items carried a small dash before the label as a
      tree line. The indent already says "this belongs to the item above", so
      the dash was doing nothing the padding was not.
   2. The dropdown caret goes entirely. Beyond the look, it returns roughly 19px
      to each of the five dropdown items, which is most of the headroom the nav
      bar did not have.
   3. Panels drop to 50% opacity. The blur goes up again to carry it - at half
      opacity whatever is behind bleeds through hard, and the navy labels need
      the background flattened to stay readable.
   --------------------------------------------------------------------------- */
.ox-dropdown__link--nested::before { content: none; }

.ox-nav__caret { display: none; }

@supports ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .ox-dropdown {
    background: rgba(255, 253, 249, 0.5);
    -webkit-backdrop-filter: blur(30px) saturate(1.7);
    backdrop-filter: blur(30px) saturate(1.7);
  }
}

/* Top-level items read as separate categories rather than one run of words.
   Two longer titles - Language School & Extracurriculars, and Community
   Connection - now take 996px of label between them, so the old 3.9vw gap
   pushed the bar past the window. Tightened, with the breakpoint below doing
   the rest. */
.ox-nav { gap: clamp(20px, 2.4vw, 44px); }


/* The timetable's last column now carries two actions - trial, then enroll.
   They wrap rather than squeeze once the column gets narrow. */
.ox-tt-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

/* Accessibility: visible keyboard focus on every interactive element.
   Double ring (white inner, navy outer) so it survives on light and navy grounds.
   WCAG 2.4.7 Focus Visible / 2.4.11 Focus Not Obscured. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--ox-navy, #0D1045);
  outline-offset: 2px;
  border-radius: 3px;
}
/* Every dark ground needs to be listed here, because the default ring above is
   navy and navy-on-navy is 1.00 — an invisible focus indicator, not a faint
   one. The list was missing .ox-mobile-nav, which is the whole navigation on a
   phone: 56 links whose focus ring could not be seen at all. Also missing were
   the Meet the Team head band and the video hero. .has-navy-background-color is
   included so a band built from the WordPress preset rather than the theme
   modifier is covered without another round of this. */
.ox-section--navy :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
.ox-section.has-navy-background-color :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
.ox-tedx :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
.ox-mobile-nav :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
.ox-band--head :where(a, button):focus-visible,
.ox-video-hero :where(a, button):focus-visible,
.ox-nav :where(a, button):focus-visible,
.ox-dropdown :where(a, button):focus-visible,
footer :where(a, button):focus-visible {
  outline-color: #FFFFFF;
  box-shadow: 0 0 0 6px var(--ox-navy, #0D1045);
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---------------------------------------------------------------------------
   Buttons wrap on phones.

   `.ox-btn` is `white-space: nowrap` with `line-height: 1` (theme.css:361-377).
   That keeps every button a crisp single line on desktop, which is the intended
   shape, but it means a long label renders a box wider than the phone it sits on
   and there is no width at which it can recover. "See the Full Course Calendar,
   Fees and Enrolment Steps" measured 487px against a 343px container at 375px:
   left-aligned it was clipped 148px off the right, and once its row was centred
   it was clipped at both ends instead.

   Below 620px - the breakpoint the rest of this file already uses for phones -
   the label is allowed to wrap. `line-height` goes to 1.25 so two lines are not
   cramped, `text-align: center` centres the wrapped text inside the inline-flex
   box (the text becomes an anonymous flex item, so `justify-content` alone only
   centres the block, not the lines within it), and `max-width: 100%` caps the
   button at its container so no future label can overflow again.

   Desktop is deliberately untouched: above 620px `nowrap` still holds, so no
   existing button changes shape on the layouts they were designed for.
   --------------------------------------------------------------------------- */
@media (max-width: 620px) {
  .ox-btn {
    white-space: normal;
    line-height: 1.25;
    text-align: center;
    max-width: 100%;
  }
}

/* ---------------------------------------------------------------------------
   Leader card: let the institutional address wrap.

   Same failure the contact lines already had. An email is a single unbroken
   token with no break opportunity, so a flex/inline item will not shrink below
   its min-content width and the address simply runs past the card - which has
   `overflow-x: hidden`, so the tail is silently clipped rather than spilling
   visibly. Measured on Meet the Team: 7 of the 14 cards clipped at 1440px,
   11 of 14 at 375px, the worst being academicprincipal@oxfordreynosa.edu.mx
   at 282px inside a 251px body.

   `overflow-wrap: anywhere` gives the browser somewhere to break; `min-width: 0`
   lets the item shrink to its container; `inline-block` plus `max-width: 100%`
   keeps the wrapped block inside the card. The address grows to two or three
   lines - card height moves by at most 9px - and stays readable and selectable,
   which a `text-overflow: ellipsis` truncation would not.
   --------------------------------------------------------------------------- */
.ox-leader-card__body a[href^="mailto:"] {
  display: inline-block;
  max-width: 100%;
  min-width: 0;
  overflow-wrap: anywhere;
  /* Read as a link, not as body copy. `.ox-leader-card a` in theme.css paints
     these at 13px in --ox-body, which is the same grey as the biography beneath
     them - so a real mailto: link looked like plain text and nobody could tell it
     was clickable. Royal is the palette's documented link colour and measures
     13.3:1 on the card and 12.66:1 on cream. The underline carries the meaning on
     its own, so the affordance does not depend on colour alone. */
  font-size: 12px;
  color: var(--ox-fg-link);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.ox-leader-card__body a[href^="mailto:"]:hover,
.ox-leader-card__body a[href^="mailto:"]:focus-visible {
  /* red-600, not the brand --ox-red: the base crimson measures 4.36:1 on cream,
     under the 4.5:1 floor for text this size. red-600 gives 5.85:1. */
  color: var(--ox-fg-brand);
  text-decoration-thickness: 2px;
}

/* ---------------------------------------------------------------------------
   2b — Campus vice principals.

   The vice principals previously fell through into the Institutional Leadership
   carousel, which read as "everyone else" rather than as a peer group to the
   campus directors. They now get their own band, deliberately given the same
   treatment as `.ox-band--directors` - nude-300 ground, hairline border, red
   top rule and red label - so the two campus-facing groups read as a matched
   pair, with institutional leadership remaining visually distinct in navy.

   Declared separately rather than by adding a class to the directors selector,
   so either band can diverge later without disturbing the other.
   --------------------------------------------------------------------------- */
.ox-band--vice {
  background: var(--ox-nude-300);
  border: 1px solid var(--ox-navy-line);
  border-top: 3px solid var(--oxford-royal);
}
.ox-band--vice .ox-band__label { color: var(--ox-fg-brand); }
.ox-band--vice .ox-leader-card__role { color: var(--ox-fg-brand); }

/* ---------------------------------------------------------------------------
   Approach cards: continue the warm/cool cycle past the sixth card.

   The accent assignment in theme.css was written for exactly six cards, as
   `nth-child(1)` through `nth-child(6)`. The How We Teach practices run to ten,
   and every card past the sixth fell back to the default `--ox-red-600` - four
   crimson cards in a row, and the alternation the pattern exists to create
   quietly stopped.

   Restated as cycles, the sequence is identical for the first six and then
   continues indefinitely, so the grid can grow without anyone having to
   remember this rule.

   Two different cycles, because the column count changes what "alternating"
   means. At one and three columns (both odd) a simple six-step cycle alternates
   across and down. At two columns it does not - the seam between cycles would
   put two warm cards in the same column - so that breakpoint uses a four-step
   cycle, which produces a true checkerboard. For cards one to four it resolves
   to exactly the values the original two-column override set.
   --------------------------------------------------------------------------- */
.ox-approach-card:nth-child(4n+1) { --ox-accent: var(--oxford-navy); }
.ox-approach-card:nth-child(4n+2) { --ox-accent: var(--oxford-royal); }
.ox-approach-card:nth-child(4n+3) { --ox-accent: var(--oxford-royal); }
.ox-approach-card:nth-child(4n)   { --ox-accent: var(--oxford-body); }


/* ==========================================================================
   OXFORDIAN — FORMATTING PATCH 01 (3.148.0)
   Colour-contrast and alignment only. No copy, markup or new class names.
   Appended to blocks.css, which is enqueued after theme.css, so these
   single-class rules win on cascade order without !important.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. RED TEXT THAT FAILS WCAG AA
   --------------------------------------------------------------------------
   Measured, not assumed. --ox-red is #e8172c:

     on cream #fdf9f1 ............ 4.36:1   FAIL
     on card  #fffdf9 ............ 4.50:1   borderline
     on --ox-red-subtle over cream 3.84:1   FAIL  (red-subtle resolves to
                                                   #fbe7e1, so badges and
                                                   chips are the worst
                                                   offenders on the site)

     --ox-red-600 #c40b21 worst case 5.16:1  PASS
     --ox-red-700 #9e0017 worst case 7.14:1  PASS

   Only TEXT is changed below. Icon fills, borders, background washes and
   hover-only states are deliberately left alone — they are graphical objects
   judged at 3:1, and red-subtle backgrounds are unaffected by this patch.
   -------------------------------------------------------------------------- */

/* 1a. Utility bar — Mural Web Login / Schedule a Visit / Contact Us.
       12.5px 600 in --ox-red sitting on the cream topbar. */
.ox-util { color: var(--ox-fg-brand); }

/* 1b. Eyebrows and badges. 11px 700 uppercase — small, tracked-out, and the
       badge sits on red-subtle, so this is the lowest-contrast text on the
       site at 3.84:1. */
.ox-eyebrow { color: var(--ox-fg-brand); }
/* --navy is excluded: theme.css gives it navy on a navy wash, and this rule is
   the same one class weight in a later sheet, so without the :not() the base
   rule quietly repaints the modifier crimson on its own navy ground. */
.ox-badge:not(.ox-badge--navy) { color: var(--ox-fg-brand); }
.ox-chip--tag { color: var(--ox-fg-brand); }

/* 1c. Small caps labels and kickers, all 10–11px 700. */
.ox-card__kicker { color: var(--ox-fg-brand); }
.ox-leader-card__role { color: var(--ox-fg-brand); }
.ox-register-pair__tag { color: var(--ox-fg-brand); }

/* 1d. Data and metadata text, 12–18px. */
.ox-journey-card__age { color: var(--ox-fg-brand); }
.ox-number-card__unit { color: var(--ox-fg-brand); }
.ox-timeline__year { color: var(--ox-fg-brand); }
.ox-timeline--compact .ox-timeline__year { color: var(--ox-fg-brand); }
.ox-becas__unit { color: var(--ox-fg-brand); }

/* 1e. Subtitles and inline links. */
.ox-pillar-card__subtitle { color: var(--ox-fg-brand); }
.ox-pillar-row .ox-pillar-row__subtitle { color: var(--ox-fg-brand); }
.ox-link-arrow { color: var(--ox-fg-brand); }
.ox-modal__class { color: var(--ox-fg-brand); }

/* 1f. Phone numbers. Same reasoning as the standing email rule —
       a contact detail must not depend on a failing colour. */
.ox-contact-card__phone a { color: var(--ox-fg-brand); }

/* 1g. Error state. Error text on red-subtle is the one place where failing
       contrast has a functional cost, so this goes to the darkest step. */
.ox-form__status--error { color: var(--oxford-crimson-hover); }

/* NOT CHANGED, and why:
     .ox-tedx__ted      clamp(28px,4vw,40px) — large text, 3:1 applies, passes
     .ox-badge-heading  clamp(24px,2.6vw,34px) — large text, passes
     .ox-*__icon        svg fills / circular washes — graphical, 3:1
     *:hover            hover-only colour shifts, left for a separate pass so
                        this patch stays reviewable
     --ox-red token     left pointing at --ox-red-500; the brand crimson is
                        unchanged for fills, rules and artwork. Only text
                        instances are corrected. */


/* --------------------------------------------------------------------------
   2. UTILITY BAR VERTICAL ALIGNMENT
   --------------------------------------------------------------------------
   .ox-topbar__inner is align-items: center, but .ox-topbar__utils overrides
   itself to align-items: flex-start in both the base rule and the <=960px
   block. Each .ox-util is a column (icon above label) of a different height
   to the crest, so flex-start pins the links to the top of the row while the
   crest centres — which is why the links read as sitting too high next to a
   clipped-looking logo. Centring them aligns the two optically.
   -------------------------------------------------------------------------- */
.ox-topbar__utils { align-items: center; }

@media (max-width: 960px) {
  .ox-topbar__utils { align-items: center; }
}


/* --------------------------------------------------------------------------
   3. "SCHEDULE A VISIT" — WITHDRAWN (3.150.0)
   --------------------------------------------------------------------------
   Patch 01 rendered this item as a filled pill above 961px to give the
   admissions action more weight than the portal login beside it. Withdrawn at
   Andrea's request; the utility bar is three identical links again.

   Do not re-add it here. Making one utility item visually distinct is a
   visual-hierarchy change and belongs to the Reviewing Committee
   (governance.md), whatever Part 1 section 8 already says about Schedule a
   Visit being the primary action. If it is approved later it should ship as
   its own patch with its own version bump, not be revived inside this one.

   Kept from Patch 01, because both apply to all three items rather than to
   this one: the --ox-red-600 text colour (1a) and the align-items: center
   correction (2).
   -------------------------------------------------------------------------- */

/* ===================== END FORMATTING PATCH 01 ===================== */


/* ==========================================================================
   OXFORDIAN — FORMATTING PATCH 02 (3.149.0)

   Completes Patch 01. Three of its single-class colour rules were outweighed
   by more specific rules already in this file, so they computed but never
   applied. Same scope as Patch 01: colour only, no copy, no markup, no new
   class names, every value an existing token.
   ========================================================================== */

/* --------------------------------------------------------------------------
   4. BAND LABELS ON THE LEADERSHIP GRID
   --------------------------------------------------------------------------
   Patch 01 set .ox-leader-card__role, but `.ox-band .ox-leader-card__role`
   (line ~772) is display: none — the band heading already names the group, so
   all 14 roles are hidden and the rule changes nothing that is on screen.

   The text that IS on screen is .ox-band__label: 11px 700 uppercase at
   0.14em tracking. On --ox-band--directors and --vice it is --ox-red over
   --ox-nude-300, which resolves to cream #fdf9f1 — 4.36:1, the same failure
   Patch 01 documents for every other red label.

     --ox-red     #e8172c on cream  4.36:1  FAIL
     --ox-red-600 #c40b21 on cream  5.85:1  PASS

   Both existing rules are (0,2,0); these match on weight and win on order.
   --head (white on navy) and --institutional (navy on ivory) already pass and
   are left alone.
   -------------------------------------------------------------------------- */
.ox-band--directors .ox-band__label,
.ox-band--vice .ox-band__label { color: var(--ox-fg-brand); }


/* --------------------------------------------------------------------------
   5. BADGES RENDERED AS PARAGRAPHS
   --------------------------------------------------------------------------
   `p.ox-badge` (line ~1648) pins the crimson at (0,1,1) and outweighs Patch
   01's `.ox-badge` (0,1,0). Nine of the ten badges on the home page are
   paragraphs, so the badge fix reached only the one span — and badges sit on
   --ox-red-subtle, which is the 3.84:1 worst case Patch 01 set out to fix.

   The :not() is load-bearing, not defensive. `p.ox-badge--light` (line ~1672)
   is also (0,1,1); a bare `p.ox-badge` here would tie it and win on order,
   repainting the closing CTA's badge from white to dark red on navy. The
   light variant must not match this rule at all.
   -------------------------------------------------------------------------- */
p.ox-badge:not(.ox-badge--light) { color: var(--ox-fg-brand); }


/* --------------------------------------------------------------------------
   6. BADGES ON CAMPUS AND GRADE LEVEL PAGES
   --------------------------------------------------------------------------
   `.single-ox_campus .ox-badge` / `.single-ox_level .ox-badge` (line ~1467)
   are (0,2,0) and outweigh Patch 01 for the same reason. These are the badges
   that sit on a --ox-red-subtle wash, so this is the 3.84:1 case exactly.

   Neither post type currently renders a light badge; the :not() carries the
   same guard as rule 5 so that adding one later cannot silently break it.
   -------------------------------------------------------------------------- */
/* Was var(--ox-fg-brand). This rule is three classes and outranked every later
   attempt to darken the badge, so the level and campus pages kept the bright
   red on the red wash at 4.01 while every other page had moved to 5.39. A
   contrast patch that pins a colour is a contrast patch that has to be revised
   when the colour changes. */
.single-ox_campus .ox-badge:not(.ox-badge--light),
.single-ox_level .ox-badge:not(.ox-badge--light) { color: #C40B21; }

/* ===================== END FORMATTING PATCH 02 ===================== */

/* ==== Approach band, four cards (3.152.0) ====
   .ox-approach-grid is three columns from 980px, which leaves a four-card band
   with a single orphan on the last row, jammed to the left of a centred grid.
   A grid centres its tracks, never a short final row. Four cards go two-up
   instead, which divides evenly and reads as a distinct band from the six-card
   one beside it. */
@media (min-width: 980px) {
  .ox-approach-grid--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==== Flat white section bands (3.153.0) ====
   Andrea, 2 Sep 2026: no tinted bands. .ox-section--surface was a pale blue grey
   everywhere and cream on campus pages, so a page read as alternating stripes.
   Both go white, leaving the page one continuous surface separated by spacing
   alone. --ox-surface itself is untouched, because media wells, dropdown hovers
   and card media still use it. */
.single-ox_campus .ox-section--surface {
  /* Sections on this site render as rounded, shadowed cards sitting on the
     page, so they take the card colour rather than the page colour. That is
     not the alternating band Andrea removed: every section is the same warm
     white, one step lighter than the cream page, and they are separated by
     spacing and shadow rather than by a tint. */
  background: var(--ox-bg-surface);
}

/* A few sections carry a WordPress preset background class in their block
   markup, which wins over the rule above. Only the warm light presets are
   listed, so a deliberately dark section is never flattened. */
.ox-section.has-page-background-color,
.ox-section.has-surface-background-color,
.ox-section.has-nude-300-background-color,
.ox-section.has-nude-600-background-color,
.ox-section.has-nude-900-background-color,
.ox-container.has-page-background-color,
.ox-container.has-surface-background-color,
.ox-container.has-nude-300-background-color,
.ox-container.has-nude-600-background-color,
.ox-container.has-nude-900-background-color {
  /* WordPress emits its preset colour utilities with !important, so matching it
     is the only way to win. Scoped to .ox-section and to the warm light presets
     only, so block level colour choices elsewhere are untouched. */
  background-color: var(--ox-bg-surface) !important;
}

/* ==== Closing call to action bands go white (3.156.0) ====
   Andrea, 2 Sep 2026: page heroes keep the navy, the closing .ox-cta band does
   not. The band was navy with white type, so the background alone cannot change
   or the whole band goes invisible. Type, badge and the ghost button all move to
   the light palette with it. The primary crimson button already reads on white
   and is left alone. */
.ox-cta {
  background: var(--ox-bg-surface);
  color: var(--ox-body);
}
.ox-cta h2 { color: var(--ox-fg-strong); }
.ox-cta p { color: var(--ox-body); }

.ox-cta .ox-badge--light {
  background: var(--ox-royal-subtle);
  color: var(--ox-fg-link);
}

.ox-cta .ox-btn--ghost-light {
  background: transparent;
  color: var(--ox-fg-link);
  border-color: var(--ox-fg-link);
}
.ox-cta .ox-btn--ghost-light:hover,
.ox-cta .ox-btn--ghost-light:focus-visible {
  background: var(--ox-royal-subtle);
  color: var(--ox-fg-link);
  border-color: var(--ox-fg-link);
}


/* ==== Sections as white cards (3.157.0) ====
   Andrea, 2 Sep 2026: with every band white, spacing alone stopped separating
   them. Each section becomes its own card instead, 15px radius and a soft
   shadow, inset from the page edge so the corners are actually visible. Heroes
   are untouched: they stay full bleed and navy. */
.wp-site-blocks .entry-content > .ox-section,
.wp-site-blocks .entry-content > .ox-cta,
.wp-site-blocks main > .ox-section,
.wp-site-blocks main > .ox-cta {
  /* Top level sections only. A section nested inside another section's
     container would otherwise get a card inside a card, which is what happened
     to Schedule a Visit on Admissions. max-width:none stops is-layout-constrained
     pinning a section to the content width so it sits narrower than its
     neighbours. */
  width: auto;
  max-width: none;
  margin-inline: clamp(12px, 2.5vw, 36px);
  margin-block: clamp(10px, 1.4vw, 18px);
  border-radius: 15px;
  box-shadow: 0 1px 2px rgba(13, 16, 69, 0.05), 0 12px 32px -20px rgba(13, 16, 69, 0.35);
}

/* White ground, but NOT .ox-section--navy: that band carries white type, so
   whitening it made Oxfordian Achievements white on white. */
.wp-site-blocks .entry-content > .ox-section:not(.ox-section--navy),
.wp-site-blocks .entry-content > .ox-cta,
.wp-site-blocks main > .ox-section:not(.ox-section--navy),
.wp-site-blocks main > .ox-cta {
  background: var(--ox-bg-surface);
}

/* Top level blocks that are not sections, a stray badge or a card grid, take the
   same gutter so they line up with the cards rather than running wider.
   .ox-rail-span is excluded: theme.js only adds it to blocks that span both
   rail columns, and those are already sized by the grid track. Keeping the
   gutter on one gave the Meet the Team video hero width:100% of the track
   plus 36px either side, which hung 20px off the right of a 1440 viewport
   with no scrollbar to show for it, because .wp-site-blocks clips its own
   overflow. */
.wp-site-blocks .entry-content > *:not(article):not(.ox-page-hero):not(.ox-section):not(.ox-cta):not(.ox-hero):not(.ox-rail-span) {
  margin-inline: clamp(12px, 2.5vw, 36px);
}

/* ==== Remaining warm tints on components (3.162.0) ====
   Found by auditing the Our School dropdown page by page. The leadership bands
   on Meet the Team and the breadcrumb bar were still tinted. The bands keep
   their coloured top rule and hairline border, which is what actually
   distinguishes the groups, so nothing is lost by dropping the ground.
   .ox-band--head stays navy: it carries white type, like the page heroes.
   Media wells such as .ox-card__media and .ox-leader-card__photo are left alone,
   they sit behind photographs rather than reading as a coloured panel. */
.ox-band--directors,
.ox-band--institutional,
.ox-band--vice,
.ox-breadcrumb {
  background: var(--ox-bg-surface);
}


/* ==== Story bodies get the card too (3.163.0) ====
   Andrea, 3 Sep 2026: on every story page the body section sat flat while the
   Continue Exploring band below it was carded, so that card read as orphaned.
   A story body is wrapped in <article>, so entry-content > .ox-section never
   matched it.

   Two parts. First the <article> gives up the gutter it was picking up from the
   top level blocks rule, because it is a wrapper, not a band: its children are
   the things that should sit on the page grid. That also puts the story page
   hero on exactly the same edge as the hero of an ordinary page, where the hero
   is a direct child of .entry-content and takes no gutter. Then the section
   inside it gets the normal card, with the same gutter every other card uses,
   so the body card and the Continue Exploring card line up on both edges. */
.wp-site-blocks .entry-content > article,
.wp-site-blocks main > article {
  margin-inline: 0;
}

.wp-site-blocks .entry-content > article > .ox-section,
.wp-site-blocks main > article > .ox-section {
  width: auto;
  max-width: none;
  margin-inline: clamp(12px, 2.5vw, 36px);
  margin-block: clamp(10px, 1.4vw, 18px);
  border-radius: 15px;
  box-shadow: 0 1px 2px rgba(13, 16, 69, 0.05), 0 12px 32px -20px rgba(13, 16, 69, 0.35);
}

.wp-site-blocks .entry-content > article > .ox-section:not(.ox-section--navy),
.wp-site-blocks main > article > .ox-section:not(.ox-section--navy) {
  background: var(--ox-bg-surface);
}


/* ==== Stories search box (3.166.0) ====
   Andrea, 4 Sep 2026: a core search block on the news pages, scoped to stories
   by the hidden post_type field that inc/page-content.php adds. Core ships it
   square and grey, so it is restyled here to the pill the jump nav buttons
   already use: white ground, hairline border, royal button. Capped in width
   because a search field stretched across the full container reads as a form,
   not as a search. */
.ox-search--stories {
  max-width: 420px;
  margin-block-end: clamp(14px, 1.6vw, 20px);
}

.ox-search--stories .wp-block-search__inside-wrapper {
  background: var(--ox-bg-surface);
  border: 1px solid rgba(13, 16, 69, 0.14);
  border-radius: 999px;
  padding: 3px 3px 3px 8px;
}

.ox-search--stories .wp-block-search__inside-wrapper:focus-within {
  border-color: var(--ox-fg-link);
  box-shadow: 0 0 0 3px var(--ox-royal-subtle);
}

.ox-search--stories .wp-block-search__input {
  background: transparent;
  border: 0;
  color: var(--ox-fg-strong);
  font-size: 15px;
  padding: 9px 10px;
}

.ox-search--stories .wp-block-search__input:focus {
  outline: 0;
}

.ox-search--stories .wp-block-search__input::placeholder {
  color: var(--ox-body);
  opacity: 1;
}

.ox-search--stories .wp-block-search__button {
  background: var(--ox-royal);
  border: 0;
  border-radius: 999px;
  color: var(--ox-white);
  /* 44px keeps the tap target at the accessible minimum on a phone, where the
     button was coming out at 43. */
  min-height: 44px;
  min-width: 44px;
  padding: 9px 15px;
}

.ox-search--stories .wp-block-search__button svg {
  fill: currentColor;
}

.ox-search--stories .wp-block-search__button:hover,
.ox-search--stories .wp-block-search__button:focus-visible {
  background: var(--ox-navy);
}


/* Result count under the stories search, written in by theme.js. */
.ox-search__status {
  color: var(--ox-body);
  font-size: 14px;
  margin: -8px 0 14px;
}


/* The typed words, marked where they occur in a matching card. Bold carries the
   emphasis so it still reads on the dark featured cards, where the tint alone
   would be invisible; colour is inherited, so nothing can fail contrast. */
mark.ox-hit {
  background: var(--ox-royal-subtle);
  border-radius: 3px;
  color: inherit;
  font-weight: 700;
  padding: 0 1px;
}


/* Why a story is in the list when the words matched its category rather than
   anything printed on the card. */
.ox-hit-why {
  background: var(--ox-royal-subtle);
  border-radius: 999px;
  color: var(--ox-fg-link);
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
  padding: 3px 9px;
  text-transform: uppercase;
}

/* ==== Dark mode: surfaces that were still pinned light (3.175.0) ====
   The dropdown panel painted a translucent cream, which stays cream whatever
   the theme, while its links had already moved to --ox-fg-strong. Light text on
   a light panel measured 2.96. The panel now follows the surface token, so it
   darkens with everything else and the links keep their contrast. */
.ox-theme-dark .ox-dropdown,
.ox-theme-auto .ox-dropdown {
  background: var(--ox-bg-surface);
  border-color: var(--ox-line-subtle);
}

/* ==== Release notes (3.176.0) ====
   An ordered list, because the entries are in a deliberate order and a screen
   reader should say so. Each entry is an article with its own heading and a
   machine readable <time>. The date sits under the headline rather than beside
   it: scanning down a column of headlines is the common case, and the date is
   what you check once you have found the entry you want. */
.ox-releases {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ox-release {
  border-top: 1px solid var(--ox-line-subtle);
  padding-block: clamp(16px, 2vw, 22px);
}

.ox-release:first-child {
  border-top: 0;
}

.ox-release__head {
  color: var(--ox-fg-strong);
  font-size: clamp(17px, 1.5vw, 19px);
  line-height: 1.3;
  margin: 0;
}

.ox-release__meta {
  margin: 4px 0 0;
}

.ox-release__meta time {
  color: var(--ox-fg-muted);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.ox-release__body {
  color: var(--ox-fg-default);
  margin: 8px 0 0;
  max-width: 68ch;
}

@media (min-width: 780px) {
  /* On a wide screen the date moves into its own column so the headlines line
     up as a single scannable edge. */
  .ox-release {
    display: grid;
    grid-template-columns: 168px minmax(0, 1fr);
    column-gap: clamp(16px, 2vw, 28px);
  }

  .ox-release article {
    display: contents;
  }

  .ox-release__meta {
    grid-column: 1;
    grid-row: 1 / span 2;
    margin: 0;
  }

  .ox-release__head {
    grid-column: 2;
  }

  .ox-release__body {
    grid-column: 2;
  }
}

/* ==== Navigation: keyboard parity with the mouse (3.177.0) ====
   The dropdown was revealed only by .is-open, which theme.js sets on focusin.
   That declaration was losing in the cascade, so a keyboard user could focus a
   top level item, hear "expanded", and still see and reach nothing: the panel
   stayed visibility:hidden, which also takes its 36 links out of the tab order.

   This sits in blocks.css, after theme.css, and adds :focus-within so the panel
   opens from the keyboard with no JavaScript at all. .is-open is kept so the
   mouse path and the Escape handler still work. The child combinator stops a
   nested panel opening its parent's. */
.ox-nav__item--has-menu:focus-within > .ox-dropdown,
.ox-nav__item--has-menu.is-open > .ox-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

/* The panel is absolutely positioned, so while it is closed it must not eat
   pointer events over the content beneath it. */
.ox-nav__item--has-menu > .ox-dropdown {
  pointer-events: none;
}

.ox-nav__item--has-menu:focus-within > .ox-dropdown,
.ox-nav__item--has-menu.is-open > .ox-dropdown {
  pointer-events: auto;
}

/* ==== Navigation sub headings (3.179.0) ====
   Our School holds ten links, which read as one undifferentiated column. Three
   labelled groups let the eye land on a section first and a link second. The
   label is a plain paragraph, not a heading element: it sits inside a
   role="group" that already carries the same text as its accessible name, so a
   heading here would announce the label twice. */
/* The panel is rgba(255,253,249,.5) over the royal header, so its real ground
   is a mid lavender near rgb(140,144,188) - neither white nor royal. The links
   are navy and sit at 5.78 on it, but --ox-fg-muted (#626780) is 1.81, so Our
   School's two group labels have never been readable and 3.213.0 added nine
   more. The label takes the same navy as the links; 11px, 700 and uppercase
   already separate it from them, so it does not need to be lighter as well.
   Measure this panel by blending its own alpha over the header, not by walking
   past it to the first opaque ancestor - that reads royal and inverts the fix. */
.ox-dropdown--grouped {
  min-width: 300px;
}

.ox-dropdown__group + .ox-dropdown__group {
  border-top: 1px solid var(--oxford-hairline);
  margin-top: 6px;
  padding-top: 6px;
}

.ox-dropdown__group-label {
  color: var(--ox-fg-strong);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  margin: 4px 0 2px;
  padding-inline: var(--space-sm);
  text-transform: uppercase;
}

/* ==== Back to top (3.179.0) ====
   All News Stories runs to roughly twenty three screens. The control only
   appears once the reader is well past the fold, so short pages never show it,
   and it sits clear of the right hand edge on a phone. */
.ox-to-top {
  position: fixed;
  right: clamp(12px, 3vw, 28px);
  bottom: clamp(12px, 3vw, 28px);
  z-index: 120;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 16px;
  border: 1px solid var(--ox-line-subtle);
  border-radius: 999px;
  background: var(--ox-bg-surface);
  color: var(--ox-fg-strong);
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 6px 24px -8px rgba(13, 16, 69, 0.35);
  cursor: pointer;
  opacity: 0;
  transform: translateY(8px);
  visibility: hidden;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
}

.ox-to-top.is-visible {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
}

.ox-to-top:hover,
.ox-to-top:focus-visible {
  border-color: var(--ox-focus-ring);
}

@media (prefers-reduced-motion: reduce) {
  .ox-to-top {
    transition: none;
  }
}

/* ============================================================================
   Sticky section menu (3.180.0)
   ----------------------------------------------------------------------------
   The jump nav already listed the sections but scrolled away with the page, so
   it was only useful in the first screen. It now pins under the header, which
   is itself sticky at 0 and 89px tall.

   Both heights are published as custom properties by theme.js rather than hard
   coded, because the header is 89px on a desktop and shorter on a phone, and an
   anchor that lands underneath a sticky bar is worse than no anchor at all.
   ========================================================================== */
:root {
  --ox-header-h: 89px;
  --ox-jumpnav-h: 0px;
}

.ox-jumpnav {
  position: sticky;
  top: var(--ox-header-h);
  /* Under the header at 200, over the page cards. */
  z-index: 190;
}

/* Stuck, the row has to stay on one line. Ten categories wrapped to three rows
   is 288px of viewport, a third of a laptop screen, so it scrolls sideways
   instead and script keeps the active chip in view. */
.ox-jumpnav .ox-jumpnav__row {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  padding-bottom: 4px;
  scroll-behavior: smooth;
}

.ox-jumpnav .ox-jumpnav__btn {
  flex: 0 0 auto;
}

.ox-jumpnav__btn.is-current {
  background: var(--ox-bg-accent-subtle);
  border-color: var(--ox-focus-ring);
  color: var(--ox-fg-link);
}

/* Anchor targets clear the header and the menu, or a heading lands behind both.
   The theme's flat 96px only ever cleared the header. */
.wp-site-blocks main [id] {
  /* html already carries scroll-padding-top: 112px, which reserves room for the
     sticky header on every jump. Adding the header height again here stacked
     the two: a jump landed 217px down and left a quarter of the previous
     section on screen. This adds only what scroll-padding cannot know about,
     the height of the jump nav when it is a bar above the content. Beside the
     content as a rail it steals no vertical space and the variable is 0. */
  scroll-margin-top: var(--ox-jumpnav-h, 0px);
}

@media (prefers-reduced-motion: reduce) {
  /* The theme sets scroll-behavior: smooth on html unconditionally. Someone who
     has asked their system for less motion should not be flung down the page. */
  html,
  .ox-jumpnav .ox-jumpnav__row {
    scroll-behavior: auto;
  }
}


/* ---- Sticky menu: bar proportions (3.180.1) ----
   The jump nav lives in a normal .ox-section, whose 56px of vertical padding is
   right for a content band and wrong for a pinned bar: with the stories search
   sharing the container it came to 234px, which under an 89px header is a third
   of a laptop screen of furniture before any content. Tightened to chrome
   proportions, and above 900px the search and the chips share one line. */
.ox-jumpnav {
  padding-block: 10px;
}

.ox-jumpnav .wp-block-search {
  margin-bottom: 10px;
}

@media (min-width: 961px) {
  .ox-jumpnav .ox-container {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: clamp(12px, 2vw, 24px);
  }

  .ox-jumpnav .wp-block-search {
    flex: 0 0 300px;
    max-width: 300px;
    margin-bottom: 0;
  }

  .ox-jumpnav .ox-jumpnav__row {
    /* Basis 0, not auto. With auto the row is sized from its content, which is
       1440px of chips, so it could never sit beside the 300px search and wrapped
       to its own line, putting the bar back to 146px. Basis 0 makes it take the
       space that is left, and min-width:0 lets it shrink below its content so
       the chips scroll sideways instead of widening the bar. */
    flex: 1 1 0;
    min-width: 0;
  }

  /* The result count drops to its own line when the search puts it there. */
  .ox-jumpnav .ox-search__status {
    flex: 1 0 100%;
    margin: 0;
  }
}


/* ---- Sticky menu: the phone layout (3.180.3) ----
   Below 560px the jump nav was designed as a full width vertical list, which is
   a fine shape for a static block at the top of a page and an impossible one for
   a pinned bar: ten stacked buttons came to 654px, 92% of a phone screen. Inside
   .ox-jumpnav the row goes back to a horizontal scroller at every width. The two
   class selectors outrank the single class rules in the max-width query, so no
   !important is needed. */
.ox-jumpnav .ox-jumpnav__row {
  flex-direction: row;
}

.ox-jumpnav .ox-jumpnav__btn {
  width: auto;
}

@media (max-width: 899px) {
  /* Stacked, the bar carries the search above the chips, so it earns its height
     back by giving up the roomier padding. */
  .ox-jumpnav {
    padding-block: 8px;
  }

  .ox-jumpnav .wp-block-search {
    margin-bottom: 8px;
  }
}


/* ---- Sticky menu: reachable start (3.180.4) ----
   The chip row is centred, which is right while it wraps and wrong the moment it
   becomes a scroller: a centred flex container spills its overflow off BOTH
   edges, and in a left to right document scrollLeft cannot go below zero, so the
   first chips sat at left:-628px and could never be scrolled back to. Anchored
   to the start, the whole row is reachable. */
.ox-jumpnav .ox-jumpnav__row {
  justify-content: flex-start;
}


/* ============================================================================
   Our Impact: square tiles (3.181.0)
   ----------------------------------------------------------------------------
   Andrea, 4 Sep 2026. The ten figures sat in a core grid group that gave them
   619x211 each, two to a row over five rows: 1443px, 14% of the whole home page
   for what is meant to be a glance. Five to a row as true squares brings it
   under 600px and makes the set read as one block of numbers.

   The container is a core "is-layout-grid" group whose own generated class
   carries the column rule, so this overrides it on specificity rather than
   with !important.
   ========================================================================== */
.ox-numbers .wp-block-group.is-layout-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(10px, 1.2vw, 16px);
}

.ox-numbers .ox-numbers-stat {
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: clamp(8px, 1.2vw, 16px);
  text-align: center;
}

/* The figure and its caption have to live inside a square, so they step down
   with the tile instead of keeping the full-width type scale. */
/* Fixed sizes, not viewport-derived: the tile stays roughly 155-240px at every
   breakpoint because the column count steps down with the width, so type scaled
   off vw shrank to 10px on tablet inside a tile that had barely changed size. */
.ox-numbers .ox-numbers-stat__value {
  font-size: 30px;
  line-height: 1.05;
  margin: 0;
}

.ox-numbers .ox-numbers-stat__label {
  font-size: 12px;
  line-height: 1.3;
  margin: 0;
  text-wrap: balance;
}

@media (max-width: 1080px) {
  .ox-numbers .ox-numbers-stat__value { font-size: 26px; }
}

.ox-numbers .ox-numbers-stat svg,
.ox-numbers .ox-numbers-stat img {
  max-height: 26px;
  width: auto;
}

@media (max-width: 1080px) {
  .ox-numbers .wp-block-group.is-layout-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .ox-numbers .wp-block-group.is-layout-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* The tile is only about 135px here and the longest caption runs 62
     characters, which left four pixels of slack. Stepping the type down buys
     back roughly fifteen so a copy edit cannot overflow the square. */
  .ox-numbers .ox-numbers-stat__value { font-size: 24px; }
  .ox-numbers .ox-numbers-stat__label { font-size: 11px; line-height: 1.25; }
  .ox-numbers .ox-numbers-stat svg { max-height: 20px; }
}

/* Below about 360px the column is only ~107px and the longest captions need
   more height than that, so aspect-ratio quietly loses to the content and the
   tiles come out ragged: 107x107 beside 107x158. Dropping the square here lets
   the grid stretch every tile in a row to one height, which is tidier than a
   square that is not actually square. */
@media (max-width: 360px) {
  .ox-numbers .ox-numbers-stat {
    aspect-ratio: auto;
    min-height: 107px;
  }
}

/* ============================================================================
   Sticky section menu: brand and accessibility alignment          (3.182.0)
   ----------------------------------------------------------------------------
   The sticky menu shipped functional but off-system. Measured against the
   guidelines and the palette scales:

     chip border  #e2e6f0 on white   1.25:1   needs 3:1  (WCAG 1.4.11)
     active chip  royal at 8%        1.16:1   invisible
     hover fill   --ox-red           4.58:1   guidelines require --ox-red-600

   Every colour below is an existing palette token. Nothing new is introduced.
   ========================================================================== */

/* Resting chip. navy-300 is the lightest step on the navy scale that clears the
   3:1 floor for a control boundary (3.58:1 on white); navy-line at 1.25:1 left
   the chips reading as bare text rather than buttons. */
.ox-jumpnav .ox-jumpnav__btn {
  border-color: var(--ox-navy-300);
  background: var(--ox-bg-surface);
  color: var(--ox-fg-strong);
  font-family: var(--font-display);
  font-weight: 600;
}

/* Hover and pointer-focus. The guidelines are explicit that the base crimson is
   not to be used for this: --ox-red-600 carries white at 6.15:1 where --ox-red
   manages only 4.58:1. */
.ox-jumpnav .ox-jumpnav__btn:hover {
  background: var(--oxford-royal);
  border-color: var(--oxford-royal);
  color: #fff;
}

/* Keyboard focus gets its own ring rather than borrowing the hover fill, so the
   two states stay tellable apart. Royal at 13.3:1 is unmistakable, and the
   offset keeps the ring clear of the pill's own border. */
.ox-jumpnav .ox-jumpnav__btn:focus-visible {
  outline: 3px solid var(--ox-royal-500);
  outline-offset: 2px;
}

/* Scrolled-into-view chips should not sit flush against the cut edge. */
.ox-jumpnav .ox-jumpnav__row {
  scroll-padding-inline: 16px;
}

/* ---- Back to top ---------------------------------------------------------
   Same three corrections: the display face, a boundary that clears 3:1, and a
   hover that actually fills rather than only tinting the border. */
.ox-to-top {
  border-color: var(--ox-navy-300);
  font-family: var(--font-display);
  font-weight: 600;
}

.ox-to-top:hover {
  background: var(--ox-navy-500);
  border-color: var(--ox-navy-500);
  color: #fff;
}

.ox-to-top:focus-visible {
  outline: 3px solid var(--ox-royal-500);
  outline-offset: 2px;
}

/* ---- The current section chip -------------------------------------------
   This is the one state the whole component exists to communicate, and the
   only open design decision left in this block.

   Available and already verified against white:
     --ox-navy-500   #0d1045   white text 17.84:1   primary brand
     --ox-royal-500  #1a227f   white text 13.30:1   secondary brand
     --ox-red-600    #c40b21   white text  6.15:1   already used by :hover
     --ox-tan / --ox-nude-*    warm accents, all under 1.2:1 as a fill

   Whatever it becomes has to stay tellable apart from the red-600 hover, and
   the pill keeps its 999px radius and 46px min-height. */
.ox-jumpnav .ox-jumpnav__btn.is-current {
  background: var(--ox-navy-500);
  border-color: var(--ox-navy-500);
  color: #fff;
}

/* ============================================================================
   Section menu: embedded left column                              (3.184.0)
   ----------------------------------------------------------------------------
   Replaces the floating rail. That version was fixed to the viewport and had
   to be given room by indenting the page; Andrea asked for it embedded in the
   page instead, down the left, starting after the introduction and running the
   length of the content.

   Two measurements made this safe. Every section below the introduction is
   flat white with no background image, so putting them in a column costs no
   full bleed artwork. And .alignfull here uses positive 36px margins rather
   than a negative margin breakout, so a grid column contains the sections
   cleanly instead of them escaping it.

   .entry-content becomes two columns. Everything above the first section the
   menu points at keeps the full width, which is the hero, the menu itself and
   the introduction. From that section down, the menu takes the left column and
   content takes the right, and the menu spans to the end of the page so that
   sticky has the whole length to travel. The script assigns the rows, because
   which section comes first differs per page.

   Below 1100px there is not enough width for two columns and the menu returns
   to the horizontal bar it already was.
   ========================================================================== */
/* ---- The rail as a vertical navigation panel --------------------------- */
/* Andrea asked for the Clarity vertical nav pattern, in our typeface and our
   palette. What is taken from Clarity is the structure, not the skin:

     - one flush full width row per destination, no pills and no gaps
     - a glyph in a fixed gutter on the left of every row
     - the current row marked by a bar on the leading edge, not by a fill
     - a control at the top that collapses the panel to its icons

   What is ours: Montserrat rather than Clarity's Metropolis, the warm surfaces,
   and the navy/royal/crimson steps. Clarity's 36px row is below the 44px tap
   floor in DESIGN-PRINCIPLES.md, so the rows are 44px here. Clarity's square
   panel becomes a 12px radius because every other card on this site has one,
   and a single square element in a rounded page reads as a mistake rather than
   as a quotation.

   The column width is a custom property so that collapsing the panel actually
   returns the space to the text, rather than leaving a 230px gutter with a
   narrow panel floating in it. */
@media (min-width: 1100px) {
  .ox-has-rail .ox-rail-flow {
    display: grid;
    grid-template-columns: var(--ox-rail-w, clamp(190px, 16vw, 244px)) minmax(0, 1fr);
    column-gap: clamp(18px, 2vw, 36px);
  }

  /* min-width:0 or a long word inside a section refuses to let the column
     shrink and the menu gets pushed off the right edge. */
  .ox-has-rail .ox-rail-flow > * {
    grid-column: 2;
    min-width: 0;
  }

  /* Above the first linked section, content still runs the full width. */
  .ox-has-rail .ox-rail-flow > .ox-rail-span {
    grid-column: 1 / -1;
  }

  .ox-has-rail .ox-rail-flow > .ox-jumpnav {
    grid-column: 1;
    align-self: start;
    position: sticky;
    top: calc(var(--ox-header-h) + 20px);
    z-index: 5;
    margin: 36px 0 0 36px;
    padding: 0;
    background: none;
    box-shadow: none;
    border: 0;
    max-height: calc(100vh - var(--ox-header-h) - 48px);
  }

  /* The container is the panel. It was a bare wrapper before, with the border
     on the link list; the collapse control has to sit inside the same boundary
     as the links it collapses, so the boundary moves out one level. */
  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-container {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background: var(--ox-bg-surface);
    border: 1px solid var(--ox-navy-300);
    border-radius: 12px;
    max-height: calc(100vh - var(--ox-header-h) - 60px);

    /* All News Stories centres this container. In a column flex box that is a
       *horizontal* alignment, and combined with the row's inherited width:100%
       and auto side margins it pushed the whole list 50px right and 49px past
       the panel, where overflow:hidden quietly cut the end off four of the ten
       labels. Nothing reported it: no scrollbar, no console, just "Achievement"
       and "Love Foundatio". Stretch is the only correct value here. */
    align-items: stretch;
  }

  /* The panel heading. This was a button that collapsed the rail to its icons
     until 3.196.0; with the glyphs gone there was nothing to collapse to, so it
     is a label again and the script no longer builds a control. */
  /* The "On this page" heading is gone at Andrea's request. The panel keeps its
     accessible name: the <nav> carries aria-label="On this page", so a screen
     reader still announces what the list is even though nothing is drawn. */

  /* ---- The link list ------------------------------------------------------
     Flush rows inside the panel, so the panel's own border is the only
     boundary. Common Region: one shared outline groups the set, and a border
     on each child would group nothing while adding nine more lines. */
  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-jumpnav__row {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 0;
    padding: 0;
    overflow-x: visible;
    overflow-y: auto;
    background: none;
    border: 0;
    border-radius: 0;

    /* The horizontal bar gives this row flex:1 so it fills the width. Inherited
       into a column, flex-basis:0 is a *height* of zero, and the panel around it
       has no definite height to distribute back, so the whole list collapsed to
       a 0px strip under the toggle with all eight links still in it. Reset the
       basis to the content, and allow shrinking to nothing so the scroller can
       take over on a short viewport. */
    flex: 0 1 auto;
    min-height: 0;

    /* Neutralise the horizontal bar's own sizing, which centres a 100% wide row
       inside its container. Down the rail the row is the container. */
    width: auto;
    max-width: 100%;
    margin: 0;
    align-self: stretch;
  }

  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-jumpnav__btn {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 44px;
    justify-content: flex-start;
    text-align: left;
    white-space: normal;
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ox-fg-strong);
    background: none;
    border: 0;
    border-radius: 0;
    border-left: 3px solid transparent;
    box-shadow: none;
    transform: none;
    transition: background-color 140ms ease, color 140ms ease;

    /* Escape hatch. Wrapping handles "Innovation & Science"; a single word
       longer than the column has nowhere to wrap and would be cut by the
       panel's own overflow with nothing to show that it had been. */
    min-width: 0;
    overflow-wrap: break-word;
  }

  /* Colour is not the only affordance: the row fills and the label underlines.
     --ox-red-600 rather than the brand crimson, which is 4.36:1 on cream. */
  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-jumpnav__btn:hover {
    background: var(--ox-surface);
    color: var(--oxford-royal);
    border-color: transparent;
    box-shadow: none;
    transform: none;
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-jumpnav__btn:focus-visible {
    outline: 3px solid var(--ox-royal-500);
    outline-offset: -3px;
  }

  /* The current row. Three cues, none of them colour alone: the leading bar,
     the weight, and the filled ground. Royal is 13.3:1 on this surface. */
  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-jumpnav__btn.is-current {
    background: var(--ox-surface);
    color: var(--ox-royal-500);
    border-left-color: var(--oxford-royal);
    font-weight: 700;
    text-decoration: none;
  }

  /* The search box on All News Stories sits above its category list. */
  .ox-has-rail .ox-rail-flow > .ox-jumpnav .wp-block-search {
    margin: 10px 10px 6px;
    max-width: none;
    flex: none;
  }

  .ox-has-rail .ox-rail-flow > .ox-jumpnav .ox-search__status {
    margin: 0 10px 8px;
  }
}

/* ============================================================================
   Icons off in menus, buttons and cards                            (3.196.0)
   ----------------------------------------------------------------------------
   Andrea's call: the glyphs come out of the three families she named. They are
   hidden rather than stripped from the markup, because this is a judgement
   about how the site should look and it should be one line to reverse, not a
   redeploy of every block renderer. Every one of them is aria-hidden already,
   so nothing is lost to a screen reader either way.

   What stays, and why: an icon that IS the control rather than decoration on
   it. Removing these would take away the only thing telling you the control is
   there.

     .ox-nav__caret          says a nav item opens a submenu
     .ox-mobile-nav__toggle  the hamburger, and .ox-icon-menu / .ox-icon-close
     .ox-modal__close        the only way out of the dialog by pointer
     .ox-campus-row__chevron says the row expands
     .ox-footer__social      the mark *is* the link; hiding it empties the link
     .ox-checklist           a tick carries meaning a bullet would not
     .ox-contact-line        marks which line is phone, which is post
   ========================================================================== */
.ox-jumpnav__ico,
.ox-btn > svg,
.ox-link-arrow,
.ox-card__foot svg,
.ox-icon-card__icon,
.ox-journey-card__icon,
.ox-campus-row__icon {
  display: none !important;
}

/* Two exceptions, at Andrea's request on 5 Sep: the utility links above the
   menu and the Impact figures. Both are sets where the glyph is the thing that
   tells them apart at a glance — three short links in a row, and ten squares
   carrying nothing but a number. */
.ox-util__icon,
.ox-numbers-stat__icon {
  display: grid !important;
}

/* One consequence to tidy. The utility links were an icon above a label, and
   below 960px each was capped at 8.5ch to keep those icon columns narrow. With
   the glyph gone that cap only wraps two of the three labels, so the row reads
   as a ragged block instead of a line of links. */
@media (max-width: 960px) {
  .ox-topbar__utils { align-items: baseline; }
  .ox-util { max-width: none; }
  .ox-util__label { white-space: nowrap; }
}

/* ============================================================================
   Grade level hero: focal point for portrait framed photos          (3.183.1)
   ----------------------------------------------------------------------------
   The level hero renders at roughly 2.35:1 and crops the featured image with
   object-fit: cover, centred. That is right for the landscape photos the other
   levels use, and wrong for a photo composed as a portrait.

   The Middle School cover is 1000x750 with the students' heads starting at
   y=62. At the hero's ratio only a 426px band of those 750px is visible, so a
   centred crop starts at y=162 and takes the tops of their heads off. The
   offset available is 750-426=324px, so an object-position of 10% starts the
   band at y=32 and clears them with about thirty pixels to spare.

   Scoped to the one page rather than applied to every level, because the same
   bias would push the other five landscape photos off their own subjects. Any
   future level given a portrait framed cover needs its own line here; the
   figure to check is where the subject starts against the visible band.
   ========================================================================== */
body.postid-281 .ox-page-hero__img {
  object-position: 50% 10%;
}

/* ============================================================================
   Utility bar: stop clipping the third link on small phones     (3.184.1)
   ----------------------------------------------------------------------------
   Measured at 320px: the crest takes 159px and the three utility links 136px,
   so with the 12px gap the row wants 307px inside a 248px container. It is
   held on one line by flex-wrap: nowrap, and .wp-site-blocks carries
   overflow: clip, so the overflow is not a scrollbar. Contact Us simply has
   its edge cut off with nothing to indicate it.

   The labels are already 9.5px and the targets 28 to 46px wide, which is at
   the floor for a tap target, so shrinking further would trade a visible
   failure for an unusable one. The row wraps instead: crest on one line, the
   three links centred beneath it. Costs about 40px of height below 400px and
   nothing above it.
   ========================================================================== */
@media (max-width: 400px) {
  .ox-topbar .ox-topbar__inner {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 10px;
  }

  .ox-topbar .ox-topbar__utils {
    width: 100%;
    margin-left: 0;
    justify-content: center;
  }
}

/* ============================================================================
   Grade levels row: six stages, not five plus an orphan          (3.189.0)
   ----------------------------------------------------------------------------
   The journey row is a five column grid and the school has six stages, so
   College Readiness sat alone on a second row looking like an afterthought
   rather than the end of the sequence.

   Three columns over two even rows rather than six across one: at the width
   this row actually gets, six columns leaves each card 150px, where three
   gives 321px and the cards stay readable.

   Scoped to a row that has exactly six children, so a five stage row
   elsewhere keeps its single line.
   ========================================================================== */
@media (min-width: 900px) {
  .ox-journey-row:has(> :nth-child(6)):not(:has(> :nth-child(7))) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* ============================================================================
   Component surfaces: keep the boundary doing the work            (3.192.0)
   ----------------------------------------------------------------------------
   Warming --ox-surface put the stat tiles in the same family as the cream
   page, which is the point, but it also cut how far they separate from it:
   1.11 against white, 1.04 against cream. A tile that is a hundredth of a
   step from its background is being defined by its border, and that border
   was --ox-navy-line at 1.25:1.

   Not a WCAG failure, since a statistic is not a control, but the same
   reasoning as the section menu chips: if the boundary is what says "this is
   a tile", the boundary has to be visible. navy-300 measures 3.27:1 on the
   warm surface, so the tile reads as a tile without needing a colour step.
   ========================================================================== */
.ox-numbers-stat,
.ox-curriculum-card,
.ox-cta-box {
  border-color: var(--ox-navy-300);
}

/* ============================================================================
   Oxfordian unified website theme — component rules                (3.197.0)
   ----------------------------------------------------------------------------
   The palette is applied through the token layer in theme.css; this is the part
   the specification states as component behaviour rather than as colour, plus
   the three places where following it literally would have broken an
   accessibility floor. Those are called out where they occur.
   ========================================================================== */

/* ---- Section grounds ------------------------------------------------------
   Pure White stays the default readable surface. Ice Blue is the informational
   ground, Light Coral the occasional editorial one — used where a section asks
   for it, never as an alternating stripe. */
.ox-section--surface { background: var(--oxford-white); }
.ox-section--coral   { background: var(--oxford-red-wash); } /* the red wash, the only tint the palette gives a surface */

/* Body copy takes --ox-body (#626780), not navy. The previous specification
   pinned it to Deep Navy; the official palette gives paragraphs and secondary
   labels their own value and navy keeps the headings and structure. */
.ox-section--surface,
.ox-section--coral { color: var(--ox-body); }

/* ---- Buttons --------------------------------------------------------------
   One crimson CTA to a section. Everything else is royal or a quiet outline, so
   the red keeps meaning what it says it means. */
.ox-btn--primary,
.ox-btn.wp-element-button.ox-btn--primary {
  background: var(--oxford-crimson);
  color: var(--oxford-white);
  border-color: var(--oxford-crimson);
}
.ox-btn--primary:hover,
.ox-btn.wp-element-button.ox-btn--primary:hover {
  background: var(--oxford-crimson-hover);
  border-color: var(--oxford-crimson-hover);
  filter: none;
}

/* The secondary action. Royal carries white at 13.30. */
.ox-btn--secondary {
  background: var(--oxford-royal);
  color: var(--oxford-white);
  border-color: var(--oxford-royal);
}
.ox-btn--secondary:hover {
  background: var(--oxford-royal-hover);
  border-color: var(--oxford-royal-hover);
  filter: none;
}

/* CONFLICT 1. The spec gives Soft Sky as the border token, and Soft Sky against
   white is 1.38:1. That is fine for a divider, which is decoration, and fails
   WCAG 1.4.11 for a control boundary, which needs 3:1. Dividers and card edges
   keep Soft Sky; anything whose edge is the only thing saying "this is a
   control" takes Steel Blue at 5.85. Held in a token so a dark band can raise
   it — Steel Blue on navy is 2.63 and would fail there. */
:root { --ox-control-border: var(--oxford-body); }

.ox-btn--ghost,
.ox-field input,
.ox-field select,
.ox-field textarea {
  border-color: var(--ox-control-border);
}

/* ---- Focus ---------------------------------------------------------------
   Vibrant Azure, as specified: 4.20 on snow, 4.39 on white, 4.01 on ice and
   3.18 on sky, all clear of the 3:1 floor for a non-text indicator. */
:focus-visible {
  outline: 2px solid var(--oxford-royal);
  outline-offset: 3px;
}

/* CONFLICT 2. Azure on crimson is 1.04:1 — on the one button that matters most,
   the specified focus ring would be invisible. Deep Navy on crimson is 3.90 and
   is the only token in the palette that clears the floor there. */
.ox-btn--primary:focus-visible,
.ox-btn.wp-element-button.ox-btn--primary:focus-visible {
  outline-color: var(--oxford-navy);
}

/* CONFLICT 3. The form fields removed the outline and signalled focus by
   changing their border colour alone — a 1px colour change, and colour as the
   only cue. They get the same ring as everything else. */
.ox-field input:focus-visible,
.ox-field select:focus-visible,
.ox-field textarea:focus-visible {
  outline: 2px solid var(--oxford-royal);
  outline-offset: 2px;
  border-color: var(--oxford-royal);
}

/* ---- Surfaces, radii and depth -------------------------------------------
   Radius and depth only. The first version of this rule also set the fill to
   Pure White, which read as harmless — every one of these is already white —
   until .ox-icon-card--navy, a navy card sitting inside the navy band on Our
   School, was repainted white with its white text still on it. Ten headings
   went to 1:1. A modifier is a promise that the base rule will not overrule it,
   and each of these components already takes its fill from the token layer, so
   the declaration bought nothing and cost that. */
.ox-card,
.ox-numbers-stat,
.ox-icon-card,
.ox-curriculum-card,
.ox-approach-card {
  border-radius: var(--oxford-radius-md);
  box-shadow: var(--oxford-shadow);
}
.ox-card:hover {
  box-shadow: var(--oxford-shadow-hover);
}

/* The page canvas. */
body { background: var(--oxford-white); }

/* The per-card kicker override is gone: every kicker is navy now. */

/* ---- Navy bands ----------------------------------------------------------
   A dark band is a theme change for everything inside it, and the components
   that land there were still reading the light theme's tokens: a royal badge
   at 1.34 on navy, a ghost button whose label was navy on navy at 1:1, a
   heading that never picked up the inverse colour. Thirty one text runs across
   three pages, all of it predating this work — the same audit against live
   returns the same thirty one.

   Fixing them one component at a time would be thirty one rules and a thirty
   second the next time a component is dropped into a navy section. Re-pointing
   the tokens inside the band fixes every one of them and everything added
   later, which is what the token layer is for.

   Measured on #0D1045: white 17.84, Soft Sky 12.94, white at 86% ≈ 13.6. */
.ox-section--navy,
.ox-tedx,
.ox-section.has-navy-background-color {
  /* These pointed at Soft Sky until the official palette retired it. Soft Sky
     became the navy hairline, which on a navy band is very nearly the band
     itself — the hero eyebrow and two badges measured 1.00. Light on dark now
     comes from the palette's own values: white, and the 60% white it names for
     muted text on dark. */
  --ox-fg-strong: var(--oxford-white);
  --ox-fg-default: rgba(255, 255, 255, 0.86);
  --ox-fg-muted: var(--oxford-on-dark-muted);
  /* Not red: the brand red on navy is 3.90, under the 4.5 small text needs. */
  --ox-fg-brand: var(--oxford-white);
  --ox-fg-link: var(--oxford-white);
  --ox-body: rgba(255, 255, 255, 0.86);
  --ox-navy-line: var(--oxford-nav-border);
  --ox-line-subtle: var(--oxford-nav-border);
  --ox-line-strong: rgba(255, 255, 255, 0.45);
  --ox-control-border: rgba(255, 255, 255, 0.45);
}

/* The badge chip on a navy band: a translucent white wash carrying Soft Sky. */
.ox-section--navy .ox-badge,
.ox-section--navy p.ox-badge,
.ox-tedx .ox-badge,
.ox-tedx p.ox-badge {
  background: rgba(255, 255, 255, 0.12);
  color: var(--oxford-white);
}

/* A ghost button on navy is the light ghost, whatever class the markup used. */
.ox-section--navy .ox-btn--ghost,
.ox-tedx .ox-btn--ghost {
  background: rgba(255, 255, 255, 0.08);
  color: var(--oxford-white);
  border-color: rgba(255, 255, 255, 0.28);
}
.ox-section--navy .ox-btn--ghost:hover,
.ox-tedx .ox-btn--ghost:hover {
  background: rgba(255, 255, 255, 0.16);
  filter: none;
}

/* The focus ring has to clear the band it sits on: azure on navy is 4.07. */
.ox-section--navy :focus-visible,
.ox-tedx :focus-visible {
  outline-color: var(--oxford-royal);
}

/* ---- Checklist items 7 and 9 ---------------------------------------------
   These two were exactly inverted: the Impact section was Pure White carrying
   Ice Blue tiles, where the checklist asks for an Ice Blue information ground
   carrying white cards. Swapped, and the card rule generalised — every card is
   white, bordered Soft Sky, with navy type on it.

   The dark modifiers are deliberately not caught by this. .ox-icon-card--navy
   is a navy card inside the navy band on Our School; painting it white is the
   regression this same rule caused earlier today, so the selectors exclude it
   rather than relying on order. */
/* Checklist item 1: forms in Montserrat. Form controls do not inherit the page
   font on their own — the browser gives them its own UI face — so the theme
   styled .ox-field and left every other input, including the stories search and
   any core block field, in Arial. */
input,
select,
textarea,
button {
  font-family: var(--font-body);
}

/* The search result count is a live region and must stay in the accessibility
   tree to announce, so it is never display:none. Empty, it collapses to
   nothing without leaving the tree. */
.ox-search__status:empty {
  margin: 0;
  padding: 0;
  height: 0;
  overflow: hidden;
}

/* ---- Dropdowns near the right edge ---------------------------------------
   The panels are 300px and anchored to the left of their trigger, so the ones
   at the end of a seven item bar run past the window: Admissions measured a
   right edge of 1508 in a 1440 viewport. Nothing reported it, because
   .wp-site-blocks carries overflow-x: clip — no scrollbar, no spill, just the
   last 68px of the panel not there. The two rightmost anchor to their trigger's
   right edge instead. */
/* A panel is centred under its trigger by default. Two of them cannot be:
   measured centred, Admissions runs past the right edge at every desktop
   width, and below about 1024px Our School runs past the left edge. Only
   those anchor to an edge; everything else drops straight down.

   This rule used to catch the last TWO items, which right-aligned Language
   School as well even though it fits centred down to about 1000px - that is
   the menu Andrea saw sectioning off to the left. It also switched to right:0
   without cancelling the base translateX(-50%), so the panel was right
   aligned and then slid left by half its own width again: 189px for Language
   School, 213px for Admissions. Both halves are fixed here. */
.ox-nav__item:last-child > .ox-dropdown {
  left: auto;
  right: 0;
  transform: translateX(0) translateY(6px);
}

.ox-nav__item--has-menu:last-child:focus-within > .ox-dropdown,
.ox-nav__item--has-menu:last-child.is-open > .ox-dropdown {
  transform: translateX(0) translateY(0);
}

/* Between the 962px nav breakpoint and about 1100px the bar is tight enough
   that the first panel overhangs the left edge and the second to last one
   overhangs the right, so those anchor too. */
@media (max-width: 1099px) {
  .ox-nav__item:first-child > .ox-dropdown {
    left: 0;
    right: auto;
    transform: translateX(0) translateY(6px);
  }

  .ox-nav__item--has-menu:first-child:focus-within > .ox-dropdown,
  .ox-nav__item--has-menu:first-child.is-open > .ox-dropdown {
    transform: translateX(0) translateY(0);
  }

  .ox-nav__item:nth-last-child(2) > .ox-dropdown {
    left: auto;
    right: 0;
    transform: translateX(0) translateY(6px);
  }

  .ox-nav__item--has-menu:nth-last-child(2):focus-within > .ox-dropdown,
  .ox-nav__item--has-menu:nth-last-child(2).is-open > .ox-dropdown {
    transform: translateX(0) translateY(0);
  }
}

/* ============================================================================
   Section badges back on their coral ground                        (3.207.1)
   ----------------------------------------------------------------------------
   Andrea asked for the badge to return to its original coral ground with red
   text. The cards stay white — cream was tried on the Impact tiles and taken
   back out at her request.

   Coral is the specification's own Light Coral, opaque rather than the old 8%
   red wash, so the contrast under the text is fixed instead of depending on
   whatever the badge is sitting on.

   The red is the darker step, and that is measurement rather than taste: on
   Light Coral, base crimson #E8172C is 4.13 against the 4.5 an 11px uppercase
   label needs, while --ox-red-600 is 5.55. The original was already the darker
   step and it stays there.

   Written as p.ox-badge, not .ox-badge. The file already records why, forty
   lines up: `.ox-section-header p` is (0,1,1) and outweighs a bare `.ox-badge`
   at (0,1,0) no matter how late it appears. I wrote the single-class version
   first and it lost silently — the badge kept its royal text and only the
   ground changed. */

/* ============================================================================
   Warm section boxes on a snow page                                (3.208.0)
   ----------------------------------------------------------------------------
   Andrea's arrangement, in three layers:

     page      Snow White  #FAFAFA   unchanged, on every page
     section   Soft Pearl  #FCF7ED   the box each section renders as
     card      Pure White  #FFFFFF   unchanged, so cards sit a step lighter

   The cream is one step lighter than the #FAF4E7 she was shown, which is what
   she asked for, and it is the step that keeps the order above intact. Measured
   against the alternatives: Soft Pearl leaves 1.068 between the section and a
   white card, where the next step lighter (Cream #FDF9F1) closes that to 1.050
   and the one after (Ivory) to 1.033. None of these is a text contrast — they
   are surfaces, and the sections also carry a shadow — but if the cards ever
   stop reading as cards, this ratio is the reason.

   Written at the same weight as the rule that paints sections white, which is
   three classes and, for the story shape, two elements as well. */
.wp-site-blocks .entry-content > .ox-section:not(.ox-section--navy),
.wp-site-blocks main > .ox-section:not(.ox-section--navy),
.wp-site-blocks .entry-content > article > .ox-section:not(.ox-section--navy),
.wp-site-blocks main > article > .ox-section:not(.ox-section--navy),
.wp-site-blocks .entry-content > .ox-numbers:not(.ox-section--navy),
.wp-site-blocks main > .ox-numbers:not(.ox-section--navy) {
  background: var(--oxford-white);
}

/* WordPress emits its preset colour utilities with !important, so the warm
   light presets have to be matched at that weight to follow the section. */
.ox-section.has-page-background-color,
.ox-section.has-surface-background-color,
.ox-section.has-nude-300-background-color,
.ox-section.has-nude-600-background-color,
.ox-section.has-nude-900-background-color {
  background-color: var(--oxford-white) !important;
}

/* ---- Section badges ------------------------------------------------------
   Coral ground with red text, as it originally was. Coral is the
   specification's Light Coral, opaque rather than the old 8% wash, so the
   contrast under the label does not depend on what is behind it.

   The red is the darker step of the crimson family, and that is measurement:
   on Light Coral, base crimson #E8172C reaches 4.13 against the 4.5 an 11px
   uppercase label needs, while --ox-red-600 reaches 5.55. The original used the
   darker step for the same reason.

   p.ox-badge, not .ox-badge: the file records forty lines up that
   `.ox-section-header p` is (0,1,1) and outweighs a bare `.ox-badge` however
   late it appears. The single-class version lost silently — the ground changed
   and the text stayed royal. */
.ox-section-header p.ox-badge,
.ox-container p.ox-badge,
p.ox-badge:not(.ox-badge--navy),
.ox-badge:not(.ox-badge--navy) {
  background: var(--oxford-red-wash);
}

/* Two classes and an element. p.ox-badge on its own (0,1,1) lost to something
   further up that the CSSOM will not show — a declaration whose value is a
   var() is not exposed on rule.style, so walking the stylesheets returns no
   matching rules even for selectors that plainly match. Behaviour settled it:
   the same selector injected last changed nothing while !important changed it
   at once, which is a more specific rule rather than a later one. */
/* Andrea's choice of the three measured options: keep the red wash, darken the
   text. The brand red on its own 8% wash is 4.01 against the 4.5 an 11px
   uppercase label needs; #C40B21 on the same wash is 5.39. */
/* Element-agnostic. A badge is a <p> where the block was converted to core
   markup and a <span> where it still renders through oxfordian/badge — Meet
   the Team is the second kind, and p.ox-badge alone left both of its badges on
   the undarkened red. */
.ox-section-header .ox-badge:not(.ox-badge--navy),
.ox-container .ox-badge:not(.ox-badge--navy),
.ox-section-header p.ox-badge:not(.ox-badge--navy),
.ox-container p.ox-badge:not(.ox-badge--navy) {
  color: #C40B21;
}

/* The closing CTA's badge sits on navy and keeps its white. */
p.ox-badge--light,
.ox-badge--light {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}

/* Cards stay Pure White. This rule was collateral damage when the cream
   experiment was removed — the tiles fell back to the Ice Blue section token
   and turned blue on a cream ground. */
.ox-numbers .ox-numbers-stat,
.ox-card:not([class*="--navy"]):not([class*="--royal"]),
.ox-icon-card:not([class*="--navy"]):not([class*="--royal"]),
.ox-curriculum-card:not([class*="--navy"]):not([class*="--royal"]),
.ox-approach-card:not([class*="--navy"]):not([class*="--royal"]) {
  background: var(--oxford-white);
  border: 1px solid var(--oxford-hairline);
  color: var(--oxford-navy);
}
/* ...but the approach card's accent IS its top rule, and the shorthand above
   re-declared all four sides, so every practice card had been reduced to a
   hairline since the palette sweep. The rule comes back here, after it, and
   the cycle is the three school colours rather than two royals and a grey, so
   a band of cards reads as a set of distinct topics (Andrea, 11 Sep 2026). */
.ox-approach-card:not([class*="--navy"]):not([class*="--royal"]) { border-top: 4px solid var(--ox-accent); }
.ox-approach-card:nth-child(3n+1) { --ox-accent: var(--oxford-navy, #0d1045); }
.ox-approach-card:nth-child(3n+2) { --ox-accent: var(--ox-red-600, #b00d1e); }
.ox-approach-card:nth-child(3n+3) { --ox-accent: var(--oxford-royal, #1a227f); }

/* ============================================================================
   Official palette: the last two places off-palette colour survived (3.209.3)
   ==========================================================================*/

/* 1. WordPress preset colour utilities. The nude presets were still painting
      cream — not on sections, which were already covered, but on inner blocks
      like .ox-split and .ox-stack that carry the class directly. WordPress
      emits these with !important, so matching that is the only way to win.
      Only the warm light presets are listed: a deliberately dark block keeps
      whatever it was given. */
.has-nude-100-background-color,
.has-nude-200-background-color,
.has-nude-300-background-color,
.has-nude-400-background-color,
.has-nude-500-background-color,
.has-nude-600-background-color,
.has-nude-900-background-color,
.has-page-background-color,
.has-surface-background-color {
  background-color: var(--oxford-white) !important;
}

/* 2. A badge on a navy band. The red pair is for light grounds; on navy the
      brand red measures 3.90 against the 4.5 an 11px label needs, so the badge
      takes white on a white wash there. Written at three classes and an element
      because the light-ground rule below is two and an element and would
      otherwise win on the navy band too — which is exactly what it was doing. */
.ox-section--navy .ox-section-header .ox-badge,
.ox-section--navy .ox-container .ox-badge,
.ox-section--navy .ox-section-header p.ox-badge,
.ox-section--navy .ox-container p.ox-badge,
.ox-tedx .ox-section-header .ox-badge,
.ox-tedx .ox-container .ox-badge,
.ox-tedx .ox-section-header p.ox-badge,
.ox-tedx .ox-container p.ox-badge {
  background: rgba(255, 255, 255, 0.12);
  color: var(--oxford-white);
}

/* ==== Embed placeholders in the editor (3.214.2) ====
   A third party iframe inside the editor canvas makes WordPress log a
   SecurityError from compose.js, because it attaches listeners to every frame
   it finds and the browser refuses across origins. The blocks render this
   instead while editing. It fills the same box, so the layout you are editing
   is still the layout you get. */
.ox-embed-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  height: 100%;
  min-height: 180px;
  padding: var(--space-md);
  text-align: center;
  border: 1px dashed var(--ox-navy-300, #7588aa);
  border-radius: var(--radius-card, 12px);
  background: var(--ox-bg-surface, #fdf9f1);
}

.ox-embed-placeholder__label {
  color: var(--ox-fg-strong);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 14px;
}

.ox-embed-placeholder__note {
  color: var(--ox-body, #626780);
  font-family: var(--font-body);
  font-size: 12px;
}

/* ==== Core principles grid (3.221.0, restyled 3.222.0) ====
   The school's printed sheet, as blocks. Every tile hands three colours down
   from PHP as custom properties, already checked for contrast: --ox-tile is the
   full-strength colour, --ox-tile-fill the opaque ground behind the text, and
   --ox-tile-ink the shade of the colour that reads on that ground. So this file
   never has to know whether it is drawing a tinted tile or a solid one.

   No icons: the printed sheet has one per tile, but decorative glyphs were
   taken out of cards site-wide in 3.196.0 and this follows that. */
.ox-values { margin-block: var(--space-lg); }

.ox-values__heading {
  font-size: clamp(28px, 4vw, 44px);
  line-height: 1.05;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-xs);
  text-transform: uppercase;
}
.ox-values__intro {
  margin: 0 0 var(--space-md);
  max-width: 58ch;
  color: var(--ox-fg-soft, #626780);
}
.ox-values--centred .ox-values__heading,
.ox-values--centred .ox-values__intro { text-align: center; margin-inline: auto; }

.ox-values__grid {
  display: grid;
  gap: clamp(10px, 1.4vw, 18px);
  /* An explicit count, not auto-fit. auto-fit packs in as many as will fit,
     which from eight tiles gives three across and an orphan row of two at some
     widths. A stated number is always an even division of the set, so the grid
     reads as the printed sheet does: four and four, then two and two, never a
     row with a hole in it. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}
.ox-values:not(.ox-values--rows)[data-cols="3"] .ox-values__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ox-values:not(.ox-values--rows)[data-cols="2"] .ox-values__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.ox-values__tile {
  padding: clamp(16px, 1.8vw, 24px);
  border: 3px solid var(--ox-tile, var(--ox-navy));
  border-radius: 4px;
  background: var(--ox-tile-fill, var(--ox-surface));
  color: var(--ox-tile-ink, var(--ox-navy));
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  container-type: inline-size;
}
.ox-values__name {
  margin: 0;
  /* Sized against the tile, not the window. At four across in a 900px column
     the tile is a sixth of the viewport, so a window-based clamp put
     ACCOUNTABILITY at 188px inside 156px of room and the word crossed its own
     border. cqi is that room, so the name shrinks when the tile does and stops
     at 21px when it does not. break-word is the net under it, for a longer
     name than any of these eight. */
  font-size: clamp(15px, 9.5cqi, 21px);
  overflow-wrap: break-word;
  font-weight: 800;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: inherit;
}
.ox-values__name::after { content: ":"; }
.ox-values__text {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 500;
  /* Sentence case, not the sheet's capitals. The banner is read from across a
     courtyard, where all caps carries; on screen it removes the word shapes
     people actually read by, and these run to ninety characters. */
}

/* Tinted: the wash carries the colour quietly, the border carries it at full
   strength, and the sentence drops back to ordinary body copy so the name is
   the only coloured text in the tile. */
.ox-values--outline .ox-values__text { color: var(--ox-body); }

/* Solid: the printed sheet. The border matches the fill, so the tile reads as
   one block of colour, and everything in it takes the ink PHP picked. */
.ox-values--filled .ox-values__text { color: inherit; opacity: 0.95; }

/* Two across on tablets rather than letting auto-fit choose three: eight tiles
   in threes leaves a row of two hanging, which is the orphan row this site has
   already been through once. Written at the same specificity as the data-cols
   rules above and after them, so it wins for every column setting. */
@media (max-width: 900px) {
  .ox-values:not(.ox-values--rows) .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="3"] .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="2"] .ox-values__grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 480px) {
  .ox-values:not(.ox-values--rows) .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="3"] .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="2"] .ox-values__grid { grid-template-columns: 1fr; }
}

/* ---- Columns follow the column, not the window -------------------------
   Four across was measured at 208px a tile on Our School at a 1280px window,
   because that page keeps a section rail down the left and hands the content
   887px, not 1160px. A window query cannot see that. These read the block's
   own width instead, so the grid answers to the room it actually has: four
   across only when four tiles clear 240px, two when they do not, one when
   even two would be narrower than a sentence wants.

   The window queries above stay as the fallback for anything too old to know
   what a container query is. */
.ox-values { container-type: inline-size; }

/* Four across holds until four tiles would be under about 180px. These are a
   name and a sentence, not a card with a picture, so they stay readable much
   narrower than a card does, and the sheet's own shape is what a reader sees
   for most of the range. */
@container (max-width: 780px) {
  .ox-values:not(.ox-values--rows) .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="4"] .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="3"] .ox-values__grid { grid-template-columns: repeat(2, 1fr); }
}

@container (max-width: 520px) {
  .ox-values:not(.ox-values--rows) .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="4"] .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="3"] .ox-values__grid,
  .ox-values:not(.ox-values--rows)[data-cols="2"] .ox-values__grid { grid-template-columns: 1fr; }
}

/* ---- Full width rows ----------------------------------------------------
   Written after the media queries above and at matching specificity, so rows
   stay rows at every width instead of the tablet rule pulling them back into
   two columns. */
.ox-values--rows .ox-values__grid,
.ox-values--rows[data-cols="3"] .ox-values__grid,
.ox-values--rows[data-cols="2"] .ox-values__grid { grid-template-columns: 1fr; }

.ox-values--rows .ox-values__tile,
.ox-values--rows .ox-values__face {
  flex-direction: row;
  align-items: center;
  gap: clamp(14px, 2.4vw, 34px);
}
.ox-values--rows .ox-values__name { flex: 0 0 clamp(120px, 20%, 220px); }
/* The row is as wide as the page. The sentence in it should not be: at 1280
   the line had room for 154 characters, which is twice what anyone reads
   comfortably in one pass. */
.ox-values--rows .ox-values__text { flex: 1 1 auto; max-width: 80ch; }
.ox-values--rows .ox-values__cue { margin: 0 0 0 auto; }

/* A row is only a row while there is width for two columns of text. */
@media (max-width: 560px) {
  .ox-values--rows .ox-values__tile,
  .ox-values--rows .ox-values__face {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  .ox-values--rows .ox-values__cue { margin: 6px 0 0; }
}

/* ---- Tiles that turn over -----------------------------------------------
   The name is on the front, the sentence on the back, and the back is always
   the printed look whatever the front is: turning a tile over hands you the
   sheet. The two faces sit in the same grid cell rather than being positioned
   absolutely, so the tile is as tall as the longer of the two and nothing has
   to be given a fixed height.

   Three ways in, because a card that only answers to one of them is broken for
   everyone else: hover for a mouse, focus for a keyboard, and a tap, which is
   the only one of the three a phone has. The first two are here; the tap is
   four lines in theme.js. */
.ox-values__tile--flip {
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  perspective: 1000px;
}

.ox-values__toggle {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.ox-values__toggle:focus-visible { outline: 3px solid var(--ox-tile-ink, var(--ox-navy)); outline-offset: 3px; }

.ox-values__card {
  display: grid;
  flex: 1 1 auto;
  transform-style: preserve-3d;
  transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.ox-values__face {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
  padding: clamp(16px, 1.8vw, 24px);
  border: 3px solid var(--ox-tile, var(--ox-navy));
  border-radius: 4px;
  backface-visibility: hidden;
}
.ox-values__face--front { background: var(--ox-tile-fill, var(--ox-surface)); color: var(--ox-tile-ink, var(--ox-navy)); }
.ox-values__face--back {
  background: var(--ox-tile, var(--ox-navy));
  color: var(--ox-tile-reverse-ink, #fff);
  transform: rotateY(180deg);
}

/* The sentence on the back sits on the colour itself, so it takes the ink PHP
   picked for that colour rather than the body grey the tinted tiles use. */
.ox-values--flip .ox-values__text { color: inherit; }

.ox-values__toggle[aria-expanded="true"] + .ox-values__card,
.ox-values__toggle:focus-visible + .ox-values__card { transform: rotateY(180deg); }

@media (hover: hover) {
  .ox-values__tile--flip:hover .ox-values__card { transform: rotateY(180deg); }
}

/* Something has to say the tile turns over. This is the control's own marker,
   not decoration, which is the one kind of glyph the site still keeps. */
.ox-values__cue {
  position: relative;
  width: 22px;
  height: 22px;
  margin-top: auto;
  border: 2px solid currentColor;
  border-radius: 50%;
  opacity: 0.85;
}
.ox-values__cue::before,
.ox-values__cue::after {
  content: "";
  position: absolute;
  background: currentColor;
  border-radius: 1px;
}
.ox-values__cue::before { top: 8px; right: 3px; left: 3px; height: 2px; }
.ox-values__cue::after { top: 3px; bottom: 3px; left: 8px; width: 2px; }

.ox-values__tile--flip:hover .ox-values__cue,
.ox-values__toggle[aria-expanded="true"] + .ox-values__card .ox-values__cue { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .ox-values__card { transition: none; }
}

/* ---- The jump menu stays put on a phone (3.235.0) ----
   Pinned, the menu costs a phone twice: the sticky site header is already
   holding the top of the screen, and a second bar under it takes another slice
   before any content appears. On a small screen that is a large share of what
   the reader can see at once.

   So below the theme's own mobile threshold the menu is an ordinary band that
   sits under the page's hero picture and scrolls away with everything else.
   960px is where the main navigation already collapses to the toggle, so
   "mobile" means one thing across the theme rather than two.

   Anchor offsets follow automatically: theme.js only counts the menu's height
   into --ox-jumpnav-h while it is actually sticky, so in flow it contributes
   nothing and a jump lands where the heading is. */
@media (max-width: 960px) {
  .ox-jumpnav {
    position: static;
    top: auto;
    z-index: auto;
  }
}

/* ==== Rows keep their shape when the window shrinks (3.236.0) ==============
   A card row used to answer a narrower window by reflowing: five across became
   three at 1400, two at 1100, then a stack. The page a visitor met at 1200px
   was a different page from the one at 1500px, and the cards kept changing
   size as they went.

   Above the mobile threshold the cards now hold their width and the row shows
   fewer of them, with arrows to page through the rest. Narrowing the window
   changes how much you can see, not what the page is.

   The class is added by theme.js rather than written into the selector list,
   because a dozen legacy rules set columns on these rows and one of them is an
   ID. One class in one place is easier to reason about than a specificity war,
   and it lets the script take the class off again below 961px, where the old
   ladder is the right answer on a screen too narrow to hold a row.

   The !important is on the single property those legacy rules also set.
   Everything else here wins on ordinary specificity. */
@media (min-width: 961px) {
  .ox-flip-row {
    grid-template-columns: none !important;
    grid-auto-flow: column;
    /* The minimum keeps a card readable; 1fr lets a short row fill the width
       instead of leaving a gap at the end. */
    grid-auto-columns: minmax(var(--ox-flip-card, 290px), 1fr);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    /* No scroll-behavior here on purpose. In CSS it applies to every scroll of
       this element, including the direct assignment the arrow falls back to
       when an engine will not animate, which left the row unable to move at
       all. theme.js asks for smoothness per call instead, so an instant scroll
       stays instant. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* Room for the lift and shadow a card takes on hover, which an overflow
       container would otherwise clip. */
    padding-bottom: 6px;
  }

  .ox-flip-row::-webkit-scrollbar { display: none; }
  .ox-flip-row > * { scroll-snap-align: start; }
}

/* The arrow bar theme.js puts above a row that overflows. The script hides it
   whenever everything already fits, because a dead pair of arrows is worse
   than none at all. */
.ox-flip-nav {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin: 0 0 8px;
}

/* Below the breakpoint the row is the theme's own touch scroller, swiped
   rather than paged. Set as a class so the inline display stays the
   scroller's to control. */
.ox-flip-nav.is-below-breakpoint { display: none; }

/* --------------------------------------------------------------------------
   Live Green in Action
   --------------------------------------------------------------------------
   These three photos are shown whole rather than cropped into tiles, so the
   gallery is a single column at full width. One of them is portrait at
   1200x1802, which at container width stands about 1,500px tall — taller than
   a laptop screen. The height cap keeps every photo complete, nothing is
   cropped, while keeping the section walkable; the width follows from the cap
   so the portrait shot simply sits narrower and centred.
   -------------------------------------------------------------------------- */

/* Paired with .wp-block-gallery to match core's own (0,2,0) width rule —
   at one class this loses and the photos sit inset from the paragraphs. */
.wp-block-gallery.ox-livegreen-gallery {
  display: grid;
  gap: var(--space-lg);
  width: 100%;
  max-width: 100%;
  margin-inline: 0;
}
.wp-block-gallery.ox-livegreen-gallery > figure { margin: 0; width: 100%; }
.wp-block-gallery.ox-livegreen-gallery img {
  width: auto;
  max-width: 100%;
  max-height: 70vh;
  margin-inline: auto;
  border-radius: var(--radius-card);
}

/* --------------------------------------------------------------------------
   Card rows in the middle band
   --------------------------------------------------------------------------
   Above 961px a card row is a band: it holds the card size, shows fewer of them
   and pages sideways (.ox-flip-row, built in theme.js). At 620px and below it
   is the theme's touch scroller. In between it used to fall back to fewer grid
   columns, and a count that does not divide by that column number strands the
   last card alone on a row — three cards over two columns being by far the
   commonest, measured at 126 rows across the site.

   So the middle band behaves like the two either side of it: the cards keep
   their size and the reader flips through them. Nothing reflows, and no row is
   left with an orphan.

   .ox-grid--2 joins only when it is carrying more than two items, because a
   genuine two-up split is a pair and should stay one. :has() is doing real work
   here, not shorthand.

   This lives in blocks.css rather than theme.css because .ox-grid.is-layout-flow
   below re-declares display:grid at (0,2,0) in this later file, so the rule has
   to sit here and pair each class with .is-layout-flow to match that weight.
   -------------------------------------------------------------------------- */

@media (min-width: 621px) and (max-width: 960px) {
  .ox-grid--2:has(> :nth-child(3)),
  .ox-grid--2.is-layout-flow:has(> :nth-child(3)),
  .ox-grid--3,
  .ox-grid--3.is-layout-flow,
  .ox-grid--4,
  .ox-grid--4.is-layout-flow,
  .ox-grid--5,
  .ox-grid--5.is-layout-flow,
  .ox-approach-grid,
  .ox-approach-grid.is-layout-flow {
    display: flex;
    gap: var(--space-sm);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    -webkit-overflow-scrolling: touch;
  }
  .ox-grid--2:has(> :nth-child(3)) > *,
  .ox-grid--3 > *,
  .ox-grid--4 > *,
  .ox-approach-grid > * { flex: 0 0 46%; scroll-snap-align: start; }
  .ox-grid--5 > * { flex: 0 0 31%; scroll-snap-align: start; }
}

/* The five campus contact cards were deliberately centred and wrapped (3+2,
   then 2+2+1) before the band standard existed, and their own rule sits at
   (0,3,0). They join the band here at the same weight, later in the file, so
   the middle range matches desktop: the cards hold their size and page
   sideways rather than stranding the fifth campus on a row of its own. */
@media (min-width: 621px) and (max-width: 960px) {
  .ox-section .ox-grid.ox-campus-contacts,
  .ox-section .ox-grid.ox-contact-cards,
  .ox-grid.ox-campus-contacts,
  .ox-grid.ox-contact-cards {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x proximity;
  }
  .ox-campus-contacts > .ox-icon-card,
  .ox-contact-cards > .ox-icon-card {
    flex: 0 0 350px;
    scroll-snap-align: start;
  }
}

/* Wider tablets have room for a third card before the band takes over. */
@media (min-width: 861px) and (max-width: 960px) {
  .ox-grid--2:has(> :nth-child(3)) > *,
  .ox-grid--3 > *,
  .ox-grid--4 > *,
  .ox-approach-grid > * { flex: 0 0 31%; }
}

/* The activity cards link to a class's block in the timetable; the sticky
   header would otherwise land on top of it. */
.ox-timetable tr[id] { scroll-margin-top: 110px; }

/* --------------------------------------------------------------------------
   After School: the slide above the timetable (3.262.0)
   --------------------------------------------------------------------------
   Andrea, 11 Sep 2026: the photographs should be a slide inside the timetable
   block, the copy short, and the whole thing white like every other section.
   One picture at a time fills its frame, the activity's name and campus count
   sit underneath it, and the activity buttons below narrow both the slide and
   the table. The arrows and dots are the control itself, so they stay within
   the icon policy. */
.ox-tt-stage { margin: 0 0 var(--space-md); scroll-margin-top: 110px; }
.ox-tt-stage__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card, 18px);
  overflow: hidden;
  background: var(--ox-navy, #0d1045);
}
.ox-tt-stage__deck { position: absolute; inset: 0; }
.ox-tt-shot { display: none; position: relative; margin: 0; height: 100%; }
.ox-tt-shot.is-current { display: block; }
/* The frame is filled by a blurred copy of the photograph itself, so an
   upright picture is shown whole with no grey bars beside it and no child
   cropped out of the top. */
.ox-tt-shot::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--ox-shot);
  background-size: cover;
  background-position: center;
  filter: blur(26px) brightness(0.72);
  transform: scale(1.12);
}
.ox-tt-shot img,
.ox-tt-shot video {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.ox-tt-stage__nav {
  position: absolute;
  top: calc(50% - 24px);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid rgba(13, 16, 69, 0.12);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.94);
  color: var(--ox-fg-strong, #0d1045);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(13, 16, 69, 0.18);
}
.ox-tt-stage__nav--prev { left: 12px; }
.ox-tt-stage__nav--next { right: 12px; }
.ox-tt-stage__nav:hover { background: #fff; }
.ox-tt-stage__nav:focus-visible { outline: 3px solid var(--ox-fg-link, #1a227f); outline-offset: 2px; }
/* display:grid above outranks the hidden attribute, so a single photo kept its arrows. */
.ox-tt-stage__nav[hidden] { display: none; }
.ox-tt-stage__caps { margin-top: var(--space-sm); }
.ox-tt-cap { display: none; }
.ox-tt-cap.is-current { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 14px; }
.ox-tt-cap__name { margin: 0; font-size: clamp(20px, 2.4vw, 26px); color: var(--ox-fg-strong, #0d1045); }
.ox-tt-cap__alt { margin-left: 10px; font-size: 0.7em; font-style: italic; font-weight: 400; color: var(--ox-fg-muted, #626780); }
.ox-tt-cap__meta {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ox-red-600, #b00d1e);
}
.ox-tt-cap__text { flex: 1 0 100%; margin: 0; color: var(--ox-body, #4a4f6a); }
.ox-tt-stage__dots { display: flex; justify-content: center; gap: 8px; margin-top: 12px; }
.ox-tt-stage__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--ox-navy-line, #c9cbdb);
  cursor: pointer;
}
.ox-tt-stage__dot.is-current { width: 26px; border-radius: 5px; background: var(--ox-fg-link, #1a227f); }
.ox-tt-stage__dot:focus-visible { outline: 2px solid var(--ox-fg-link, #1a227f); outline-offset: 3px; }
@media (max-width: 780px) {
  .ox-tt-stage__frame { aspect-ratio: 4 / 3; }
  .ox-tt-stage__nav { width: 42px; height: 42px; top: calc(50% - 21px); }
}
/* On a navy ground the slide keeps its own light chrome, but the words under it
   have to reverse. */
.ox-section--navy .ox-tt-cap__name { color: #fff; }
.ox-section--navy .ox-tt-cap__text { color: rgba(255, 255, 255, 0.78); }
.ox-section--navy .ox-tt-cap__alt { color: rgba(255, 255, 255, 0.6); }
.ox-section--navy .ox-tt-cap__meta { color: #fff; }

/* The timetable's own chrome on a white section. The block was designed for the
   navy band, so the pieces that were set in white are re-coloured here rather
   than left invisible: the campus count, the enrollment line under a fee, and
   the two row buttons. */
.ox-section:not(.ox-section--navy) .ox-timetable__count { color: var(--ox-fg-muted, #626780); }
.ox-section:not(.ox-section--navy) .ox-timetable__enrol { color: var(--ox-fg-muted, #626780); }
.ox-section:not(.ox-section--navy) .ox-trial-btn { border-color: var(--ox-navy-line, #d8dae8); color: var(--ox-fg-link, #1a227f); }
.ox-section:not(.ox-section--navy) .ox-trial-btn:hover { background: var(--ox-surface, #f4f5fa); border-color: var(--ox-fg-link, #1a227f); }
.ox-section:not(.ox-section--navy) .ox-timetable .ox-btn--ghost { color: var(--ox-fg-link, #1a227f); border-color: var(--ox-navy-line, #d8dae8); background: transparent; }
.ox-section:not(.ox-section--navy) .ox-timetable .ox-btn--ghost:hover { background: var(--ox-surface, #f4f5fa); border-color: var(--ox-fg-link, #1a227f); }
/* The filter chips are the light ones by default now; a navy section reverses
   them instead of the markup carrying a modifier. */
.ox-section--navy .ox-filter__btn {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.78);
}
.ox-section--navy .ox-filter__btn:hover { border-color: var(--ox-fg-brand, #1a227f); color: #fff; }
.ox-section--navy .ox-filter__btn.is-active { background: var(--oxford-royal, #1a227f); border-color: var(--ox-fg-brand, #1a227f); color: #fff; }

/* The stacked table on a phone and a tablet was written for the navy band as
   well: each row becomes a card, and its labels, rules and separators were all
   set in white. On the white section they have to be re-coloured or the labels
   read as blank space beside the values. */
@media (max-width: 900px) {
  .ox-section:not(.ox-section--navy) .ox-timetable tbody tr:not(.ox-timetable__group) {
    background: #fff;
    border-color: var(--ox-navy-line, #e3e5ef);
  }
  .ox-section:not(.ox-section--navy) .ox-timetable tbody tr:not(.ox-timetable__group) > td::before { color: var(--ox-fg-muted, #626780); }
  /* The pinned-column shadow belongs to the table that scrolls sideways. Here
     each row is a card and the row header is its title, so the shadow paints
     as a grey tab off the right edge of every card. */
  .ox-section:not(.ox-section--navy) .ox-timetable tbody tr:not(.ox-timetable__group) > th:first-child { box-shadow: none; }
  /* The enrollment line is part of the fee, so it stays in the value column
     rather than dropping into the label column underneath. */
  .ox-timetable tbody tr:not(.ox-timetable__group) > td.ox-timetable__fee .ox-timetable__enrol { grid-column: 2; }
  /* Two buttons in a row are one row of buttons. */
  .ox-timetable .ox-tt-actions .ox-btn { min-width: 128px; justify-content: center; }
  .ox-section:not(.ox-section--navy) .ox-timetable tbody tr:not(.ox-timetable__group) .ox-timetable__slot + .ox-timetable__slot::before { color: var(--ox-navy-line, #c9cbdb); }
}

/* A row of buttons is one row of buttons: same height, same shape, same width,
   whatever each one says (Andrea, 11 Sep 2026). */
.ox-cta__actions { align-items: stretch; width: 100%; }
/* The buttons take a row of their own inside a CTA box, rather than a narrow
   column beside the words: three of them side by side, each the same width and
   each on one line. */
.ox-cta-box > .ox-hero__actions { flex: 1 0 100%; align-self: stretch; justify-content: center; }
.ox-cta__actions > .ox-btn {
  flex: 0 1 250px;
  align-self: stretch;
  /* The editor's own block spacing lands on the second and third button and
     makes each of them shorter than the first by exactly that margin. */
  margin-block: 0;
  justify-content: center;
  min-height: 52px;
  text-align: center;
}
@media (max-width: 520px) {
  /* Each button takes the full width of the card, and they share one height:
     a label that wraps on to a second line would otherwise make its button
     taller than the two beside it. */
  .ox-cta__actions > .ox-btn { flex: 1 1 100%; min-height: 70px; }
}



/* ---------------------------------------------------------------------------
   Wide frequency tables
   ---------------------------------------------------------------------------
   A rate table of nine or more columns (the Las Quintas kinder grid on
   Cocurriculars has eleven) outgrew its card: nowrap headers made it 948px in
   an 844px card, and the last column was clipped with nothing to say it
   scrolled. Its headers now wrap, which brings it down to 771px, so it sits as
   a real table in a desktop card. Where the card is narrower than the table's
   own minimum, each grade becomes one block of labelled pairs in a grid, the
   same pairs the phone view lists one per line.

   Sized on the card (a container query), not the window, because the side
   rail can leave a desktop card narrower than a tablet one. The breakpoints
   are the measured minimums, 771px for eleven columns and 734px for ten, plus
   a margin; nine columns (566px) always fit above the phone layout. */
.ox-rates table.is-stackable:has(thead th:nth-child(9)) thead th:not(:first-child) {
  white-space: normal;
  vertical-align: bottom;
}
.ox-rates table.is-stackable:has(thead th:nth-child(9)) tbody td:first-child { white-space: nowrap; }
.ox-rates:has(> table.is-stackable thead th:nth-child(10)) { container: ox-rates-10 / inline-size; }
.ox-rates:has(> table.is-stackable thead th:nth-child(11)) { container-name: ox-rates-11; }
@media (min-width: 621px) {
  @container ox-rates-11 (max-width: 790px) {
    .ox-rates table.is-stackable thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .ox-rates table.is-stackable,
    .ox-rates table.is-stackable tbody { display: block; width: 100%; }
    .ox-rates table.is-stackable tbody tr { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 2px 28px; padding: 12px 0 14px; border-bottom: 1px solid var(--ox-navy-line); }
    .ox-rates table.is-stackable tbody tr:last-child { border-bottom: 0; }
    .ox-rates table.is-stackable tbody td { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 4px 0; border: 0; white-space: normal; }
    .ox-rates table.is-stackable tbody td:first-child { display: block; grid-column: 1 / -1; font-size: 16px; padding-bottom: 6px; }
    .ox-rates table.is-stackable tbody td[data-label]:not(:first-child)::before { content: attr(data-label); font-family: var(--font-display); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ox-body); }
    .ox-rates table.is-stackable tbody td:empty { display: none; }
  }
  @container ox-rates-10 (max-width: 750px) {
    .ox-rates table.is-stackable thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .ox-rates table.is-stackable,
    .ox-rates table.is-stackable tbody { display: block; width: 100%; }
    .ox-rates table.is-stackable tbody tr { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 2px 28px; padding: 12px 0 14px; border-bottom: 1px solid var(--ox-navy-line); }
    .ox-rates table.is-stackable tbody tr:last-child { border-bottom: 0; }
    .ox-rates table.is-stackable tbody td { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 4px 0; border: 0; white-space: normal; }
    .ox-rates table.is-stackable tbody td:first-child { display: block; grid-column: 1 / -1; font-size: 16px; padding-bottom: 6px; }
    .ox-rates table.is-stackable tbody td[data-label]:not(:first-child)::before { content: attr(data-label); font-family: var(--font-display); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ox-body); }
    .ox-rates table.is-stackable tbody td:empty { display: none; }
  }
}


/* ---------------------------------------------------------------------------
   Tabs
   ---------------------------------------------------------------------------
   Built by theme.js from a group .ox-tabs of groups .ox-tabs__panel, each
   named by a first paragraph .ox-tabs__label. The tabs take the look of the
   filter buttons so the site has one pill control, with a 44px target. With
   no script, and on paper, every panel shows under its label. */
.ox-tabs__list {
  display: flex;
  gap: var(--space-xs);
  overflow-x: auto;
  scrollbar-width: thin;
  padding: 2px 2px 12px;
  margin-bottom: var(--space-lg);
  border-bottom: 1px solid var(--ox-navy-line);
}
.ox-tabs__tab {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 10px 18px;
  background: var(--ox-card);
  border: 1px solid var(--ox-navy-line);
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  color: var(--ox-body);
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.ox-tabs__tab:hover { border-color: var(--ox-fg-brand); color: var(--ox-fg-brand); }
.ox-tabs__tab[aria-selected="true"] { background: var(--ox-navy); border-color: var(--ox-fg-strong); color: #fff; }
.ox-tabs__tab:focus-visible { outline: 3px solid var(--ox-fg-brand); outline-offset: 2px; }
.ox-tabs__panel[hidden] { display: none !important; }
.ox-tabs__panel:focus-visible { outline: 3px solid var(--ox-fg-brand); outline-offset: 6px; border-radius: 8px; }
.ox-tabs.is-tabbed .ox-tabs__label { display: none; }
.ox-tabs:not(.is-tabbed) .ox-tabs__panel + .ox-tabs__panel { margin-top: var(--space-xl); }
@media (prefers-reduced-motion: reduce) { .ox-tabs__tab { transition: none; } }
@media print {
  .ox-tabs__list { display: none; }
  .ox-tabs__panel[hidden] { display: block !important; }
  .ox-tabs.is-tabbed .ox-tabs__label { display: inline-block; }
}


/* ---------------------------------------------------------------------------
   Portrait video
   ---------------------------------------------------------------------------
   A phone-shot reel (9:16) placed beside text with a core Video block and the
   class ox-portrait-video. Held to phone width so it does not tower over the
   column, rounded like the photos, and centred when the columns stack. */
.wp-block-video.ox-portrait-video { margin: 0 auto; max-width: 340px; width: 100%; }
.wp-block-video.ox-portrait-video video {
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  border-radius: var(--radius-card, 18px);
  background: var(--ox-navy);
}
.wp-block-video.ox-portrait-video figcaption { margin-top: 10px; font-size: 13px; color: var(--ox-body); text-align: center; }


/* ---------------------------------------------------------------------------
   Masonry gallery
   ---------------------------------------------------------------------------
   Add the class ox-masonry to a campus gallery group and its photos flow in
   columns at their own shape, so a tall mural sits beside wide event shots
   without cropping or ragged rows. Three columns on desktop, two below 960px,
   the same structure at every size. Carries the weight of the flex rule that
   .ox-campus-gallery gets above 561px, and comes after it, so it wins. */
.ox-grid.ox-campus-gallery.ox-masonry,
.ox-campus-gallery.ox-masonry.is-layout-flow,
.ox-campus-gallery.ox-masonry {
  display: block;
  columns: 3;
  column-gap: var(--space-md, 16px);
}
.ox-campus-gallery.ox-masonry > * {
  display: block;
  width: 100%;
  max-width: none;
  margin: 0 0 var(--space-md, 16px);
  break-inside: avoid;
}
.ox-campus-gallery.ox-masonry img { display: block; width: 100%; height: auto; }
@media (max-width: 960px) {
  .ox-grid.ox-campus-gallery.ox-masonry,
  .ox-campus-gallery.ox-masonry.is-layout-flow,
  .ox-campus-gallery.ox-masonry { columns: 2; column-gap: 12px; }
  .ox-campus-gallery.ox-masonry > * { margin-bottom: 12px; }
}


/* ---------------------------------------------------------------------------
   Photo lightbox
   ---------------------------------------------------------------------------
   theme.js turns every photo inside .ox-lightbox into a button that opens a
   full-screen slideshow on the navy ground. The arrows and the close mark are
   the controls themselves, so they stay (see the icon policy), each with a
   48px target and a visible focus ring. On phones the arrows move under the
   photo so it can use the full width. */
.ox-lightbox__open {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-card, 18px);
  background: none;
  cursor: zoom-in;
}
.ox-lightbox__open img { display: block; width: 100%; height: auto; transition: filter 0.2s ease; }
.ox-lightbox__open:hover img { filter: brightness(1.06); }
.ox-lightbox__open:focus-visible { outline: 3px solid var(--ox-fg-brand); outline-offset: 3px; }

.ox-lightbox-dialog {
  box-sizing: border-box;
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 64px 24px 20px;
  border: 0;
  background: rgba(13, 16, 69, 0.95);
  color: #fff;
}
.ox-lightbox-dialog[open] {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
}
.ox-lightbox-dialog::backdrop { background: rgba(13, 16, 69, 0.6); }
.ox-lightbox-dialog__frame {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 0;
  height: 100%;
  margin: 0;
}
.ox-lightbox-dialog__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: calc(100dvh - 190px);
  object-fit: contain;
  border-radius: 12px;
}
.ox-lightbox-dialog__caption { max-width: 60ch; margin-top: 12px; font-size: 15px; line-height: 1.5; text-align: center; color: rgba(255, 255, 255, 0.88); }
.ox-lightbox-dialog__count { grid-column: 1 / -1; grid-row: 2; margin: 0; font-size: 13px; letter-spacing: 0.08em; text-align: center; color: rgba(255, 255, 255, 0.75); }
.ox-lightbox-dialog__nav,
.ox-lightbox-dialog__close {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 30px;
  line-height: 1;
  cursor: pointer;
}
.ox-lightbox-dialog__nav:hover,
.ox-lightbox-dialog__close:hover { background: rgba(255, 255, 255, 0.2); }
.ox-lightbox-dialog__nav:focus-visible,
.ox-lightbox-dialog__close:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.ox-lightbox-dialog__nav--prev { grid-column: 1; grid-row: 1; }
.ox-lightbox-dialog__nav--next { grid-column: 3; grid-row: 1; }
.ox-lightbox-dialog__close { position: absolute; top: 12px; right: 12px; }
html.ox-lightbox-open { overflow: hidden; }
@media (max-width: 620px) {
  .ox-lightbox-dialog { padding: 64px 12px 16px; }
  .ox-lightbox-dialog[open] { grid-template-columns: 1fr 1fr; grid-template-rows: minmax(0, 1fr) auto auto; }
  .ox-lightbox-dialog__frame { grid-column: 1 / -1; }
  .ox-lightbox-dialog__nav--prev { grid-column: 1; grid-row: 2; justify-self: end; }
  .ox-lightbox-dialog__nav--next { grid-column: 2; grid-row: 2; justify-self: start; }
  .ox-lightbox-dialog__count { grid-row: 3; }
  .ox-lightbox-dialog__img { max-height: calc(100dvh - 230px); }
}
@media (prefers-reduced-motion: reduce) { .ox-lightbox__open img { transition: none; } }


/* ---------------------------------------------------------------------------
   Gallery preview
   ---------------------------------------------------------------------------
   With ox-gallery-preview on a campus gallery the page shows only the first
   three photos, in an even row cropped to 4:3; the others stay in the markup
   so the slideshow still holds the full set. theme.js puts "+N more" on the
   third tile and a "View all" button under the row. On phones the first photo
   spans the width and the other two sit beneath it. */
.ox-grid.ox-campus-gallery.ox-gallery-preview,
.ox-campus-gallery.ox-gallery-preview.is-layout-flow,
.ox-campus-gallery.ox-gallery-preview {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-md, 16px);
  columns: auto;
}
.ox-campus-gallery.ox-gallery-preview > * { width: 100%; max-width: none; margin: 0; flex: none; }
.ox-campus-gallery.ox-gallery-preview > :nth-child(n+4) { display: none; }
.ox-campus-gallery.ox-gallery-preview img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
.ox-lightbox__open { position: relative; overflow: hidden; }
.ox-lightbox__more {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: inherit;
  background: rgba(13, 16, 69, 0.58);
  color: #fff;
  font-family: var(--font-display);
  font-size: clamp(22px, 3vw, 32px);
  font-weight: 700;
}
.ox-lightbox__open:hover .ox-lightbox__more { background: rgba(13, 16, 69, 0.68); }
.ox-lightbox__actions { display: flex; justify-content: center; margin-top: var(--space-md, 16px); }
@media (max-width: 620px) {
  .ox-grid.ox-campus-gallery.ox-gallery-preview,
  .ox-campus-gallery.ox-gallery-preview.is-layout-flow,
  .ox-campus-gallery.ox-gallery-preview { grid-template-columns: 1fr 1fr; gap: 12px; }
  .ox-campus-gallery.ox-gallery-preview > :first-child { grid-column: 1 / -1; }
}


/* ---------------------------------------------------------------------------
   Stage feature layout
   ---------------------------------------------------------------------------
   The layout Andrea is trialling on the Vista Hermosa Middle School tab: a
   larger, lighter intro beside the photo, two floating feature cards with
   tick lists, a "tool belt" of resources, a dotted divider before the next
   steps, centred buttons and small italic print under the class hours. All
   core blocks with these classes, so an editor can build it again for any
   stage. The cards stack on narrow screens. */
.ox-tab-lead { margin: 0; font-size: clamp(18px, 1.7vw, 20px); font-weight: 400; line-height: 1.65; color: var(--ox-body); }
.ox-panel-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md, 16px); margin-top: var(--space-lg, 32px); }
.ox-panel-card {
  padding: var(--space-lg, 28px);
  background: var(--ox-card);
  border: 1px solid var(--ox-navy-line);
  border-radius: var(--radius-card, 18px);
  box-shadow: 0 14px 34px rgba(13, 16, 69, 0.09);
}
.ox-panel-card > * { margin-block: 0; }
.ox-panel-card > * + * { margin-top: 14px; }
.ox-panel-card h4 { font-size: 21px; line-height: 1.25; color: var(--ox-fg-strong); }
.ox-panel-card h5 { margin-top: var(--space-md, 20px); font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ox-fg-strong); }
.ox-panel-card p { line-height: 1.6; }
.ox-ticks { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; }
/* An intro line directly above a tick list needs the same breathing room the list keeps between its items. */
p + .ox-ticks { margin-top: 12px; }
.ox-ticks li { position: relative; padding-left: 30px; line-height: 1.55; }
.ox-ticks li::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 6px;
  width: 7px;
  height: 13px;
  border-right: 3px solid var(--ox-fg-brand);
  border-bottom: 3px solid var(--ox-fg-brand);
  transform: rotate(45deg);
}
.ox-toolbelt { margin-top: 10px; padding: 16px 18px; border: 1px dashed var(--ox-navy-line); border-radius: 12px; }
.ox-toolbelt > * { margin-block: 0; }
.ox-toolbelt > * + * { margin-top: 10px; }
.wp-block-separator.ox-divider-dotted {
  width: 100%;
  max-width: none;
  height: 0;
  margin: var(--space-lg, 32px) 0 15px;
  border: 0;
  border-top: 2px dotted var(--ox-navy-line);
  background: none;
  opacity: 1;
}
.ox-microcopy { margin-top: 10px; font-size: 13px; font-style: italic; color: var(--ox-body); }
@media (max-width: 760px) {
  .ox-panel-cards { grid-template-columns: minmax(0, 1fr); }
  .ox-panel-card { padding: 22px 20px; }
}


/* A rate table whose cells hold course names rather than numbers (the
   Preparatoria semester grid on Cocurriculars) adds ox-rates--wrap so its
   cells may wrap; the default nowrap is for short figures. */
.ox-rates.ox-rates--wrap th,
.ox-rates.ox-rates--wrap td { white-space: normal; }
.ox-rates.ox-rates--wrap tbody td:first-child { white-space: nowrap; }


/* Stage layout fixes (3.257.2): the flow layout's sibling margin pushed the
   second card down; core's separator width rule, (0,3,0) through its :not()
   list, held the dotted divider at 100px; and the two buttons in the next-steps
   row sat on different baselines. */
.ox-panel-cards > * { margin: 0; }
/* Three stated columns, for a stage panel that carries three cards (the Vista
   Hermosa Kínder panel): two columns would leave the third card alone on its
   own row. Stacks with the two-column grid below 960px. */
.ox-panel-cards.ox-panel-cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 960px) { .ox-panel-cards.ox-panel-cards--3 { grid-template-columns: minmax(0, 1fr); } }
hr.wp-block-separator.ox-divider-dotted:not(.is-style-wide):not(.is-style-dots) { width: 100%; max-width: none; }
.ox-tabs__panel .ox-cta__actions { align-items: center; }
.ox-tabs__panel .ox-cta__actions > * { margin-block: 0; }


/* Course names such as "Administration" and "Probabilidad" set a minimum
   column width the table could not shrink below: 20px too wide at 768 and
   40px in the phone stack. They may break, cells tighten under 960px, and the
   stacked label column narrows (3.257.3). */
.ox-rates.ox-rates--wrap th,
.ox-rates.ox-rates--wrap td { overflow-wrap: anywhere; }
@media (max-width: 960px) {
  .ox-rates.ox-rates--wrap th,
  .ox-rates.ox-rates--wrap td { padding: 8px; font-size: 14px; }
}
@media (max-width: 620px) {
  .ox-rates.ox-rates--wrap table.is-stackable tbody td { grid-template-columns: minmax(0, 7em) minmax(0, 1fr); }
}


/* Headings of a wrapping rate table stay whole: breaking anywhere split
   "SEMESTER" and "ATHLETICS" mid-word (3.257.4). Only the body cells, which
   hold the long course names, may break. */
.ox-rates.ox-rates--wrap thead th { overflow-wrap: normal; word-break: keep-all; }


/* Feature cards with a drop-down (3.257.5): Andrea asked for the card detail
   to fold away so the Middle School tab is not too long. Each card keeps its
   kicker, title and one-line intro; the list sits in a core Details block
   styled as a plain disclosure row inside the card, not a second card. The
   cards align to the top so opening one does not stretch the other. */
.ox-panel-cards { align-items: start; }
.ox-panel-card .wp-block-details {
  margin: 4px 0 0;
  padding: 0;
  border: 0;
  border-top: 1px solid var(--ox-navy-line);
  border-radius: 0;
  background: none;
}
.ox-panel-card .wp-block-details > summary {
  padding: 14px 0 2px;
  font-size: 15px;
  color: var(--ox-fg-link, var(--ox-fg-brand));
}
.ox-panel-card .wp-block-details > summary:focus-visible { outline-offset: 2px; border-radius: 6px; }
.ox-panel-card .wp-block-details > *:not(summary) { margin: 14px 0 0; }
.ox-panel-card .wp-block-details > *:not(summary):last-child { margin-bottom: 4px; }


/* ---------------------------------------------------------------------------
   Activity cards on After School (3.262.0)
   ---------------------------------------------------------------------------
   The card photograph fills the card, as every other card on the site does:
   letterboxing eleven photographs of different shapes left the row looking
   like a contact sheet. The crop is taken from the top, where the children
   are, and the galleries lead on a wide photograph so the crop has room. The
   uncropped photograph is what the slide above the timetable shows. The
   selected card carries a crimson ring, matching the activity chosen in the
   table. */
.ox-feature-card[data-term] img { object-position: center 30%; }
.ox-feature-card[data-term] { min-height: 340px; }
/* The card is a photograph with words on it, and the words are the shortest
   part: the title, one line about the class and the link. Nothing is allowed
   to run past the bottom edge, where the card would clip it. */
.ox-feature-card[data-term] .ox-feature-card__body { display: grid; gap: 6px; align-content: end; }
.ox-feature-card[data-term] p { margin: 0; }
.ox-feature-card[data-term].is-selected { outline: 3px solid var(--ox-red-600, #b00d1e); outline-offset: 3px; }
.ox-feature-card[data-term]:focus-visible { outline: 3px solid var(--ox-fg-brand, #1a227f); outline-offset: 3px; }
/* The activity heading in the table, when it is the one a family picked. */
.ox-timetable__group th { scroll-margin-top: 110px; }

/* ---------------------------------------------------------------------------
   A film in the page header (3.263.0)
   ---------------------------------------------------------------------------
   The home page header is the institutional film rather than six photographs.
   A film is brighter and busier than a still and it keeps moving while the
   words stay put, so it sits a little further back than a photograph would,
   under the same scrim. */
.ox-page-hero__clip { opacity: 0.85; }
@media (prefers-reduced-motion: reduce) {
  /* The still is what a visitor who asked for less movement keeps, so it is
     shown at the same strength a photograph would be. */
  .ox-page-hero__clip { opacity: 1; }
}

/* ---------------------------------------------------------------------------
   Strand slideshow on Cocurriculars (3.264.0)
   ---------------------------------------------------------------------------
   Andrea, 11 Sep 2026: one slideshow for the whole school day rather than four
   stacked bands, with the strands as clear buttons across the width of it. The
   frame is the campus gallery's photograph treatment; the buttons sit on the
   picture, glass over it, and the chosen one turns solid. */
.ox-strand-show__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card, 18px);
  overflow: hidden;
  background: var(--ox-navy, #0d1045);
}
.ox-strand-show__slide { position: absolute; inset: 0; display: none; margin: 0; }
.ox-strand-show__slide.is-current { display: block; }
.ox-strand-show__slide .ox-lightbox__open,
.ox-strand-show__slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
}
.ox-strand-show__slide .ox-lightbox__open { padding: 0; border: 0; background: none; cursor: zoom-in; }
.ox-strand-show__tabs {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  padding: 0;
  background: rgba(13, 16, 69, 0.35);
  backdrop-filter: blur(6px);
}
.ox-strand-show__tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 16px 10px;
  border: 0;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-family: var(--font-display, inherit);
  font-size: clamp(13px, 1.3vw, 16px);
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.2;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.ox-strand-show__tab:hover { background: rgba(255, 255, 255, 0.2); }
.ox-strand-show__tab.is-current { background: #fff; color: var(--ox-fg-strong, #0d1045); }
.ox-strand-show__tab:focus-visible { outline: 3px solid #fff; outline-offset: -3px; }
.ox-strand-show__nav {
  position: absolute;
  top: calc(50% - 46px);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid rgba(13, 16, 69, 0.12);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ox-fg-strong, #0d1045);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(13, 16, 69, 0.2);
}
.ox-strand-show__nav--prev { left: 12px; }
.ox-strand-show__nav--next { right: 12px; }
.ox-strand-show__nav:hover { background: #fff; }
.ox-strand-show__nav:focus-visible { outline: 3px solid var(--ox-fg-link, #1a227f); outline-offset: 2px; }
@media (max-width: 620px) {
  /* Four buttons need two rows on a phone, so the frame stands taller to keep
     the photograph the larger half of it. */
  .ox-strand-show__frame { aspect-ratio: 4 / 5; }
  .ox-strand-show__tab { flex: 1 1 48%; padding: 11px 8px; font-size: 13px; }
  .ox-strand-show__nav { width: 40px; height: 40px; top: calc(50% - 60px); }
}

/* The strand name over the photograph in the gallery slideshow. */
.ox-lightbox-dialog__shot { position: relative; display: inline-block; max-width: 100%; min-height: 0; }
.ox-lightbox-dialog__strand {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  padding: 10px 18px;
  border-top-left-radius: 12px;
  border-bottom-right-radius: 12px;
  background: rgba(13, 16, 69, 0.78);
  color: #fff;
  font-family: var(--font-display, inherit);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.ox-lightbox-dialog__frame { position: relative; }


/* ---------------------------------------------------------------------------
   A film header shows the whole frame (3.265.0)
   ---------------------------------------------------------------------------
   Andrea: the faces were being cropped out. A photograph header is a band of a
   fixed height with the picture cropped into it; a film header takes its height
   from the film instead, so the frame arrives whole across the full width of
   the page. The floor keeps the words readable on a phone, where the band would
   otherwise be 211px tall. */
/* Andrea, 11 Sep 2026, pointing at asfm.edu.mx: a wide landscape strip rather
   than a tall band, so the child and what they are doing fill it. Measured
   there: 1440 x 700, a shade over 2:1, with the film cropped to cover. */
.ox-page-hero--film { min-height: 420px; }
/* The ratio is only applied where the band is wide enough for it to be the
   larger of the two: an aspect-ratio that loses to min-height starts driving
   the WIDTH instead, which pushed the header 560px wide inside a 375px phone. */
@media (min-width: 780px) {
  .ox-page-hero--film { aspect-ratio: 2 / 1; min-height: 380px; }
}
@media (min-width: 1500px) {
  .ox-page-hero--film { aspect-ratio: auto; min-height: 0; height: min(50vw, 80vh); }
}

/* A pillar folded into an approach card: the long paragraph sits behind a
   disclosure so four of them are a band rather than a page. */
.ox-approach-card .wp-block-details {
  margin: 6px 0 0;
  padding: 0;
  border: 0;
  border-top: 1px solid var(--ox-navy-line, #e3e5ef);
  border-radius: 0;
  background: none;
}
.ox-approach-card .wp-block-details > summary {
  padding: 12px 0 2px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ox-fg-link, var(--ox-fg-brand));
  cursor: pointer;
}
.ox-approach-card .wp-block-details > summary:focus-visible { outline-offset: 2px; border-radius: 6px; }
.ox-approach-card .wp-block-details > *:not(summary) { margin: 10px 0 0; }


/* ---------------------------------------------------------------------------
   Video at reading measure
   ---------------------------------------------------------------------------
   A core Video block runs the full width of its container, which upscales a
   phone-shot interview (848 x 480 here) to about 1250px and makes it soft. The
   class holds the clip at roughly its native width and centres it, so the
   picture stays sharp and the block is still an ordinary Video block in the
   editor. */
.wp-block-video.ox-video-measure { margin-inline: auto; max-width: 860px; width: 100%; }
.wp-block-video.ox-video-measure video {
  display: block;
  width: 100%;
  border-radius: var(--radius-card, 18px);
  background: var(--ox-navy);
}


/* ---------------------------------------------------------------------------
   Two clips side by side
   ---------------------------------------------------------------------------
   Testimonies arrive in whatever shape the person filming held the phone. One
   of these is 848x480 landscape, the other 464x832 portrait, and dropping both
   into a row at their own proportions gives one clip three times the height of
   its neighbour.

   So the frame is the constant and the picture fits inside it: equal squares,
   object-fit: contain, navy behind. The portrait clip shows navy at its sides
   and the landscape one above and below, which reads as a matched pair rather
   than an accident. Navy and not grey — a pale gap looks like a loading error,
   a brand ground looks chosen.

   The pair stacks below 780px, where two columns would leave each clip too
   small to read a face in. */
.ox-video-pair {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: 1fr;
  margin-inline: auto;
  max-width: 900px;
}

@media (min-width: 780px) {
  .ox-video-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.ox-video-pair .wp-block-video {
  margin: 0;
  aspect-ratio: 1 / 1;
  background: var(--ox-navy, #0d1045);
  border-radius: var(--radius-card, 18px);
  overflow: hidden;
}

.ox-video-pair .wp-block-video video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}


/* ---------------------------------------------------------------------------
   The WhatsApp launcher
   ---------------------------------------------------------------------------
   Fixed to the bottom right on every page, printed from wp_footer. z-index 300
   puts it over the page and over the sticky section nav at 210, and under the
   enquiry dialog at 500 and the skip link at 999 — a fixed control that covers
   a modal is a trap, and one that covers the skip link defeats the first thing
   a keyboard user reaches.

   The ground is #0f7a6c, a deep WhatsApp green, measured at 5.24:1 against
   white. The brand's own #25D366 carries white at 1.87:1 and cannot hold a
   label at all.

   Below 780px the word drops and the circle remains: the accessible name comes
   from aria-label, so nothing is lost to a screen reader, and on a phone the
   pill would sit across a third of the screen. */
.ox-whatsapp {
  position: fixed;
  z-index: 300;
  right: max(16px, env(safe-area-inset-right));
  bottom: max(16px, env(safe-area-inset-bottom));
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 64px;
  padding: 0 18px;
  border-radius: 999px;
  background: #0f7a6c;
  color: #fff;
  font-weight: 600;
  font-size: 16px;
  line-height: 1;
  text-decoration: none;
  box-shadow: 0 10px 30px rgba(13, 16, 69, .28);
  transition: background-color .18s ease, transform .18s ease;
}

.ox-whatsapp:hover,
.ox-whatsapp:focus-visible {
  background: #0b5f54;
  color: #fff;
  text-decoration: none;
  transform: translateY(-2px);
}

.ox-whatsapp__mark {
  display: inline-flex;
  flex: 0 0 auto;
}

.ox-whatsapp__label { white-space: nowrap; }

@media (max-width: 779px) {
  .ox-whatsapp {
    width: 64px;
    padding: 0;
    justify-content: center;
  }

  .ox-whatsapp__label { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ox-whatsapp { transition: none; }
  .ox-whatsapp:hover,
  .ox-whatsapp:focus-visible { transform: none; }
}

/* The footer's last row would otherwise sit under the button. */
.ox-footer__bottom { padding-bottom: 72px; }

@media print {
  .ox-whatsapp { display: none; }
}


/* ---------------------------------------------------------------------------
   A film beside a section
   ---------------------------------------------------------------------------
   A parent reading about grade levels, or about how a day is shaped, should be
   able to watch the film while they read rather than instead of reading. The
   media column sticks as the body column scrolls, so one 57-second clip can
   accompany two full bands of text.

   The clip is portrait, which is the shape a phone films in and the shape that
   sits happily in a narrow column. 340px is wide enough to read a child's face
   and narrow enough to leave the text a full measure.

   Below 980px it stops being a column and becomes a normal block above the
   text: side by side at phone width would give both halves too little. */
.ox-video-aside {
  display: grid;
  gap: var(--space-lg);
  grid-template-columns: 1fr;
  align-items: start;
}

/* A grid track is never narrower than its item's min-content unless it is told
   it may be. The tab row inside the body column is a flex row that does not
   wrap — it scrolls instead — so its min-content is all five pills laid end to
   end, and a single 1fr track grew to 651px inside a 343px phone. The page did
   not scroll sideways, because the row clips, but the video hung off the edge.
   One line, and the track is free to be as narrow as the screen. */
.ox-video-aside > * { min-width: 0; }

@media (min-width: 980px) {
  .ox-video-aside { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); }

  .ox-video-aside__media {
    position: sticky;
    /* Clear of the sticky header, as the campus rail at line 3590 does. */
    top: calc(var(--ox-header-h, 89px) + 20px);
  }
}

.ox-video-aside__media .wp-block-video { margin: 0; }

.ox-video-aside__media video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-card, 18px);
  background: var(--ox-navy, #0d1045);
}

/* The body column holds more than one band, so they need their own rhythm. */
.ox-video-aside__body > * + * { margin-top: var(--space-xl); }


/* ---------------------------------------------------------------------------
   Card content as a disclosure list
   ---------------------------------------------------------------------------
   The same facts the ox-approach-card row carries, read as a list: every title
   visible at once, each description behind its own arrow. Four cards side by
   side make a reader choose where to look; four titles in a column let them
   scan the set first and open only what they want.

   Built from core Details blocks, so the arrow, the open and closed states and
   the keyboard behaviour are the browser's own and the school can edit each row
   in the editor like any other block.

   The accent cycles navy, crimson, royal down the list, the same three the
   cards use, so the two treatments read as one family. */
.ox-detail-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ox-detail-list .wp-block-details {
  margin: 0;
  border: 1px solid var(--ox-line, #e7e4dd);
  border-left: 4px solid var(--ox-accent, var(--oxford-navy, #0d1045));
  border-radius: var(--radius-md, 12px);
  background: var(--ox-card, #fffefc);
  padding: 0;
}

.ox-detail-list .wp-block-details:nth-child(3n+1) { --ox-accent: var(--oxford-navy, #0d1045); }
.ox-detail-list .wp-block-details:nth-child(3n+2) { --ox-accent: var(--ox-red-600, #b00d1e); }
.ox-detail-list .wp-block-details:nth-child(3n+3) { --ox-accent: var(--oxford-royal, #1a227f); }

.ox-detail-list .wp-block-details > summary {
  cursor: pointer;
  list-style: none;
  padding: 16px 18px;
  /* 44px of target even when the title is one short line. */
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-weight: 700;
  font-size: 18px;
  color: var(--ox-fg-strong, #0d1045);
}

.ox-detail-list .wp-block-details > summary::-webkit-details-marker { display: none; }

/* The arrow is drawn rather than imported: it is the control's own state, not
   an icon bolted on, so it turns with the disclosure. */
.ox-detail-list .wp-block-details > summary::after {
  content: "";
  margin-left: auto;
  width: 10px;
  height: 10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .18s ease;
}

.ox-detail-list .wp-block-details[open] > summary::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.ox-detail-list .wp-block-details > summary:focus-visible {
  outline: 3px solid var(--ox-focus-ring, #1a227f);
  outline-offset: -3px;
  border-radius: var(--radius-md, 12px);
}

.ox-detail-list .wp-block-details > :not(summary) {
  margin: 0 18px 16px;
  color: var(--ox-body, #626780);
}

@media (prefers-reduced-motion: reduce) {
  .ox-detail-list .wp-block-details > summary::after { transition: none; }
}


/* ---------------------------------------------------------------------------
   Slideshow
   ---------------------------------------------------------------------------
   A group of photos you step through with arrows. Give it BOTH classes —
   `ox-slideshow ox-lightbox` — and the lightbox script still turns every photo
   into a button, so the picture on show is also the way into the full-size
   pop-up. The slideshow is a layer over a gallery that already worked.

   It does not move on its own. An auto-playing show has to carry a stop control
   and still steals attention from the text beside it; arrows put the reader in
   charge and need no such apology.

   The frame holds its own height so the page does not jump as pictures of
   different proportions take their turn, and the photo is CONTAINED on navy
   rather than cropped — these are archive photographs in every shape from
   square to portrait, and cropping them to a common ratio would cut heads off.

   theme.js builds the viewport, the track, the arrows and the counter, so the
   block markup stays a plain group of images the school can reorder in the
   editor. */
.ox-slideshow { position: relative; }

.ox-slideshow__viewport {
  overflow: hidden;
  border-radius: var(--radius-card, 18px);
  background: var(--ox-navy, #0d1045);
  aspect-ratio: 3 / 2;
}

.ox-slideshow__track {
  display: flex;
  height: 100%;
  transition: transform .45s ease;
}

.ox-slideshow__track > * {
  flex: 0 0 100%;
  /* Without this a wide photo refuses to shrink and every slide after it is
     pushed out of step with the track. */
  min-width: 0;
  height: 100%;
  margin: 0;
}

.ox-slideshow img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.ox-slideshow .ox-lightbox__open {
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
}

.ox-slideshow__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(13, 16, 69, .82);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.ox-slideshow__nav:hover { background: rgba(13, 16, 69, .95); }
.ox-slideshow__nav:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.ox-slideshow__nav--prev { left: 12px; }
.ox-slideshow__nav--next { right: 12px; }

.ox-slideshow__count {
  margin: 10px 0 0;
  font-size: 13px;
  letter-spacing: .06em;
  text-align: center;
  color: var(--ox-body, #626780);
}

@media (prefers-reduced-motion: reduce) {
  .ox-slideshow__track { transition: none; }
}


/* ---------------------------------------------------------------------------
   Tabs as a chevron ribbon
   ---------------------------------------------------------------------------
   The grade level picker and the learning journey used to be two sections that
   said the same thing twice: a row of numbered steps that did nothing, and a
   row of pills that did the work. Adding ox-tabs--chevrons to the tab
   group makes the journey the picker — press Explore and Nursery opens.

   The chevrons are the tab list the script already builds, so every bit of the
   ARIA tabs behaviour (roles, arrow keys, Home and End, the roving tabindex)
   comes along unchanged. Only the skin is different.

   The second line comes from a paragraph with the class ox-tabs__sub inside
   each panel, hidden once the tabs are running.

   The notch is clip-path, which browsers do not paint a focus ring around
   cleanly, so the selected and focused states are carried by an inset ring
   drawn inside the shape instead. */
.ox-tabs--chevrons .ox-tabs__list {
  display: grid;
  grid-template-columns: repeat(var(--ox-chev-cols, 5), minmax(0, 1fr));
  gap: 6px;
  padding: 0;
  margin-bottom: var(--space-lg);
  border-bottom: 0;
  overflow: visible;
}

.ox-tabs--chevrons .ox-tabs__tab {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Not flex-start. A column flex item sized to its own content takes its
     max-content width, so the second line laid itself out on one long line and
     the clip-path sliced it off. Stretching the items to the button, with
     min-width: 0 below, is what lets the line wrap inside the padding box. */
  align-items: stretch;
  text-align: left;
  /* The pill row sets white-space: nowrap so a tab name never breaks across
     lines. A chevron carries two lines on purpose, so it has to opt out. */
  white-space: normal;
  min-height: 84px;
  /* The notch cuts the top-right and bottom-right corners, so the first and
     last lines of text need to stay clear of them. */
  padding: 16px 26px 16px 30px;
  border: 0;
  border-radius: 0;
  background: var(--oxford-navy, #0d1045);
  color: #fff;
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 50%, calc(100% - 18px) 100%, 0 100%, 18px 50%);
  transition: filter .18s ease;
}

.ox-tabs--chevrons .ox-tabs__tab:first-child {
  padding-left: 20px;
  border-radius: 12px 0 0 12px;
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 50%, calc(100% - 18px) 100%, 0 100%);
}

.ox-tabs--chevrons .ox-tabs__tab:last-child {
  border-radius: 0 12px 12px 0;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 18px 50%);
}

.ox-tabs--chevrons .ox-tabs__tab:nth-child(3n+2) { background: var(--ox-red-600, #b00d1e); }
.ox-tabs--chevrons .ox-tabs__tab:nth-child(3n+3) { background: var(--oxford-royal, #1a227f); }

.ox-tabs--chevrons .ox-tabs__tab-name {
  min-width: 0;
  font-weight: 700;
  font-size: 16px;
  line-height: 1.2;
}

.ox-tabs--chevrons .ox-tabs__tab-sub {
  min-width: 0;
  font-size: 12.5px;
  line-height: 1.35;
  color: rgba(255, 255, 255, .85);
}

/* Unselected stages step back so the open one reads as the open one. A tab that
   is merely dimmed is still above 4.5:1 on its own ground. */
.ox-tabs--chevrons .ox-tabs__tab[aria-selected="false"] { filter: saturate(.55) brightness(.82); }

.ox-tabs--chevrons .ox-tabs__tab[aria-selected="true"] {
  background: var(--oxford-navy, #0d1045);
  color: #fff;
  box-shadow: inset 0 0 0 3px rgba(255, 255, 255, .9);
}

.ox-tabs--chevrons .ox-tabs__tab[aria-selected="true"]:nth-child(3n+2) { background: var(--ox-red-600, #b00d1e); }
.ox-tabs--chevrons .ox-tabs__tab[aria-selected="true"]:nth-child(3n+3) { background: var(--oxford-royal, #1a227f); }

.ox-tabs--chevrons .ox-tabs__tab:hover { filter: none; }

.ox-tabs--chevrons .ox-tabs__tab:focus-visible {
  outline: 0;
  box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 6px var(--oxford-navy, #0d1045);
}

.ox-tabs.is-tabbed .ox-tabs__sub { display: none; }

/* Five chevrons need about 150px each to hold two lines. Below that they stack
   into a column and the notch flips to point down, because a sequence that
   reads left to right on a laptop reads top to bottom on a phone. */
@media (max-width: 900px) {
  .ox-tabs--chevrons .ox-tabs__list { grid-template-columns: 1fr; gap: 4px; }

  .ox-tabs--chevrons .ox-tabs__tab,
  .ox-tabs--chevrons .ox-tabs__tab:first-child,
  .ox-tabs--chevrons .ox-tabs__tab:last-child {
    min-height: 0;
    padding: 14px 18px 22px;
    border-radius: 0;
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - 14px), 50% 100%, 0 calc(100% - 14px));
  }

  .ox-tabs--chevrons .ox-tabs__tab:first-child { border-radius: 12px 12px 0 0; }

  .ox-tabs--chevrons .ox-tabs__tab:last-child {
    border-radius: 0 0 12px 12px;
    padding-bottom: 14px;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ox-tabs--chevrons .ox-tabs__tab { transition: none; }
}

/* ==== Achievements cards: body copy reads as body copy (3.274.0) ====
   `.ox-icon-card:not(--navy):not(--royal)` (line ~4254) paints the whole card
   navy so the h3 lands right, and the paragraph inherited it. On the navy band
   this never showed; on the white section Andrea asked for it made every card
   a block of navy text with no tonal step between title and body.

   Scoped to the section rather than to `.ox-icon-card`, because the same card
   carries `.ox-contact-line` on the campus and contact pages, whose colour is
   set at (0,1,0) and would lose to any class-level rule written here. */
#oxfordian-achievements .ox-icon-card p { color: var(--ox-body); }



/* ==========================================================================
   Card groups are rows, at every width (3.278.0)
   ==========================================================================
   Andrea, 14 Sep 2026: the campus cards on Admissions and After School, "and
   potentially elsewhere", were stacked instead of in a row, which breaks the
   rule she prefers. A card group is a row.

   Measured before writing, across 27 pages at 375 / 768 / 1440. The groups
   that broke the rule did it three ways:

   - At a phone, every .ox-grid--2/3/4/5 without .ox-scroller, every approach
     grid, the campus and contact cards, the enrichment and certification
     cards and the campus tab cards fell to a single column.
   - Between 621 and 960 the campus cards were a row but ragged. .ox-icon-card
     carries height:100%, and a percentage height is not auto, so flex stretch
     never applied to it.
   - At desktop the campus and contact cards wrapped 3+2 and 2+2, because
     their centred flex-wrap rule is older than the flip row.

   So now: one row at every width. Arrows above 961px (theme.js builds them),
   a swipe below, the next card half in frame so the gesture is obvious, and
   every card in a row the same height. Groups that were already the same at
   all three widths (stat tiles, curriculum accordions, story lists, the
   campus list) are deliberately not in this list.

   Everything is written through one :is() so every group sits at the same
   weight, (0,5,0), which is above the campus cards' own (0,3,0) rules and the
   (0,2,0) .is-layout-flow pairings, without an !important. */

/* Equal heights. The campus tab cards are left out on purpose: they align to
   the top so opening the drop-down in one does not stretch its neighbour
   (3.257.5). */
:is(
  .ox-grid:is(.ox-grid--2, .ox-grid--3, .ox-grid--4, .ox-grid--5):has(> :is([class*="card"], .ox-cta-option)):not(.ox-scroller):not(:has(> .ox-step)),
  .ox-approach-grid:not(.ox-scroller),
  .ox-enrichment-grid,
  .ox-cert-cards:has(> .ox-cert-card)
) {
  align-items: stretch;
}
:is(
  .ox-grid:is(.ox-grid--2, .ox-grid--3, .ox-grid--4, .ox-grid--5):has(> :is([class*="card"], .ox-cta-option)):not(.ox-scroller):not(:has(> .ox-step)),
  .ox-approach-grid:not(.ox-scroller),
  .ox-enrichment-grid,
  .ox-cert-cards:has(> .ox-cert-card)
) > :is([class*="card"], .ox-cta-option) {
  height: auto;
}

/* Phone: a swipe row, never a stack. */
@media (max-width: 620px) {
  :is(
    .ox-grid:is(.ox-grid--2, .ox-grid--3, .ox-grid--4, .ox-grid--5):has(> :is([class*="card"], .ox-cta-option)):not(.ox-scroller):not(:has(> .ox-step)),
    .ox-approach-grid:not(.ox-scroller),
    .ox-enrichment-grid,
    .ox-cert-cards:has(> .ox-cert-card),
    .ox-panel-cards
  ) {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-start;
    grid-template-columns: none;
    gap: var(--space-sm);
    max-width: none;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    /* Room for the card shadow, which an overflow container would clip. */
    padding-bottom: 8px;
  }
  :is(
    .ox-grid:is(.ox-grid--2, .ox-grid--3, .ox-grid--4, .ox-grid--5):has(> :is([class*="card"], .ox-cta-option)):not(.ox-scroller):not(:has(> .ox-step)),
    .ox-approach-grid:not(.ox-scroller),
    .ox-enrichment-grid,
    .ox-cert-cards:has(> .ox-cert-card),
    .ox-panel-cards
  )::-webkit-scrollbar { display: none; }
  :is(
    .ox-grid:is(.ox-grid--2, .ox-grid--3, .ox-grid--4, .ox-grid--5):has(> :is([class*="card"], .ox-cta-option)):not(.ox-scroller):not(:has(> .ox-step)),
    .ox-approach-grid:not(.ox-scroller),
    .ox-enrichment-grid,
    .ox-cert-cards:has(> .ox-cert-card),
    .ox-panel-cards
  ) > * {
    /* 88%, not the scroller's 82%: measured at 375, the rows land between
       239 and 279px of content, and 82% of that left the Library cards 196px
       wide. Still enough of the next card shows to say "swipe". */
    flex: 0 0 88%;
    min-width: 0;
    max-width: none;
    margin-inline: 0;
    scroll-snap-align: start;
  }
  /* The desktop card padding, 40px a side, left a 116px text column once the
     card was 196px wide. The same 20px the approach cards already use. */
  :is(
    .ox-grid:is(.ox-grid--2, .ox-grid--3, .ox-grid--4, .ox-grid--5):has(> :is([class*="card"], .ox-cta-option)):not(.ox-scroller):not(:has(> .ox-step)),
    .ox-approach-grid:not(.ox-scroller),
    .ox-enrichment-grid,
    .ox-panel-cards
  ) > :is(.ox-icon-card, .ox-approach-card, .ox-cta-option, .ox-enrichment-card, .ox-panel-card) {
    padding: var(--space-md);
  }
}

/* Tablet: the band already covers the numbered grids and the campus cards.
   These three were never in it, so they wrapped instead. */
@media (min-width: 621px) and (max-width: 960px) {
  :is(.ox-enrichment-grid, .ox-cert-cards:has(> .ox-cert-card), .ox-panel-cards) {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-start;
    grid-template-columns: none;
    gap: var(--space-sm);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    padding-bottom: 8px;
  }
  :is(.ox-enrichment-grid, .ox-cert-cards:has(> .ox-cert-card), .ox-panel-cards)::-webkit-scrollbar { display: none; }
  /* The two :is() lists lift this to (0,3,0): the certification cards carry
     their own `.ox-cert-cards .ox-cert-card { flex: none }` at (0,2,0), and
     their faces are absolutely positioned, so under flex:none four of the five
     shrank to zero width and vanished. */
  :is(.ox-enrichment-grid, .ox-cert-cards:has(> .ox-cert-card), .ox-panel-cards) > :is(*, .ox-cert-card) {
    flex: 0 0 46%;
    min-width: 0;
    max-width: none;
    scroll-snap-align: start;
  }
}

/* Desktop: the campus and contact cards page like every other row instead of
   wrapping 3+2 around the middle. theme.js has already put .ox-flip-row and
   the arrow bar on them, because they carry an .ox-grid--N class; it was this
   flex-wrap that kept the arrows with nothing to move. */
@media (min-width: 961px) {
  .ox-grid.ox-grid.ox-flip-row:is(.ox-campus-contacts, .ox-contact-cards) {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    padding-bottom: 8px;
  }
  /* 350px, not 300: the longest address on these cards,
     op.viceprincipal@oxfordreynosa.edu.mx, measures 263px at card size, and
     300px less 40px of padding each side left 220px, so every one of them
     broke inside "edu". */
  .ox-grid.ox-grid.ox-flip-row:is(.ox-campus-contacts, .ox-contact-cards) > .ox-icon-card {
    flex: 0 0 350px;
    max-width: none;
    scroll-snap-align: start;
  }
}


/* Meet the Team: the director and vice principal rows theme.js builds were a
   row at desktop, two up at tablet and a single column on a phone. They join
   the rule. They stay aligned to the top rather than equal height, like the
   campus tab cards, because each bio opens in place and a stretched row would
   grow every neighbour when one is read. */
@media (max-width: 960px) {
  .ox-band .ox-band__row {
    display: flex;
    flex-wrap: nowrap;
    grid-template-columns: none;
    gap: var(--space-sm);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    padding-bottom: 8px;
  }
  .ox-band .ox-band__row::-webkit-scrollbar { display: none; }
  .ox-band .ox-band__row > * { flex: 0 0 46%; min-width: 0; scroll-snap-align: start; }
}
@media (max-width: 620px) {
  /* The band has its own 20px of padding inside the section, which left the
     row 237px and a leader card 194px. The row runs out to the band edge and
     pads itself back in, so the first card still lines up with the label. */
  .ox-band .ox-band__row {
    margin-inline: calc(var(--space-md) * -1);
    padding-inline: var(--space-md);
    scroll-padding-inline: var(--space-md);
  }
  .ox-band .ox-band__row > * { flex-basis: 88%; }
}

/* ==========================================================================
   The last two groups join the row rule (3.279.0)
   ==========================================================================
   Andrea, 14 Sep 2026: "make the campus list and accordions rows too". The
   audit had left them out because they were the same at every width, but a
   list is still a stack, and she wants every group to read as a row.

   Both use the same behaviour as the other card rows: arrows above 961px
   (theme.js now includes them in the flip-row list), a swipe below, the next
   card half in frame. */

/* Home: the five campuses beside the map. The first row used to be the only
   raised card, "one clear entry point", while the rest sat flat. In a row that
   reads as one card and four ghosts, so all five take the card now. */
.ox-campus-list {
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: var(--space-sm);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 8px;
}
.ox-campus-list::-webkit-scrollbar { display: none; }

/* The row must not size its own column. Five 220px cards are 1148px of fixed
   width, and a grid track's automatic minimum is its content's min-content, so
   the stack beside the map took 1148px and the split went to `0px 1148px`:
   the map vanished and the cards ran off the panel. min-width:0 on the stack
   and the list gives the track back to the grid, and the list scrolls inside
   it instead. */
.ox-stack:has(> .ox-campus-list),
.ox-campus-list {
  min-width: 0;
  max-width: 100%;
}
.ox-campus-list .ox-campus-row {
  /* Two campuses and the edge of a third fit the 469px column beside the map. */
  flex: 0 0 220px;
  min-width: 0;
  scroll-snap-align: start;
  background: var(--ox-card);
  border-color: var(--ox-navy-line);
  padding-inline: var(--space-md);
  box-shadow: 0 4px 16px rgba(13, 16, 69, 0.06);
}

/* Grade levels: the year groups. They still fold, because the folding is what
   keeps the page short; opening one grows that card only, which is why the
   row aligns to the top instead of stretching. A short row (three year
   groups) shares the width; a long one (six semesters) pages. */
.ox-curriculum-grid--accordion {
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: var(--space-sm);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 8px;
}
.ox-curriculum-grid--accordion::-webkit-scrollbar { display: none; }
.ox-curriculum-grid--accordion > .ox-curriculum-card {
  flex: 1 0 280px;
  min-width: 0;
  scroll-snap-align: start;
}

@media (max-width: 960px) {
  .ox-campus-list .ox-campus-row { flex-basis: 46%; }
  .ox-curriculum-grid--accordion > .ox-curriculum-card { flex: 0 0 46%; }
}
@media (max-width: 620px) {
  .ox-campus-list,
  .ox-curriculum-grid--accordion {
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
  }
  .ox-campus-list .ox-campus-row { flex-basis: 88%; }
  .ox-curriculum-grid--accordion > .ox-curriculum-card { flex-basis: 88%; }
}


/* The seven top-level titles measure 996px before a single gap is added, so a
   one-line bar needs roughly 1,120px of window. Below that the menu button is
   the honest answer - a squeezed or wrapped bar is worse than a tidy button.
   This is deliberately its own query: the 960px breakpoint elsewhere carries
   dozens of unrelated layout rules and must not move with it. */
@media (max-width: 1120px) {
  .ox-nav,
  .ox-header__actions .ox-btn { display: none; }
  .ox-nav-toggle { display: inline-flex; }
  .ox-dropdown { display: none; }
}

/* ==== Tabbed nav sections (3.284.0) ====
   Grade Levels and Campuses are one menu section now, and listed together they
   are twelve rows - most of the hero on a laptop and four screens on a phone.
   The group headings become a tab strip, one group shows at a time, and the
   panel holds at six rows. The markup is the menu's own: a heading is an
   ox-group menu item, so tabs are added and renamed in Appearance > Menus.

   theme.js only tabs a panel where every group carries a heading, so the mixed
   sections keep their plain list and none of this applies to them. */
.ox-dropdown__tabs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin: 2px 4px 10px;
}

.ox-dropdown__tab {
  background: transparent;
  border: 1px solid var(--oxford-hairline, rgba(13, 16, 69, 0.16));
  border-radius: 999px;
  color: var(--ox-fg-strong);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 9px 6px;
  text-transform: uppercase;
  white-space: nowrap;
}

.ox-dropdown__tab[aria-selected="true"] {
  background: var(--ox-navy);
  border-color: var(--ox-navy);
  color: #fff;
}

/* The tab carries the heading now, and the panel is 320px so two pills sit
   side by side without either wrapping. */
.ox-dropdown--tabbed {
  min-width: 320px;
}

.ox-dropdown--tabbed .ox-dropdown__group-label {
  display: none;
}

/* The grouped panel separates its groups with a rule and a gap. One group
   shows at a time here, so both would hang off the top of the list. */
.ox-dropdown--tabbed .ox-dropdown__group + .ox-dropdown__group {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

.ox-dropdown__group[hidden],
.ox-mobile-nav__group[hidden] {
  display: none;
}

/* ---- the same section on a phone ----
   It opens as its own screen: the other six sections step aside and a way back
   sits above the tabs, so twelve links never push Admissions off the bottom. */
.ox-mobile-nav__tabs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin: 4px 0 10px;
}

.ox-mobile-nav__tab {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 999px;
  color: #fff;
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 11px 6px;
  text-transform: uppercase;
}

/* White on navy, not navy tinted: the drawer ground is #0D1045 and a tinted
   fill would drop the selected tab under AA. */
.ox-mobile-nav__tab[aria-selected="true"] {
  background: #fff;
  border-color: #fff;
  color: var(--ox-navy);
}

.ox-mobile-nav__back {
  align-items: center;
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  color: #fff;
  cursor: pointer;
  display: flex;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  gap: 8px;
  margin-bottom: 6px;
  padding: 10px 0;
  width: 100%;
}

.ox-mobile-nav__sub.is-tabbed .ox-mobile-nav__group-label {
  display: none;
}

.ox-mobile-nav__sub.is-tabbed .ox-mobile-nav__group + .ox-mobile-nav__group {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

.ox-mobile-nav.is-detail .ox-mobile-nav__list > li:not(.is-detail) {
  display: none;
}

/* ==== TEDx: the blue band (3.284.0) ====
   The section's content sat inside an ox-split--top with a single child, so it
   was laid out in the left half of a two column grid: a 390px column of text
   inside a 919px band, and a section 5,397px tall. The wrapper is gone from the
   page, and what is left is centred in the band.

   Andrea's own alignment wins: a block she sets to left or right in the editor
   carries has-text-align-*, and this rule steps aside for it. */
.ox-tedx > .ox-container > :is(h2, h3, p):not([class*="has-text-align-"]) {
  text-align: center;
}

.ox-tedx > .ox-container > hr {
  margin-inline: auto;
}

/* Three speaker cards across, the rest paged with the arrows. The row is the
   theme's own band: theme.js sees ox-grid--3 above 961px, adds .ox-flip-row and
   builds the bar. 250px is what fits three of these portrait cards across the
   919px band; the 290px default shows two and a sliver. */
.ox-tedx .ox-grid--3 {
  --ox-flip-card: 250px;
}

/* The arrow is drawn for a light panel — a near white circle with a navy
   chevron. On this band that is a navy chevron on navy, so it reads as two
   empty rings. Same control, its own ink. */
.ox-tedx .ox-band__arrow {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.4);
}

.ox-tedx .ox-band__arrow::before {
  border-color: #fff;
}

.ox-tedx .ox-band__arrow:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.12);
  border-color: #fff;
}

.ox-tedx .ox-band__arrow:hover:not(:disabled)::before {
  border-color: #fff;
}

.ox-tedx .ox-band__arrow:disabled {
  opacity: 0.35;
}

/* Below 621px every card row on this site becomes a ladder, which for six
   portrait cards is 4,990px of section. This one row swipes sideways instead —
   the same gesture the 621-960 range already uses, and the reason the band
   exists at all. Scoped to the TEDx band, deliberately: the ladder is still the
   right answer for rows whose cards are mostly text. */
@media (max-width: 620px) {
  .ox-tedx .ox-grid--3 {
    display: flex;
    gap: var(--space-sm);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    -webkit-overflow-scrolling: touch;
  }

  .ox-tedx .ox-grid--3 > * {
    flex: 0 0 74%;
    scroll-snap-align: start;
  }
}


/* ==== The "learn more" link inside the visit dialog (3.284.0) ====
   `.ox-modal__head p` paints its text at 62% white on navy, which is right for
   the intro sentence and wrong for a link: on the navy it measured as a faint
   grey and did not read as clickable at all. White, underlined, with the same
   offset the prose links use, and a margin so it sits apart from the sentence
   above rather than looking like its last line. */
.ox-modal__head p.ox-modal__more {
  margin-top: 10px;
  color: #fff;
  font-size: 14px;
}
.ox-modal__head p.ox-modal__more a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.ox-modal__head p.ox-modal__more a:hover,
.ox-modal__head p.ox-modal__more a:focus-visible {
  text-decoration-thickness: 2px;
}

/* The cafeteria dish cards are portrait 4:5 artwork on a pale pink ground, not
   the landscape archive photographs the slideshow was built around. Left alone
   they sit in a 3:2 viewport with navy bars down both sides. Matching the shape
   and the ground makes the fit invisible, and the width cap stops a portrait
   photo towering over the paragraphs it sits beside. */
.ox-slideshow--dish { max-width: 460px; margin-inline: auto; }

.ox-slideshow--dish .ox-slideshow__viewport {
  aspect-ratio: 4 / 5;
  background: #f5e6e9;
}

/* The cafeteria opener runs the heading and its first line down the left and the
   dish photographs down the right. Once the split collapses the photographs
   belong directly under the heading, where they set the scene before the reader
   starts on the words. Restating the areas moves them without touching the
   source order, so the block list an editor sees stays in reading order. */
.ox-split--dish {
  grid-template-areas:
    "head  show"
    "prose show";
  /* The photo column spans both rows, so auto rows would share its spare height
     between them and open a gap under the heading. Pin the heading row to its
     content and let the text row absorb the rest. */
  grid-template-rows: max-content 1fr;
  align-items: start;
}

.ox-split--dish > .ox-section-header { grid-area: head; }
.ox-split--dish > .ox-dish-points    { grid-area: prose; }
.ox-split--dish > .ox-slideshow      { grid-area: show; align-self: start; }

@media (max-width: 860px) {
  .ox-split--dish {
    grid-template-areas:
      "head"
      "show"
      "prose";
  }
}

.ox-dish-points { display: flex; flex-direction: column; gap: var(--space-md); }

.ox-dish-lede {
  font-size: 19px;
  line-height: 1.6;
  color: var(--ox-fg-strong);
  margin: 0;
}

/* The three service points read as a list because that is what they are: three
   separate facts of equal weight, not a paragraph that happens to have breaks. */
.ox-dish-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 78ch;
  padding-left: 22px;
}

.ox-dish-list strong { color: var(--ox-fg-strong); }

/* --------------------------------------------------------------------------
   The language control in the utility band
   --------------------------------------------------------------------------
   Words, not flags. A flag stands for a country rather than a language, and a
   British flag for English at a school in Reynosa reads oddly, so the two
   codes carry it. It also keeps the header's rule that a mark there is the
   control itself and never decoration.

   It sits beside the icon items rather than among them: those are an icon over
   a label, and a third shape in that row would read as a fourth destination
   rather than a choice between two states of the page you are already on. */
.ox-lang {
  display: flex;
  align-items: center;
  gap: 2px;
  font-family: var(--font-display);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.2;
  margin-left: 4px;
}

.ox-lang__opt {
  display: block;
  padding: 5px 7px;
  border-radius: 7px;
  color: var(--ox-fg-brand);
  text-decoration: none;
  letter-spacing: 0.04em;
  transition: color 0.15s ease, background 0.15s ease;
}

.ox-lang__opt:hover,
.ox-lang__opt:focus-visible {
  color: var(--ox-fg-strong);
  text-decoration: none;
}

/* The page you are on, said in more than colour: the chip is the second
   signal, so the state does not rest on hue alone. */
.ox-lang__opt.is-current {
  color: var(--ox-fg-strong);
  background: var(--ox-royal-subtle);
}

.ox-lang__sep {
  color: var(--ox-body);
  opacity: 0.45;
}

/* ---- After School conversion pass, 5 Oct 2026 ------------------------------
   The enrollment form, the timetable and the Carta Compromiso are all rendered
   by PHP, and theme PHP cannot be written on this host at the moment, so the
   structure these rules dress is assembled by theme.js from the markup the
   blocks already emit. Each rule degrades to the current page if that script
   does not run.

   The Carta Compromiso's own wording is deliberately untouched: the Spanish
   text is the version that governs and rewording it is the school's call. */

/* The trial class is the low-friction way in, so it is the filled button in
   every timetable row and Enroll beside it keeps the quiet outline. Royal, not
   crimson: this section already spends its one crimson CTA on the banner above,
   and twenty-seven crimson buttons in a table would stop the colour meaning
   anything. Royal carries white at 13.30.

   The white-section override for .ox-trial-btn sets a royal *text* colour at
   0-3-0, which would leave royal-on-royal, so these rules have to out-specify
   it rather than simply declaring the fill. */
.ox-section:not(.ox-section--navy) .ox-timetable .ox-trial-btn.ox-btn--secondary,
.ox-section--navy .ox-timetable .ox-trial-btn.ox-btn--secondary {
  background: var(--oxford-royal);
  border-color: var(--oxford-royal);
  color: var(--oxford-white);
}
.ox-section:not(.ox-section--navy) .ox-timetable .ox-trial-btn.ox-btn--secondary:hover,
.ox-section--navy .ox-timetable .ox-trial-btn.ox-btn--secondary:hover {
  background: var(--oxford-royal-hover);
  border-color: var(--oxford-royal-hover);
  color: var(--oxford-white);
}
/* No focus-ring override here. --oxford-royal-hover is #0D1045, the same value
   as --oxford-navy, so the navy ring the crimson button uses would measure
   1.25:1 against a royal fill. The global royal ring is correct anyway: it sits
   3px outside the button, so what it has to contrast with is the white card
   behind it, where royal measures 13.30. */

/* The optional questions, folded into the terms panel the theme already
   styles. It is a grid item, so it has to be told to cross both columns or it
   sits in the left half with the right half empty. */
.ox-enroll__grid > .ox-terms-details { grid-column: 1 / -1; }
.ox-enroll__grid > .ox-disclosure--fields .ox-terms-details__body {
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ox-enroll__grid > .ox-disclosure--fields .ox-field--wide { grid-column: 1 / -1; }
@media (max-width: 700px) {
  .ox-enroll__grid > .ox-disclosure--fields .ox-terms-details__body {
    grid-template-columns: 1fr;
  }
}
/* A panel holding fields needs the gap under its summary that a panel holding
   prose gets from the prose's own leading. */
.ox-disclosure--fields .ox-terms-details__body { padding-top: 4px; }

/* ---- After School, option D: the cards are the picker (5 Oct 2026) ---------
   The page used to present the same fourteen activities four times over — a
   card row, a photo carousel, a chip row, and a table with all twenty-seven
   campus groups open at once. None of the three choosers reduced the fourth.
   Measured on a phone, that table alone was 9,986 px: 56% of the page.

   The cards keep the job. These three rules are all the styling that needed. */

/* The chip row the cards duplicate. Hidden rather than deleted, because the
   theme's own filter and slideshow still listen to those buttons and the cards
   press them; display:none also takes them out of the accessibility tree, so a
   keyboard or screen-reader visitor meets one chooser rather than two. */
.ox-filter.is-superseded { display: none; }

/* An activity whose photographs have not been taken yet still needs a card, or
   with the chips gone it would have no way in at all. Its card is built exactly
   like the others; this stands in for the picture, under the same navy wash
   every card already carries, so a photo-less card reads as deliberate rather
   than broken. Acrojazz and Artistic Expression are the two. */
.ox-feature-card__ph { position: absolute; inset: 0; background: var(--oxford-navy); }

/* The cards and the detail they govern are one unit now, so the gap between
   their two sections closes. They stay two sections in the markup because the
   jump nav and the #class-... deep links point into them separately. */
#timetable.ox-section { padding-top: 0; }

/* ---- After School, option F: the mosaic and the campus filter (5 Oct 2026)
   Andrea's note on option D: opening on one activity hides the range, and the
   card row only showed about one and a half cards on a phone before you had to
   swipe. So the page now opens on an index of all fourteen, and the detail is
   not on the page until one is chosen. A campus filter sits above it, because a
   family attends one campus: Las Quintas runs 11 of the 14, Middle Years 3. */

/* The card row becomes a mosaic: every activity on screen at once, two across
   on a phone and four on a laptop, at roughly a third of the card height.

   The weight of this selector is deliberate. A .ox-grid--4 card row WITHOUT
   .ox-scroller is turned back into a sideways flex row by the adaptive-row
   rules, which are written through one :is() at (0,5,0) precisely so they beat
   everything below them. A plain .is-mosaic would lose. This is (0,6,0), and
   every part of it is true of this row and of nothing else on the site. */
.ox-grid.ox-grid--4.ox-activity-cards.is-mosaic:not(.ox-scroller):has(> .ox-feature-card) {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  overflow: visible;
  margin-inline: 0;
  padding-inline: 0;
  max-width: none;
}
@media (min-width: 621px) {
  .ox-grid.ox-grid--4.ox-activity-cards.is-mosaic:not(.ox-scroller):has(> .ox-feature-card) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (min-width: 961px) {
  /* theme.js adds .ox-flip-row to card rows at this width, and that rule carries
     grid-template-columns: none !important together with grid-auto-flow: column,
     which turned the mosaic into a single fourteen-column paging row. Nothing
     but !important gets past an !important, and this is the same property the
     theme marks, for the opposite reason. */
  .ox-grid.ox-grid--4.ox-activity-cards.is-mosaic:not(.ox-scroller):has(> .ox-feature-card) {
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
    grid-auto-flow: row;
    grid-auto-columns: auto;
    overflow-x: visible;
  }
}

/* A tile is the same card, smaller: the description goes, the campus count
   takes its place, and the navy wash and the photograph underneath stay. */
.ox-activity-cards.is-mosaic .ox-feature-card { min-height: 132px; }
.ox-activity-cards.is-mosaic .ox-feature-card__body { padding: 12px 13px; }
.ox-activity-cards.is-mosaic .ox-feature-card h3 { font-size: 15px; line-height: 1.25; }
.ox-activity-cards.is-mosaic .ox-feature-card p { display: none; }
.ox-feature-card__count { display: none; }
.ox-activity-cards.is-mosaic .ox-feature-card__count {
  display: block;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.78);
  margin-top: 3px;
}

/* An activity the chosen campus does not run. */
.ox-activity-cards .ox-feature-card.is-off { display: none; }

/* A mosaic does not scroll, so the paging arrows the scroller builds above it
   have nothing to page. They sit before the row, hence the :has(+ ...). */
.ox-flip-nav:has(~ .ox-activity-cards.is-mosaic) { display: none; }

/* The campus filter, and the way back from a chosen activity. */
.ox-asf { display: flex; flex-direction: column; gap: 10px; margin-bottom: var(--space-md); }
.ox-asf__label { font-size: 12px; font-weight: 600; color: var(--ox-body); }
.ox-asf__chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ox-asf__back {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 44px; padding: 0 2px;
  background: none; border: 0; cursor: pointer;
  font-family: inherit; font-size: 14px; font-weight: 600;
  color: var(--ox-fg-link);
  text-decoration: underline; text-underline-offset: 3px;
}
.ox-asf__back:hover { color: var(--oxford-navy); }
.ox-asf__back:focus-visible { outline: 2px solid var(--oxford-royal); outline-offset: 3px; }

/* Two states. Browsing the index, the detail is not on the page at all — that
   is where the length went. Reading one activity, the mosaic steps aside and
   the campus filter stays, so narrowing to your own campus still works. */
body.ox-as-index #timetable { display: none; }
body.ox-as-index .ox-asf__back { display: none; }
body.ox-as-detail #activities .ox-section-header p { display: none; }
/* Out-specifies the mosaic rule above, which is (0,6,0). */
body.ox-as-detail .ox-grid.ox-grid--4.ox-activity-cards.is-mosaic { display: none; }

/* A row for a campus the visitor has filtered out. The stacked-card rule for
   phones sets display:block at (0,3,0), so this has to be heavier. */
.ox-timetable tbody tr.is-off:not(.ox-timetable__group) { display: none; }

/* ======================================================================
   Leader profile dialog                                  (added 2026-10)
   A leader card shows three lines of the biography. The whole of it opens
   in a wide overlay — photograph beside text — when the photograph or the
   card's own button is pressed, so reading about a member of staff is not
   done through a three-line window.

   The biography stays in the card's own markup and the dialog borrows a
   copy of it at open. One source of the text: editing the page edits the
   dialog, the words stay in the document for search engines, and print
   (which unclamps below) still shows them.
   ====================================================================== */

/* The photograph is a control, so it reads as one. A button, not a div with
   a click handler: Tab reaches it, Enter and Space fire it, and a screen
   reader is told it opens something. */
.ox-leader-card__open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  /* The photo wells are square-cornered inside the card's own rounding;
     inheriting height keeps the band's 4/5 aspect box intact. */
  height: 100%;
}
.ox-leader-card__open img { display: block; }

/* Hover and focus lift the photograph very slightly and name what pressing
   it will do. The cue is text, not an icon — the page carries no decorative
   icons — and it is absent until wanted, so the grid stays quiet. */
.ox-leader-card__cue {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 18px 10px 8px;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(to top, rgba(13, 16, 69, 0.82), rgba(13, 16, 69, 0));
  opacity: 0;
  transition: opacity .18s ease;
}
.ox-leader-card__open:hover .ox-leader-card__cue,
.ox-leader-card__open:focus-visible .ox-leader-card__cue { opacity: 1; }
.ox-leader-card__open:focus-visible {
  outline: 3px solid var(--ox-fg-link);
  outline-offset: -3px;
}
/* A touch screen has no hover, so the cue would never show and the only
   affordance there is the card's own button. Show it always instead. */
@media (hover: none) {
  .ox-leader-card__cue { opacity: 1; }
}

/* The name is the card's keyboard control: a button that looks exactly like
   the name it replaces, inheriting the <strong> around it, and showing it is a
   control the way every other link on the site does — colour plus an
   underline, never colour alone. */
.ox-leader-card__name {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}
.ox-leader-card__name:hover,
.ox-leader-card__name:focus-visible {
  color: var(--ox-fg-brand);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Every description is cut to the same three lines AND given that height, so
   the cards in a row are the same length whatever the biography runs to — a
   126-word bio and a 256-word one end level. Nothing is lost by cutting: the
   whole text is one press away in the dialog.

   14px x 1.65 = 23.1px a line, plus the rule's own padding-top and 1px border,
   which count because the theme is border-box. This rule sits after the one in
   the band block above and overrides it. */
.ox-leader-card__bio.is-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc((23.1px * 3) + var(--space-sm) + 1px);
}

/* The directors' and vice principals' rows set align-items:start, so each card
   was only as tall as its own text and the row ended ragged — 571 / 548 / 525
   across the four directors. The institutional band, a grid of equal columns,
   was already level. Stretching the row makes every card in it the same
   height, which is the house rule for a card group anyway. */
.ox-band__row { align-items: stretch; }

/* Equal card heights are not enough on their own: the job titles run to one,
   two or four lines, so each description started at a different height and the
   row still read as ragged, with the slack collected at the bottom of the
   shortest card. Making the card a column and giving the description
   `margin-top:auto` drops every one of them onto the same baseline, so the
   descriptions line up across the row and the slack sits above them, where the
   titles already differ anyway.

   The head-of-school band is excluded: that card is a two-column grid, photo
   beside text, and turning it into a flex column would undo it.
   The photograph is flex:0 0 auto or it shrinks to fit the column. */
.ox-band:not(.ox-band--head) .ox-leader-card {
  display: flex;
  flex-direction: column;
}
.ox-band:not(.ox-band--head) .ox-leader-card__photo { flex: 0 0 auto; }
.ox-band:not(.ox-band--head) .ox-leader-card__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.ox-band:not(.ox-band--head) .ox-leader-card__bio { margin-top: auto; }
/* The pending note takes the same baseline, so a card without a biography
   still ends level with the ones that have it. */
.ox-band:not(.ox-band--head) .ox-leader-card__pending { margin-top: auto; }

/* A card whose biography has not been written yet. Quiet, in the body colour
   at a smaller size, so it reads as a note rather than as copy about the
   person — and it is gone the moment a biography arrives. */
.ox-leader-card__pending {
  /* The same box as a clamped description — rule above, padding, three lines
     of height — so a card still waiting for its biography ends level with the
     ones that have one, instead of leaving a short gap in the row. */
  margin: var(--space-sm) 0 0;
  padding-top: var(--space-sm);
  border-top: 1px solid var(--ox-navy-line);
  min-height: calc((23.1px * 3) + var(--space-sm) + 1px);
  font-size: 13px;
  font-style: italic;
  color: var(--ox-fg-muted);
}

/* ── The overlay ──────────────────────────────────────────────────────── */

.ox-profile {
  position: fixed;
  inset: 0;
  z-index: 520;             /* above the lead modal's 500 */
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--gutter);
  background: rgba(13, 16, 69, 0.6);
  backdrop-filter: blur(4px);
}
.ox-profile.is-open { display: flex; }

.ox-profile__dialog {
  position: relative;
  display: grid;
  /* Wide: the photograph holds a column of its own and the biography gets a
     comfortable measure beside it rather than a narrow card column. */
  grid-template-columns: 300px minmax(0, 1fr);
  /* The row must be allowed to be shorter than its content, or the grid sizes
     itself to the longest biography, max-height clips the overflow, and the
     last paragraphs are unreachable — which is what happened at 768px with
     Ms. Arellano's 256 words. minmax(0, 1fr) hands the height back to the
     dialog so .ox-profile__body can scroll inside it. */
  grid-template-rows: minmax(0, 1fr);
  width: min(940px, 100%);
  max-height: calc(100vh - 48px);
  /* On a phone browser 100vh is the chrome-less height, so the foot of the
     dialog sits under the address bar. dvh is the visible height; the vh line
     above stays as the fallback for anything that does not know it. */
  max-height: calc(100dvh - 48px);
  overflow: hidden;
  background: var(--ox-card);
  border-radius: var(--radius-card);
  box-shadow: 0 32px 80px rgba(0, 0, 0, 0.32);
}

.ox-profile__media {
  background: var(--ox-navy);
  overflow: hidden;
}
.ox-profile__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;   /* a face is near the top of a portrait */
}

.ox-profile__body {
  padding: var(--space-lg);
  min-height: 0;                 /* a grid item defaults to min-content; without
                                    this, overflow-y never engages */
  overflow-y: auto;              /* the text scrolls, the photograph does not */
  -webkit-overflow-scrolling: touch;
}
.ox-profile__role {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ox-fg-brand);
}
.ox-profile__name {
  margin: 0 0 4px;
  font-size: clamp(24px, 3vw, 32px);
  line-height: 1.15;
  color: var(--ox-fg-strong);
}
.ox-profile__title {
  margin: 0 0 var(--space-sm);
  font-size: 15px;
  color: var(--ox-fg-muted);
}
.ox-profile__mail {
  display: inline-block;
  margin-bottom: var(--space-md);
  font-size: 14px;
  word-break: break-word;        /* a long address must not widen the dialog */
}
.ox-profile__bio { color: var(--ox-fg-default); }
.ox-profile__bio p { margin: 0 0 var(--space-sm); }
.ox-profile__bio p:last-child { margin-bottom: 0; }

.ox-profile__close {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 0;
  background: rgba(13, 16, 69, 0.72);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.ox-profile__close:hover { background: var(--ox-navy); }

/* Tablets: the photograph narrows before it is dropped, because the face is
   the point of the dialog. */
@media (max-width: 860px) {
  .ox-profile__dialog { grid-template-columns: 220px minmax(0, 1fr); }
  .ox-profile__body { padding: var(--space-md); }
}

/* Phones: two columns cannot both hold their measure, so the dialog becomes
   one scrolling column with the photograph capped — a full-height portrait
   would push every word below the fold. */
@media (max-width: 620px) {
  .ox-profile { padding: 0; }
  .ox-profile__dialog {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    width: 100%;
    max-height: 100vh;
    max-height: 100dvh;
    border-radius: 0;
  }
  .ox-profile__media { max-height: 42vh; }
  .ox-profile__media img { object-position: center 22%; }
  .ox-profile__body { padding: var(--space-md) var(--space-md) var(--space-lg); }
}

/* Reduced motion: the cue is the only movement here and it is an opacity
   fade, which is kept, but the lift is not. */
@media (prefers-reduced-motion: reduce) {
  .ox-leader-card__cue { transition: none; }
}

@media print {
  /* The dialog itself is screen furniture. The biographies it holds are not,
     so the clamp on the card comes off and the whole text prints. */
  .ox-profile,
  .ox-leader-card__cue,
  .ox-leader-card__more { display: none !important; }
  .ox-leader-card__bio.is-clamped {
    display: block !important;
    -webkit-line-clamp: none;
    overflow: visible;
    min-height: 0;
  }
}

/* ======================================================================
   Next-grade links restored                               (added 2026-10)
   The site icon policy hides decorative glyphs, and its list included
   `.ox-link-arrow` — which is not a glyph, it is the whole link. Two
   different things wear that class: a plain <span> inside a card that is
   itself clickable (the home page's "Explore" and "Read full story", where
   hiding it is harmless), and a real <a href> carrying the only route from
   one grade level to the next. The rule was emptying the second kind.

   So: a link wearing the class comes back, and only its arrow glyph stays
   hidden — which is what the policy was actually after. The policy's own
   comment already names this trap for .ox-footer__social, "the mark *is*
   the link; hiding it empties the link".
   ====================================================================== */
/* The policy rule is `display: none !important`, and the file's own two
   exceptions (.ox-util__icon, .ox-numbers-stat__icon) override it the same
   way, so this does too. The glyph inside needs no !important — nothing
   targets it that heavily once its parent is visible again. */
a.ox-link-arrow { display: inline-flex !important; }
a.ox-link-arrow > svg { display: none; }

/* ======================================================================
   One behaviour across the three overlays                 (added 2026-10)
   The site has exactly three: the Schedule a Visit modal, the photo
   lightbox and the leader profile dialog. They are built differently on
   purpose — the lightbox is a native <dialog>, so the browser gives it
   Escape, focus containment and an inert background — but what a visitor
   meets should not differ.

   Scroll lock. The modal and the profile each set body.style.overflow
   directly, so whichever closed first unlocked the page under the other.
   The lightbox added `ox-lightbox-open` to <html> and **no rule ever
   matched it**, so opening a photo did not lock the page at all and the
   page scrolled away behind it. Both classes are honoured here, and
   theme.js counts the locks so nesting is safe.
   ====================================================================== */
html.ox-scroll-locked,
html.ox-lightbox-open { overflow: hidden; }

/* Close control. 48px in the lightbox, 34px in the other two. 44px is the
   WCAG 2.5.5 target size and is the same in all three, so closing an
   overlay is the same gesture in the same place wherever you are. */
.ox-modal__close,
.ox-profile__close,
.ox-lightbox-dialog__close {
  width: 44px;
  height: 44px;
}

/* The grade-level prev/next strip, once its duplicate visit button went.
   .ox-cta-box is padded for a row with a button in it; holding one or two
   short links instead, that padding left a tall empty slab — worst on Nursery
   and High School, which have a neighbour on one side only. Tighten the block
   padding when the box has no button. Other pages' cta-boxes still have one,
   so they are untouched. */
.ox-cta-box:not(:has(.ox-btn)) {
  padding-block: var(--space-sm);
}

/* ======================================================================
   The modal template                                      (added 2026-10)
   The site has three overlays — the Schedule a Visit modal, the photo
   lightbox and the leader profile dialog. Measured open at 1440px on
   8 Oct 2026 they ran three different scales: titles 27 and 32,
   supporting lines 14 and 15, meta 13 and 14. The profile was already
   closest to the page's own type, so the other two move to it.

   Six roles, one size each: eyebrow 11, title 32 (28 in a dialog under
   860px), supporting 15, body 17/1.65, meta 13, form input 16 minimum.
   Widths are untouched — 620 for the form, full screen for the lightbox,
   940 for the profile. They do different jobs; only the type is shared.

   The form fields already sit at 17px, above the 16px floor that stops
   iOS zooming the page on focus, so they are left alone.
   ====================================================================== */

/* Title. Was 27px, against the profile's 32px for the same rank. 28 is the
   narrow-dialog size; the profile keeps 32 at 940px wide. */
.ox-modal__head h3 {
  font-size: 28px;
  line-height: 1.15;
}

/* Supporting — the line under the title. Was 14px here, 15px in the
   lightbox caption and the profile's role line, which do the same job. */
.ox-modal__head p {
  font-size: 15px;
  line-height: 1.5;
}

/* Field labels inside the dialog take the same supporting size. The
   uppercase display treatment is the house form style and stays; only the
   size moves. Scoped to the overlay, so the forms on ordinary pages are
   untouched until that is asked for. */
.ox-modal .ox-field > label,
.ox-modal .ox-field label {
  font-size: 15px;
}

/* The lightbox's own arrows were 48px against its 44px close button, so the
   three controls in one overlay did not agree with each other. */
.ox-lightbox-dialog__nav {
  width: 44px;
  height: 44px;
}

/* ======================================================================
   Where an overlay sends you next                    (added 9 Oct 2026)

   The template above settled the type across the three overlays. This
   settles the action. Schedule a Visit already ended in one unmistakable
   control — .ox-btn--primary, the full width of its column, 47px tall.
   The leader profile ended in a 14px address link that read as a footnote
   although it was the only thing a parent could do from that dialog. It
   now carries the same button in the same place, so "what do I do from
   here" is answered the same way wherever you are.
   ====================================================================== */

.ox-profile__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--ox-navy-line);
}

/* The address is a button now, so the quiet inline rule further up no longer
   suits it. Said here rather than edited in place, so that rule still serves
   any markup keeping the bare link.
   .ox-btn carries white-space: nowrap; a long name — "Email Ms. Contreras" —
   would widen the dialog on a phone rather than run to two lines. */
.ox-profile__actions .ox-profile__mail {
  display: inline-flex;
  min-height: 44px;
  margin-bottom: 0;
  font-size: 15px;
  white-space: normal;
  text-align: center;
}

/* A card that ends in a button puts the button on the card's floor, so a row
   of cards shows one line of controls instead of a ragged edge. The cards are
   already equal height and already a flex column — the Volunteering and
   University Guidance rows measured 301px and 301px with their buttons 25px
   apart — so only the push was missing. */
.ox-icon-card > .ox-btn:last-child {
  margin-top: auto;
}

/* ======================================================================
   Meet the Team: the job title, and the height of a face
                                                     (added 12 Oct 2026)

   Two cards of fourteen — Mr. Cortez and Ms. Tamayo — carried
   .is-director-title, which set the job title in crimson display bold.
   The other twelve set the same line in the body grey at 500. It was not
   a mark of seniority: Ms. Contreras, Ms. Arellano, Ms. Corpus and
   Ms. Saldaña are all directors and all read grey. It was an anomaly on
   two cards, and it stacked a second crimson run directly beneath the
   crimson role eyebrow with only the navy name between them. Ms. Tamayo's
   title is 68 characters, which came to 115px of crimson bold — taller
   than the photograph's own text column.

   The class stays in the markup; only its paint is withdrawn, so the two
   cards now read exactly like the other twelve.
   ====================================================================== */
.ox-band .is-director-title {
  color: var(--ox-fg-muted);
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: normal;
}

/* The portraits were framed 4:5 while the photographs themselves are
   900 × 1275, which is 5:7. The frame was cropping the top and bottom off
   every one of them to make each face squarer than it was taken. At 5:7
   the frame is the photograph's own shape: almost nothing is cut, and the
   portraits stand up rather than sitting back. The head band keeps its own
   3:4, where the photograph sits beside the text rather than above it. */
.ox-band .ox-leader-card__photo { aspect-ratio: 5 / 7; }
