
  :root{
    /* From the ESPIN logo */
    --teal:#57b3a0;   --teal-deep:#2b7566;  --teal-tint:#e2f2ee;
    --coral:#f0776d;  --coral-deep:#c44536; --coral-tint:#fde8e6;
    --gold:#f4b63e;   --gold-deep:#8f6408;  --gold-tint:#fdf1d8;
    --lav:#a98bd3;    --lav-deep:#7856ac;   --lav-tint:#efe9f8;
    --sky:#6fa8dc;    --sky-deep:#406d9c;   --sky-tint:#e4eef9;
    --leaf:#9cc97e;
    --ink:#2f3b56;        /* navy from "PRESCHOOL CENTER" */
    --ink-soft:#5d6880;
    --paper:#fffdf9;      /* cream white */
    --cream:#fffefc;
    --mist:#f5f1e9;
    --line:rgba(47,59,86,.12);

    /* The container now grows with the screen instead of freezing at 1180px,
       which left ~40% of a 1920 monitor as dead margin and squeezed the
       5-across card grids down to ~200px columns. Gutters stay generous. */
    --maxw:min(92vw,1460px);
    --maxw-text:74rem;         /* text-led sections that should stay readable */
    --section-y:clamp(3.5rem,2.6rem + 3.4vw,7rem);
    --section-y-tight:clamp(2.8rem,2.2rem + 2.4vw,5rem);
    --h-card:clamp(1.15rem,1.05rem + .35vw,1.45rem);   /* card + tile titles */
    --cta-gap:1.9rem;          /* supporting copy -> its call to action */
    --coral-ink:#b53d2f;       /* coral text on coral tint (chip only) */
    --gap-eyebrow:.85rem;      /* section label -> the heading it names */
    --track-eyebrow:.17em;     /* tracking eases as the label gets larger */
    /* Page gutter. The old floor of 1.15rem left only 18px beside content
       on a 390px screen, which reads as pressed against the edge. */
    --page-pad:clamp(1.4rem,4vw,2.5rem);
    --radius:24px; --radius-sm:16px;
    --shadow:0 26px 60px -30px rgba(47,59,86,.32);
    --shadow-sm:0 14px 34px -22px rgba(47,59,86,.35);
    --f-display:"Poppins",system-ui,sans-serif;
    --f-body:"Nunito",system-ui,-apple-system,sans-serif;

    /* Typography scale. Tracking is a function of size, not of element:
       display type needs to be pulled in, small bold type needs to be let
       out. One negative value across every heading is what made the page
       read as compressed. */
    --track-display:-.012em;   /* 2.5rem and up: optical correction only */
    --track-heading:-.004em;   /* section headings */
    --track-title:.005em;      /* card and sub headings */
    --track-ui:.012em;         /* nav, buttons, small bold labels */
    --track-label:.14em;       /* uppercase micro labels */
    --leading-display:1.12;
    --leading-heading:1.18;
    --leading-title:1.3;
    --leading-body:1.68;
    /* Space under a heading, in the heading's own em. Poppins drops about
       .34em below the baseline, while tight display leading leaves only
       (lh - 1)/2 of half-leading to hold it - so roughly .25-.28em of the
       descender hangs outside the box and eats the gap. This clears that
       and adds a real optical gap on top. A rem value cannot do this: a
       64px h1 overflows twice as far as a 32px h2. */
    --gap-heading:.62em;       /* heading -> supporting copy */
    --ease:cubic-bezier(.22,.61,.36,1);
  }
  *,*::before,*::after{box-sizing:border-box;-webkit-tap-highlight-color:transparent;}
  html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;text-size-adjust:100%;overflow-x:clip;color-scheme:light;}
  body{margin:0;font-family:var(--f-body);color:var(--ink);background:var(--paper);font-size:clamp(1rem,.96rem + .2vw,1.08rem);line-height:var(--leading-body);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:clip;}
  a,button,input,select,textarea,label,.btn,.filter{touch-action:manipulation;}
  h1,h2,h3,h4{font-family:var(--f-display);font-weight:600;line-height:var(--leading-heading);margin:0;letter-spacing:var(--track-heading);}
  h1{font-size:clamp(2.3rem,1.75rem + 3.13vw,5.5rem);line-height:var(--leading-display);letter-spacing:var(--track-display);}
  h2{font-size:clamp(2rem,1.35rem + 2.1vw,3.9rem);}
  h3,h4{line-height:var(--leading-title);letter-spacing:var(--track-title);}
  h3{font-size:clamp(1.25rem,1.05rem + .78vw,1.8rem);}
  /* Keeps "Where little learners belong." from leaving a single orphaned
     word on its own line, and stops paragraphs ending on a lone word. */
  h1,h2{text-wrap:balance;}
  p,.lede{text-wrap:pretty;}
  p{margin:0 0 1rem;}
  a{color:inherit;text-decoration:none;}
  img{max-width:100%;display:block;}
  ul{margin:0;padding:0;list-style:none;}
  .wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--page-pad);padding-left:max(var(--page-pad),env(safe-area-inset-left));padding-right:max(var(--page-pad),env(safe-area-inset-right));}
  section{scroll-margin-top:88px;}
  .band{padding:var(--section-y) 0;}
  /* Card-heavy bands carry their own height, so they need less air around
     them than text-led ones. This is what stops every section feeling the
     same depth on a long scroll. */
  .band--tight{padding:var(--section-y-tight) 0;}
  /* Section label. At .74rem this read as decoration rather than as the name
     of the section, so a parent scanning the page had two strong tiers and a
     whisper. Raised to a legible size and bound more tightly to the heading
     it introduces. The statement below it is deliberately left as the
     dominant type: it carries the message, the label only names the section. */
  .eyebrow{
    font-family:var(--f-display);font-weight:700;
    font-size:clamp(.8rem,.72rem + .35vw,1rem);
    letter-spacing:var(--track-eyebrow);text-transform:uppercase;
    color:var(--teal-deep);display:inline-flex;align-items:center;gap:.55rem;
    margin-bottom:var(--gap-eyebrow);
  }
  .eyebrow::before{content:"";width:9px;height:9px;margin-right:1.9rem;border-radius:50%;background:var(--coral);box-shadow:14px 0 0 -1px var(--gold),28px 0 0 -2px var(--sky);}
  .lede{font-size:clamp(1.05rem,.98rem + .5vw,1.4rem);color:var(--ink-soft);max-width:58ch;}
  .sec-head{max-width:min(52rem,90%);margin-bottom:clamp(2.2rem,1.6rem + 1.6vw,3.6rem);}
  /* The gap between a heading and the copy under it was zero: headings carry
     no bottom margin and .lede carries no top margin. Scoped to section
     heads so card titles, which already set their own margins, are left be. */
  /* Any heading that has something after it gets the gap; a heading that ends
     its container does not. This replaces enumerating containers, which kept
     missing cases (the contact address heading sits outside .sec-head). */
  h1:not(:last-child),h2:not(:last-child){margin-bottom:var(--gap-heading);}
  /* Measured in em so it tracks the heading's own size: large type can carry a
     longer line than body copy, and 62ch (a body measure) was forcing section
     headings into four short lines. */
  .sec-head h1,.sec-head h2{max-width:17em;}
  .sec-head.center h1,.sec-head.center h2,.sec-head.center .lede{margin-inline:auto;}
  /* Left-aligned section heads become a two-column editorial row on a wide
     canvas: label across the top, statement left, supporting copy set
     against it on the right. Previously the statement sat in a narrow
     column with half the section empty beside it, which read as unused
     space rather than as composition. Centred heads are left as they are. */
  @media(min-width:1100px){
    .sec-head:not(.center):not(.sec-head--stack):not(.sec-head--aside):has(p){
      display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);
      /* Row gap is set separately: letting the generous column gap double as
         the row gap pushed the label 66px off its heading, while stacked
         section heads sit at 16px. Same relationship, one rhythm. */
      column-gap:clamp(2rem,3.5vw,4.5rem);row-gap:0;
      align-items:end;max-width:none;
    }
    .sec-head:not(.center):not(.sec-head--stack):not(.sec-head--aside):has(p) .eyebrow{grid-column:1/-1;}
    .sec-head:not(.center):not(.sec-head--stack):not(.sec-head--aside):has(p) h2{margin-bottom:0;max-width:none;}
    .sec-head:not(.center):not(.sec-head--stack):not(.sec-head--aside):has(p) .lede{max-width:44ch;padding-bottom:.4rem;}
    /* .sec-head--stack keeps the standfirst directly beneath the statement
       instead of setting it alongside. */
    .sec-head--stack{max-width:min(68rem,100%);margin-inline:auto;}
  }
  .sec-head > :last-child{margin-bottom:0;}
  .sec-head.center{margin-inline:auto;text-align:center;}
  .sec-head.center .eyebrow{justify-content:center;}

  /* Buttons */
  .btn{display:inline-flex;align-items:center;gap:.55rem;justify-content:center;font-family:var(--f-display);font-weight:600;font-size:1rem;padding:.95rem 1.85rem;border-radius:999px;border:2px solid transparent;cursor:pointer;line-height:1.15;letter-spacing:var(--track-ui);text-align:center;white-space:nowrap;-webkit-appearance:none;appearance:none;transition:transform .25s var(--ease),background .25s,box-shadow .25s,color .25s,border-color .25s;}
  .btn:focus-visible{outline:3px solid var(--gold);outline-offset:3px;}
  .btn:active{transform:translateY(0) scale(.97);}
  .btn--primary{background:var(--coral-deep);color:#fff;box-shadow:var(--shadow-sm);}
  .btn--primary:hover{transform:translateY(-2px);background:#a93a2c;}
  .btn--teal{background:var(--teal-deep);color:#fff;box-shadow:var(--shadow-sm);}
  .btn--teal:hover{transform:translateY(-2px);background:#26685a;}
  .btn--ghost{background:var(--cream);color:var(--ink);border-color:var(--line);}
  .btn--ghost:hover{border-color:var(--teal-deep);transform:translateY(-2px);}
  .btn--gold{background:var(--gold);color:#5a4208;}
  .btn--gold:hover{transform:translateY(-2px);background:#dfa71f;}
  .btn .arrow{transition:transform .25s var(--ease);}
  .btn:hover .arrow{transform:translateX(3px);}

  /* Header */
  .site-header{position:sticky;top:0;z-index:100;background:rgba(255,253,249,.86);backdrop-filter:blur(14px);border-bottom:1px solid transparent;transition:border-color .3s,box-shadow .3s;padding-top:env(safe-area-inset-top);}
  .site-header.scrolled{border-bottom-color:var(--line);box-shadow:0 8px 30px -24px rgba(47,59,86,.5);}
  .nav{display:flex;align-items:center;justify-content:space-between;gap:1.5rem;padding:.8rem 0;}
  .brand{display:flex;align-items:center;gap:.15rem;font-family:var(--f-display);font-weight:700;font-size:1.55rem;letter-spacing:.01em;}
  .brand .l1{color:var(--teal);} .brand .l2{color:var(--coral);} .brand .l3{color:var(--gold);} .brand .l4{color:var(--lav);} .brand .l5{color:var(--sky);}
  .brand small{font-family:var(--f-display);font-weight:600;font-size:.62rem;letter-spacing:.16em;color:var(--ink);margin-left:.5rem;text-transform:uppercase;}
  .nav-links{display:flex;align-items:center;gap:clamp(.9rem,2vw,1.8rem);}
  .nav-links a{font-weight:700;font-size:.94rem;letter-spacing:var(--track-ui);color:var(--ink-soft);position:relative;padding:.35rem 0;transition:color .2s;}
  .nav-links a::after{content:"";position:absolute;left:0;bottom:-2px;height:3px;width:0;border-radius:3px;background:linear-gradient(90deg,var(--teal),var(--gold));transition:width .28s var(--ease);}
  .nav-links a:hover,.nav-links a:focus-visible{color:var(--ink);}
  .nav-links a:hover::after,.nav-links a:focus-visible::after{width:100%;}
  .nav-cta{display:flex;align-items:center;gap:.75rem;}
  .nav-toggle{display:none;background:none;border:0;padding:.5rem;cursor:pointer;color:var(--ink);min-width:44px;min-height:44px;}

  /* Hero */
  .hero{position:relative;overflow:hidden;padding-top:clamp(1.6rem,1.2rem + 2.4vw,3.2rem);background:linear-gradient(180deg,#ffffff 0%,var(--paper) 78%);}
  .float{position:absolute;border-radius:50%;z-index:0;pointer-events:none;opacity:.55;animation:drift 9s ease-in-out infinite alternate;}
  .float.f1{width:150px;height:150px;background:var(--teal-tint);top:8%;left:-40px;}
  .float.f2{width:110px;height:110px;background:var(--gold-tint);top:2%;right:12%;animation-delay:2s;}
  .float.f3{width:90px;height:90px;background:var(--lav-tint);bottom:6%;left:38%;animation-delay:4s;}
  .float.f4{width:120px;height:120px;background:var(--coral-tint);bottom:14%;right:-30px;animation-delay:1s;}
  @keyframes drift{from{transform:translateY(0)}to{transform:translateY(-16px)}}
  .hero-grid{position:relative;z-index:2;display:grid;grid-template-columns:1.04fr .96fr;gap:clamp(2rem,4vw,4rem);align-items:center;padding-bottom:clamp(2.4rem,3.6vw,4.2rem);}
  /* On a wide canvas the headline needs room to sit on two balanced lines
     rather than stranding a single word; the image still has plenty. */
  @media(min-width:1400px){
    .hero-grid{grid-template-columns:1.22fr .78fr;}
  }
  /* .hero h1 margin-bottom comes from --gap-heading: the old 1.15rem was
     .29em here, which the descender consumed entirely. */
  .hero h1 .hl{color:var(--teal-deep);position:relative;white-space:nowrap;}
  .hero h1 .hl::after{content:"";position:absolute;left:0;right:0;bottom:.05em;height:.3em;background:var(--gold-tint);border-radius:6px;z-index:-1;}
  .hero-mark{margin-bottom:1.3rem;}
  .hero-word{font-family:var(--f-display);font-weight:700;font-size:clamp(4.6rem,3.2rem + 6.8vw,8rem);line-height:1;letter-spacing:-.01em;}
  /* The wordmark is the logo, not text: it keeps the original brand hues and
     is pinned to literals so contrast work on the text palette can never
     alter the mark. It is aria-hidden and repeats the header logo. */
  .hero-word .l1{color:var(--teal);} .hero-word .l2{color:var(--coral);} .hero-word .l3{color:#d99a1d;} .hero-word .l4{color:var(--lav);} .hero-word .l5{color:#4f88c2;}
  .hero-sub{font-family:var(--f-body);font-weight:800;font-size:.82rem;letter-spacing:.22em;color:var(--ink);margin-top:.3rem;}
  .hero-tagline{display:flex;gap:.4rem;flex-wrap:wrap;margin-top:.7rem;font-family:var(--f-display);font-weight:600;font-size:.85rem;color:var(--ink-soft);}
  .hero-tagline span:nth-child(1){color:var(--teal-deep);} .hero-tagline span:nth-child(3){color:var(--coral-deep);}
  .hero-tagline span:nth-child(5){color:var(--gold-deep);} .hero-tagline span:nth-child(7){color:var(--lav-deep);}
  .hero .lede{margin-bottom:1.9rem;}
  .hero-actions{display:flex;flex-wrap:wrap;gap:.9rem;margin-bottom:1.9rem;}
  .hero-trust{display:flex;align-items:center;gap:.7rem;color:var(--ink-soft);font-size:.92rem;}
  .hero-trust .stars{color:var(--gold-deep);letter-spacing:2px;}
  .hero-media{position:relative;}
  .hero-media .ph{aspect-ratio:4/5;border-radius:var(--radius);box-shadow:var(--shadow);align-items:flex-end;}
  .hero-media .dot{position:absolute;border-radius:50%;z-index:2;}
  .hero-media .dot.d1{width:70px;height:70px;background:var(--gold);opacity:.85;top:-20px;left:-20px;}
  .hero-media .dot.d2{width:46px;height:46px;background:var(--sky);opacity:.8;bottom:18%;right:-16px;}
  .hero-badge{position:absolute;z-index:3;right:-12px;bottom:24px;background:#fff;border-radius:18px;padding:.85rem 1.05rem;box-shadow:var(--shadow);display:flex;align-items:center;gap:.7rem;max-width:240px;}
  .hero-badge .ico{width:42px;height:42px;border-radius:14px;background:var(--teal-tint);display:grid;place-items:center;color:var(--teal-deep);flex:0 0 auto;}
  .hero-badge b{font-family:var(--f-display);font-weight:600;display:block;font-size:.98rem;line-height:1.15;}
  /* No text-wrap here: it is a shorthand that also sets text-wrap-mode, and
     at this selector's specificity it silently cancelled .nb's nowrap. */
  .hero-badge span{font-size:.8rem;color:var(--ink-soft);}
  /* Leading has to be set on the block, not the inline span: the block's own
     strut (body leading, 1.68) is what was opening the gap. */
  .hero-badge > div{line-height:1.4;}
  /* Hyphenated terms browsers would otherwise split across lines. */
  .nb{white-space:nowrap;}

  /* image placeholder system */
  .ph{position:relative;overflow:hidden;background:linear-gradient(140deg,#bcd9d2,#8fbfb4);display:flex;align-items:flex-end;isolation:isolate;border-radius:var(--radius);}
  .ph::before{content:"";position:absolute;inset:0;background-image:radial-gradient(rgba(255,255,255,.25) 1px,transparent 1.4px);background-size:16px 16px;opacity:.5;z-index:0;}
  .ph--coral{background:linear-gradient(140deg,#f6b0a9,#ec8077);}
  .ph--gold{background:linear-gradient(140deg,#f8d68b,#efb64a);}
  .ph--lav{background:linear-gradient(140deg,#cdb9e8,#a98bd3);}
  .ph--sky{background:linear-gradient(140deg,#b3cfec,#7fb0dd);}
  .ph--teal{background:linear-gradient(140deg,#a9d8cd,#6cb6a6);}
  .ph.has-photo::before{display:none;}
  .ph img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;display:block;}
  .ph .cap{position:relative;z-index:1;margin:.8rem;font-size:.72rem;line-height:1.35;font-weight:700;color:#3a4358;background:rgba(255,255,255,.65);backdrop-filter:blur(3px);padding:.4rem .6rem;border-radius:8px;display:inline-flex;align-items:center;gap:.4rem;}

  /* Trust bar */
  .trust{background:#fff;border-block:1px solid var(--line);}
  .trust .row{display:flex;flex-wrap:wrap;justify-content:center;gap:clamp(1rem,2.5vw,2.4rem);padding:1.3rem 0;}
  .trust .t{display:inline-flex;align-items:center;gap:.55rem;font-family:var(--f-display);font-weight:600;font-size:.92rem;letter-spacing:var(--track-ui);color:var(--ink-soft);}
  .trust .t svg{flex:0 0 auto;}
  .trust .t:nth-child(1) svg{color:var(--teal-deep);} .trust .t:nth-child(2) svg{color:var(--coral-deep);}
  .trust .t:nth-child(3) svg{color:var(--gold-deep);} .trust .t:nth-child(4) svg{color:var(--lav-deep);}
  .trust .t:nth-child(5) svg{color:var(--sky-deep);} .trust .t:nth-child(6) svg{color:var(--teal-deep);}

  /* Why cards */
  .why-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.25rem;}
  .card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:1.8rem 1.55rem;transition:transform .3s var(--ease),box-shadow .3s;}
  .card:hover{transform:translateY(-6px);box-shadow:var(--shadow);}
  .card .ico{width:58px;height:58px;border-radius:18px;display:grid;place-items:center;margin-bottom:1.05rem;}
  .card:nth-child(1) .ico{background:var(--teal-tint);color:var(--teal-deep);}
  .card:nth-child(2) .ico{background:var(--coral-tint);color:var(--coral-deep);}
  .card:nth-child(3) .ico{background:var(--gold-tint);color:var(--gold-deep);}
  .card:nth-child(4) .ico{background:var(--sky-tint);color:var(--sky-deep);}
  /* Two of these four headings wrap; reserving the second line keeps every
     card's body copy starting on the same baseline across the row. */
  .card h3{margin-bottom:.4rem;font-size:var(--h-card);min-height:calc(2 * var(--leading-title) * 1em);}
  .card p{color:var(--ink-soft);font-size:.95rem;margin:0;}

  /* ===== ESPIN PHILOSOPHY (signature) ===== */
  .philo{background:linear-gradient(180deg,#fff 0%,var(--mist) 100%);position:relative;overflow:hidden;}
  .philo-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:1.1rem;}
  /* Five cards only earn five columns on a wide canvas; below that they drop
     to two with the fifth spanning, which reads as composed rather than as a
     row that ran out of room. */
  @media(max-width:1300px){
    .philo-grid{grid-template-columns:repeat(2,1fr);}
    .philo-grid .pcard:last-child{grid-column:1/-1;}
  }
  .pcard{position:relative;background:#fff;border-radius:var(--radius);padding:1.7rem 1.3rem 1.6rem;border:1px solid var(--line);border-top:6px solid;transition:transform .35s var(--ease),box-shadow .35s;overflow:hidden;}
  .pcard:hover{transform:translateY(-8px) rotate(-.6deg);box-shadow:var(--shadow);}
  .pcard:nth-child(even):hover{transform:translateY(-8px) rotate(.6deg);}
  .pcard .letter{font-family:var(--f-display);font-weight:700;font-size:3.2rem;line-height:1;display:block;margin-bottom:.4rem;}
  .pcard .picon{position:absolute;top:1.2rem;right:1.1rem;opacity:.9;}
  .pcard h3{font-size:var(--h-card);margin-bottom:.4rem;}
  .pcard p{font-size:.88rem;color:var(--ink-soft);margin:0;}
  .pcard.pe{border-color:var(--teal);}  .pcard.pe .letter,.pcard.pe .picon{color:var(--teal);}
  .pcard.ps{border-color:var(--coral);} .pcard.ps .letter,.pcard.ps .picon{color:var(--coral);}
  .pcard.pp{border-color:var(--gold);}  .pcard.pp .letter,.pcard.pp .picon{color:var(--gold);}
  .pcard.pi{border-color:var(--lav);}   .pcard.pi .letter,.pcard.pi .picon{color:var(--lav);}
  .pcard.pn{border-color:var(--sky);}   .pcard.pn .letter,.pcard.pn .picon{color:var(--sky);}
  .pcard::after{content:"";position:absolute;bottom:-26px;right:-26px;width:86px;height:86px;border-radius:50%;opacity:.12;background:currentColor;}
  .pcard.pe::after{color:var(--teal);} .pcard.ps::after{color:var(--coral);} .pcard.pp::after{color:var(--gold);} .pcard.pi::after{color:var(--lav);} .pcard.pn::after{color:var(--sky);}
  .philo-tag{display:flex;justify-content:center;gap:.6rem;flex-wrap:wrap;margin-top:2.2rem;font-family:var(--f-display);font-weight:600;font-size:.98rem;}
  .philo-tag span{padding:.45rem 1.05rem;border-radius:999px;}
  .philo-tag .w1{background:var(--teal-tint);color:var(--teal-deep);}
  .philo-tag .w2{background:var(--coral-tint);color:var(--coral-ink);}
  .philo-tag .w3{background:var(--gold-tint);color:var(--gold-deep);}
  .philo-tag .w4{background:var(--lav-tint);color:var(--lav-deep);}

  /* Programs */
  /* The classroom cards are white, and they sat on the near-white page
     background - almost no separation between card and band. A soft
     lavender-to-sky field behind them gives the cards an edge to stand
     against, and a real shadow lifts them off it. The cards themselves are
     unchanged. */
  .band--rooms{
    background:
      radial-gradient(70rem 30rem at 12% 0%, rgba(244,182,62,.16), transparent 60%),
      radial-gradient(60rem 34rem at 92% 100%, rgba(111,168,220,.18), transparent 60%),
      linear-gradient(180deg, var(--lav-tint) 0%, #ece6f6 100%);
  }
  .band--rooms .prog{box-shadow:var(--shadow-sm);}
  .band--rooms .prog:hover{box-shadow:var(--shadow);}
  .prog-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.3rem;}
  .prog{background:#fff;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column;transition:transform .3s var(--ease),box-shadow .3s;}
  .prog:hover{transform:translateY(-6px);box-shadow:var(--shadow);}
  .prog .ph{aspect-ratio:16/11;border-radius:0;}
  .prog-body{padding:1.3rem 1.35rem 1.5rem;flex:1;display:flex;flex-direction:column;}
  .prog-ratio{font-size:.86rem;color:var(--teal-deep);font-weight:700;margin:.15rem 0 .9rem;}
  .prog .age{font-family:var(--f-display);font-size:.72rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;}
  .prog:nth-child(1) .age{color:var(--teal-deep);} .prog:nth-child(2) .age{color:var(--coral-deep);}
  .prog:nth-child(3) .age{color:var(--gold-deep);} .prog:nth-child(4) .age{color:var(--lav-deep);}
  .prog h3{margin:.3rem 0 .4rem;font-size:var(--h-card);}
  .prog p{color:var(--ink-soft);font-size:.92rem;flex:1;}
  .prog .more{margin-top:.8rem;font-family:var(--f-display);font-weight:600;color:var(--teal-deep);display:inline-flex;align-items:center;gap:.4rem;font-size:.92rem;}
  .prog .more .arrow{transition:transform .25s var(--ease);}
  .prog:hover .more .arrow{transform:translateX(4px);}

  /* Curriculum chips + grid */
  .curric{background:var(--teal-tint);}
  .cur-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.1rem;}
  .cur{background:#fff;border-radius:var(--radius);padding:1.7rem 1.5rem;transition:transform .3s var(--ease),box-shadow .3s;}
  .cur:hover{transform:translateY(-5px);box-shadow:var(--shadow);}
  .cur .ico{width:52px;height:52px;border-radius:16px;display:grid;place-items:center;margin-bottom:.95rem;}
  .cur:nth-child(1) .ico{background:var(--coral-tint);color:var(--coral-deep);}
  .cur:nth-child(2) .ico{background:var(--sky-tint);color:var(--sky-deep);}
  .cur:nth-child(3) .ico{background:var(--gold-tint);color:var(--gold-deep);}
  .cur:nth-child(4) .ico{background:var(--lav-tint);color:var(--lav-deep);}
  .cur:nth-child(5) .ico{background:var(--teal-tint);color:var(--teal-deep);}
  .cur:nth-child(6) .ico{background:var(--coral-tint);color:var(--coral-deep);}
  .cur h3{margin-bottom:.35rem;font-size:var(--h-card);}
  .cur p{color:var(--ink-soft);font-size:.92rem;margin:0;}

  /* Environment gallery */
  .masonry{columns:3;column-gap:1rem;}
  .masonry .tile{break-inside:avoid;margin-bottom:1rem;}
  .masonry .tile .ph{border-radius:var(--radius-sm);}
  .masonry .g1 .ph{aspect-ratio:3/4;} .masonry .g2 .ph{aspect-ratio:1/1;} .masonry .g3 .ph{aspect-ratio:4/3;}

  /* Teachers */
  .team-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.4rem;}
  .team-grid--3{grid-template-columns:repeat(3,1fr);max-width:64rem;margin-inline:auto;}
  .teacher .ph{aspect-ratio:1/1;border-radius:var(--radius);margin-bottom:1rem;box-shadow:var(--shadow-sm);}
  /* The team section sits on the lavender tint, not white, so this label
     uses the deeper ink for the same reason the chips do. */
  .teacher .role{font-family:var(--f-display);font-size:.72rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--coral-ink);}
  .teacher h3{font-size:var(--h-card);margin:.25rem 0 .3rem;}
  .teacher p{color:var(--ink-soft);font-size:.9rem;margin:0 0 .5rem;}
  .teacher .exp{font-size:.82rem;color:var(--teal-deep);font-weight:800;display:inline-flex;align-items:center;gap:.4rem;}

  /* ------------------------------------------------------------------
     Team orbit

     A shallow 3D carousel of the staff. Depth is carried by translateZ
     inside a preserve-3d stage rather than by z-index, so the browser sorts
     the cards by real distance and they cross behind one another without
     popping. Scale is left to the perspective divide for the same reason:
     one source of depth, not two that have to be kept in agreement.
     ------------------------------------------------------------------ */
  .orbit[hidden],.team-grid[hidden],.orbit-toggle[hidden]{display:none;}
  #team{overflow-x:clip;}
  /* The 3D cards become composited layers. With no stacking context of
     its own, WebKit paints them above the sticky header and the section
     heading; Chromium happens to contain them, which is why this only
     appeared in Safari. isolation pins the stage into one context that
     sits below the header's z-index in the ordinary way. */
  .orbit{position:relative;z-index:0;isolation:isolate;}
  /* The featured portrait is the whole composition, so the base card is
     much larger than a grid tile; every other ring is this size reduced by
     the perspective divide. */
  .orbit{--card:clamp(12rem,23vw,21rem);}
  .orbit-stage{
    position:relative;
    /* No `perspective` here on purpose. Relying on the parent's perspective
       plus a preserve-3d chain works in Chromium but WebKit flattens it, and
       every card then paints at full size with no foreshortening at all. Each
       card carries its own perspective() instead, which both engines apply. */
    /* The group arcs well above the featured portrait, so the stage is
       much taller than the front card. */
    height:calc(var(--card) + 17rem);
    margin-bottom:-2rem;
    /* No overflow clip here on purpose. Clipping a perspective container
       stops Chromium hit-testing anything pushed back in Z, which made every
       card except the featured one unclickable. The sideways offsets are a
       share of the stage width, so the visible cards already fit; the parked
       ones are fully transparent, and .band below keeps them off the page
       scroll width. */
    display:flex;align-items:center;justify-content:center;
    margin-bottom:.5rem;
  }
  /* The turn from what ESPIN does to who does it. One photograph closing a
     text-only section, sized as a plate rather than a banner: the library is
     portrait and a wide crop would lose both faces. */
  .bridge{
    margin:clamp(2.4rem,4vw,3.6rem) auto 0;
    width:min(23rem,72vw);
  }
  .bridge img{
    display:block;width:100%;height:auto;
    border-radius:var(--radius);
    box-shadow:0 22px 50px rgba(31,42,68,.16);
  }

  /* ------------------------------------------------------------------
     Playful depth stage

     The environment the carousel rotates through. Decorative, behind the
     people, and built from flat tint fills with generous radii rather than
     stacked soft gradients: overlapping gradients turned the whole band into
     one pink haze with no readable layers, and blur at this size is the one
     thing that would cost the carousel its frame budget.

     Depth uses the same z-index scale as the cards (featured 40, near 35,
     mid 30, rear 25), so a form can sit BETWEEN depth planes instead of
     everything piling up behind. --pd is each layer's parallax factor.
     ------------------------------------------------------------------ */
  .stage-env{position:absolute;left:-10%;right:-10%;top:-4%;bottom:-14%;pointer-events:none;}
  .env{
    position:absolute;display:block;
    transform:translate3d(calc(var(--px,0px) * var(--pd,1)),0,0);
    transition:transform 900ms cubic-bezier(.22,.61,.36,1);
  }
  /* Far: an arch, not a blob. Sky against the lavender band so it reads as
     a form; oversized and running off the right edge so the stage feels cut
     from something larger. */
  .env--arch{
    --pd:.18;
    width:40%;height:150%;right:-4%;top:-16%;
    border-radius:999px 999px 40% 40%;
    background:var(--sky-tint);opacity:.72;
    z-index:2;
  }
  /* Mid: coral circle bleeding off the left. Deliberately not mirrored. */
  .env--coral{
    --pd:.34;
    width:34%;height:86%;left:-13%;top:14%;border-radius:50%;
    background:var(--coral-tint);opacity:.75;
    z-index:3;
  }
  /* Mid: a warm oval deep behind centre-left, small enough to stay quiet. */
  .env--gold{
    --pd:.26;
    width:30%;height:34%;left:19%;top:22%;border-radius:50%;
    background:var(--gold-tint);opacity:.75;
    z-index:2;
  }
  /* The one form living BETWEEN depth planes: in front of the rear pair,
     behind everyone nearer. That single overlap is what stops the stage
     reading as a flat backdrop. Kept low and right, clear of every face. */
  .env--mint{
    --pd:.62;
    width:22%;height:30%;right:4%;bottom:34%;
    border-radius:52% 48% 40% 60% / 46% 54% 46% 54%;
    background:var(--teal-tint);opacity:.85;
    z-index:27;
  }
  /* A soft light field behind the featured person: lifts them off the
     environment without reading as a halo. Masked rather than blurred so the
     tint can crossfade cheaply as the featured person changes. */
  .env--portal{
    --pd:.1;
    width:52%;height:112%;left:24%;top:-4%;
    background-color:var(--portal,#ffffff);
    -webkit-mask-image:radial-gradient(closest-side,#000 22%,transparent 100%);
    mask-image:radial-gradient(closest-side,#000 22%,transparent 100%);
    opacity:.7;
    transition:transform 900ms cubic-bezier(.22,.61,.36,1),background-color 2000ms ease;
    z-index:20;
  }
  /* Grounding plane: atmosphere, not a floor. Wide, shallow and barely
     there, and it does more than anything else to say this group stands on
     one horizontal surface rather than hanging on a wheel. */
  .env--ground{
    --pd:.4;
    width:50%;height:12%;left:25%;bottom:23%;border-radius:50%;
    background:radial-gradient(closest-side,
      rgba(47,59,86,.22) 0%, rgba(47,59,86,.08) 50%, rgba(47,59,86,0) 100%);
    z-index:21;
  }
  /* Bubbles reuse .eb-b, the homepage bubble surface, so this is the same
     visual language rather than a second bubble system. Only these few, and
     never over a face. */
  /* ---- Team environment: the set, never the choreography ----------------
     Everything below decorates the space around the staff carousel. Nothing
     here touches .orbit-card, .orbit-track or .orbit-label, which own the
     approved card movement. The readable layers already sit at z-index 30;
     these stay beneath that. */

  /* ---- Team colour environment ------------------------------------------
     Large organic fields behind the whole section. They are clipped to the
     section by .team-field rather than by the section itself, so nothing here
     can ever clip a staff card: the cards are positioned by the approved
     animation and must stay free to sit wherever it puts them.
     Roughly 60 percent of the section stays light, 30 percent carries soft
     colour and 10 percent is the stronger accent work, so the colour reads as
     composition rather than as a tint over everything. */
  .band--team{position:relative;background:var(--lav-tint);}
  .team-field{position:absolute;inset:0;overflow:clip;pointer-events:none;z-index:0;}
  .team-field .tf{position:absolute;display:block;}
  .tf--cream{inset:0;background:linear-gradient(168deg,#fff6e8 0%,#fdeae6 30%,#f0e6fa 64%,#e6f2ef 100%);}
  .tf--lav{
    width:78%;height:74%;left:-16%;top:4%;
    border-radius:56% 44% 48% 52% / 52% 46% 54% 48%;
    background:radial-gradient(closest-side,rgba(169,139,211,.62),rgba(169,139,211,0));
  }
  .tf--coral{
    width:52%;height:52%;right:-14%;top:-8%;
    border-radius:48% 52% 56% 44% / 46% 54% 46% 54%;
    background:radial-gradient(closest-side,rgba(240,119,109,.55),rgba(240,119,109,0));
  }
  .tf--gold{
    width:44%;height:40%;right:6%;top:34%;
    border-radius:52% 48% 44% 56% / 50% 50% 50% 50%;
    background:radial-gradient(closest-side,rgba(244,182,62,.58),rgba(244,182,62,0));
  }
  .tf--mint{
    width:46%;height:42%;left:-8%;bottom:6%;
    border-radius:50% 50% 56% 44% / 48% 52% 48% 52%;
    background:radial-gradient(closest-side,rgba(87,179,160,.52),rgba(87,179,160,0));
  }
  .tf--sky{
    width:40%;height:38%;right:-6%;bottom:10%;
    border-radius:54% 46% 50% 50% / 46% 54% 46% 54%;
    background:radial-gradient(closest-side,rgba(111,168,220,.54),rgba(111,168,220,0));
  }
  /* a wide, very soft band that carries the eye from the intro into the stage
     and stops the zones reading as stacked coloured rectangles */
  .tf--wave{
    left:-6%;right:-6%;top:26%;height:34%;
    border-radius:50%;
    background:radial-gradient(58% 100% at 50% 50%,rgba(255,255,255,.72),rgba(255,255,255,0));
  }
  /* The section met the next one as a hard horizontal edge, which is exactly
     the stacked-coloured-rectangles look the colour work is meant to avoid.
     A soft fade at the top and bottom lets the field resolve into the pages
     either side. Only this section is touched. */
  .team-field::after{
    content:"";position:absolute;left:0;right:0;bottom:0;height:16%;
    background:linear-gradient(to bottom,rgba(255,255,255,0),rgba(255,255,255,.92));
  }
  .team-field::before{
    content:"";position:absolute;left:0;right:0;top:0;height:9%;
    background:linear-gradient(to top,rgba(255,255,255,0),rgba(255,255,255,.7));
  }
  .band--team > .wrap{position:relative;z-index:1;}

  /* The heading text comes from the content database and is applied with
     textContent, so any span inside it is destroyed on load. The colour has
     to come from the element itself: a soft brand stroke beneath the whole
     heading rather than a coloured word. */
  /* A stroke behind the heading cut across it once the heading wrapped to two
     lines, so the colour sits under the heading as a short rule instead. It
     reads the same at any line count. */
  .team-intro h2{margin-bottom:.75rem;}
  .team-intro h2 + .lede::before{
    content:"";display:block;width:104px;height:6px;margin:0 auto 1.15rem;border-radius:999px;
    background:linear-gradient(90deg,var(--gold),var(--coral) 52%,var(--lav));
  }
  /* a coloured word, with a soft stroke under it */
  .hl-magic,.hl-person{position:relative;color:var(--lav-deep);white-space:nowrap;}
  .hl-magic::after,.hl-person::after{
    content:"";position:absolute;left:-.06em;right:-.06em;bottom:.02em;height:.34em;
    background:linear-gradient(90deg,rgba(244,182,62,.55),rgba(240,119,109,.42));
    border-radius:999px;z-index:-1;
  }
  .hl-person{color:var(--coral-deep);}
  .hl-person::after{background:linear-gradient(90deg,rgba(87,179,160,.5),rgba(111,168,220,.42));}

  /* classroom colour identity carried onto the roster card itself */
  .teacher:has(.room--bee) .ph{box-shadow:0 0 0 3px rgba(244,182,62,.38),var(--shadow-sm);}
  .teacher:has(.room--fly) .ph{box-shadow:0 0 0 3px rgba(169,139,211,.4),var(--shadow-sm);}
  .teacher:has(.room--drag) .ph{box-shadow:0 0 0 3px rgba(87,179,160,.4),var(--shadow-sm);}
  .teacher:has(.room--lady) .ph{box-shadow:0 0 0 3px rgba(240,119,109,.38),var(--shadow-sm);}

  /* the closing invitation as a colour composition */
  .team-outro{
    background:linear-gradient(150deg,#fffaf0 0%,#fdf2f0 40%,#f3ecfb 100%);
    border-radius:var(--radius);
    padding:clamp(2rem,4vw,3rem) clamp(1.4rem,4vw,3rem) clamp(2.2rem,4vw,3rem);
    overflow:clip;box-shadow:0 22px 54px rgba(31,42,68,.10);
  }
  .team-outro > *{position:relative;z-index:2;}
  .outro-art{position:absolute;inset:0;z-index:1;pointer-events:none;}
  .outro-art i{position:absolute;display:block;border-radius:50%;}
  .outro-art i:nth-child(1){width:190px;height:190px;left:-58px;top:-64px;background:radial-gradient(closest-side,rgba(244,182,62,.5),rgba(244,182,62,0));}
  .outro-art i:nth-child(2){width:220px;height:220px;right:-70px;bottom:-80px;background:radial-gradient(closest-side,rgba(169,139,211,.46),rgba(169,139,211,0));}
  .outro-art i:nth-child(3){width:130px;height:130px;right:12%;top:-40px;background:radial-gradient(closest-side,rgba(87,179,160,.42),rgba(87,179,160,0));}
  .outro-art i:nth-child(4){width:14px;height:14px;left:12%;bottom:22%;background:var(--coral);opacity:.5;}
  .outro-art i:nth-child(5){width:10px;height:10px;right:16%;top:26%;background:var(--sky);opacity:.55;}
  .team-dots--outro{margin-bottom:.8rem;}
  .team-outro .btn{margin-top:.4rem;}

  /* The richer colour field darkened the ground under the body copy: measured
     against the real rendered gradient, the biography fell to 4.10:1 and the
     intro to 4.21:1, both under the 4.5 AA needs for text this size. The copy
     in this section is darkened just enough to clear it (5.58:1 and 5.72:1),
     which is the right direction anyway - the colour should sit behind the
     words, not compete with them. */
  .band--team .lede,
  .band--team .orbit-bio p,
  .band--team .teacher p,
  .band--team .team-outro p{color:#4a5468;}

  /* three brand dots above the section eyebrow */
  .team-dots{display:flex;justify-content:center;gap:.42rem;margin:0 0 .9rem;}
  .team-dots i{width:9px;height:9px;border-radius:50%;display:block;}
  .team-dots i:nth-child(1){background:var(--coral);}
  .team-dots i:nth-child(2){background:var(--gold);}
  .team-dots i:nth-child(3){background:var(--teal);}
  .team-intro .lede{max-width:34rem;margin-left:auto;margin-right:auto;}

  /* classroom tag - shown only where the owner has confirmed the room */
  .room{
    display:inline-flex;align-items:center;gap:.4em;vertical-align:baseline;
    font-family:var(--f-display);font-size:.66rem;font-weight:700;
    letter-spacing:.07em;text-transform:uppercase;line-height:1;
    padding:.3em .6em .32em;border-radius:999px;
    background:var(--room-bg);color:var(--room-ink);border:1px solid var(--room-br);
  }
  .room::before{content:"";width:.4em;height:.4em;border-radius:50%;background:var(--room-dot);flex:none;}
  .room--bee {--room-bg:var(--gold-tint); --room-ink:var(--gold-deep); --room-br:rgba(244,182,62,.45); --room-dot:var(--gold);}
  .room--lady{--room-bg:var(--coral-tint);--room-ink:var(--coral-ink); --room-br:rgba(240,119,109,.42);--room-dot:var(--coral);}
  .room--drag{--room-bg:var(--teal-tint); --room-ink:var(--teal-deep); --room-br:rgba(87,179,160,.45); --room-dot:var(--teal);}
  .room--fly {--room-bg:var(--lav-tint);  --room-ink:var(--lav-deep);  --room-br:rgba(169,139,211,.45);--room-dot:var(--lav);}
  /* In the carousel label the tag drops its pill: a taller label would change
     the card box the approved animation positions. Colour and dot survive. */
  .orbit-label .room{background:none;border:0;padding:0;color:inherit;font-size:inherit;letter-spacing:inherit;}

  /* closing invitation */
  .team-outro{
    max-width:34rem;margin:clamp(2.2rem,4vw,3.4rem) auto 0;text-align:center;
    position:relative;z-index:30;
  }
  .team-outro h3{font-size:var(--h-card);margin:0 0 .5rem;}
  .team-outro p{color:var(--ink-soft);margin:0 0 1rem;}
  /* .more is scoped to the programme cards, so this link inherited body ink
     and did not read as a link. Matched to the site's link colour. */
  .more--lg{
    font-size:1.02rem;font-weight:800;color:var(--teal-deep);
    display:inline-flex;align-items:center;gap:.45rem;
  }
  .more--lg .arrow{transition:transform 220ms cubic-bezier(.22,.61,.36,1);}
  .more--lg:hover .arrow,.more--lg:focus-visible .arrow{transform:translateX(4px);}

  /* a soft blue form, and small accent dots drifting with the other layers */
  .env--sky{
    --pd:.5;
    width:18%;height:22%;left:6%;bottom:26%;
    border-radius:58% 42% 46% 54% / 52% 48% 52% 48%;
    background:var(--sky-tint);opacity:.62;z-index:2;
  }
  .env-dots{--pd:.72;inset:0;z-index:3;}
  .env-dots i{
    position:absolute;display:block;border-radius:50%;
    animation:env-dot 19s ease-in-out infinite alternate;
  }
  .env-dots i:nth-child(1){width:12px;height:12px;background:var(--coral);opacity:.34;left:11%;top:24%;}
  .env-dots i:nth-child(2){width:9px; height:9px; background:var(--gold); opacity:.4; left:83%;top:38%;animation-delay:-6s;}
  .env-dots i:nth-child(3){width:14px;height:14px;background:var(--teal); opacity:.28;left:73%;top:76%;animation-delay:-11s;}
  .env-dots i:nth-child(4){width:8px; height:8px; background:var(--lav);  opacity:.38;left:23%;top:82%;animation-delay:-3s;}
  @keyframes env-dot{from{transform:translate3d(0,0,0);}to{transform:translate3d(0,-16px,0);}}

  .env-bubs{--pd:.8;inset:0;z-index:4;}
  .env-bub{position:absolute;width:var(--eb-size);height:var(--eb-size);}
  .env-bub .eb-b{animation:env-drift 17s ease-in-out infinite alternate;}
  .env-bub--far{opacity:.3;}
  .env-bub--mid{opacity:.42;z-index:28;}
  .env-bub--near{opacity:.36;z-index:28;}
  .env-bub--mid .eb-b{animation-duration:21s;animation-delay:-7s;}
  .env-bub--near .eb-b{animation-duration:26s;animation-delay:-13s;}
  .env-bub:nth-child(2) .eb-b{animation-duration:19s;animation-delay:-4s;}
  @keyframes env-drift{
    from{transform:translate3d(0,0,0);}
    to{transform:translate3d(7px,-30px,0);}
  }

  /* Tablet keeps the depth but drops the busiest forms. */
  @media(max-width:1100px){
    .env--gold,.env-bub--near{display:none;}
    .env--arch{width:52%;right:-14%;}
    .env--coral{width:44%;left:-18%;}
    .env--mint{width:28%;right:-2%;bottom:38%;}
  }
  /* Phones: one dominant form, one partial, two bubbles. Anything more is
     clutter at this width, not atmosphere. */
  @media(max-width:700px){
    /* Everything is pulled inside the card band. At this width the forms
       have nowhere to go, and an arch that reaches the heading or a portal
       that washes across the biography reads as damage, not atmosphere. */
    .stage-env{top:6%;bottom:2%;}
    .env--gold,.env--mint,.env-bub--near,.env-bub--mid{display:none;}
    .env--arch{width:80%;right:-26%;top:12%;height:62%;}
    .env--coral{width:58%;left:-30%;top:34%;height:46%;}
    .env--portal{width:76%;left:12%;top:2%;height:72%;}
    .env--ground{width:72%;left:14%;bottom:30%;height:9%;}
  }
  /* Reduced motion: the composition stays, the movement goes. Depth still
     reads through scale, overlap, position and colour. */
  @media(prefers-reduced-motion:reduce){
    .env{transition:none;transform:none;}
    .env-bub .eb-b{animation:none;}
    .env-dots i{animation:none;}
  }

  /* Small screens carry less decoration. The portraits and the words are
     the whole point on a phone, and every layer removed here is one that
     was only ever adding depth on a wide canvas. The card animation's own
     responsive behaviour is untouched. */
  @media(max-width:700px){
    .env--sky,.env--gold{display:none;}
    .env-dots i:nth-child(2),.env-dots i:nth-child(3){display:none;}
    .env-bub--far{display:none;}
    .team-dots i{width:8px;height:8px;}
  }

  .orbit-track{
    list-style:none;margin:0;padding:0;
    position:relative;width:100%;height:100%;
    display:flex;align-items:center;justify-content:center;
  }
  .orbit-card{
    position:absolute;top:50%;left:50%;
    width:var(--card);
    margin:calc(var(--card) / -2 + 3.5rem) 0 0 calc(var(--card) / -2);
    /* Plain 2D: the depth divide is computed in JS and arrives as --sc, so
       nothing here depends on the browser painting a 3D projection. */
    transform:translate3d(var(--x,0),var(--y,0),0)
              scale(var(--sc,1))
              rotateY(var(--rot,0deg));
    z-index:var(--zi,1);
    /* Only on/off stage lives here. The depth fade sits on the portrait
       instead, because fading the whole card also fades the role label and
       drops it below the 4.5:1 contrast floor. */
    opacity:var(--card-op,1);
    /* transform and opacity only: nothing here can trigger layout */
    transition:transform 1100ms cubic-bezier(.42,0,.18,1),opacity 1100ms cubic-bezier(.42,0,.18,1);
    /* No will-change here. Promoting each card to its own composited layer
       inside a perspective + preserve-3d container stops Chromium
       hit-testing anything pushed back in Z, so every card except the
       featured one became unclickable. Transform and opacity transitions
       are composited anyway; the hint bought nothing and cost the
       interaction. */
  }
  .orbit-card.is-hidden{pointer-events:none;}
  .orbit-pick{
    display:block;width:100%;padding:0;border:0;background:none;
    font:inherit;color:inherit;text-align:center;cursor:pointer;
    border-radius:var(--radius);
  }
  .orbit-pick .ph{
    aspect-ratio:1/1;border-radius:var(--radius);
    opacity:var(--op,1);
    filter:blur(var(--bl,0px));
    box-shadow:0 8px 20px rgba(31,42,68,.10);
    transition:box-shadow 380ms ease,transform 380ms ease,
               opacity 1100ms cubic-bezier(.42,0,.18,1),
               filter 1100ms cubic-bezier(.42,0,.18,1);
  }
  .orbit-card.is-featured .orbit-pick .ph{box-shadow:0 30px 64px rgba(31,42,68,.24);}
  /* Restrained hover: a small lift on the featured portrait only. */
  @media(hover:hover){
    .orbit-card.is-featured .orbit-pick:hover .ph{transform:translateY(-4px);box-shadow:0 28px 56px rgba(31,42,68,.26);}
    /* The other cards are selectable, so they answer the pointer too: a
       smaller lift, and they come up to full strength to say "click me". */
    .orbit-card:not(.is-featured) .orbit-pick:hover .ph{
      transform:translateY(-3px);opacity:1;box-shadow:0 16px 34px rgba(31,42,68,.2);
    }
    .orbit-card:not(.is-featured) .orbit-pick:hover .orbit-label{opacity:1;}
  }
  .orbit-pick:focus-visible{outline:3px solid var(--teal-deep);outline-offset:6px;}
  .orbit-label{
    display:block;margin-top:1rem;
    opacity:var(--label,1);
    transition:opacity 520ms ease;
  }
  .orbit-role{
    display:block;font-family:var(--f-display);font-size:.72rem;font-weight:600;
    letter-spacing:.12em;text-transform:uppercase;color:var(--coral-ink);
  }
  .orbit-name{display:block;font-family:var(--f-display);font-weight:700;font-size:1.35rem;color:var(--ink);}

  /* The reserved height belongs to the longest biography, so shorter ones
     are centred in it rather than leaving all the slack underneath. */
  /* Everything readable in this section is lifted above the decoration.
     The biography, the controls and the heading are non-positioned, so they
     paint in an earlier phase than any positioned element carrying a
     z-index - and every environment layer has one. Without this the soft
     forms wash straight over the text, which reads as blurred and clipped
     copy rather than as a background. */
  #team .sec-head,
  .orbit-bio,
  .orbit-ctrls,
  .team-more{position:relative;z-index:30;}

  /* The panel is held at the height of the longest biography so the carousel
     does not jump between people. Centring the text inside that fixed height
     pushed every shorter biography into the middle of the panel, away from
     the name it belongs to - the longest one looked correct and the rest
     looked detached. Aligning to the top keeps every biography the same
     short distance below the name, whatever its length. */
  .orbit-bio{
    max-width:40rem;margin:.2rem auto 0;text-align:center;min-height:5rem;
    display:flex;flex-direction:column;justify-content:flex-start;
    opacity:1;transition:opacity 320ms ease;
  }
  .orbit-bio.is-out{opacity:0;}
  .orbit-bio-role{
    font-family:var(--f-display);font-size:.72rem;font-weight:600;
    letter-spacing:.12em;text-transform:uppercase;color:var(--coral-ink);margin:0;
  }
  .orbit-bio-name{font-size:var(--h-card);margin:.2rem 0 .6rem;}
  .orbit-bio p{color:var(--ink-soft);margin:0 0 .7rem;font-size:1.02rem;}
  .orbit-bio .exp{font-size:.82rem;color:var(--teal-deep);font-weight:800;}

  .orbit-ctrls{display:flex;align-items:center;justify-content:center;gap:1rem;margin-top:1.1rem;}
  .orbit-arrow{
    width:44px;height:44px;border-radius:50%;
    display:inline-flex;align-items:center;justify-content:center;
    border:1px solid rgba(31,42,68,.16);background:#fff;color:var(--ink);
    cursor:pointer;transition:background 200ms ease,border-color 200ms ease;
  }
  .orbit-arrow:hover{background:var(--lav-tint);border-color:rgba(31,42,68,.3);}
  .orbit-arrow:focus-visible{outline:3px solid var(--teal-deep);outline-offset:3px;}
  /* Eleven-plus people, so the indicators stay small and read as a run of
     dots rather than a row of buttons. Touch target stays 24px via padding. */
  .orbit-dots{display:flex;align-items:center;gap:.34rem;flex-wrap:wrap;justify-content:center;max-width:16rem;}
  .orbit-dot{
    width:8px;height:8px;padding:0;border:0;border-radius:50%;
    background:rgba(31,42,68,.22);cursor:pointer;
    transition:background 260ms ease,transform 260ms ease;
  }
  .orbit-dot.is-on{background:var(--teal-deep);transform:scale(1.5);}
  .orbit-dot:focus-visible{outline:2px solid var(--teal-deep);outline-offset:3px;}
  .team-more{display:flex;justify-content:center;margin-top:1.6rem;}

  @media(max-width:1100px){
    .orbit{--card:clamp(11rem,28vw,17rem);}
    .orbit-stage{height:calc(var(--card) + 19rem);}
  }
  @media(max-width:700px){
    .orbit{--card:min(50vw,14rem);}
    .orbit-stage{height:calc(var(--card) + 21rem);}
    /* The reserved height is set by the longest bio, which is a lot taller
       at this width. Top-aligning keeps every profile tight under its
       portrait and gathers the slack above the controls. */
    .orbit-bio{justify-content:flex-start;padding-top:.4rem;}
  }
  @media(prefers-reduced-motion:reduce){
    .orbit-card,.orbit-bio,.orbit-label,.orbit-pick .ph{transition:none;}
  }

  /* ------------------------------------------------------------------
     Moments at ESPIN

     An editorial arrangement rather than a grid: six frames across six
     columns at deliberately different widths, two of them nudged off the
     baseline so the row does not read as a filmstrip. Nearly every source
     is portrait, so the cells are shaped to the photographs instead of the
     photographs being squeezed into equal boxes, and each image carries its
     own object-position because a blind centre crop cuts faces.
     ------------------------------------------------------------------ */
  .mo-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:1.15rem;align-items:start;}
  .mo{margin:0;border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);background:var(--lav-tint);}
  .mo img{display:block;width:100%;height:100%;object-fit:cover;}
  .mo--a{grid-column:1/3;aspect-ratio:3/4;}
  .mo--b{grid-column:3/5;aspect-ratio:3/4;transform:translateY(1.75rem);}
  .mo--c{grid-column:5/7;aspect-ratio:3/4;}
  .mo--d{grid-column:1/4;aspect-ratio:3/2;}
  .mo--e{grid-column:4/6;aspect-ratio:3/4;transform:translateY(-1.25rem);}
  .mo--f{grid-column:6/7;aspect-ratio:3/4;}
  /* Focal points, set per photograph rather than by rule. */
  .mo--a img{object-position:50% 30%;}
  .mo--b img{object-position:50% 32%;}
  .mo--c img{object-position:50% 38%;}
  .mo--d img{object-position:50% 45%;}
  .mo--e img{object-position:50% 34%;}
  .mo--f img{object-position:50% 30%;}
  .mo-more{display:flex;justify-content:center;margin-top:2.6rem;}

  @media(max-width:1024px){
    /* Tablet: the stagger goes, the two narrowest frames widen. */
    .mo--b,.mo--e{transform:none;}
    .mo--a{grid-column:1/4;} .mo--b{grid-column:4/7;}
    .mo--c{grid-column:1/3;} .mo--d{grid-column:3/7;}
    .mo--e{grid-column:1/4;} .mo--f{grid-column:4/7;}
  }
  @media(max-width:640px){
    /* Phone: one lead photograph, then pairs. The two most redundant frames
       drop out rather than making the page six screens long. */
    .mo-grid{grid-template-columns:repeat(2,1fr);gap:.8rem;}
    /* Rows are pinned rather than left to auto-placement: with two figures
       hidden, the browser pushes the pair onto separate rows and the
       composition falls apart. */
    .mo--a{grid-column:1/3;grid-row:1;aspect-ratio:4/3;}
    .mo--c{grid-column:1/2;grid-row:2;}
    .mo--e{grid-column:2/3;grid-row:2;}
    .mo--d{grid-column:1/3;grid-row:3;}
    .mo--b,.mo--f{display:none;}
    .mo-more{margin-top:1.8rem;}
  }

  /* Testimonials carousel */
  /* Parent reviews. One review at a time on a stage, cycling with the same
     rhythm as the staff carousel on About. Cards stack in the stage and
     crossfade; the stage is held at the tallest card by script so nothing
     below it moves. Long reviews clamp and open in place. */
  .tst{background:var(--gold-tint);}
  .tst .lede{margin-left:auto;margin-right:auto;text-align:center;}
  .review-stage{position:relative;max-width:46rem;margin:0 auto;outline:none;}
  .review-stage:focus-visible{outline:3px solid var(--teal-deep);outline-offset:6px;border-radius:var(--radius);}
  .review{
    position:absolute;inset:0 0 auto 0;background:#fff;border-radius:var(--radius);
    padding:2rem 2.1rem 1.8rem;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:1rem;
    opacity:0;visibility:hidden;transform:translateY(8px);
    transition:opacity 340ms ease,transform 340ms cubic-bezier(.22,.61,.36,1),visibility 0s linear 340ms;
  }
  .review.is-on{position:relative;opacity:1;visibility:visible;transform:none;transition-delay:0s,0s,0s;}
  .review-stage.no-fade .review{transition:none;}
  .review::before{
    content:"\201C";position:absolute;top:.35rem;right:1.3rem;font-family:var(--f-display);font-weight:700;
    font-size:5.6rem;line-height:1;color:var(--gold);opacity:.35;pointer-events:none;
  }
  .review .stars{color:var(--gold);letter-spacing:.14em;font-size:1.05rem;line-height:1;}
  .review .quote{display:flex;flex-direction:column;gap:.8rem;}
  .review .quote p{margin:0;font-size:clamp(1.02rem,.98rem + .25vw,1.14rem);line-height:1.68;color:var(--ink);}
  .review.is-long:not(.is-open) .quote{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:8;overflow:hidden;}
  .review.is-long:not(.is-open) .quote p+p{margin-top:.8rem;}
  .review-more{
    align-self:flex-start;background:none;border:0;padding:0;font-family:var(--f-body);font-weight:800;
    font-size:.92rem;color:var(--teal-deep);cursor:pointer;text-decoration:underline;text-underline-offset:3px;
  }
  .review-more:focus-visible{outline:3px solid var(--gold);outline-offset:3px;border-radius:4px;}
  .review .who{margin-top:auto;padding-top:.9rem;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:.15rem;}
  .review .who b{font-family:var(--f-display);font-weight:600;font-size:.94rem;}
  .review .who span{font-size:.82rem;color:var(--ink-soft);}
  .review-ctrls{margin-top:1.4rem;position:relative;z-index:2;}
  .sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;}
  @media(max-width:640px){
    .review{padding:1.5rem 1.3rem 1.4rem;}
    .review.is-long:not(.is-open) .quote{-webkit-line-clamp:7;}
  }

  /* CTA */
  .cta{position:relative;background:linear-gradient(135deg,var(--teal-deep),#2e7263);color:#fff;border-radius:var(--radius);padding:clamp(2.8rem,3vw + 1rem,4.6rem) clamp(1.5rem,4vw,3rem);overflow:hidden;text-align:center;}
  .cta::before{content:"";position:absolute;width:200px;height:200px;border-radius:50%;background:rgba(244,182,62,.22);top:-70px;left:6%;}
  .cta::after{content:"";position:absolute;width:150px;height:150px;border-radius:50%;background:rgba(240,119,109,.2);bottom:-50px;right:10%;}
  /* A single photograph tucked into the corner of the enrolment panel:
     small, tilted, and clipped by the panel's own overflow so it reads as
     part of the card rather than pasted on. It is an accent, not the hero -
     the copy and the two buttons stay the subject. Hidden below 900px,
     where it would crowd centred text instead of framing it. */
  .cta-accent{
    position:absolute;right:clamp(1rem,3vw,2.6rem);bottom:calc(-1 * clamp(1rem,2vw,2rem));
    width:clamp(7rem,13vw,11.5rem);height:auto;
    border-radius:var(--radius-sm);
    transform:rotate(-4deg);
    box-shadow:0 18px 40px rgba(20,40,36,.28);
    outline:5px solid rgba(255,255,255,.9);outline-offset:-1px;
  }
  @media(max-width:900px){ .cta-accent{display:none;} }
  .cta h2{color:#fff;position:relative;margin-bottom:var(--gap-heading);}
  .cta p{color:rgba(255,255,255,.88);max-width:48ch;margin:0 auto 1.9rem;position:relative;}
  .cta .hero-actions{justify-content:center;position:relative;margin:0;}

  /* Tour form + contact */
  .contact-grid{display:grid;grid-template-columns:1fr 1.05fr;gap:clamp(2rem,4vw,3.5rem);align-items:start;}
  .info-row{display:flex;gap:1rem;padding:1.05rem 0;border-bottom:1px solid var(--line);}
  .info-row:last-child{border-bottom:0;}
  .info-row .ico{flex:0 0 auto;width:46px;height:46px;border-radius:16px;display:grid;place-items:center;}
  .info-row:nth-child(2) .ico{background:var(--coral-tint);color:var(--coral-deep);}
  .info-row:nth-child(3) .ico{background:var(--sky-tint);color:var(--sky-deep);}
  .info-row:nth-child(4) .ico{background:var(--gold-tint);color:var(--gold-deep);}
  .info-row:nth-child(5) .ico{background:var(--lav-tint);color:var(--lav-deep);}
  .info-row b{display:block;font-size:1rem;}
  .info-row span,.info-row a{color:var(--ink-soft);font-size:.94rem;}
  .info-row a:hover{color:var(--teal-deep);}
  /* Contact map. Ships as a self-contained panel with no third-party
     request in it; the Google embed replaces this only on the click the
     privacy page already tells visitors about. */
  .map{margin-top:1.4rem;}
  .map-shell{
    aspect-ratio:16/7;border-radius:var(--radius-sm);overflow:hidden;
    background:linear-gradient(140deg,var(--sky-tint),var(--lav-tint));
    border:1px solid var(--line);
    display:flex;align-items:center;justify-content:center;
  }
  .map-shell iframe{width:100%;height:100%;border:0;display:block;}
  .map-pre{display:flex;flex-direction:column;align-items:center;gap:.5rem;text-align:center;padding:1.2rem;}
  .map-pin{color:var(--sky-deep);line-height:0;}
  .map-addr{margin:0;font-family:var(--f-display);font-weight:700;color:var(--ink);line-height:1.35;}
  .map-acts{display:flex;flex-wrap:wrap;gap:.6rem;justify-content:center;margin-top:.4rem;}
  .map-acts .btn{padding:.55rem 1.1rem;font-size:.9rem;flex-direction:column;gap:0;line-height:1.2;}
  /* Set as a colour rather than an opacity: fading the label under the
     button's ink dropped it to 3.97:1, below the 4.5:1 floor. */
  .map-note{display:block;font-size:.66rem;font-weight:600;letter-spacing:.03em;color:var(--ink-soft);margin-top:.1rem;}
  /* Once the embed is in, the panel is all map. */
  .map.is-live .map-shell{background:none;padding:0;}
  @media(max-width:560px){ .map-shell{aspect-ratio:4/3;} }
  .form{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:clamp(1.5rem,3vw,2.2rem);box-shadow:var(--shadow-sm);}
  .form h3{margin-bottom:.3rem;}
  .form .sub{color:var(--ink-soft);font-size:.92rem;margin-bottom:1.3rem;}
  .field{margin-bottom:1rem;}
  .field label{display:block;font-size:.85rem;font-weight:800;margin-bottom:.35rem;}
  .field input,.field select,.field textarea{width:100%;font-family:var(--f-body);font-size:1rem;color:var(--ink);padding:.78rem .9rem;border:2px solid var(--line);border-radius:14px;background:var(--paper);transition:border-color .2s,box-shadow .2s;}
  .field input,.field textarea{-webkit-appearance:none;appearance:none;}
  .field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--teal);box-shadow:0 0 0 3px var(--teal-tint);}
  .two{display:grid;grid-template-columns:1fr 1fr;gap:1rem;}
  .form .btn{width:100%;margin-top:.3rem;}
  .form-note{font-size:.8rem;color:var(--ink-soft);margin-top:.85rem;text-align:center;}
  .form-ok{display:none;background:var(--teal-tint);border:1px solid var(--teal);border-radius:14px;padding:1rem;color:var(--teal-deep);font-size:.95rem;margin-top:1rem;}
  .form-ok.show{display:block;}
  .form-err{display:none;background:var(--coral-tint);border:1px solid var(--coral);border-radius:14px;padding:1rem;color:var(--coral-deep);font-size:.95rem;margin-top:1rem;}
  .form-err.show{display:block;}
  .form-err a{color:var(--coral-deep);font-weight:800;text-decoration:underline;}
  /* Spam honeypot: hidden from people, visible to bots that fill every field. */
  .hp-field{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}
  .btn:disabled{opacity:.6;cursor:progress;}
  .book-direct{display:flex;align-items:center;gap:.8rem;background:var(--gold-tint);border-radius:14px;padding: .9rem 1rem;margin-bottom:1.2rem;font-size:.92rem;}
  .book-direct b{font-family:var(--f-display);}

  /* Footer */
  .footer{background:var(--ink);color:rgba(255,255,255,.75);padding:clamp(3rem,3vw,4.5rem) 0 calc(2rem + env(safe-area-inset-bottom));}
  .footer-grid{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:2rem;margin-bottom:2.4rem;}
  .footer .brand{margin-bottom:1rem;}
  .footer .brand small{color:rgba(255,255,255,.85);}
  .footer p{font-size:.92rem;max-width:34ch;}
  .footer h4{font-family:var(--f-display);font-weight:600;font-size:.76rem;letter-spacing:var(--track-label);text-transform:uppercase;color:var(--gold);margin-bottom:1rem;}
  .footer li{margin-bottom:.55rem;}
  .footer a{font-size:.93rem;transition:color .2s;}
  .footer a:hover{color:#fff;}
  .socials{display:flex;gap:.7rem;margin-top:1.2rem;}
  .socials a{width:40px;height:40px;border-radius:12px;background:rgba(255,255,255,.1);display:grid;place-items:center;color:#fff;transition:background .2s;}
  .socials a:hover{background:var(--teal);}
  .footer-bottom{border-top:1px solid rgba(255,255,255,.16);padding-top:1.4rem;display:flex;justify-content:space-between;flex-wrap:wrap;gap:1rem;font-size:.83rem;}
  .footer-bottom a{font-size:inherit;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;}
  .footer-tag{display:flex;gap:.4rem;flex-wrap:wrap;font-family:var(--f-display);font-weight:600;font-size:.8rem;margin-top:.8rem;}
  .footer-tag span:nth-child(1){color:#6cc3ae;} .footer-tag span:nth-child(2){color:#f79287;}
  .footer-tag span:nth-child(3){color:var(--gold);} .footer-tag span:nth-child(4){color:#bda2e3;}

  /* Sticky mobile tour button */
  .sticky-tour{display:none;}

  /* Reveal */
  .reveal{opacity:0;transform:translateY(22px);transition:opacity .7s var(--ease),transform .7s var(--ease);}
  .reveal.in{opacity:1;transform:none;}
  .reveal[data-d="1"]{transition-delay:.08s;} .reveal[data-d="2"]{transition-delay:.16s;} .reveal[data-d="3"]{transition-delay:.24s;} .reveal[data-d="4"]{transition-delay:.32s;}
  .skip{position:absolute;left:-999px;top:0;background:var(--ink);color:#fff;padding:.7rem 1.1rem;border-radius:0 0 10px 0;z-index:200;}
  .skip:focus{left:0;}
  .m-menu{display:none;}

  /* Responsive */
  /* The team grid holds a whole staff now, so it steps down a column at a
     time rather than going straight from four to two. */
  @media(max-width:1320px){
    .team-grid{grid-template-columns:repeat(3,1fr);}
  }

  @media(max-width:1100px){
    .why-grid{grid-template-columns:repeat(2,1fr);}
    .prog-grid{grid-template-columns:repeat(2,1fr);}
    .cur-grid{grid-template-columns:repeat(2,1fr);}
    .philo-grid .pcard:last-child{grid-column:1/-1;}
    .masonry{columns:2;}
    .footer-grid{grid-template-columns:1fr 1fr;}
  }
  @media(max-width:900px){
    .team-grid{grid-template-columns:repeat(2,1fr);}
  }

  @media(max-width:760px){
    .nav-links{display:none;}
    .nav-cta .btn{display:none;}
    .nav-toggle{display:inline-flex;align-items:center;justify-content:center;}
    .hero-grid{grid-template-columns:1fr;}
      .hero-copy{text-align:center;}
    .hero .eyebrow{justify-content:center;}
    .hero .lede{margin-inline:auto;}
    .hero-actions{justify-content:center;}
    .hero-trust{justify-content:center;}
    .hero-media{max-width:430px;margin:0 auto;}
    .hero-media .ph{aspect-ratio:5/4;}
    .hero-media .dot{display:none;}
    .hero-badge{position:static;margin:.9rem auto 0;max-width:none;width:max-content;}
    .contact-grid,.two{grid-template-columns:1fr;}
    /* visibility, not just transform: an off-screen menu that stays visible is
       still in the tab order, so closed menu links were reachable by keyboard. */
    .m-menu{display:block;visibility:hidden;position:fixed;inset:0;z-index:150;background:var(--paper);transform:translateX(100%);transition:transform .35s var(--ease),visibility .35s;padding:calc(4.5rem + env(safe-area-inset-top)) max(1.5rem,env(safe-area-inset-right)) calc(6rem + env(safe-area-inset-bottom)) max(1.5rem,env(safe-area-inset-left));overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;}
    .m-menu.open{transform:none;visibility:visible;}
    .m-menu a{display:block;font-family:var(--f-display);font-weight:600;font-size:1.6rem;padding:.6rem 0;border-bottom:1px solid var(--line);}
    /* .m-menu a sets padding:.6rem 0 and outranks .btn, which stripped the
       padding from the menu's primary action and left it a 43px target.
       Restoring it also gives the CTA its proper visual weight. */
    .m-menu .btn{width:100%;margin-top:1.4rem;font-size:1.05rem;display:inline-flex;padding:1rem 1.5rem;}
    .m-close{position:absolute;top:calc(1.1rem + env(safe-area-inset-top));right:calc(1.1rem + env(safe-area-inset-right));background:none;border:0;padding:.5rem;cursor:pointer;color:var(--ink);min-width:44px;min-height:44px;}
    /* sticky mobile CTA */
    .sticky-tour{display:block;position:fixed;left:0;right:0;bottom:0;z-index:120;padding:.7rem max(1rem,env(safe-area-inset-right)) calc(.7rem + env(safe-area-inset-bottom)) max(1rem,env(safe-area-inset-left));background:rgba(255,253,249,.92);backdrop-filter:blur(12px);border-top:1px solid var(--line);}
    .sticky-tour .btn{width:100%;font-size:1.05rem;padding:1.05rem 1.5rem;}
    body{padding-bottom:86px;}
  }
  @media(max-width:560px){
    /* The hero wordmark is decorative (aria-hidden) and repeats the logo
       already in the header. At full size it pushed the actual headline
       roughly 40% down the viewport before a parent read a word of it. */
    .hero-word{font-size:clamp(3rem,2rem + 5.4vw,4.2rem);}
    .hero-mark{margin-bottom:1rem;}
    .hero .lede{margin-bottom:1.5rem;}
    .why-grid,.prog-grid,.team-grid,.cur-grid,.philo-grid{grid-template-columns:1fr;}
    .teacher{display:grid;grid-template-columns:6.5rem 1fr;column-gap:1rem;align-items:start;}
    .teacher .ph{margin-bottom:0;}
    .teacher .role,.teacher h3,.teacher p,.teacher .exp{grid-column:2;}
    .teacher h3{margin-top:.15rem;}

    .philo-grid .pcard:last-child{grid-column:auto;}
    .masonry{columns:1;}
    .footer-grid{grid-template-columns:1fr;}
  }

  /* ------------------------------------------------------------------
     Responsive typography.
     The type scale clamps down on small screens but the tracking did not,
     so display headings kept desktop's negative tracking at half the size,
     which is where the page felt tightest. Leading opens slightly as the
     measure shortens and headings wrap more often.
     ------------------------------------------------------------------ */
  @media(max-width:760px){
    h1{letter-spacing:-.008em;}
    h2{letter-spacing:0;}
    h3,h4{letter-spacing:.008em;}
    .btn{letter-spacing:.014em;}
    .m-menu a{letter-spacing:-.004em;line-height:1.25;}
  }
  @media(max-width:430px){
    .hero-trust{flex-direction:column;gap:.3rem;}
    .hero-trust span{text-wrap:balance;}
    h1{line-height:1.16;letter-spacing:-.002em;}
    h2{line-height:1.24;letter-spacing:.002em;}
    h3{line-height:1.32;}
    .lede{line-height:1.62;}
    /* The longest label ("See all programs & curriculum") sits right on the
       limit at this width. A slightly smaller label brings it back onto one
       line, where the trailing arrow reads as part of the phrase instead of
       being pushed to the far edge by a wrap. The wrap allowance stays as a
       safety net for any longer label added later. */
    .btn{padding-inline:1.4rem;font-size:.95rem;white-space:normal;text-wrap:balance;}
  }
  @media (hover:none){
    .card:hover,.prog:hover,.cur:hover,.pcard:hover,.review:hover,.teacher:hover,.btn:hover{transform:none;}
    .card:active,.prog:active,.cur:active,.pcard:active{transform:scale(.995);}
  }
  @media(prefers-reduced-motion:reduce){
    html{scroll-behavior:auto;}
    *,*::before,*::after{transition:none!important;animation:none!important;}
    .reveal{opacity:1;transform:none;}
  }

  /* Interior page header banner */
  .page-header{padding:clamp(3rem,2.4rem + 3vw,5.5rem) 0 clamp(2.2rem,2vw,3.2rem);text-align:center;position:relative;overflow:hidden;}
  .page-header .wrap{position:relative;z-index:1;}
  .page-header .eyebrow{justify-content:center;}
  .page-header p{margin:0 auto;max-width:56ch;color:var(--ink-soft);font-size:clamp(1rem,.96rem + .3vw,1.18rem);}
  .page-header .crumb{display:flex;justify-content:center;gap:.4rem;font-size:.85rem;color:var(--ink-soft);margin-bottom:1rem;}
  .page-header .crumb a{color:var(--teal-deep);font-weight:700;}
  .ph-teal{background:var(--teal-tint);}
  .ph-coral{background:var(--coral-tint);}
  .ph-gold{background:var(--gold-tint);}
  .ph-lav{background:var(--lav-tint);}
  .ph-sky{background:var(--sky-tint);}

  .nav-links a.active{color:var(--ink);}
  .nav-links a.active::after{width:100%;}
  .m-menu a.active{color:var(--teal-deep);}

  /* About page: teachers + contact combined layout */
  .about-lede{max-width:64ch;margin:0 auto 3rem;text-align:center;color:var(--ink-soft);font-size:1.05rem;}
  /* The last paragraph before a button row uses the site's CTA gap, so the
     action reads as belonging to the copy above it. */
  .about-lede:has(+ .hero-actions){margin-bottom:var(--cta-gap);}


  /* ---------- Booking calendar (self-contained, no external site) ---------- */
  .booker{background:#fff;border-radius:var(--radius);box-shadow:var(--shadow);border:1px solid var(--line);padding:clamp(1.3rem,2vw,2rem);}
  .booker-panel{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(1.5rem,3vw,2.5rem);}
  .booker-panel#bookerStep2{grid-template-columns:1fr;max-width:560px;margin-inline:auto;}

  .cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;}
  .cal-month{font-family:var(--f-display);font-weight:700;font-size:1.1rem;}
  .cal-nav{width:38px;height:38px;border-radius:50%;border:2px solid var(--line);background:#fff;color:var(--ink);cursor:pointer;display:grid;place-items:center;transition:all .2s;}
  .cal-nav:hover{border-color:var(--teal-deep);color:var(--teal-deep);}
  .cal-nav:disabled{opacity:.35;cursor:not-allowed;}
  .cal-nav:focus-visible{outline:3px solid var(--gold);outline-offset:2px;}

  .cal-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin-bottom:.4rem;}
  .cal-weekdays span{text-align:center;font-size:.74rem;font-weight:800;color:var(--ink-soft);letter-spacing:.04em;}

  .cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;}
  .cal-day{aspect-ratio:1;border:0;background:transparent;border-radius:12px;font-family:var(--f-body);font-weight:700;font-size:.92rem;color:var(--ink);cursor:pointer;display:grid;place-items:center;transition:background .18s,color .18s,transform .18s;}
  .cal-day:hover:not([aria-disabled="true"]):not(.cal-day--empty){background:var(--teal-tint);}
  .cal-day:focus-visible{outline:3px solid var(--gold);outline-offset:2px;}
  .cal-day--empty{cursor:default;pointer-events:none;}
  .cal-day--disabled{color:var(--line);cursor:not-allowed;}
  /* Unavailable days stay focusable (aria-disabled, not disabled) so keyboard
     users can cross the month and hear why a date is closed. */
  .cal-day[aria-disabled="true"]{color:var(--line);cursor:not-allowed;}
  .cal-day--today{box-shadow:inset 0 0 0 2px var(--gold);}
  .cal-day--selected{background:var(--teal-deep)!important;color:#fff;}
  .cal-note{margin:1rem 0 0;font-size:.8rem;color:var(--ink-soft);}

  .booker-times h3{font-size:1.05rem;margin-bottom:.3rem;}
  .times-hint{font-size:.88rem;color:var(--ink-soft);margin:0 0 1rem;}
  .time-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.6rem;max-height:340px;overflow-y:auto;padding-right:.2rem;}
  .time-slot{padding:.7rem .6rem;border-radius:12px;border:2px solid var(--line);background:var(--paper);font-family:var(--f-body);font-weight:700;font-size:.88rem;color:var(--ink);cursor:pointer;transition:all .18s;}
  .time-slot:hover{border-color:var(--teal-deep);}
  .time-slot:focus-visible{outline:3px solid var(--gold);outline-offset:2px;}
  .time-slot--selected{background:var(--teal-deep);border-color:var(--teal-deep);color:#fff;}

  .booker-summary{background:var(--teal-tint);border-radius:14px;padding:1rem 1.2rem;margin-bottom:1.4rem;font-weight:700;color:var(--teal-deep);text-align:center;}
  .booker-actions{display:flex;gap:.8rem;flex-wrap:wrap;margin-top:.4rem;}
  .booker-actions .btn--teal{flex:1;}
  .booker-actions .btn--ghost{flex:0 0 auto;}

  .booker-done{text-align:center;max-width:480px;margin-inline:auto;padding:1rem 0;}
  .done-icon{display:inline-grid;place-items:center;width:64px;height:64px;border-radius:50%;background:var(--teal-tint);color:var(--teal-deep);margin-bottom:1rem;}
  .booker-done h3{margin-bottom:.5rem;}
  .booker-done p{color:var(--ink-soft);}
  .done-sub{font-size:.9rem;}
  .done-sub a{color:var(--teal-deep);font-weight:700;}

  @media(max-width:760px){
    .booker-panel{grid-template-columns:1fr;}
    .time-grid{max-height:none;}
  }

  /* Cross-page deep links (e.g. programs.html#infants) target articles, not
     sections, so they need the same sticky-header clearance. */
  [id]:target{scroll-margin-top:88px;}


  /* Legal / policy pages (privacy, accessibility) */
  .legal{max-width:72ch;margin:0 auto;}
  .legal .meta{font-size:.9rem;color:var(--ink-soft);border:1px solid var(--line);border-radius:var(--radius-sm);padding:.9rem 1.1rem;margin-bottom:2.4rem;background:var(--cream);}
  .legal h2{font-size:clamp(1.3rem,1.2rem + .5vw,1.65rem);margin:2.6rem 0 .7rem;}
  .legal h2:first-of-type{margin-top:0;}
  .legal h3{font-size:1.08rem;margin:1.6rem 0 .4rem;}
  .legal p{color:var(--ink-soft);margin:0 0 1rem;}
  .legal ul{color:var(--ink-soft);margin:0 0 1.1rem;padding-left:1.2rem;}
  .legal li{margin-bottom:.45rem;}
  .legal a{color:var(--teal-deep);font-weight:700;}
  .legal .callout{background:var(--teal-tint);border-radius:var(--radius-sm);padding:1.1rem 1.3rem;margin:0 0 1.2rem;}
  .legal .callout p{color:var(--ink);margin:0;}
  .legal .toc{background:var(--mist);border-radius:var(--radius-sm);padding:1.1rem 1.3rem 1.1rem 2.3rem;margin-bottom:2.4rem;}
  .legal .toc li{margin-bottom:.3rem;}

  /* Site notice banner (managed in /admin, sits above the header) */
  .espin-banner{background:var(--teal-tint);border-bottom:1px solid rgba(61,148,132,.25);color:var(--ink);font-size:.95rem;padding-top:env(safe-area-inset-top);}
  .espin-banner.is-urgent{background:var(--coral-tint);border-bottom-color:rgba(221,92,81,.3);}
  .espin-banner-inner{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:.35rem 1rem;padding:.8rem 0;text-align:center;}
  .espin-banner strong{font-family:var(--f-display);font-weight:600;}
  .espin-banner strong::after{content:"";display:inline-block;width:.5rem;}
  .espin-banner span{color:var(--ink-soft);}
  .espin-banner.is-urgent span{color:#8d3b34;}
  .espin-banner-link{font-weight:800;color:var(--teal-deep);text-decoration:underline;text-underline-offset:3px;white-space:nowrap;}
  .espin-banner.is-urgent .espin-banner-link{color:var(--coral-deep);}
  @media(max-width:560px){
    .espin-banner{font-size:.9rem;}
    .espin-banner-inner{flex-direction:column;gap:.2rem;padding:.7rem 0;}
    .espin-banner strong::after{display:none;}
  }

  /* ============================================================
     Rotating announcement banner. Markup sits above .site-header in every
     page; the rotation is driven by assets/promo-banner.js.

     One message at a time on a pastel plate, each with its own colour and its
     own drawn shapes, handing over with a short lift-and-fade. Nothing here
     scrolls sideways: a marquee is unreadable on a phone and impossible to
     tap accurately.

     The palette is the logo's own tints, deepened just enough to hold their
     own as a background. Every accent is checked against its plate -- the
     lowest pairing is 4.6:1 and the headline ink is above 9:1 -- because a
     pastel bar is exactly where contrast quietly goes wrong. Coral is the one
     exception to the -deep scale: --coral-deep is 4.2:1 on blush, so the
     banner uses --coral-ink, which is what that variable was mixed for.

     Only transform and opacity animate, so a bar that is always on screen
     costs a compositor pass and nothing else.
     ============================================================ */
  .espin-promo{
    position:relative;overflow:hidden;contain:paint;
    background:var(--promo-bg);
    border-bottom:1px solid var(--promo-line);
    padding-top:env(safe-area-inset-top);
    transition:background-color .85s var(--ease),border-color .85s var(--ease);
  }
  .espin-promo[data-theme="sprout"] {--promo-bg:#e6f2ea;--promo-accent:#2b7566;--promo-line:rgba(43,117,102,.2); --promo-art:rgba(43,117,102,.42);}
  .espin-promo[data-theme="bloom"]  {--promo-bg:#fae9e6;--promo-accent:#b53d2f;--promo-line:rgba(181,61,47,.18); --promo-art:rgba(181,61,47,.4);}
  .espin-promo[data-theme="play"]   {--promo-bg:#fbf0d7;--promo-accent:#8f6408;--promo-line:rgba(143,100,8,.2);  --promo-art:rgba(143,100,8,.4);}
  .espin-promo[data-theme="sparkle"]{--promo-bg:#eee8f8;--promo-accent:#7856ac;--promo-line:rgba(120,86,172,.18);--promo-art:rgba(120,86,172,.42);}
  .espin-promo[data-theme="home"]   {--promo-bg:#e5eef9;--promo-accent:#406d9c;--promo-line:rgba(64,109,156,.2); --promo-art:rgba(64,109,156,.42);}

  /* A real school notice outranks an advertisement. assets/espin-live.js sets
     this class when it renders one, and the promotion steps aside. */
  .has-espin-notice .espin-promo{display:none;}

  .espin-promo-inner{
    max-width:var(--maxw);margin:0 auto;
    padding:0 max(var(--page-pad),env(safe-area-inset-right)) 0 max(var(--page-pad),env(safe-area-inset-left));
    display:flex;align-items:center;gap:.4rem;
  }
  /* The slides are stacked in one grid cell rather than positioned absolutely,
     so the bar's height comes from the message inside it and cannot be left
     wrong by a font that loads late. */
  .espin-promo-stage{position:relative;flex:1;min-width:0;display:grid;}
  .espin-promo-slide{
    grid-area:1/1;
    display:flex;align-items:center;justify-content:center;gap:.15rem .85rem;flex-wrap:wrap;
    min-height:52px;padding:.55rem 0;
    text-align:center;
    opacity:0;visibility:hidden;transform:translateY(.6rem);
    transition:opacity var(--promo-handover,460ms) var(--ease),
               transform var(--promo-handover,460ms) var(--ease),
               visibility 0s linear var(--promo-handover,460ms);
  }
  .espin-promo-slide.is-live{opacity:1;visibility:visible;transform:none;transition-delay:0s;}
  .espin-promo-slide.is-leaving{opacity:0;visibility:visible;transform:translateY(-.6rem);}

  .espin-promo-text{display:inline-flex;align-items:baseline;justify-content:center;flex-wrap:wrap;gap:.1rem .5rem;min-width:0;}
  .espin-promo-head{font-family:var(--f-display);font-weight:600;font-size:.97rem;letter-spacing:var(--track-ui);color:var(--ink);}
  .espin-promo-sub{color:var(--ink-soft);font-size:.92rem;}
  .espin-promo-cta{
    display:inline-flex;align-items:center;gap:.32rem;white-space:nowrap;
    font-weight:800;font-size:.92rem;letter-spacing:var(--track-ui);color:var(--promo-accent);
    text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;
    transition:text-decoration-color .3s var(--ease);
  }
  .espin-promo-slide:hover .espin-promo-cta{text-decoration-color:currentColor;}
  .espin-promo-slide:focus-visible{outline:2px solid var(--promo-accent);outline-offset:-4px;border-radius:var(--radius-sm);}

  /* The arrow leans forward every few seconds -- enough to read as an
     invitation, small enough not to twitch in the corner of the eye. */
  .espin-promo-arrow{width:1.02rem;height:1.02rem;flex:none;animation:promo-nudge 3.4s var(--ease) infinite;}
  .espin-promo-slide:hover .espin-promo-arrow{animation:none;transform:translateX(.22rem);transition:transform .25s var(--ease);}
  @keyframes promo-nudge{0%,66%,100%{transform:translateX(0);}80%{transform:translateX(.2rem);}}

  /* Drawn shapes, placed in the gutters either side of the message. They are
     decoration and nothing else: aria-hidden in the DOM, pointer-transparent
     here, and clipped by the bar. */
  .espin-promo-decor{position:absolute;inset:0;pointer-events:none;color:var(--promo-art);opacity:0;transition:opacity .6s var(--ease);}
  .espin-promo.is-ready .espin-promo-decor{opacity:1;}
  .espin-promo-shape{
    position:absolute;left:var(--px);top:var(--py);
    width:calc(2.3rem * var(--ps));height:calc(2.3rem * var(--ps));
    margin:calc(-1.15rem * var(--ps)) 0 0 calc(-1.15rem * var(--ps));
    animation:promo-float 7.5s var(--ease) var(--pd) infinite alternate;
  }
  .espin-promo-shape svg{width:100%;height:100%;}
  .espin-promo-shape.is-star{animation:promo-twinkle 3.6s var(--ease) var(--pd) infinite alternate;}
  @keyframes promo-float{from{transform:translateY(.2rem) rotate(-2.5deg);}to{transform:translateY(-.22rem) rotate(2.5deg);}}
  @keyframes promo-twinkle{from{opacity:.5;transform:scale(.88) rotate(-6deg);}to{opacity:1;transform:scale(1.06) rotate(6deg);}}

  /* Time left on this message. It is also the pause indicator: holding the
     rotation holds the line. */
  .espin-promo-progress{
    display:none;
    position:absolute;left:0;bottom:0;height:2px;width:100%;
    background:var(--promo-accent);opacity:.26;
    transform:scaleX(0);transform-origin:0 50%;
    animation:promo-progress var(--promo-dwell,4600ms) linear forwards;
  }
  .espin-promo.is-held .espin-promo-progress{animation-play-state:paused;}
  .espin-promo.is-paused .espin-promo-progress{animation-play-state:paused;opacity:.14;}
  @keyframes promo-progress{from{transform:scaleX(0);}to{transform:scaleX(1);}}

  /* The pause button and the progress line both describe a rotation, and
     there is no rotation until promo-banner.js has said so. Until then the
     page shows the one message in its HTML and nothing that implies more. */
  .espin-promo.is-ready .espin-promo-progress{display:block;}
  .espin-promo.is-ready .espin-promo-toggle{display:inline-flex;}
  .espin-promo-toggle{
    flex:none;display:none;align-items:center;justify-content:center;
    width:30px;height:30px;padding:0;border:0;border-radius:50%;
    background:transparent;color:var(--promo-accent);opacity:.4;cursor:pointer;
    transition:opacity .25s var(--ease),background-color .25s var(--ease);
  }
  .espin-promo-toggle:hover{opacity:.9;background:rgba(255,255,255,.55);}
  .espin-promo-toggle:focus-visible{opacity:1;outline:2px solid var(--promo-accent);outline-offset:2px;}
  .espin-promo-toggle svg{width:14px;height:14px;}
  .espin-promo-toggle .promo-play{display:none;}
  .espin-promo.is-paused .espin-promo-toggle .promo-play{display:block;}
  .espin-promo.is-paused .espin-promo-toggle .promo-pause{display:none;}
  .espin-promo.is-paused .espin-promo-toggle{opacity:.72;}

  /* Phones get the short wording, one line, no supporting phrase, and only
     the two outermost shapes -- which sit in the page gutter, clear of the
     text. The call to action and its arrow never drop. */
  .espin-promo-short{display:none;}
  @media(max-width:700px){
    .espin-promo-long{display:none;}
    .espin-promo-short{display:inline;}
    .espin-promo-sub{display:none;}
    .espin-promo-slide{min-height:46px;gap:.1rem .55rem;padding:.5rem 0;flex-wrap:nowrap;}
    .espin-promo-head{font-size:.88rem;}
    .espin-promo-cta{font-size:.85rem;}
    .espin-promo-text{flex-wrap:nowrap;}
    .espin-promo-head,.espin-promo-cta .espin-promo-short{white-space:nowrap;}
    .espin-promo-shape{opacity:.6;}
    .espin-promo-shape:not(:first-child){display:none;}
    .espin-promo-shape:first-child{left:1%;--ps:.52;}
    .espin-promo-inner{gap:.15rem;}
    .espin-promo-toggle{width:26px;height:26px;}
  }
  @media(min-width:701px) and (max-width:1080px){
    .espin-promo-shape:not(:first-child):not(:last-child){display:none;}
  }
  @media(max-width:380px){
    .espin-promo-head{font-size:.83rem;}
    .espin-promo-cta{font-size:.8rem;}
    .espin-promo-shape{display:none;}
  }

  /* Reduced motion: the script never starts the rotation on its own here, so
     what is left is one still message. Should the visitor press play, the
     handover becomes a plain fade and the shapes stay put. */
  @media(prefers-reduced-motion:reduce){
    .espin-promo-slide{transform:none!important;transition-duration:.25s;}
    .espin-promo-arrow,.espin-promo-shape{animation:none!important;}
    .espin-promo-slide:hover .espin-promo-arrow{transform:none;}
    .espin-promo{transition-duration:.25s;}
  }

  /* School announcements (managed in /admin) */
  .ann-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.1rem;}
  .ann{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:1.5rem 1.6rem;box-shadow:var(--shadow-sm);}
  .ann h3{font-size:1.15rem;margin-bottom:.45rem;}
  .ann p{color:var(--ink-soft);font-size:.94rem;margin:0;}
  .ann-when{display:inline-block;margin-top:.9rem;font-size:.78rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--teal-deep);}

  /* Testimonial avatars past the third (the first three are coloured above) */
  .review .avatar{background:var(--lav);}

  /* ============================================================
     Homepage intro: rising bubbles. Driven by assets/bubbles.js.

     Two fixed layers give the effect depth without a 3D engine:
       .eb-layer--back   z-index 1   sits behind .hero-grid (z-index 2)
       .eb-layer--front  z-index 90  sits above the hero, below .site-header (100)
     Both are fixed and clipped, so nothing here can shift layout, and
     pointer-events:none keeps the whole thing decorative.

     Per-bubble values arrive as custom properties set inline by the script.
     Only transform and opacity animate, so every frame is compositor work.
     ============================================================ */
  .eb-layer{position:fixed;inset:0;overflow:hidden;pointer-events:none;contain:layout paint;}
  .eb-layer--back{z-index:1;}
  .eb-layer--front{z-index:90;}

  /* Position and appearance only. The movement itself is generated per bubble
     in assets/bubbles.js and played through the Web Animations API, because a
     two-keyframe CSS sway can only ever describe one curve shared by every
     bubble - which is what made the old motion read as buoyancy through water
     rather than drift through air. Still transform and opacity only, so it
     composites exactly as before. */
  .eb{
    position:absolute;bottom:0;left:var(--eb-x);
    width:var(--eb-size);height:var(--eb-size);
    margin-left:calc(var(--eb-size) / -2);
    opacity:0;
    will-change:transform,opacity;
    -webkit-backface-visibility:hidden;backface-visibility:hidden;
  }
  /* Depth blur. Kept small, and only on the far tier: blur is the one
     expensive property here, and Safari feels it first. */
  .eb--far{filter:blur(calc(var(--eb-size) * .020));}
  .eb--mid{filter:blur(calc(var(--eb-size) * .011));}


  /* The bubble itself. The sway runs on its own clock, and a negative
     delay drops each one at a different point in the cycle, so the rise
     and the drift never fall into a repeating pattern. */
  .eb-b{
    position:absolute;inset:0;border-radius:50%;
    filter:hue-rotate(var(--eb-hue,0deg)) saturate(1.04);
    -webkit-backface-visibility:hidden;backface-visibility:hidden;
    /* Painted top layer first. The whole read is built at the edge: the
       core stays clear so headline text shows straight through a large
       bubble, while the rim - bright inner ring, fine cool outer edge -
       is what makes it a sphere. White-on-white fill would be invisible
       on this hero, which is why almost nothing lives in the middle.
       closest-side keeps 100% at the circle's radius; the default
       farthest-corner puts it at 141% and clips the rim off entirely. */
    background:
      radial-gradient(circle at 26% 20%, rgba(255,255,255,.85) 0%, rgba(255,255,255,0) 5%),
      radial-gradient(circle at 31% 26%, rgba(255,255,255,.95) 0%, rgba(255,255,255,.42) 6%, rgba(255,255,255,0) 16%),
      radial-gradient(circle at 71% 76%, rgba(255,255,255,.40) 0%, rgba(255,255,255,0) 24%),
      radial-gradient(circle closest-side at 50% 50%, rgba(255,255,255,0) 74%, rgba(255,255,255,.88) 95%, rgba(255,255,255,.26) 100%),
      radial-gradient(circle closest-side at 50% 50%, rgba(47,59,86,0) 84%, rgba(47,59,86,.22) 98%, rgba(47,59,86,.08) 100%),
      radial-gradient(circle at 36% 72%, rgba(87,179,160,.17) 0%, rgba(87,179,160,0) 56%),
      radial-gradient(circle at 74% 34%, rgba(169,139,211,.17) 0%, rgba(169,139,211,0) 52%),
      radial-gradient(circle at 57% 89%, rgba(244,182,62,.14) 0%, rgba(244,182,62,0) 46%),
      radial-gradient(circle closest-side at 50% 50%, rgba(226,242,238,.13) 0%, rgba(226,242,238,.04) 68%, rgba(226,242,238,0) 100%);
    box-shadow:
      inset 0 0 calc(var(--eb-size) * .12) rgba(255,255,255,.35),
      inset calc(var(--eb-size) * -.04) calc(var(--eb-size) * -.06) calc(var(--eb-size) * .16) rgba(87,179,160,.09),
      0 calc(var(--eb-size) * .03) calc(var(--eb-size) * .10) rgba(47,59,86,.05);
  }


  /* Thin-film interference. On a real bubble the colour sits in narrow bands
     where the film is thinnest, right at the rim, never as a wash through the
     middle - so this is masked to the last fifth of the radius and the centre
     is left completely clear. Per-bubble hue rotation on .eb-b moves each one
     to a different part of the pastel range. */
  .eb-b::after{
    content:"";position:absolute;inset:0;border-radius:50%;pointer-events:none;
    background:
      conic-gradient(from 210deg at 50% 50%,
        rgba(169,139,211,0) 0deg,
        rgba(240,119,109,.20) 52deg,
        rgba(244,182,62,.17) 104deg,
        rgba(87,179,160,.19) 168deg,
        rgba(111,168,220,.21) 232deg,
        rgba(169,139,211,.18) 292deg,
        rgba(169,139,211,0) 360deg);
    /* The film carries through the middle now rather than being clipped to a
       ring: partial alpha across the centre, rising to full just inside the
       rim, then off at the very edge. Colour still concentrates where a real
       film is thinnest, but the middle is no longer empty. */
    -webkit-mask-image:radial-gradient(circle closest-side at 50% 50%,
      rgba(0,0,0,.46) 0%, rgba(0,0,0,.54) 42%, rgba(0,0,0,.72) 72%,
      #000 90%, #000 99%, transparent 100%);
    mask-image:radial-gradient(circle closest-side at 50% 50%,
      rgba(0,0,0,.46) 0%, rgba(0,0,0,.54) 42%, rgba(0,0,0,.72) 72%,
      #000 90%, #000 99%, transparent 100%);
    mix-blend-mode:screen;
  }

  /* Phones: no blur at all, and a shorter gradient stack. The silhouette,
     the specular highlight and the rim are what read at this size anyway. */
  @media(max-width:760px){
    .eb--far,.eb--mid{filter:none;}
    .eb-b{
      background:
        radial-gradient(circle at 30% 25%, rgba(255,255,255,.92) 0%, rgba(255,255,255,.40) 6%, rgba(255,255,255,0) 17%),
        radial-gradient(circle closest-side at 50% 50%, rgba(255,255,255,0) 77%, rgba(255,255,255,.76) 95%, rgba(255,255,255,.20) 100%),
        radial-gradient(circle closest-side at 50% 50%, rgba(47,59,86,0) 87%, rgba(47,59,86,.13) 99%, rgba(47,59,86,.04) 100%),
        radial-gradient(circle at 40% 68%, rgba(87,179,160,.16) 0%, rgba(87,179,160,0) 54%),
        radial-gradient(circle closest-side at 50% 50%, rgba(226,242,238,.12) 0%, rgba(226,242,238,0) 100%);
      box-shadow:inset 0 0 calc(var(--eb-size) * .11) rgba(255,255,255,.3);
    }
  }

  /* Belt and braces: the global reduced-motion rule already disables every
     animation, and the script never builds the layers in the first place. */
  @media(prefers-reduced-motion:reduce){
    .eb-layer{display:none!important;}
  }

  /* ============================================================
     Enrollment + FAQ components
     ============================================================ */

  /* Numbered enrollment path. The numbering is real sequence, not decoration:
     each step must happen before the next. */
  .steps{display:grid;gap:1rem;counter-reset:step;list-style:none;padding:0;margin:0;}
  @media(min-width:900px){.steps{grid-template-columns:repeat(2,1fr);gap:1.1rem 1.6rem;}}
  .step{
    display:flex;gap:1.1rem;align-items:flex-start;background:#fff;
    border:1px solid var(--line);border-radius:var(--radius-sm);
    padding:1.35rem 1.5rem;box-shadow:var(--shadow-sm);
  }
  .step-n{
    flex:0 0 auto;width:38px;height:38px;border-radius:50%;
    background:var(--teal-tint);color:var(--teal-deep);
    display:grid;place-items:center;font-family:var(--f-display);
    font-weight:700;font-size:1.05rem;
  }
  .step h3{margin:.1rem 0 .3rem;}
  .step p{color:var(--ink-soft);font-size:.95rem;margin:0;}

  /* Supporting panel + tick list */
  .a-panel{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:1.5rem 1.6rem;box-shadow:var(--shadow-sm);}
  .a-panel h3{margin-bottom:.8rem;}
  .ticks{display:flex;flex-direction:column;gap:.55rem;margin:0 0 1rem;}
  .ticks li{position:relative;padding-left:1.7rem;color:var(--ink-soft);font-size:.95rem;}
  .ticks li::before{
    content:"";position:absolute;left:0;top:.42em;width:1.05rem;height:1.05rem;border-radius:50%;
    background:var(--teal-tint);
  }
  .ticks li::after{
    content:"";position:absolute;left:.29rem;top:.72em;width:.42rem;height:.22rem;
    border-left:2px solid var(--teal-deep);border-bottom:2px solid var(--teal-deep);
    transform:rotate(-45deg);
  }
  .muted-note{font-size:.88rem;color:var(--ink-soft);margin:0;}

  /* FAQ: native details/summary, so it works without JavaScript */
  .faq-layout{display:grid;gap:clamp(1.6rem,3vw,3rem);}
  @media(min-width:1000px){.faq-layout{grid-template-columns:minmax(0,15rem) minmax(0,1fr);align-items:start;}}
  .faq-nav{display:flex;flex-wrap:wrap;gap:.45rem;}
  @media(min-width:1000px){
    .faq-nav{position:sticky;top:calc(88px + 1rem);flex-direction:column;gap:.15rem;}
  }
  .faq-nav a{
    font-family:var(--f-display);font-weight:600;font-size:.9rem;color:var(--ink-soft);
    padding:.45rem .75rem;border-radius:999px;background:var(--mist);transition:color .2s,background .2s;
  }
  @media(min-width:1000px){.faq-nav a{background:none;padding:.4rem .6rem;border-radius:10px;}}
  .faq-nav a:hover{color:var(--teal-deep);background:var(--teal-tint);}
  .faq-body{min-width:0;}
  .faq-group{
    font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-deep);
    font-family:var(--f-display);font-weight:600;margin:2.6rem 0 .9rem;scroll-margin-top:96px;
  }
  .faq-group:first-child{margin-top:0;}
  .faq-item{
    background:#fff;border:1px solid var(--line);border-radius:var(--radius-sm);
    margin-bottom:.6rem;overflow:hidden;
  }
  .faq-item summary{
    cursor:pointer;list-style:none;padding:1.05rem 3rem 1.05rem 1.3rem;position:relative;
    font-family:var(--f-display);font-weight:600;font-size:1.02rem;line-height:1.35;
  }
  .faq-item summary::-webkit-details-marker{display:none;}
  .faq-item summary::after{
    content:"";position:absolute;right:1.3rem;top:1.5rem;width:.6rem;height:.6rem;
    border-right:2px solid var(--teal-deep);border-bottom:2px solid var(--teal-deep);
    transform:rotate(45deg);transition:transform .25s var(--ease);
  }
  .faq-item[open] summary::after{transform:rotate(-135deg);}
  .faq-item summary:hover{color:var(--teal-deep);}
  .faq-item summary:focus-visible{outline:3px solid var(--gold);outline-offset:-3px;}
  .faq-a{padding:0 1.3rem 1.2rem;color:var(--ink-soft);}
  .faq-a p{margin:0 0 .8rem;font-size:.97rem;}
  .faq-a p:last-child{margin-bottom:0;}
  .faq-a a{color:var(--teal-deep);font-weight:700;text-decoration:underline;text-underline-offset:3px;}

  /* A typical day: a real sequence, so it reads as a timeline rather than
     a grid of equal cards. No clock times are shown: the rhythm is ordered,
     not scheduled. */
  .day{list-style:none;margin:0;padding:0;display:grid;gap:0;}
  .day-step{position:relative;padding:0 0 1.6rem 2.4rem;border-left:2px solid rgba(217,154,29,.35);}
  .day-step:last-child{border-left-color:transparent;padding-bottom:0;}
  .day-step::before{
    content:"";position:absolute;left:-.52rem;top:.5rem;width:.95rem;height:.95rem;
    border-radius:50%;background:var(--gold);box-shadow:0 0 0 5px var(--gold-tint);
  }
  .day-step h3{margin:0 0 .45rem;font-size:clamp(1.18rem,1.02rem + .58vw,1.55rem);}
  .day-step p{color:var(--ink-soft);font-size:clamp(1.02rem,.96rem + .3vw,1.2rem);line-height:1.62;margin:0;max-width:62ch;}
  @media(min-width:900px){
    .day{grid-template-columns:repeat(2,1fr);column-gap:2.6rem;}
    /* The grid fills row by row, so the sequence still reads left-to-right.
       The connecting rule is dropped here because a vertical line would
       imply reading down each column instead, which skips steps. */
    .day-step{padding-bottom:1.9rem;border-left-color:transparent;}
  }
  /* Two full-width columns rather than four narrow ones: the day still fills
     the section, but each moment gets room for type a parent can read at a
     glance. */
  @media(min-width:1240px){
    .day{column-gap:clamp(3rem,5vw,6rem);}
    .day-step p{max-width:60ch;}
  }

  /* Portrait slots show the person's initials until a photo is uploaded, so
     an empty tile reads as a considered avatar rather than a missing image.
     The initials disappear as soon as a real photo lands in the slot. */
  .teacher .ph[data-initials]::after,
  .orbit-pick .ph[data-initials]::after{
    content:attr(data-initials);
    position:absolute;inset:0;display:grid;place-items:center;
    font-family:var(--f-display);font-weight:700;
    font-size:clamp(2.2rem,1.6rem + 2vw,3.2rem);
    color:rgba(255,255,255,.82);letter-spacing:.02em;
  }
  .teacher .ph.has-photo[data-initials]::after,
  .orbit-pick .ph.has-photo[data-initials]::after{display:none;}

  /* Italic standfirst: a short line of scene-setting under a heading. */
  .lede--em{font-style:italic;}

  /* Centred statement with its supporting line set beneath and to the right,
     in italics. Used on the enrolment page so each section opens with the
     statement and then hands off to a quieter aside. */
  .sec-head--aside{max-width:none;text-align:center;}
  .sec-head--aside .eyebrow{justify-content:center;}
  .sec-head--aside h1,.sec-head--aside h2{max-width:22ch;margin-inline:auto;}
  .sec-head--aside .lede{
    font-style:italic;text-align:right;max-width:44ch;
    margin-left:auto;margin-right:0;margin-top:var(--gap-heading);
  }
  @media(max-width:760px){
    /* On a narrow screen a right-aligned aside reads as a mistake, so the
       supporting line centres under the statement instead. */
    .sec-head--aside .lede{text-align:center;margin-inline:auto;}
  }

  /* The label grows with the viewport, so on narrow screens the tracking eases
     to keep the longest names ("Why families choose ESPIN") on one line. */
  @media(max-width:560px){
    :root{--track-eyebrow:.13em;}
  }
  /* At the narrowest widths the two longest labels ("Why families choose
     ESPIN", "Our learning environment") ran onto a second line, which left
     the dots stranded on their own. Easing the tracking and the gap after
     the dots keeps every label on one line. */
  @media(max-width:400px){
    :root{--track-eyebrow:.09em;}
    .eyebrow::before{margin-right:1.45rem;}
  }
