/* ==========================================================================
   Master Irvin's House of Discipline — components
   Tokens live in tokens.css. Avoid hard-coded values in here.
   ========================================================================== */

/* ---- Reset -------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

img, picture, svg, video { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
button { font: inherit; }

:focus-visible {
  outline: 3px solid var(--c-gold);
  outline-offset: 3px;
}

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

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--c-ink); color: var(--text-on-dark);
  padding: var(--sp-2) var(--sp-3); font-family: var(--font-ui);
}
.skip-link:focus { left: var(--sp-3); top: var(--sp-3); }

/* ---- Layout ------------------------------------------------------------- */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.header > .wrap { max-width: calc(var(--maxw) + 2 * var(--gutter)); }
.wrap--narrow { max-width: var(--maxw-narrow); }
.section { padding-block: var(--section-y); }
.section--dark { background: var(--bg-dark); color: var(--text-on-dark); }
.section--alt  { background: var(--bg-alt); }
.section--alt-2 { background: var(--bg-alt-2); }
.section--tight { padding-block: calc(var(--section-y) * .6); }
.stack > * + * { margin-top: var(--sp-3); }

/* The header is fixed, so any anchor target would otherwise land underneath it.
   Applies to the scroll cue, the programs.html#… deep links from the home page
   cards, and the skip link. */
[id] { scroll-margin-top: calc(var(--header-h) + var(--sp-3)); }
.stack-lg > * + * { margin-top: var(--sp-5); }

/* ---- Type --------------------------------------------------------------- */
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--font-display-weight); line-height: var(--lh-head); text-wrap: balance; }
h1 { font-size: var(--fs-h1); line-height: var(--lh-tight); text-transform: uppercase; letter-spacing: .005em; }
h2 { font-size: var(--fs-h2); text-transform: uppercase; }
h3 { font-size: var(--fs-h3); }
p  { max-width: var(--measure); }
.lead { font-size: var(--fs-lead); line-height: 1.5; }

.eyebrow {
  font-family: var(--font-ui);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  font-weight: 500;
  /* --accent is 4.47:1 on bone-2 and 3.89:1 on bone-3, both under the 4.5:1
     needed at this size. Dark sections override this back to gold below. */
  color: var(--accent-ink);
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}

.section--dark .eyebrow { color: var(--c-gold); }


.section-head { margin-bottom: var(--sp-6); }
/* :not(.eyebrow) matters. `.section-head p` outranks `.eyebrow`, so without it
   every eyebrow sitting inside a section head loses its accent colour and
   renders as muted body grey — on light and dark sections alike. */
.section-head p:not(.eyebrow) { color: var(--text-muted); }
.section--dark .section-head p:not(.eyebrow),
.section--dark .muted:not(.eyebrow) { color: var(--text-on-dark-muted); }
.muted { color: var(--text-muted); }

.hangul { font-family: var(--font-hangul); letter-spacing: .06em; }

/* ---- Buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55em;
  font-family: var(--font-ui); font-weight: 500;
  letter-spacing: var(--track-wide); text-transform: uppercase;
  font-size: .95rem;
  padding: .95em 1.7em;
  min-height: 48px;
  border: 2px solid transparent;
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn--ghost { background: transparent; color: var(--text); border-color: currentColor; }
.btn--ghost:hover { background: var(--text); color: var(--bg); }
.section--dark .btn--ghost,
.hero .btn--ghost,
.banner .btn--ghost,
.lineage .btn--ghost,
.cta .btn--ghost { color: var(--text-on-dark); }
.section--dark .btn--ghost:hover,
.hero .btn--ghost:hover,
.banner .btn--ghost:hover,
.lineage .btn--ghost:hover,
.cta .btn--ghost:hover { background: var(--text-on-dark); color: var(--c-ink); }
.btn--block { width: 100%; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* ---- Load screen -------------------------------------------------------- */
.loader {
  position: fixed; inset: 0; z-index: 999;
  display: grid; place-items: center;
  background: var(--c-ink);
  opacity: 1; transition: opacity .42s var(--ease);
}
.loader.is-done { opacity: 0; pointer-events: none; }
.loader__inner { display: grid; justify-items: center; gap: var(--sp-4); padding: var(--gutter); }
.loader__mark { width: 104px; height: 104px; }
.loader__name {
  font-family: var(--font-display); text-transform: uppercase;
  color: var(--text-on-dark); text-align: center; line-height: 1.15;
  font-size: 1.05rem; letter-spacing: .05em;
}
.loader__name span {
  display: block; font-size: .64rem; letter-spacing: .2em;
  font-weight: 300; color: var(--c-gold); margin-top: .25rem;
}
.loader__track {
  width: min(240px, 60vw); height: 3px;
  background: rgba(245, 241, 232, .16); overflow: hidden;
}
.loader__bar {
  display: block; width: 100%; height: 100%;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left center;
  transition: transform 1.15s var(--ease);
}
.loader__bar.is-complete { transition-duration: .26s; }
/* Hold the page still while the overlay is up. */
.is-loading, .is-loading body { overflow: hidden; }

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

/* ---- Header / nav ------------------------------------------------------- */
.header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  color: var(--text-on-dark);
  padding: var(--frame) var(--frame) 0;
  transition: background var(--dur) var(--ease), padding var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
/* Solid once you scroll off the hero, or while the mobile menu is open. */
.header.is-solid, .header.nav-open {
  padding: 0;
  background: rgba(11, 11, 12, .97);
  box-shadow: 0 1px 0 var(--rule-on-dark);
}
@supports (backdrop-filter: blur(8px)) {
  .header.is-solid { background: rgba(11, 11, 12, .82); backdrop-filter: blur(10px); }
}
.header__inner {
  min-height: var(--header-h);
  display: grid; align-items: center;
  /* minmax(0,1fr), not 1fr: a 1fr track's minimum is its content, so a wider
     item on one side outgrows the other and drags the centred logo off-centre. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: var(--sp-2);
}
/* grid-row is not optional: with only an explicit column, auto-placement pushes
   later items onto a second row instead of sitting beside the logo. */
.header__inner > .wordmark       { grid-column: 2; grid-row: 1; justify-self: center; }
.header__inner > .nav-toggle     { grid-column: 1; grid-row: 1; justify-self: start; }
/* Without justify-self the list stretches across the whole column and the
   glyphs end up at its left edge, beside the logo instead of in the corner. */
.header__inner > .header__social { grid-column: 3; grid-row: 1; justify-self: end; }
.header > .wrap { max-width: calc(var(--maxw) + 2 * var(--gutter)); }

/* Badge is the original logo with the three swastika ornaments removed
   (assets/logo-cleaned.png). The name is live text beside it, since the badge's
   own ring lettering is unreadable at this size. */
.wordmark { display: flex; align-items: center; gap: .6rem; text-decoration: none; padding-block: .6rem; }
.wordmark__mark { width: 56px; height: 56px; flex: none; }
/* Archivo Black ships no italic file, so this is the browser's synthesised
   oblique. On a face this heavy it holds up; a real italic would be better if
   the family ever gets one. */
.wordmark__text {
  font-family: var(--font-display); font-style: italic; text-transform: uppercase;
  line-height: 1.05; white-space: nowrap;
  /* The header is transparent over the hero, so these three lines sit directly
     on a photograph whose brightness is not knowable. Imperceptible once the
     header goes solid. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
}
/* Scales with the viewport: in Archivo Black this lockup is ~1.4x the width it
   was in Oswald, which pushed the header controls off a 375px screen. */
.wordmark__name { display: block; font-size: clamp(.78rem, .62rem + .48vw, 1.2rem); letter-spacing: .03em; }
.wordmark__sub  { display: block; font-size: clamp(.54rem, .44rem + .25vw, .68rem); letter-spacing: .14em; color: var(--c-gold); font-weight: 300; }
/* Third line of the lockup. Oswald, not the display face: Archivo Black at this
   size closes up and stops being readable. */
.wordmark__motto {
  display: block; font-family: var(--font-ui); font-weight: 400;
  /* Floor is 9px: below that this line stops being readable on a phone, and
     the badge already carries the motto anyway. */
  font-size: clamp(.5625rem, .5rem + .18vw, .625rem); letter-spacing: .1em;
  /* Muted grey lost the fight with the hero photo at this size; white wins. */
  color: var(--text-on-dark); opacity: .9; margin-top: .18rem;
}

/* Below ~350px the centred lockup grows into the MENU button. The header is
   position: fixed, so this never shows up as page overflow — it just collides.
   The lockup has to stay under (viewport - 2 * the toggle's right edge). */
@media (max-width: 350px) {
  /* The three header columns share ~272px here. The corner icons need 48 and
     the MENU button ~49, so the lockup has to stay under ~175 — otherwise the
     side columns (minmax(0,1fr)) get squeezed and the icons shrink to fit. */
  .wordmark { gap: .45rem; }
  .wordmark__mark { width: 46px; height: 46px; }
  .wordmark__name { font-size: .72rem; }
  /* HOUSE OF DISCIPLINE is the widest line here, and it is the tracking doing
     it, not the size. */
  .wordmark__sub { letter-spacing: .06em; }
  /* The quote marks pushed this past the sub line; it is the widest again. */
  .wordmark__motto { font-size: .47rem; letter-spacing: .04em; }
}

.nav-toggle {
  display: inline-flex; align-items: center; gap: .5rem;
  background: none; border: 1px solid var(--rule-on-dark); color: inherit;
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: var(--track-wide);
  font-size: .78rem; padding: .7rem .9rem; border-radius: var(--radius); cursor: pointer;
  min-height: 44px;
}
/* Below 480px the word will not fit beside a centred logo and the icons, and
   the three of them overlap. The bars alone still read as a menu. */
@media (max-width: 479px) { .nav-toggle__label { display: none; } }
.nav-toggle__bars { display: grid; gap: 3px; }
.nav-toggle__bars span { display: block; width: 16px; height: 2px; background: currentColor; }

.nav { display: none; }
.nav[data-open="true"] {
  display: block;
  position: absolute; left: 0; right: 0; top: 100%;
  background: var(--c-ink-2);
  border-bottom: 3px solid var(--accent);
  padding: var(--sp-3) var(--gutter) var(--sp-4);
}
.nav__list { list-style: none; padding: 0; margin: 0; display: grid; gap: 2px; }
.nav__list a {
  display: block; text-decoration: none;
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: var(--track-wide);
  font-size: .86rem; padding: .85rem .2rem;
  border-bottom: 1px solid var(--rule-on-dark);
}
.nav__list a[aria-current="page"] { color: var(--c-gold); }
.nav__cta { margin-top: var(--sp-3); }

/* The header switches to its desktop layout at 1150px, not the usual 900px:
   six nav items either side of a centred logo need that width. */
@media (min-width: 1150px) {
  .nav-toggle { display: none; }
  .nav, .nav[data-open="true"] {
    display: block; position: static; background: none; border: 0; padding: 0;
  }
  .header__inner > .nav { grid-column: 1; grid-row: 1; justify-self: start; }
  .nav__list { grid-auto-flow: column; gap: var(--sp-1); align-items: center; }
  .nav__list a {
    border: 0; font-size: .72rem; letter-spacing: .06em; padding: .6rem .52rem;
    color: rgba(245, 241, 232, .86);
    border-radius: var(--radius); transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .nav__list a:hover { background: rgba(245, 241, 232, .16); color: var(--text-on-dark); }
  .nav__list a[aria-current="page"] { background: var(--c-bone); color: var(--c-ink); font-weight: 500; }
  .nav__cta { display: none; }
  /* Mark left, name to its right, a little larger than the mobile lockup. */
  .wordmark { gap: .75rem; }
  .wordmark__mark { width: 84px; height: 84px; }
  .wordmark__name { font-size: 1.28rem; }
}

/* ---- Header social icons ----------------------------------------------- */
/* Rendered only when a URL is configured — see SOCIAL in build.py. With none
   set the right column is simply empty, and the centred logo stays centred
   because both side columns are minmax(0,1fr). */
.header__social {
  list-style: none; padding: 0; margin: 0;
  display: flex; align-items: center; gap: var(--sp-2);
}
/* Plain glyphs tucked into the corner rather than buttons: no box, no border,
   just the mark. The padding keeps a ~40px tap target without drawing one. */
.header__social a {
  display: grid; place-items: center;
  padding: 10px;
  color: var(--text-on-dark);
  opacity: .9;
  transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease);
}
.header__social a:hover { opacity: 1; color: var(--c-red-bright); }
.header__social svg { display: block; }
/* On a phone the corner marks and the centred lockup both want the same 375px
   bar. The glyph shrinks and the horizontal padding goes, but the VERTICAL
   padding stays: that keeps each target 26x44, which clears the 24x24 minimum.
   Trimming the height too would have made them 20x34 and failed it. */
@media (max-width: 420px) {
  .header__social { gap: 0; }
  /* flex: none is the point. These sit in a flex row inside a squeezed grid
     column, so without it they shrink below their own padding and the targets
     come out 19px wide. */
  .header__social a { flex: none; padding: 14px 4px; }
  .header__social svg { width: 16px; height: 16px; }
}

/* ---- Hero --------------------------------------------------------------- */
.hero {
  position: relative; overflow: hidden;
  margin: var(--frame); border-radius: 0;
  background: var(--c-ink); color: var(--text-on-dark);
  /* svh keeps mobile browser chrome from cropping it; vh is the fallback. */
  min-height: 72vh;
  min-height: var(--hero-h);
  display: flex; align-items: center;
}
.hero__media { position: absolute; inset: 0; }
.hero__media picture { display: block; width: 100%; height: 100%; }
.hero__media img {
  width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 30%;          /* portrait crop: hold the kick */
  /* Barely graded. The warmth of the room is the reason this photo works. */
  filter: saturate(1.14) contrast(1.05) brightness(1.03);
}
@media (min-width: 800px) {
  .hero__media img { object-position: 50% 36%; }
}
.hero__scrim {
  position: absolute; inset: 0;
  background:
    /* 1. flat wash — 0 by default, see --hero-wash-alpha */
    linear-gradient(rgba(var(--hero-wash), var(--hero-wash-alpha)),
                    rgba(var(--hero-wash), var(--hero-wash-alpha))),
    /* 2. The hero carries no copy, so all that is needed is weight at the very
          top for the floating nav and a little at the foot. The photo keeps its
          colour everywhere else. To put a text block back, restore the pool:
            radial-gradient(52% 32% at 50% 55%,
              rgba(11,11,12,.80) 0%, rgba(11,11,12,.74) 42%,
              rgba(11,11,12,.32) 74%, rgba(11,11,12,.04) 100%), */
    linear-gradient(to bottom,
      rgba(11,11,12,.44) 0%,
      rgba(11,11,12,.10) 16%,
      rgba(11,11,12,0) 32%,
      rgba(11,11,12,0) 80%,
      rgba(11,11,12,.34) 100%);
}
.hero__inner {
  position: relative; width: 100%;
  padding-top: calc(var(--header-h) + var(--sp-6));
  padding-bottom: clamp(1.6rem, 1rem + 2.2vw, 2.6rem);
}
.hero__content {
  max-width: 780px; margin-inline: auto;
  display: grid; gap: var(--sp-4); justify-items: center;
  text-align: center;
}
@media (min-width: 800px) {
  .hero__content { max-width: min(1120px, 92%); }
}
/* The hero carries no copy at the moment. These are kept so the block can be
   dropped back in without rebuilding its type. */
.hero h1 {
  font-weight: var(--font-display-weight);
  font-size: clamp(1.8rem, .3rem + 3.25vw, 4.1rem);
  line-height: 1.08; letter-spacing: -.02em; max-width: none; margin: 0 auto;
}
.hero h1 mark { background: none; color: var(--c-red-bright); }
.hero__sub {
  font-family: var(--font-ui); font-weight: 300;
  text-transform: uppercase; letter-spacing: .065em;
  font-size: clamp(.72rem, .68rem + .2vw, .82rem); line-height: 1.5;
  color: var(--text-on-dark); max-width: 52ch; margin-inline: auto;
}

/* Wide block button. Not only hero styling — do not delete with the hero copy. */
.btn--wide {
  width: 100%; max-width: 520px;
  background: var(--c-bone); color: var(--c-ink); border-color: var(--c-bone);
  text-transform: uppercase; letter-spacing: .1em;
  font-size: .86rem; font-weight: 500;
  padding: 1.1em 1.6em;
}
.btn--wide:hover { background: #fff; border-color: #fff; }
.hero .btn--wide {
  width: auto; max-width: none;
  background: var(--accent); color: #fff; border-color: var(--accent);
  padding: 1.15em 2.4em;
}
.hero .btn--wide:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

/* page banner (interior pages) — same treatment as the home hero */
.banner {
  background: var(--c-ink); color: var(--text-on-dark);
  position: relative; overflow: hidden;
  margin: var(--frame); border-radius: 0;
  min-height: 72vh;
  min-height: var(--hero-h);
  display: flex; align-items: flex-end;
}
.banner__media { position: absolute; inset: 0; }
.banner__media img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 40%;
  filter: saturate(1.12) contrast(1.04) brightness(1.02);
}
.banner::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(rgba(var(--hero-wash), var(--hero-wash-alpha)),
                    rgba(var(--hero-wash), var(--hero-wash-alpha))),
    linear-gradient(to top,
      rgba(11,11,12,.86) 0%,
      rgba(11,11,12,.66) 18%,
      rgba(11,11,12,.22) 40%,
      rgba(11,11,12,.02) 60%,
      rgba(11,11,12,.02) 76%,
      rgba(11,11,12,.40) 100%),
    linear-gradient(to right, rgba(11,11,12,.30) 0%, rgba(11,11,12,.02) 58%);
}
.banner__inner {
  position: relative; z-index: 2; width: 100%;
  padding-top: calc(var(--header-h) + var(--sp-5));
  padding-bottom: clamp(2.2rem, 1.2rem + 3vw, 3.6rem);
}
.banner__content { max-width: min(680px, 62%); }
@media (max-width: 799px) {
  .banner__content { max-width: none; }
}
.banner .lead { color: var(--text-on-dark-muted); max-width: 54ch; }
.banner__rule { width: 72px; height: 4px; background: var(--accent); margin-top: var(--sp-4); }

/* ---- Breadcrumb-ish page intro ----------------------------------------- */
.kicker {
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: var(--track-wide);
  font-size: var(--fs-eyebrow); color: var(--c-gold); margin-bottom: var(--sp-3);
}

/* ---- Split feature ------------------------------------------------------
   Copy on a panel one side, photograph bleeding to the edge on the other.
   No gutter between them and no page padding — the join is the point. */
.feature { display: grid; }
@media (min-width: 900px) {
  .feature { grid-template-columns: 1fr 1fr; align-items: stretch; }
}
.feature__body {
  background: var(--bg-alt);
  display: flex; align-items: center;
  /* A band, not a page of copy: the block should read wider than it is tall. */
  padding: clamp(2.9rem, 1.9rem + 2.8vw, 4.5rem) var(--gutter);
}
.feature__inner { max-width: 34rem; margin-inline: auto; }
.feature__inner > * + * { margin-top: var(--sp-4); }
/* Larger than a standard section heading — this is the page's welcome. */
.feature__inner h2 {
  margin-top: 0;
  font-size: clamp(2.2rem, 1.4rem + 3.2vw, 4rem);
  line-height: .98; letter-spacing: -.02em;
}
.feature__inner .btn { margin-top: var(--sp-4); }
.feature__media { min-height: 60vw; }
.feature__cutout { display: none; }
@media (min-width: 900px) {
  /* The photo must NOT set the band's height. It is a portrait (326x365), so an
     in-flow img at 50% width resolves to ~717px tall and drags the whole band
     with it. Taking it out of flow lets the copy panel define the row, which is
     what keeps this a rectangle instead of a page-deep block. */
  .feature__media { min-height: 0; position: relative; }
  /* The stage reaches above the band by --feature-break. Both images fill it
     with identical cover geometry, so the only thing separating them is where
     each is clipped — and the two clips are exact complements, which is why the
     seam is invisible. Nothing here may set overflow: hidden. */
  .feature__stage { position: absolute; left: 0; right: 0; bottom: 0; top: calc(-1 * var(--feature-break)); }
  .feature__stage img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; }
  .feature__photo  { clip-path: inset(var(--feature-break) 0 0 0); }
  .feature__cutout { display: block; clip-path: inset(0 0 calc(100% - var(--feature-break)) 0); }
}
/* The source is only 326px wide (see BLOCKED.md); anchor the crop below centre
   so the upscaling falls on the floor rather than on their faces. */
.feature__media img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; }

/* ---- Grid helpers ------------------------------------------------------- */
.grid { display: grid; gap: var(--sp-4); }
.grid--2 { grid-template-columns: 1fr; }
.grid--3 { grid-template-columns: 1fr; }
@media (min-width: 720px) { .grid--2 { grid-template-columns: repeat(2, 1fr); } .grid--3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .grid--3 { grid-template-columns: repeat(3, 1fr); } }

.split { display: grid; gap: var(--sp-5); align-items: center; }
@media (min-width: 880px) { .split { grid-template-columns: 1fr 1fr; gap: var(--sp-6); } .split--media-right > :first-child { order: -1; } }

/* ---- Cards -------------------------------------------------------------- */
/* Portrait photo, a dark name plaque tucked under it, then the copy centred
   below. The plaque is deliberately narrower than the photo — that inset is
   what reads as a plaque rather than a caption bar. */
.card {
  background: transparent; border: 0;
  display: flex; flex-direction: column;
  text-align: center;
}
.card__media {
  aspect-ratio: 4 / 5; overflow: hidden; background: var(--c-ink-2);
  border-radius: var(--radius-lg);
  box-shadow: 0 10px 24px rgba(11, 11, 12, .10);
}
.card__media img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 32%; }
.card__plaque {
  width: min(74%, 20rem); margin: 0 auto;
  background: var(--c-ink); color: var(--text-on-dark);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  padding: var(--sp-3);
  font-size: clamp(.95rem, .88rem + .25vw, 1.1rem);
  line-height: 1.15;
  /* Names run to one or two lines. A floor keeps every plaque in a row the same
     depth, so their bottom edges line up across the grid. */
  min-height: 4.75rem; display: grid; place-items: center;
}

/* For programs we have no honest photo of yet. Reads as a deliberate branded
   panel rather than a stand-in picture of the wrong thing. */
.card__media--plain {
  display: grid; place-items: center;
  background:
    radial-gradient(120% 120% at 30% 15%, rgba(205,46,58,.28) 0%, rgba(205,46,58,0) 55%),
    var(--c-ink);
}
/* Portrait sources where the faces sit high in the frame. */
.card__media--upper img { object-position: 50% 16%; }

.card__media--plain .hangul {
  font-size: clamp(2rem, 1.2rem + 2.4vw, 3rem);
  color: var(--c-gold); opacity: .85;
}
.card__body {
  padding: var(--sp-4) var(--sp-3) 0; display: flex; flex-direction: column;
  align-items: center; gap: var(--sp-2); flex: 1;
}
/* The opening phrase carries the colour. --c-red is 4.47:1 on --bg-alt, which
   misses 4.5:1 for text this size; the deep red measures 7.5:1. */
.card__body strong { color: var(--accent-ink); font-weight: 600; }
.card__age {
  font-family: var(--font-ui); font-size: .72rem; letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--accent-ink);
}
.card h3 { text-transform: uppercase; }
.card p { font-size: .98rem; max-width: 34ch; }
.card__link {
  margin-top: auto; font-family: var(--font-ui); text-transform: uppercase;
  letter-spacing: var(--track-wide); font-size: .78rem; color: var(--accent-ink);
  text-decoration: none; padding-top: var(--sp-2);
}
.card__link:hover { text-decoration: underline; }


/* ---- Program detail block ----------------------------------------------- */
/* Two columns on desktop: the name and the one-line promise on the left, the
   detail on the right. Six single-column blocks stacked was the length problem. */
.program {
  display: grid; gap: var(--sp-4);
  border-top: 1px solid var(--rule);
  padding-top: var(--sp-5);
}
.program + .program { margin-top: var(--sp-6); }
@media (min-width: 900px) {
  .program { grid-template-columns: 5fr 7fr; gap: var(--sp-6); }
  /* Keeps the title beside its detail while the right column scrolls past. */
  .program__aside { position: sticky; top: calc(var(--header-h) + var(--sp-4)); align-self: start; }
}
.program__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.program__aside .lead { color: var(--text-muted); }
.program__body > * + * { margin-top: var(--sp-3); }
.program__times { font-size: .93rem; color: var(--text-muted); }
.program__tag {
  font-family: var(--font-ui); font-size: .72rem; letter-spacing: var(--track-wide);
  text-transform: uppercase; background: var(--accent); color: #fff; padding: .35em .8em;
}
.program__tag--quiet { background: var(--c-ink); }

/* One quiet link per program instead of six red slabs; the page still ends on
   the full call to action. */
.program__link {
  display: inline-flex; align-items: center; gap: .5em;
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: var(--track-wide);
  font-size: .78rem; color: var(--accent); text-decoration: none;
  margin-top: var(--sp-4); min-height: 44px;
}
.program__link::after { content: "\2192"; transition: transform var(--dur) var(--ease); }
.program__link:hover { text-decoration: underline; }
.program__link:hover::after { transform: translateX(4px); }

/* ---- Program index ------------------------------------------------------ */
.program-index { margin-bottom: var(--sp-6); }
.program-index ul {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
}
.program-index a {
  display: inline-block;
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: .1em;
  font-size: .74rem; text-decoration: none;
  padding: .6rem .9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.program-index a:hover {
  background: var(--c-ink); color: var(--text-on-dark); border-color: var(--c-ink);
}

/* ---- Lists -------------------------------------------------------------- */
.ticks { list-style: none; padding: 0; display: grid; gap: var(--sp-2); max-width: var(--measure); }
.ticks li { position: relative; padding-left: 1.7rem; }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 10px; height: 10px; background: var(--accent); transform: rotate(45deg);
}
.section--dark .ticks li::before { background: var(--c-gold); }

.creds { list-style: none; padding: 0; display: grid; gap: var(--sp-3); }
.creds li { padding-left: var(--sp-4); border-left: 3px solid var(--c-gold); }

/* ---- Credibility strip -------------------------------------------------- */
.stats { display: grid; gap: var(--sp-4); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 860px) { .stats { grid-template-columns: repeat(4, 1fr); } }
.stat__num {
  font-family: var(--font-display); font-size: clamp(2.2rem, 1.4rem + 3vw, 3.4rem);
  line-height: 1; color: var(--c-gold);
}
.stat__label {
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: .1em;
  font-size: .74rem; color: var(--text-on-dark-muted); margin-top: var(--sp-2);
}

/* ---- Parallax band ------------------------------------------------------
   The photo holds still and the TEXT drifts over it. Because nothing about the
   image moves, its box needs no oversizing and the picture is shown whole —
   moving the image instead would crop it by twice the travel distance.
   Done with a transform, not background-attachment: fixed, which iOS Safari
   does not handle. Movement is skipped under prefers-reduced-motion.
   --parallax-range is how far the text travels. */
.parallax { position: relative; overflow: hidden; --parallax-range: 60px; }
.parallax__media {
  position: absolute; left: 0; right: 0; z-index: 0;
  /* Exceeds the section by the travel distance so no edge is ever exposed.
     This is the cost of a visible parallax: twice the travel is cropped. */
  top: calc(-1 * var(--parallax-range) - 4px);
  bottom: calc(-1 * var(--parallax-range) - 4px);
}
.parallax__media img {
  /* The section is wide and short; the source is a tall portrait, so this
     crops height only. y=9% keeps the raised foot, his face and his fist
     in the band rather than showing ceiling. */
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 9%;
  opacity: .46;
  filter: grayscale(.45) contrast(1.22) brightness(.94);
  will-change: transform;
}
/* Keeps the stats readable wherever the photo is bright. */
.parallax::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  /* Veil is set against the brightest part of the photo (the lit wall) so the
     small stat labels keep roughly 5:1 contrast. Raising image opacity means
     raising this too. */
  background:
    linear-gradient(rgba(11,11,12,.48), rgba(11,11,12,.48)),
    linear-gradient(to bottom, rgba(11,11,12,.32) 0%, rgba(11,11,12,0) 38%, rgba(11,11,12,.32) 100%);
}
.parallax__content { position: relative; z-index: 2; }

@media (prefers-reduced-motion: reduce) {
  .parallax__media img { transform: none !important; }
}

/* ---- Credibility band ---------------------------------------------------
   Sized to the photo's own 3:2 proportions rather than squeezed into a short
   strip, so the kick, the instructor and the seated class all stay in frame.
   Capped at 86vh so it never runs taller than the screen on wide monitors. */
.credibility {
  position: relative;
  aspect-ratio: 16 / 9;
  min-height: 440px;
  max-height: 86vh;
  display: flex; align-items: center;
  padding: 0;
  /* Travel costs 2x this in crop. The group photo here is 16:9 in a 16:9 box,
     so vertical travel eats into the WIDTH — too much and the kids at the far
     left and right get clipped. 50px keeps them all in frame. */
  --parallax-range: 50px;
}
.credibility__inner {
  width: 100%; text-align: center;
  padding-block: clamp(1.8rem, 1rem + 3vw, 3.2rem);
}
.credibility .section-head { margin-bottom: var(--sp-5); }
.credibility .section-head h2 { margin-inline: auto; }
/* margin-inline matters as much as the justify: the eyebrow is a <p>, so it
   inherits max-width:62ch and would otherwise sit as a narrow block pinned to
   the left with its text centred inside it. */
.credibility .eyebrow { justify-content: center; margin-inline: auto; }
.credibility .stat__num { font-size: clamp(1.9rem, 1.2rem + 2.4vw, 3rem); }

/* The copy sits in the middle of the frame now, so the weight goes there —
   a soft centre vignette over a light overall wash, rather than a floor. */
.credibility.parallax::after {
  background:
    /* Extra depth behind the centred copy... */
    radial-gradient(82% 68% at 50% 50%,
      rgba(11,11,12,.18) 0%,
      rgba(11,11,12,.12) 55%,
      rgba(11,11,12,0) 100%),
    /* ...over a uniform floor. The stats run the full width, so the outer two
       columns sit where a vignette alone is weakest; the floor is what carries
       them. This image is at full opacity, so the floor has to do real work —
       at .46 the small labels measured 1.9:1. */
    linear-gradient(rgba(11,11,12,.62), rgba(11,11,12,.62));
}
/* Muted grey is the right hierarchy on a flat panel and the wrong one over a
   photograph — these labels are small and need the contrast. */
.credibility .stat__label { color: var(--text-on-dark); }
/* The standard gold measures 4.1:1 here, under the 4.5 needed for the eyebrow,
   and lands exactly on 3.0 for the figures. The soft gold clears both. */
.credibility .eyebrow,
.credibility .stat__num { color: var(--c-gold-soft); }
.credibility .parallax__media img { opacity: 1; filter: contrast(1.04) saturate(1.02); }

@media (max-width: 719px) {
  /* Portrait phones: the 3:2 box gets short, so give the text room instead. */
  .credibility { aspect-ratio: auto; min-height: 78svh; }
  /* The stats stack into 2x2 here, so the text block is much taller and its top
     edge reaches into the bright part of the photo. Darken earlier to match. */
  .credibility.parallax::after {
    background:
      radial-gradient(95% 55% at 50% 50%,
        rgba(11,11,12,.86) 0%,
        rgba(11,11,12,.74) 55%,
        rgba(11,11,12,.40) 85%,
        rgba(11,11,12,.30) 100%),
      linear-gradient(rgba(11,11,12,.28), rgba(11,11,12,.28));
  }
}

/* ---- Pull quote / positioning ------------------------------------------ */
.pull {
  border-left: 5px solid var(--accent);
  padding: var(--sp-2) 0 var(--sp-2) var(--sp-4);
  font-size: var(--fs-lead); line-height: 1.45;
}
.pull cite { display: block; margin-top: var(--sp-3); font-style: normal; font-family: var(--font-ui);
  text-transform: uppercase; letter-spacing: var(--track-wide); font-size: .74rem; color: var(--text-muted); }
.section--dark .pull cite { color: var(--text-on-dark-muted); }

/* ---- Figures ------------------------------------------------------------ */
.figure { margin: 0; }
.figure img { width: 100%; border-radius: var(--radius); }
.figure figcaption {
  font-family: var(--font-ui); font-size: .74rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-muted); margin-top: var(--sp-2);
}
.section--dark .figure figcaption { color: var(--text-on-dark-muted); }
.figure--framed img { border: 8px solid var(--c-ink); }
/* Some source photos are small (see BLOCKED.md). Cap them rather than upscale. */
.figure--small { max-width: 340px; }
.figure--small img { width: 100%; }

/* ---- Table -------------------------------------------------------------- */
.table-wrap { overflow-x: auto; border: 1px solid var(--rule); background: #fff; }
.section--dark .table-wrap { border-color: var(--rule-on-dark); background: var(--bg-dark-alt); }
table.schedule { width: 100%; border-collapse: collapse; min-width: 560px; }
table.schedule caption { text-align: left; padding: var(--sp-3) var(--sp-4); font-family: var(--font-ui);
  text-transform: uppercase; letter-spacing: var(--track-wide); font-size: .74rem; color: var(--text-muted); }
table.schedule th, table.schedule td { text-align: left; padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--rule); vertical-align: top; }
.section--dark table.schedule th, .section--dark table.schedule td { border-color: var(--rule-on-dark); }
table.schedule thead th {
  font-family: var(--font-ui); text-transform: uppercase; letter-spacing: var(--track-wide);
  font-size: .74rem; background: var(--c-ink); color: var(--text-on-dark); border-top: 0;
}
table.schedule tbody th { font-family: var(--font-display); font-weight: var(--font-display-weight); white-space: nowrap; }
table.schedule td.time { font-variant-numeric: tabular-nums; white-space: nowrap; }
tr.is-appointment { background: rgba(205, 46, 58, .06); }

/* ---- Schedule cards (mobile mirror) ------------------------------------ */
.slot { border-top: 1px solid var(--rule); padding-block: var(--sp-3); }
.slot__day { font-family: var(--font-display); text-transform: uppercase; }
.slot__time { font-family: var(--font-ui); font-size: .8rem; letter-spacing: .08em; color: var(--accent); }

/* ---- CTA band ----------------------------------------------------------- */
.cta {
  position: relative; overflow: hidden;
  background: var(--c-ink); color: var(--text-on-dark);
  border-top: 6px solid var(--accent);
}
.cta__media { position: absolute; inset: 0; z-index: 0; }
.cta__media img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%;
  opacity: .30;
  filter: grayscale(.4) contrast(1.1);
}
/* The CTA carries the smallest text on the site (the address line) over this
   photo, so the veil is weighted under the left-aligned copy. Raising the image
   opacity above means raising this too. */
.cta::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to right, rgba(11,11,12,.86) 0%, rgba(11,11,12,.62) 48%, rgba(11,11,12,.40) 100%),
    linear-gradient(to bottom, rgba(11,11,12,.45) 0%, rgba(11,11,12,.15) 40%, rgba(11,11,12,.55) 100%);
}
.cta__inner {
  position: relative; z-index: 2;
  padding-block: var(--section-y); display: grid; gap: var(--sp-4); justify-items: start;
}
.cta h2 { max-width: 18ch; }
.cta p { color: var(--text-on-dark-muted); }
/* Same trap as .section-head p: `.cta p` outranks `.eyebrow`, so without these
   the CTA's eyebrow greys out and its rule line is drawn in the light colour. */
.cta .eyebrow { color: var(--c-gold); }

.cta__phone {
  font-family: var(--font-display); font-size: clamp(1.9rem, 1.2rem + 3vw, 3rem);
  color: var(--c-gold); text-decoration: none; letter-spacing: .01em;
}
.cta__phone:hover { color: var(--c-gold-soft); }
.cta__note { font-family: var(--font-ui); font-size: .74rem; letter-spacing: .1em; text-transform: uppercase; }

/* ---- Footer ------------------------------------------------------------- */
/* Social marks top left, the practical details top right, then the name run
   edge to edge underneath. */
.footer { background: var(--c-ink-2); color: var(--text-on-dark); padding-block: var(--sp-6) var(--sp-4); }
/* The details columns that sat here are parked in
   src/partials/_footer-details.off.html; their rules (.footer__meta,
   .footer__col, .footer h4, .footer address, .footer__value*) went with them. */
.footer__top { display: grid; gap: var(--sp-5); }
.footer a { color: var(--text-on-dark); }

.footer__nav {
  margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--rule-on-dark);
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-5);
  font-family: var(--font-ui); text-transform: uppercase;
  letter-spacing: var(--track-wide); font-size: .78rem;
}
.footer__nav a { text-decoration: none; color: var(--text-on-dark-muted); }
.footer__nav a:hover { color: var(--text-on-dark); }

/* One line, no wrap, sized from the available width so it stays flush to both
   edges at every viewport. 8.117 is this exact 12-character run's width in ems
   at this letter-spacing — measured, not guessed. Change the word or the
   tracking and it has to be re-measured. A plain vw fraction does NOT work:
   the gutter is a fixed px value, so the overshoot grows as the screen
   narrows (it clipped the last S at 375px). */
.footer { container-type: inline-size; }
.footer__wordmark {
  margin: var(--sp-5) 0 0; padding: 0 var(--gutter);
  font-family: var(--font-display); font-weight: var(--font-display-weight);
  font-size: calc((100vw - 2 * var(--gutter)) / 8.117);
  line-height: .78; letter-spacing: -.035em;
  white-space: nowrap; overflow: hidden; color: var(--text-on-dark);
}
/* cqw excludes the scrollbar; vw does not, and would clip a sliver on any
   platform with a classic scrollbar. */
@supports (font-size: 1cqw) {
  .footer__wordmark { font-size: calc((100cqw - 2 * var(--gutter)) / 8.117); }
}

.social { list-style: none; padding: 0; margin: 0; display: flex; gap: var(--sp-4); }
.social a { display: block; color: var(--text-on-dark); opacity: .78; transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease); }
.social a:hover { opacity: 1; color: var(--c-red-bright); }
.social svg { display: block; }

.footer__bottom {
  margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--rule-on-dark);
  display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: space-between;
  font-size: .82rem; color: var(--text-on-dark-muted);
}

/* ---- Mobile sticky tap-to-call ----------------------------------------- */

/* ---- Map ---------------------------------------------------------------- */
.map { border: 0; width: 100%; aspect-ratio: 4 / 3; background: var(--bg-alt); }
@media (min-width: 880px) { .map { aspect-ratio: 16 / 10; } }

/* ---- Notice (internal placeholders) ------------------------------------ */
.notice {
  border: 1px dashed var(--accent); background: rgba(205,46,58,.05);
  padding: var(--sp-3) var(--sp-4); font-size: .9rem;
}

/* ---- Testimonials ------------------------------------------------------- */
/* Oversized red headline, a cut-out figure anchored bottom-left, and dark quote
   cards to its right. The figure is allowed to run past the top of the card
   column — that overlap is the whole effect, so nothing here clips. */
.testimonials { background: var(--bg); }
.testimonials__head { text-align: center; margin-bottom: var(--sp-6); }
.testimonials__title {
  color: var(--accent);
  font-size: clamp(2.4rem, 1rem + 5.4vw, 5.2rem);
  line-height: .95; letter-spacing: -.01em; margin: 0;
}
.testimonials__sub {
  font-family: var(--font-ui); text-transform: uppercase;
  letter-spacing: var(--track-wide); font-weight: 500;
  font-size: clamp(.8rem, .7rem + .5vw, 1.15rem);
  color: var(--text); margin: var(--sp-3) 0 0;
  /* The global `p { max-width: var(--measure) }` would leave this a narrow
     left-hugging block inside a centred head. */
  max-width: none;
}
.testimonials__body { display: grid; gap: var(--sp-5); }
.testimonials__figure { margin: 0; text-align: center; }
.testimonials__figure img { width: min(62%, 15rem); height: auto; }
.testimonials__quotes { display: grid; gap: var(--sp-4); align-content: start; }
.quote {
  margin: 0; background: var(--bg-dark-alt); color: var(--text-on-dark);
  border-radius: var(--radius-lg); padding: var(--sp-4);
}
.quote p { margin: 0; font-size: 1rem; }
.quote cite {
  display: block; margin-top: var(--sp-3); text-align: right;
  font-style: normal; font-size: .82rem; color: var(--text-on-dark-muted);
}
@media (min-width: 900px) {
  /* The figure column is narrower than the figure: the cut-out overhangs into
     the gutter and the quotes start beside it. */
  .testimonials__body { grid-template-columns: minmax(0, 21rem) minmax(0, 1fr); gap: var(--sp-6); align-items: end; }
  .testimonials__figure { align-self: end; }
  /* Capped by HEIGHT, not width: the cut-out is 0.39:1, so sizing it by width
     alone makes it tower over the quote column. */
  .testimonials__figure img { width: auto; max-width: 100%; max-height: 38rem; display: block; margin-inline: auto; }
  .testimonials__quotes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); align-content: end; }
  .testimonials__quotes > :nth-child(3) { grid-column: 2; grid-row: 1 / span 2; }
}

/* ---- Lineage ------------------------------------------------------------ */
/* Badge one side, the credentials the other, on black. The badge's own outer
   ring is black, so it reads as the mark floating rather than as a sticker. */
.lineage { background: var(--bg-dark); color: var(--text-on-dark); }
.lineage__inner { display: grid; gap: var(--sp-5); align-items: center; }
.lineage__mark { display: grid; place-items: center; }
.lineage__mark img { width: min(70vw, 20rem); height: auto; }
.lineage__body h2 {
  margin: 0;
  font-size: clamp(2rem, 1.3rem + 2.6vw, 3.4rem);
  line-height: 1; letter-spacing: -.015em;
}
/* Nothing in the base sheet separates stacked paragraphs here, so they ran
   together as one block. */
.lineage__body > * + * { margin-top: var(--sp-4); }
.lineage__body p { color: var(--text-on-dark-muted); margin-bottom: 0; }
.lineage__body strong { color: var(--text-on-dark); font-weight: 600; }
.lineage__body .btn { margin-top: var(--sp-4); }
@media (min-width: 900px) {
  .lineage__inner { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: var(--sp-6); }
  .lineage__mark img { width: 100%; max-width: 26rem; }
}
