/* ==========================================================================
   Capeway — "Departure Board"
   --------------------------------------------------------------------------
   The business exists because it publishes prices its competitors hide. So
   the design treats a figure the way a fare board does: monospaced, tabular,
   lined up on a shared axis, and stamped with the date it was checked.

   Two grounds. The deep Atlantic ink carries data and photography; the bone
   carries reading. Nothing sits in between, so every band knows which it is.

   Palette roles are fixed by the brand sheet: cyan drives every action, sage
   is status only and never a button. Saffron is the editorial accent — it
   marks the headline figure, the step numbers and the progress rule, and it
   is never something you can click.
   ========================================================================== */

:root {
  /* -- ground: the Atlantic, at three depths */
  --abyss:  #08161d;
  --ink:    #0c222b;
  --ink-2:  #123540;
  --ink-3:  #1a4551;

  /* -- ground: paper. Warm grey, not cream — it has to read as stock, not as
        parchment, because a grotesque sits on it. */
  --bone:   #f6f4f0;
  --paper:  #ffffff;
  --shell:  #ebe8e2;
  --line:   #e0dcd4;
  --line-2: #c8c3b8;

  /* -- brand, fixed */
  --cyan:   #028090;
  --cyan-d: #01606d;
  --cyan-l: #14a8bb;
  --cyan-w: #e3f0f2;
  --cyan-t: #01606d;   /* cyan as lettering on a light ground */
  --sage:   #52b788;
  --sage-d: #2b6d4c;
  --sage-w: #e7f4ed;

  /* -- editorial accent. Never an action. */
  --saffron:   #de8f2c;
  --saffron-l: #f0b25c;
  --saffron-w: #fbf1e1;
  --saffron-t: #a05f10; /* saffron as lettering on a light ground */
  --madder:    #b8452f;

  /* -- type colour */
  --ink-t:   #0d1c22;
  --slate:   #495661;
  --slate-2: #5d6a74;
  --fog:     #9aabb4;
  --fog-2:   #8496a0;

  /* -- awaiting-data flag */
  --flag:      #9a6316;
  --flag-wash: #fbf2e3;

  /* -- the five schools, in the order the price data lists them. Carried
        through the cards, the comparison columns and the index, so colour is
        a fact about which school you are reading, not decoration. Hues are
        spaced around the wheel and kept clear of the roles: cyan is action,
        sage is status, saffron is editorial, so none of these may be green. */
  --sc-1: #0f7f8c;   /* International House */
  --sc-2: #a85c17;   /* Good Hope Studies   */
  --sc-3: #6a5aa8;   /* Cape Studies        */
  --sc-4: #a03a6b;   /* Avenue English      */
  --sc-5: #1f5fa0;   /* UCT ELC             */

  /* -- faces */
  --display: "Bricolage Grotesque", "Archivo", "IBM Plex Sans Arabic", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --body:    "Instrument Sans", "IBM Plex Sans Arabic", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, sans-serif;
  --mono:    "IBM Plex Mono", "IBM Plex Sans Arabic", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --t-2xs: .6875rem; --t-xs: .8125rem; --t-sm: .875rem;
  --t-base: 1rem; --t-md: 1.125rem; --t-lg: 1.3125rem;
  --t-xl:  clamp(1.5rem, 1.08rem + 1.7vw, 2.05rem);
  --t-2xl: clamp(2rem, 1.3rem + 3.1vw, 3.35rem);
  --t-3xl: clamp(2.5rem, 1.1rem + 5.8vw, 5.2rem);
  --t-fig: clamp(2.1rem, 1.3rem + 3vw, 3.5rem);

  --s1: .25rem; --s2: .5rem; --s3: .75rem; --s4: 1rem;
  --s5: 1.5rem; --s6: 2rem; --s7: 3rem; --s8: 4.5rem; --s9: 7rem;

  --bleed: clamp(1.25rem, 4vw, 3.25rem);
  --measure: 1240px;
  --mast: 72px;

  /* Radius belongs to printed matter — cards, inputs, panels. The boards and
     the full-bleed bands stay square, so the two registers never blur. */
  --r: 3px; --r-lg: 4px;

  --lift-1: 0 1px 2px rgba(12,34,43,.05), 0 2px 8px rgba(12,34,43,.04);
  --lift-2: 0 2px 4px rgba(12,34,43,.05), 0 16px 34px rgba(12,34,43,.09);
  --lift-3: 0 4px 10px rgba(12,34,43,.06), 0 28px 60px rgba(12,34,43,.14);

  --ease:   cubic-bezier(.22,.61,.36,1);
  --ease-o: cubic-bezier(.16,1,.3,1);
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%; scroll-behavior: smooth;
  scroll-padding-top: calc(var(--mast) + 16px);
}

body {
  margin: 0; background: var(--bone); color: var(--slate);
  font-family: var(--body); font-size: 1.0125rem; line-height: 1.62;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  font-family: var(--display); color: var(--ink-t);
  line-height: 1.06; letter-spacing: -.025em; margin: 0 0 var(--s3); font-weight: 600;
}
h1 { font-size: var(--t-3xl); font-weight: 700; letter-spacing: -.042em; }
h2 { font-size: var(--t-2xl); font-weight: 700; letter-spacing: -.034em; }
h3 { font-size: var(--t-lg); font-weight: 600; letter-spacing: -.022em; }
h4 { font-size: var(--t-base); font-weight: 600; letter-spacing: -.008em; }

p { margin: 0 0 var(--s4); }
p:last-child { margin-bottom: 0; }
a { color: var(--cyan-t); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--ink-t); }
img, svg { max-width: 100%; }
strong { font-weight: 600; color: var(--ink-t); }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--cyan); outline-offset: 3px; border-radius: var(--r);
}
::selection { background: var(--cyan); color: #fff; }

/* ---------------------------------------------------------- the lettering

   One treatment for every label, stamp, caption and column head on the site:
   mono, small, tracked, uppercase. It is what makes a price look transcribed
   rather than advertised, so it is set once and shared. */

.stamp, .kicker, .folio, .quicklink, .indexbtn, .pick__eyebrow, .pagehead__eyebrow,
.hero__eyebrow, .tally__head, .totebar__label, .sheet__eyebrow, .docket__kicker,
.index__meta, .pick__place, .schoolcard__place, .fold__hint, legend,
thead th, .foot h4, .switch button, .stay__chips button {
  font-family: var(--mono); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
}

/* The provenance stamp. Where a figure comes from, and when it was checked.
   Sage, because it reports a state rather than offering an action. */
.stamp {
  display: inline-flex; align-items: center; gap: .55rem;
  font-size: var(--t-2xs); color: var(--sage-d); white-space: nowrap;
}
.stamp::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--sage); flex: none;
}
.band--ink .stamp, .plate .stamp, .board .stamp { color: var(--sage); }

/* ------------------------------------------------------------- primitives */

.shell { width: 100%; max-width: var(--measure); margin-inline: auto; padding-inline: var(--bleed); }
.shell--narrow { max-width: 820px; }

.band { padding-block: clamp(var(--s7), 6.5vw, var(--s9)); position: relative; }
.band--slim { padding-block: clamp(var(--s6), 4vw, var(--s7)); }
.band--paper { background: var(--paper); }
.band--shell { background: var(--shell); }
.band--ink { background: var(--ink); color: var(--fog); }
.band--ink h2, .band--ink h3, .band--ink h4 { color: #fff; }
.band--edge { border-top: 1px solid var(--line); }

.stack > * + * { margin-top: var(--s6); }
.stack-sm > * + * { margin-top: var(--s4); }

.lede { font-size: var(--t-md); line-height: 1.58; max-width: 58ch; color: var(--slate); }
.tiny { font-size: var(--t-sm); color: var(--slate-2); line-height: 1.52; }
/* The age note under a date of birth is written before there is a date to
   read, and an empty paragraph still takes its margin. */
.tiny:empty { display: none; }
.band--ink .tiny { color: var(--fog-2); }

/* The section label. A rule runs out of it to the edge of the band, which is
   what says a new subject has started without setting a second heading. */
.kicker {
  display: flex; align-items: center; gap: var(--s4);
  font-size: var(--t-2xs); color: var(--cyan-t);
  margin-bottom: var(--s5);
}
.kicker::after { content: ""; height: 1px; flex: 1 1 auto; background: var(--line); }
.band--ink .kicker, .plate .kicker { color: var(--saffron-l); }
.band--ink .kicker::after, .plate .kicker::after { background: rgba(255,255,255,.16); }

.head-block { max-width: none; margin-bottom: var(--s6); }
.head-block h1, .head-block h2, .head-block h3 { max-width: 20ch; }
.head-block p, .head-block .lede { max-width: 60ch; }

/* Every figure on the site sits on the same axis. */
.figure b, .pick__price b, .schoolcard__rate b, .sheet__fact b, .vital b,
.tally__amt, .tally__sum b, .totebar__amt, .pick__amt, .docket__sum b,
.cell-n, .stay__num input, .board__fig {
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- buttons

   Squared, tracked, no glow. Cyan carries every action; everything else is
   an outline. The arrow moves, the button does not. */

/* A corner clipped off the trailing edge, the way a boarding pass is punched
   on the way through the gate. The rest of the site is squared, and a plain
   filled rectangle is what every site uses; one cut corner is enough to make
   the silhouette the site's own without inventing a second shape language.

   The cut is what decides everything else here. clip-path takes the border
   with it, so the two outlined variants become quiet fills instead -- an
   outline with one corner sliced off reads as a rendering fault, not a
   decision. It takes the focus ring too, so that is drawn inside the shape
   further down. */
.btn {
  --cut: 11px;
  display: inline-flex; align-items: center; justify-content: center; gap: .85rem;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .15em; text-transform: uppercase; line-height: 1;
  padding: 1.15rem 1.7rem; min-height: 50px;
  border: 0; border-radius: 0; cursor: pointer;
  text-decoration: none; position: relative;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%);
  transition: background .22s var(--ease), color .22s var(--ease);
}
/* The cut belongs on the edge you are travelling towards, and in Arabic that
   is the other one. */
[dir="rtl"] .btn {
  clip-path: polygon(0 0, 100% 0, 100% 100%, var(--cut) 100%, 0 calc(100% - var(--cut)));
}
.btn svg { width: 14px; height: 14px; flex: none; transition: transform .3s var(--ease-o); }
.btn:hover svg { transform: translateX(4px); }

/* The ring goes inside the clip, because an outline outside it is cut off at
   the corner and a box-shadow never paints at all. */
.btn:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px #fff, inset 0 0 0 4px var(--cyan);
}
.btn--onink:focus-visible, .btn--line:focus-visible {
  box-shadow: inset 0 0 0 2px var(--ink), inset 0 0 0 4px var(--cyan);
}

.btn--go { background: var(--cyan); color: #fff; }
.btn--go:hover { background: var(--cyan-d); color: #fff; }

/* Was an outline. A fill has to carry the whole shape now, so it is dark
   enough to separate from the paper it sits on -- shell was near enough to
   bone that the cut corner was the only thing saying a button was there.
   Filling with the ink on hover is the state change the border used to make. */
.btn--line { background: var(--line-2); color: var(--ink-t); }
.btn--line:hover { background: var(--ink-t); color: var(--paper); }

.btn--onink { background: #fff; color: var(--ink-t); }
.btn--onink:hover { background: var(--saffron); color: var(--ink-t); }

.btn--glass { background: rgba(255,255,255,.16); color: #fff; }
.btn--glass:hover { background: rgba(255,255,255,.3); color: #fff; }

.btn--wide { width: 100%; }
/* Butted together they used to share a border. They cannot now -- the cut
   would bite into its neighbour -- so they sit apart by the width of the cut
   and the gap reads as deliberate. */
.btn-set { display: flex; flex-wrap: wrap; gap: 7px; }
@media (max-width: 520px) {
  .btn-set { flex-direction: column; align-items: stretch; }
}

/* A text link that behaves like a control: the rule retracts on hover. */
.pull {
  position: relative; display: inline-flex; align-items: center; gap: .7rem;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .15em; text-transform: uppercase; color: var(--ink-t);
  text-decoration: none; padding-bottom: 8px;
}
.pull::after {
  content: ""; position: absolute; inset-inline-start: 0; inset-inline-end: 0; bottom: 0; height: 1.5px;
  background: var(--cyan); transform-origin: right;
  transition: transform .4s var(--ease-o);
}
.pull:hover { color: var(--cyan-t); text-decoration: none; }
.pull:hover::after { transform: scaleX(0); transform-origin: left; }
.pull svg { width: 14px; height: 14px; transition: transform .3s var(--ease-o); }
.pull:hover svg { transform: translateX(4px); }
.plate .pull, .band--ink .pull { color: #fff; }
.plate .pull::after, .band--ink .pull::after { background: var(--saffron); }
.plate .pull:hover, .band--ink .pull:hover { color: var(--saffron-l); }

/* ------------------------------------------------------------------- tags */

.tag {
  display: inline-flex; align-items: center; gap: .45rem;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .05em; padding: .34rem .6rem; border-radius: var(--r); white-space: nowrap;
}
.tag--live { background: var(--sage-w); color: var(--sage-d); }
.tag--live::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--sage); flex: none;
  box-shadow: 0 0 0 0 rgba(82,183,136,.6); animation: pulse 2.6s var(--ease) infinite;
}
@keyframes pulse {
  0%, 70%, 100% { box-shadow: 0 0 0 0 rgba(82,183,136,0); }
  20% { box-shadow: 0 0 0 5px rgba(82,183,136,.28); }
}
.tag--note  { background: var(--cyan-w); color: var(--cyan-d); }
.tag--wait  { background: var(--flag-wash); color: var(--flag); }
.tag--plain { background: var(--bone); color: var(--slate); border: 1px solid var(--line); }
.tag-row { display: flex; flex-wrap: wrap; gap: var(--s2); }

.skip {
  position: absolute; inset-inline-start: var(--bleed); top: 0; z-index: 90;
  transform: translateY(-140%);
  background: var(--ink); color: #fff; text-decoration: none;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .15em; text-transform: uppercase;
  padding: .9rem 1.2rem;
  transition: transform .25s var(--ease-o);
}
.skip:focus { transform: translateY(0); color: #fff; }

/* --------------------------------------------------------------- masthead

   A nameplate, a rule, and a folio saying which part of the publication you
   are in. The navigation proper lives in the index.

   The bar paints nothing itself; its ground is a separate plate that is only
   ever moved, never recoloured. A sticky bar that transitions its own
   background repaints its full width every frame, and over a parallaxing
   photograph that repaint is what read as a flicker.
   ======================================================================== */

.masthead { position: sticky; top: 0; z-index: 60; background: transparent; }

.masthead::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--abyss);
  box-shadow: 0 1px 0 rgba(255,255,255,.1);
  transform: translateY(-101%); opacity: 0;
  transition: transform .34s var(--ease-o), opacity .34s var(--ease-o);
}
.masthead[data-over="false"]::after { transform: none; opacity: 1; }
.masthead[data-index="open"]::after { transform: translateY(-101%); opacity: 0; }

.masthead__bar {
  max-width: none;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: clamp(var(--s4), 3vw, var(--s7));
  min-height: var(--mast);
}

/* ------------------------------------------------------------- nameplate

   The company logo, placed as artwork rather than drawn in the page. It
   arrives in four files cut from the one master by Capeway/scripts/build-logo.py,
   along two axes.

   By ground: the logo's loud half is near black, and near black on a
   photograph is a hole, so `ink` is the artwork as drawn, for the white bar of
   the upload page, and `paper` hands the white to that half and lets the grey
   one sit back, for the dark bar and the footer.

   By layer: `shell` is the grey C and the bracket, `land` is the green ridge
   and the blue water. Stacked in register they are the logo. Split, the water
   can move without the gate moving, which a single flat picture could never
   do -- and that is the whole hover.
   ======================================================================== */

.nameplate {
  display: inline-flex; align-items: center; gap: .8rem;
  color: var(--paper); text-decoration: none;
}
.nameplate:hover { color: var(--paper); }

.nameplate__mark, .creed__mark { position: relative; display: block; line-height: 0; }
.nameplate__mark img, .creed__mark img { display: block; height: 100%; width: auto; }
.mark__land { position: absolute; left: 0; top: 0; }
.nameplate__mark { height: 42px; flex: none; }

/* Hover: the landscape drifts out through the gate, and a light runs across
   the enclosure behind it, left to right, the way the bracket points.

   The light is the logo's own alpha used as a mask over a moving diagonal
   gradient: what actually sweeps is a plain box, and all that survives the
   mask is light where the shell is. */
.nameplate__mark::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0;
  -webkit-mask: url(../assets/img/logo-paper-shell.png) center / contain no-repeat;
          mask: url(../assets/img/logo-paper-shell.png) center / contain no-repeat;
  background: linear-gradient(104deg,
    transparent 43%, #fff 50%, transparent 57%) -40% 0 / 260% 100% no-repeat;
}
@keyframes mark-sheen {
    0% { opacity: 0; background-position: -40% 0; }
   14% { opacity: 1; }
   86% { opacity: 1; }
  100% { opacity: 0; background-position: 150% 0; }
}
.mark__land { transition: transform .6s var(--ease-o); }
.nameplate:hover .mark__land { transform: translateX(3px); }
.nameplate:hover .nameplate__mark::after { animation: mark-sheen .85s var(--ease); }

/* The upload page's nameplate is a span, not a link -- nothing to hover. */
.masthead--plain .nameplate:hover .mark__land { transform: none; }
.masthead--plain .nameplate:hover .nameplate__mark::after { animation: none; }

/* On a phone the bar also carries the language toggle, the quote link and the
   index button, and the logo is the only one of the four that can give ground.
   The footer keeps it at full size; there is nothing next to it down there. */
@media (max-width: 760px) {
  .masthead .nameplate { gap: .55rem; }
  .masthead .nameplate__mark { height: 32px; }
}
/* Below this the four of them will not fit: once the language toggle has its
   Arabic in it, the name runs straight into it. The name is what goes -- the
   logo is a whole nameplate on its own, which is more than the ridge that
   stood here before it could claim. */
@media (max-width: 480px) {
  .masthead .nameplate__type { display: none; }
  .masthead .nameplate__mark { height: 34px; }
}

.nameplate__type {
  font-family: var(--display); font-weight: 700; font-size: 1rem;
  letter-spacing: .2em; text-transform: uppercase; line-height: 1;
}

/* ----------------------------------------------------------------- folio */

.masthead__folio { display: flex; align-items: center; gap: var(--s4); min-width: 0; }
.masthead__folio::before, .masthead__folio::after {
  content: ""; height: 1px; flex: 1 1 auto; background: rgba(255,255,255,.2);
}

.folio {
  font-size: var(--t-2xs); color: rgba(255,255,255,.62);
  white-space: nowrap; flex: none;
}

@media (max-width: 760px) { .masthead__folio { display: none; } }

/* ------------------------------------------------------------------ acts */

.masthead__acts {
  grid-column: -2 / -1;
  display: flex; align-items: center; gap: clamp(var(--s4), 2.2vw, var(--s6));
}

.arabic {
  font-family: "IBM Plex Sans Arabic", var(--body); font-size: 13px; font-weight: 500;
  color: var(--saffron-l); white-space: nowrap; line-height: 1;
}
@media (max-width: 940px) { .masthead .arabic { display: none; } }

.quicklink {
  position: relative; font-size: var(--t-2xs); color: #fff; text-decoration: none;
  padding-bottom: 5px; white-space: nowrap;
}
.quicklink::after {
  content: ""; position: absolute; inset-inline-start: 0; inset-inline-end: 0; bottom: 0; height: 1.5px;
  background: var(--saffron); transform-origin: right; transition: transform .4s var(--ease-o);
}
.quicklink:hover { color: #fff; text-decoration: none; }
.quicklink:hover::after { transform: scaleX(0); transform-origin: left; }

@media (max-width: 620px) {
  .indexbtn [data-index-label] {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
}

/* The masthead is set to be read, not prodded: the language chip is 29px of
   lettering and the quote link is 23px of it. Both are right, and both are
   under a thumb on a phone.

   So the target is grown without the design moving. Each of these already
   establishes a containing block, and an invisible pseudo-element stretched
   past its edges takes the tap for it — the bar lays out exactly as before
   and the thing you actually hit is 44px tall. */
@media (pointer: coarse) {
  .masthead .quicklink::before,
  .masthead .langtoggle::before,
  .masthead .nameplate::before {
    content: ""; position: absolute;
    inset-block-start: 50%; block-size: 44px; translate: 0 -50%;
    inset-inline-start: -6px; inset-inline-end: -6px;
  }
  .masthead .langtoggle, .masthead .nameplate { position: relative; }
}

/* A word, not a bare hamburger — three destinations are not discoverable
   behind an icon alone. */
.indexbtn {
  display: inline-flex; align-items: center; gap: .7rem; background: none; border: 0;
  color: #fff; cursor: pointer; padding: .8rem 0; min-height: 44px;
  font-size: var(--t-2xs);
}
/* Once the word is gone the button is as wide as the three rules it draws,
   which is 22px — half a thumb. The bar it belongs to has no room to give,
   so the target grows outwards instead: padding here, and a pulled-in
   negative margin so the row is laid out as though nothing changed. */
@media (max-width: 620px) {
  .indexbtn { padding-inline: 11px; margin-inline-end: -11px; }
}
.indexbtn i { position: relative; display: block; width: 22px; height: 8px; }
.indexbtn i::before, .indexbtn i::after {
  content: ""; position: absolute; inset-inline-start: 0; inset-inline-end: 0; height: 1.5px; background: currentColor;
  transition: transform .4s var(--ease-o), width .4s var(--ease-o);
}
.indexbtn i::before { top: 0; }
.indexbtn i::after { bottom: 0; width: 60%; }
.indexbtn:hover i::after { width: 100%; }
.indexbtn[aria-expanded="true"] i::before { transform: translateY(3.25px) rotate(45deg); }
.indexbtn[aria-expanded="true"] i::after { width: 100%; transform: translateY(-3.25px) rotate(-45deg); }

/* ================================================================= INDEX

   Full-bleed. The left plate answers whichever row you point at with a
   photograph; the right column is the index itself, set at reading-out-loud
   size with a real figure against each line.
   ======================================================================== */

.index {
  position: fixed; inset: 0; z-index: 55;
  display: grid; grid-template-columns: minmax(0, 44%) minmax(0, 56%);
  background: var(--abyss);
  visibility: hidden; opacity: 0;
  transition: opacity .4s var(--ease), visibility 0s linear .4s;
}
.index.is-open { visibility: visible; opacity: 1; transition: opacity .4s var(--ease); }

.index__plate { position: relative; overflow: hidden; background: var(--ink-2); }
.index__plate img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transform: scale(1.04);
  transition: opacity .7s var(--ease), transform 1.2s var(--ease-o);
}
.index__plate img.is-shown { opacity: 1; transform: none; }
.index__plate::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(8,22,29,.7) 0%, rgba(8,22,29,.12) 16%, transparent 26%),
    linear-gradient(90deg, rgba(8,22,29,.3) 0%, transparent 42%, rgba(8,22,29,.62) 100%);
}
@media (max-width: 900px) {
  .index { grid-template-columns: 1fr; }
  .index__plate { display: none; }
}

.index__side {
  display: flex; flex-direction: column;
  padding: calc(var(--mast) + clamp(var(--s5), 5vh, var(--s7))) clamp(var(--s5), 5vw, var(--s8)) clamp(var(--s5), 4vh, var(--s7));
  overflow-y: auto;
}

.index__rows {
  border-top: 1px solid rgba(255,255,255,.12);
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center;
}

.index__row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline;
  gap: var(--s4); padding: clamp(var(--s4), 3.4vh, var(--s6)) 0;
  border-bottom: 1px solid rgba(255,255,255,.12);
  text-decoration: none; color: inherit;
  opacity: 0; transform: translateY(16px);
  transition: opacity .5s var(--ease-o), transform .5s var(--ease-o);
  transition-delay: var(--in, 0ms);
  position: relative; outline: none;
}
.index.is-open .index__row { opacity: 1; transform: none; }
.index__row:hover { text-decoration: none; }
.index__row::before {
  content: ""; position: absolute; inset-inline-start: calc(var(--s4) * -1); top: 18%; bottom: 18%;
  width: 2px; background: var(--saffron); transform: scaleY(0); transform-origin: center;
  transition: transform .3s var(--ease-o);
}
.index__row:focus-visible::before { transform: scaleY(1); }

.index__name {
  font-family: var(--display); font-weight: 700; color: rgba(255,255,255,.72);
  font-size: clamp(1.8rem, 1rem + 2.8vw, 3.6rem); line-height: 1.02; letter-spacing: -.04em;
  transition: color .3s var(--ease), transform .5s var(--ease-o);
  display: inline-block;
}
.index__row:hover .index__name,
.index__row:focus-visible .index__name { color: #fff; transform: translateX(10px); }
.index__row[aria-current="page"] .index__name { color: #fff; }

/* A real figure, not an ornamental 01 / 02 / 03: these are not a sequence,
   so the slot carries something worth knowing instead. */
.index__meta {
  font-size: var(--t-2xs); letter-spacing: .18em; color: var(--fog-2);
  text-align: end; white-space: nowrap;
  transition: color .3s var(--ease);
}
.index__row:hover .index__meta { color: var(--saffron-l); }

/* The five schools, folded under their row, each behind its own colour. */
.index__sub { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); padding: var(--s4) 0 var(--s5); }
.index__sublink {
  font-family: var(--body); font-size: var(--t-sm); font-weight: 500;
  color: rgba(255,255,255,.56); text-decoration: none;
  display: inline-flex; align-items: baseline; gap: .55rem;
  transition: color .25s var(--ease);
}
.index__sublink::before {
  content: ""; width: 7px; height: 7px; flex: none; border-radius: 50%;
  background: var(--sc, var(--fog-2)); transform: translateY(-1px);
}
.index__sub > .index__sublink:nth-child(1) { --sc: var(--sc-1); }
.index__sub > .index__sublink:nth-child(2) { --sc: var(--sc-2); }
.index__sub > .index__sublink:nth-child(3) { --sc: var(--sc-3); }
.index__sub > .index__sublink:nth-child(4) { --sc: var(--sc-4); }
.index__sub > .index__sublink:nth-child(5) { --sc: var(--sc-5); }
.index__sublink:hover { color: #fff; text-decoration: none; }
.index__sublink b {
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500; color: var(--saffron-l);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}

.index__colophon {
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s6); align-items: baseline;
  padding-top: var(--s5); margin-top: auto;
  border-top: 1px solid rgba(255,255,255,.12);
  font-size: var(--t-xs); color: var(--fog-2);
  opacity: 0; transition: opacity .5s var(--ease) .34s;
}
.index.is-open .index__colophon { opacity: 1; }
.index__colophon a { color: rgba(255,255,255,.72); text-decoration: none; }
.index__colophon a:hover { color: #fff; }
.index__colophon .arabic { display: inline; }

@media (max-width: 640px) {
  .index__row { grid-template-columns: 1fr; gap: var(--s2); padding: var(--s5) 0; }
  .index__meta { text-align: start; white-space: normal; }
  .index__row::before { inset-inline-start: calc(var(--s3) * -1); }
}

/* ============================================================== THE COAST

   The photograph is the design, and the word stands in the middle of it,
   half in the water until the reader raises it. Everything else on this
   screen is set under the word and kept quiet.
   ======================================================================== */

.hero {
  position: relative; isolation: isolate;
  /* The whole screen and no more. svh rather than vh or dvh: on a phone the
     browser's own toolbars are counted in, so nothing at the foot of the hero
     ends up underneath them, and the height does not lurch as they retract.
     There is no pixel ceiling any more — on a tall desk monitor the
     photograph is supposed to fill it. */
  min-height: 100vh;
  min-height: 100svh;
  margin-top: calc(var(--mast) * -1);
  padding-top: var(--mast);
  display: flex; flex-direction: column; justify-content: center;
  overflow: hidden; background: var(--abyss);
}

/* Overscanned so the parallax shift never exposes a gap.

   The photograph is then lifted inside that overscan until the bay's horizon
   meets the middle of the screen, which is where the word stands: the line
   the letters are cut off at and the line of the water are the same line, at
   any height of screen. The lift is a share of the media box, not a pixel
   count, so it holds as the box grows. The parallax rate is low enough that
   the drift can never eat the 8% of slack left under it. */
.hero__media { position: absolute; inset: -30% 0; z-index: 0; }
.hero__media img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%;
  transform: translateY(var(--swell, -13.4%));
  animation: heroIn 1.6s var(--ease-o) both;
}
@keyframes heroIn { from { transform: translateY(var(--swell, -13.4%)) scale(1.07); opacity: 0 }
                    to   { transform: translateY(var(--swell, -13.4%)); opacity: 1 } }

/* Two scrims. One holds the masthead. The other is a floor the type stands
   on: the water under the headline is the brightest part of the photograph,
   and white type needs a real ground there, not a wash. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(8,22,29,.6) 0%, rgba(8,22,29,.14) 20%, transparent 38%),
    linear-gradient(0deg, rgba(8,22,29,.9) 0%, rgba(8,22,29,.68) 24%, rgba(8,22,29,.46) 46%, rgba(8,22,29,.3) 62%, rgba(8,22,29,.06) 84%, transparent 94%),
    radial-gradient(76% 44% at 50% 52%, rgba(8,22,29,.46), transparent 72%);
}

.hero__inner {
  position: relative; z-index: 3; width: 100%;
  padding-block: clamp(var(--s6), 7vh, var(--s8));
  display: block; text-align: center;
}

/* The hero is the one full-bleed thing on the page, and the masthead over it
   runs edge to edge on the same gutter. Held to the reading column the type
   stood a long way in from both, marooned near the middle of a wide screen
   while the logo above it hugged the corner. On a screen wide enough to show
   the difference the headline takes the masthead's gutter and stands under
   the logo; narrower than that there is no difference to take. */
@media (min-width: 1180px) {
  .hero__inner { max-width: none; }
}

.hero__say { min-width: 0; }

.hero__eyebrow {
  display: block; color: rgba(255,255,255,.74);
  font-size: var(--t-2xs); letter-spacing: .26em;
  margin-bottom: clamp(var(--s4), 3vh, var(--s5));
  animation: riseIn .9s var(--ease-o) .35s both;
}

.hero__word {
  margin: 0 auto; color: #fff; font-family: var(--display);
  font-weight: 700; letter-spacing: -.05em;
  font-size: clamp(3.2rem, 21vw, 12rem);
  line-height: .88;
  white-space: nowrap;
  animation: riseIn 1.1s var(--ease-o) .45s both;
}

.hero__sub {
  margin: clamp(var(--s5), 4vh, var(--s6)) auto 0; max-width: 46ch;
  color: rgba(255,255,255,.9); font-size: var(--t-md); line-height: 1.55;
  animation: riseIn 1s var(--ease-o) .62s both;
}

/* The motto's Arabic half, on its own line under the English. */
.hero__sub span[dir="ltr"] { display: block; }

.hero__sub span[lang="ar"] {
  display: block; margin-top: var(--s2); text-align: center;
  font-family: "IBM Plex Sans Arabic", var(--body); font-weight: 500;
  font-size: .95em; line-height: 1.9; letter-spacing: 0; color: rgba(255,255,255,.78);
}

.hero__acts {
  justify-content: center;
  margin-top: clamp(var(--s5), 3.5vh, var(--s6));
  animation: riseIn 1s var(--ease-o) .74s both;
}

@keyframes riseIn { from { opacity: 0; transform: translateY(22px) } to { opacity: 1; transform: none } }

/* Scroll-linked exit: the hero lets go of the type before the page covers it,
   which is what stops the join reading as a cut. --exit is set once a frame
   by initHeroMotion(). */
.hero__inner {
  opacity: calc(1 - var(--exit, 0) * 1.15);
  transform: translateY(calc(var(--exit, 0) * -46px));
}

/* No scroll cue. The primary action already carries a down arrow to the same
   place, and a second prompt on the same screen is one accessory too many. */

/* On a phone the photograph was losing to the furniture standing on it: an
   eyebrow, a headline, a rule, a paragraph, two full-width buttons and a
   bordered panel, stacked down a screen barely wider than the panel. Every
   one of them was right on a desk and one too many here.

   So the hero keeps what it cannot do without — the word, the sentence, the
   price and the two ways on — and gives up the decoration around them. The
   eyebrow says what the headline says. The rule is a flourish. The panel's
   border and fill were drawing a box on a photograph; the figure inside it
   can stand on the scrim like the rest of the type. */
@media (max-width: 640px) {
  .hero__eyebrow { display: none; }

  .hero__word { letter-spacing: -.045em; }
  .hero__sub {
    margin-top: var(--s6);
    font-size: var(--t-sm); line-height: 1.5; max-width: 32ch;
    color: rgba(255,255,255,.82);
  }

  /* One button and, under it, a link. Side by side would have fitted at 375px
     with nothing to spare and wrapped on the first long translation, and only
     one of these is the thing to do next anyway. */
  .hero__acts {
    flex-direction: column; align-items: center; gap: var(--s4);
    margin-top: var(--s5);
  }
  .hero__acts > .btn + .btn { margin-top: 0; margin-inline-start: 0; }
  .hero__acts .btn--go { padding: .95rem 1.25rem; min-height: 46px; }
  .hero__acts .btn--glass {
    border: 0; background: none; padding: 0; min-height: 0;
    clip-path: none;
    text-decoration: underline; text-underline-offset: 6px;
    text-decoration-color: rgba(255,255,255,.4);
  }
  .hero__acts .btn--glass:focus-visible {
    box-shadow: none; outline: 2px solid var(--cyan-l); outline-offset: 4px;
  }
  .hero__acts .btn--glass:hover { background: none; text-decoration-color: #fff; }

  /* Less type to hold up, so the floor under it can lift and give the water
     and the ridge line back. */
  .hero::before {
    background:
      linear-gradient(180deg, rgba(8,22,29,.55) 0%, rgba(8,22,29,.1) 22%, transparent 40%),
      linear-gradient(0deg, rgba(8,22,29,.84) 0%, rgba(8,22,29,.6) 22%, rgba(8,22,29,.24) 48%, transparent 74%);
  }
}

/* The page below rides up over the photograph. */
.stage { position: relative; z-index: 5; background: var(--bone); }

/* ================================================================ THE BOARD

   The signature. Four figures the site is willing to be held to, set on a
   dark plate with the date they were checked stamped on it. The last one is
   the headline number, so it is the one in saffron.
   ======================================================================== */

.figures {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  background: var(--ink); color: var(--fog);
  border-top: 2px solid var(--saffron);
  padding: clamp(var(--s5), 3vw, var(--s6));
  gap: clamp(var(--s4), 2vw, var(--s6));
}
.figure { position: relative; padding-inline-end: clamp(var(--s4), 2vw, var(--s6)); }
.figure + .figure { border-inline-start: 1px solid rgba(255,255,255,.12); padding-inline-start: clamp(var(--s4), 2vw, var(--s6)); }
.figure b {
  display: block; font-family: var(--display); font-size: var(--t-fig); font-weight: 700;
  color: #fff; line-height: .94; letter-spacing: -.05em; margin-bottom: var(--s3);
}
.figure:last-child b { color: var(--saffron-l); }
.figure span {
  display: block; font-size: var(--t-xs); color: var(--fog-2); line-height: 1.45; max-width: 22ch;
}

.figures__note {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s5);
  justify-content: space-between;
  margin-top: var(--s4); color: var(--slate-2);
}

@media (max-width: 860px) {
  .figures { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--s6); }
  .figure:nth-child(odd) { border-inline-start: 0; padding-inline-start: 0; }
  .figure:nth-child(n+3) { border-top: 1px solid rgba(255,255,255,.12); padding-top: var(--s5); }
}
@media (max-width: 480px) {
  .figures { grid-template-columns: 1fr; }
  .figure + .figure { border-inline-start: 0; padding-inline-start: 0; border-top: 1px solid rgba(255,255,255,.12); padding-top: var(--s5); }
}

/* ============================================================ SPLIT CARDS

   A photograph overhanging a white panel, with the school's own colour on
   the spine. Closed it gives the name and the rate; open it gives up the
   detail and the way in.
   ======================================================================== */

.pick__grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(26px, 3vw, 44px) clamp(20px, 2.4vw, 34px);
  align-items: stretch;
}
/* Cards are stretched to the tallest in the row, and a name that wraps to two
   lines would otherwise take that height out of the photograph. Pin the shot
   to its own aspect and let the panel absorb the slack, so the row of images
   keeps one bottom edge. */
#school-cards > .pick__card:not(.pick__card--wide) { grid-template-rows: auto 1fr; }

/* The odd one out gets the width its difference deserves. */
#school-cards > .pick__card--wide {
  grid-column: 1 / -1;
  grid-template-columns: minmax(0, 44%) minmax(0, 1fr);
}

.pick__card {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr);
  align-items: stretch; text-decoration: none; color: inherit; cursor: pointer;
  --sc: var(--cyan);
}
#school-cards > .pick__card:nth-child(1) { --sc: var(--sc-1); }
#school-cards > .pick__card:nth-child(2) { --sc: var(--sc-2); }
#school-cards > .pick__card:nth-child(3) { --sc: var(--sc-3); }
#school-cards > .pick__card:nth-child(4) { --sc: var(--sc-4); }
#school-cards > .pick__card:nth-child(5) { --sc: var(--sc-5); }
.pick__card:hover, .pick__card:focus-visible { text-decoration: none; outline: none; }
.pick__card:focus-visible .pick__panel { outline: 2px solid var(--cyan); outline-offset: 3px; }

.pick__shot {
  position: relative; z-index: 2; overflow: hidden;
  aspect-ratio: 4 / 3; background: var(--shell);
}
#school-cards > .pick__card--wide .pick__shot { aspect-ratio: 16 / 9; }
.pick__shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 1s var(--ease-o);
}
.pick__card:hover .pick__shot img,
.pick__card:focus-visible .pick__shot img { transform: scale(1.05); }

.pick__panel {
  background: var(--paper);
  border-top: 3px solid var(--sc, var(--cyan));
  padding: var(--s5);
  display: flex; flex-direction: column;
  box-shadow: var(--lift-1);
  transition: box-shadow .5s var(--ease-o), transform .4s var(--ease-o);
}
.pick__card:hover .pick__panel { transform: translateY(-3px); }
#school-cards > .pick__card--wide .pick__panel { justify-content: center; }
.pick__card:hover .pick__panel,
.pick__card:focus-visible .pick__panel { box-shadow: var(--lift-3); }
/* The way in lines up across the row whatever length the name above it ran
   to; the rate stays under the name, which is where it is read. */
.pick__go { margin-top: auto; padding-top: var(--s5); }
#school-cards > .pick__card--wide .pick__go { margin-top: var(--s5); padding-top: 0; }

.pick__eyebrow { font-size: var(--t-2xs); color: var(--sc, var(--cyan-t)); margin-bottom: 10px; }
.pick__name { font-size: clamp(1.35rem, .95rem + 1.1vw, 1.85rem); margin: 0; letter-spacing: -.035em; line-height: 1.1; }
.pick__rule { width: 44px; height: 2px; background: var(--sc, var(--cyan)); margin: 14px 0 0; transition: width .5s var(--ease-o); }
.pick__card:hover .pick__rule, .pick__card:focus-visible .pick__rule { width: 76px; }

/* Height animates through the 0fr → 1fr grid track, so the copy keeps its
   natural height instead of being pinned to a guessed max-height. */
.pick__extra, .pick__extra > div { display: contents; }
.pick__extra p {
  margin: 14px 0 0; font-size: var(--t-sm); color: var(--slate); line-height: 1.58;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}

.pick__go {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 16px;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-t);
}
.pick__go svg { width: 13px; height: 13px; transition: transform .35s var(--ease-o); }
.pick__card:hover .pick__go svg { transform: translateX(4px); }

.pick__price {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; margin-top: 16px;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--slate-2);
}
.pick__price b {
  font-family: var(--display); font-size: 1.7rem; font-weight: 700;
  color: var(--ink-t); letter-spacing: -.04em; text-transform: none; line-height: 1;
}

@media (max-width: 1000px) {
  .pick__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #school-cards > .pick__card--wide { grid-template-columns: minmax(0, 40%) minmax(0, 1fr); }
}
@media (max-width: 700px) {
  .pick__grid, #school-cards > .pick__card--wide { grid-template-columns: 1fr; }
  #school-cards > .pick__card--wide .pick__shot { aspect-ratio: 16 / 10; min-height: 0; }
  .pick__shot { aspect-ratio: 16 / 10; }
  .pick__panel { padding: var(--s4); }
}

/* ================================================================= SHEET
   Quick look. Photograph left, facts right, the way to the full price list
   at the foot of the panel.
   ======================================================================== */

.sheet {
  position: fixed; inset: 0; z-index: 90;
  display: grid; grid-template-columns: minmax(0, 54%) minmax(0, 46%);
  visibility: hidden; pointer-events: none;
}
.sheet.is-open { visibility: visible; pointer-events: auto; }

.sheet__scrim {
  position: absolute; inset: 0; z-index: 0; background: rgba(8,22,29,.6);
  opacity: 0; transition: opacity .5s var(--ease);
}
.sheet.is-open .sheet__scrim { opacity: 1; }

.sheet__shot {
  position: relative; z-index: 1; overflow: hidden; background: var(--ink-2);
  transform: translateX(-101%); transition: transform .62s var(--ease-o);
}
.sheet.is-open .sheet__shot { transform: none; }
.sheet__shot img { width: 100%; height: 100%; object-fit: cover; }
.sheet__shot::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8,22,29,.5) 0%, transparent 32%);
}

.sheet__panel {
  position: relative; z-index: 1; background: var(--paper); overflow-y: auto;
  padding: clamp(var(--s6), 5vw, var(--s8)) clamp(var(--s5), 4vw, var(--s7));
  transform: translateX(101%); transition: transform .62s var(--ease-o);
}
.sheet.is-open .sheet__panel { transform: none; }

.sheet__close {
  position: absolute; z-index: 3; top: 22px; inset-inline-start: 22px;
  width: 44px; height: 44px; border-radius: 0; border: 1px solid rgba(255,255,255,.34);
  background: rgba(8,22,29,.4); color: #fff; cursor: pointer;
  display: grid; place-items: center;
  transition: background .25s var(--ease), transform .25s var(--ease);
}
.sheet__close:hover { background: rgba(8,22,29,.72); transform: translateX(-3px); }
.sheet__close svg { width: 18px; height: 18px; }

.sheet__eyebrow { font-size: var(--t-2xs); color: var(--slate-2); margin-bottom: 12px; }
.sheet__panel h2 { font-size: var(--t-2xl); margin-bottom: var(--s4); }
.sheet__panel h2 + .pick__rule { margin-top: 0; margin-bottom: var(--s5); background: var(--cyan); }

.sheet__facts {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 1px; margin: var(--s5) 0; background: var(--line);
  border: 1px solid var(--line);
}
.sheet__fact { background: var(--paper); padding: var(--s4); }
.sheet__fact b {
  display: block; font-family: var(--display); font-size: var(--t-lg); font-weight: 700;
  color: var(--ink-t); letter-spacing: -.035em; line-height: 1.15; margin-bottom: 2px;
}
.sheet__fact span { font-family: var(--mono); font-size: var(--t-2xs); letter-spacing: .06em; color: var(--slate-2); }

.sheet__list { list-style: none; margin: 0 0 var(--s5); padding: 0; }
.sheet__list li {
  position: relative; padding-inline-start: 22px; margin-bottom: 10px;
  font-size: var(--t-sm); color: var(--slate); line-height: 1.55;
}
.sheet__list li::before {
  content: ""; position: absolute; inset-inline-start: 0; top: .6em;
  width: 10px; height: 1.5px; background: var(--saffron);
}

@media (max-width: 900px) {
  .sheet { grid-template-columns: 1fr; grid-template-rows: 30vh 1fr; }
  .sheet__shot { transform: translateY(-101%); }
  .sheet.is-open .sheet__shot { transform: none; }
}

/* ========================================================== PHOTO BANDS */

.plate { position: relative; isolation: isolate; overflow: hidden; background: var(--ink); }
.plate__media { position: absolute; inset: -24% 0; z-index: 0; }
.plate__media img { width: 100%; height: 100%; object-fit: cover; }
.plate::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, rgba(8,22,29,.95) 0%, rgba(8,22,29,.9) 44%, rgba(8,22,29,.84) 100%);
}
.plate--mid::before {
  background:
    linear-gradient(180deg, rgba(8,22,29,.78) 0%, rgba(8,22,29,.72) 50%, rgba(8,22,29,.88) 100%),
    radial-gradient(ellipse at 50% 42%, rgba(8,22,29,.3) 0%, rgba(8,22,29,.72) 72%);
}
.plate .shell { position: relative; z-index: 2; }
.plate h2, .plate h3 { color: #fff; }
.plate p { color: rgba(255,255,255,.84); }
.plate .tiny { color: var(--fog-2); }

/* ------------------------------------------------------------------ cards */

.cols { display: grid; gap: var(--s5); }
.cols--3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.cols--2 { grid-template-columns: repeat(2, minmax(0,1fr)); gap: clamp(var(--s5), 4vw, var(--s8)); }
@media (max-width: 980px) { .cols--3 { grid-template-columns: 1fr; } }
@media (max-width: 800px) { .cols--2 { grid-template-columns: 1fr; } }

.panel {
  display: flex; flex-direction: column; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-lg); padding: var(--s5);
}
/* Flat panels are a set of statements, not a set of boxes: a rule over each
   one is enough to separate them. */
.panel--flat {
  background: transparent; border: 0; border-top: 1px solid var(--ink-t);
  border-radius: 0; padding: var(--s4) var(--s5) 0 0;
}
.panel--flat h3 { margin-bottom: var(--s3); }
.band--ink .panel--flat, .plate .panel--flat { border-top-color: var(--saffron); }
.panel--tap { text-decoration: none; transition: border-color .25s var(--ease), box-shadow .25s var(--ease), transform .25s var(--ease); }
.panel--tap:hover { border-color: var(--line-2); box-shadow: var(--lift-3); transform: translateY(-4px); }

/* School card — the price is the product, so it is set as display type. */
.schoolcard { position: relative; overflow: hidden; padding-top: var(--s6); --sc: var(--cyan); }
#school-cards > .schoolcard:nth-child(1), .cols > .schoolcard:nth-child(1) { --sc: var(--sc-1); }
#school-cards > .schoolcard:nth-child(2), .cols > .schoolcard:nth-child(2) { --sc: var(--sc-2); }
#school-cards > .schoolcard:nth-child(3), .cols > .schoolcard:nth-child(3) { --sc: var(--sc-3); }
#school-cards > .schoolcard:nth-child(4), .cols > .schoolcard:nth-child(4) { --sc: var(--sc-4); }
#school-cards > .schoolcard:nth-child(5), .cols > .schoolcard:nth-child(5) { --sc: var(--sc-5); }
.schoolcard::before {
  content: ""; position: absolute; inset-inline: 0; top: 0; height: 3px;
  background: var(--sc);
  transform: scaleX(1); transform-origin: left;
}

.schoolcard__place {
  display: block; font-size: var(--t-2xs); color: var(--sc, var(--cyan-t)); margin-bottom: var(--s2);
}
.schoolcard__name { margin: 0 0 var(--s3); font-size: var(--t-xl); letter-spacing: -.035em; }

.schoolcard__rate {
  display: flex; align-items: baseline; gap: var(--s2);
  padding: var(--s3) 0 var(--s4); margin-bottom: var(--s4);
  border-bottom: 1px solid var(--line);
}
.schoolcard__rate b {
  font-family: var(--display); font-size: 2.6rem; font-weight: 700; color: var(--ink-t);
  line-height: .9; letter-spacing: -.05em;
}
.schoolcard__rate span { font-family: var(--mono); font-size: var(--t-xs); color: var(--slate-2); }

.schoolcard__points { list-style: none; margin: 0 0 var(--s5); padding: 0; }
.schoolcard__points li { position: relative; padding-inline-start: 1.4rem; margin-bottom: var(--s2); font-size: var(--t-sm); line-height: 1.5; }
.schoolcard__points li::before {
  content: ""; position: absolute; inset-inline-start: 0; top: .62em; width: 8px; height: 1.5px;
  background: var(--sc, var(--cyan));
}
.schoolcard__foot {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-top: auto;
  padding-top: var(--s4); border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: var(--t-2xs); letter-spacing: .06em; color: var(--slate-2);
}
.schoolcard__go {
  display: inline-flex; align-items: center; gap: .5rem; margin-top: var(--s4);
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--cyan-t);
}
.schoolcard__go svg { width: 14px; height: 14px; transition: transform .25s var(--ease-o); }
.panel--tap:hover .schoolcard__go svg { transform: translateX(4px); }

/* ---------------------------------------------------------------- journey

   These really are a sequence, so they really are numbered. The figure is
   set large in mono against a hairline, which is cheaper and more legible
   than a badge and keeps the eye on the copy.
   ======================================================================== */

.journey {
  counter-reset: leg;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(var(--s5), 3vw, var(--s7));
  row-gap: clamp(var(--s6), 4vw, var(--s8));
}
/* Where each step carries a second paragraph of detail, two across. */
.journey--deep { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.leg { display: block; padding-top: var(--s4); border-top: 1px solid var(--line-2); }
.plate .leg, .band--ink .leg { border-top-color: rgba(255,255,255,.24); }

.leg__no {
  counter-increment: leg; display: block; margin-bottom: var(--s4);
  font-family: var(--mono); font-weight: 500; font-size: 1.375rem;
  color: var(--saffron-t); line-height: 1; letter-spacing: .02em;
}
.plate .leg__no, .band--ink .leg__no { color: var(--saffron-l); }
.leg__no::before { content: counter(leg, decimal-leading-zero); }
.leg h3 { margin-bottom: var(--s2); font-size: var(--t-md); }
.leg p { font-size: var(--t-sm); margin: 0; }
.leg p + p { margin-top: var(--s3); }

@media (max-width: 980px) { .journey { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) {
  .journey, .journey--deep { grid-template-columns: 1fr; row-gap: var(--s6); }
}

/* ================================ the argument ==========================
   A claim column that scrolls against a heading that does not, so the point
   being argued and the evidence for it stay on screen together.
   ======================================================================== */

.argue {
  display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: clamp(var(--s6), 5vw, var(--s8));
  align-items: start;
}
@media (max-width: 900px) { .argue { grid-template-columns: 1fr; gap: var(--s6); } }

@media (min-width: 901px) {
  .argue__head { position: sticky; top: calc(var(--mast) + var(--s6)); }
}
.argue__head h2 { margin-bottom: var(--s5); }
.argue__head .tiny { max-width: 38ch; margin-top: var(--s6); }

.argue__list { display: grid; gap: 0; }

.claim { padding: var(--s7) 0; border-top: 1px solid var(--line); }
.claim:first-child { padding-top: 0; border-top: 0; }
.claim:last-child { padding-bottom: 0; }
.claim__tag {
  display: block; margin-bottom: var(--s3);
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--saffron-t);
}
.claim h3 {
  font-size: clamp(1.35rem, 1rem + 1.1vw, 1.75rem);
  letter-spacing: -.032em; margin-bottom: var(--s3); max-width: 22ch;
}
.claim p { margin: 0; font-size: var(--t-base); max-width: 56ch; color: var(--slate); }

/* The heading stands alone above the cards, so it takes the full measure
   and has nothing left to scroll against. */
.argue--solo { grid-template-columns: 1fr; }
.argue--solo .argue__head { position: static; }
.argue--solo .argue__head h2 { max-width: 20ch; }
.argue--solo .argue__head .lede { max-width: 58ch; }
.argue--solo .argue__head .tiny { max-width: 58ch; }

/* Below the cards the three claims read across, not down: one rule
   between them instead of one above each. */
.claim-row { margin-top: clamp(var(--s7), 5vw, var(--s8)); }
@media (min-width: 901px) {
  .claim-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(var(--s6), 4vw, var(--s7));
    border-top: 1px solid var(--line); padding-top: var(--s7);
  }
  .claim-row .claim { padding: 0; border-top: 0; }
  .claim-row .claim h3 { font-size: clamp(1.2rem, .95rem + .5vw, 1.45rem); max-width: none; }
  .claim-row .claim p { font-size: var(--t-sm); }
}


/* ================================== the creed ===========================
   The slogan, and the one set piece on the About page.

   The logo is a landscape held in a gate, and the gate comes to a point on
   its right. Here that point is given somewhere to go: the mark sits at the
   left, where the English starts, and a single line leaves its tip and runs
   to the right edge, where the Arabic ends. So the block is read the way the
   sentence is meant -- the route we took on the left, the route you take on
   the right, one line joining them.

   The first sentence is past tense and set dimmer than the second, which is
   the promise. Nothing here is a link; the saffron is lettering, as always.
   ======================================================================== */

.creed { max-width: min(880px, 100%); }
.creed__en {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.75rem, .95rem + 3.4vw, 3.5rem);
  letter-spacing: -.042em; line-height: 1.04;
  color: rgba(255,255,255,.5); margin: 0;
}
.creed__en b { display: block; font-weight: 600; color: #fff; }

/* The logo, enlarged, with the rule leaving it at the point of the bracket
   and running to the right edge -- so the line under the Arabic is the line
   coming out of the gate, and the two are one gesture rather than a mark with
   a border under it. 0.49 is where the point sits in the artwork, measured off
   the file; the rule is placed against the mark's own height so it stays on
   the point at every width. */
.creed__horizon {
  --mark-h: clamp(64px, 7.2vw, 96px);
  display: flex; align-items: flex-start;
  margin: clamp(var(--s5), 3.5vw, var(--s7)) 0 0;
}
.creed__mark { height: var(--mark-h); flex: none; }
/* A border rather than a 1.5px box with a background: a box that height is
   painted across two device pixels at partial coverage and the saffron washes
   out to grey. */
.creed__rule {
  flex: 1 1 auto; height: 0; border-top: 1.5px solid var(--saffron);
  margin-top: calc(var(--mark-h) * .49);
}

.creed__ar {
  font-family: "IBM Plex Sans Arabic", var(--body); font-weight: 500;
  font-size: clamp(1rem, .78rem + .85vw, 1.45rem);
  line-height: 1.95; letter-spacing: 0; color: var(--saffron-l);
  margin: var(--s5) 0 0; text-align: right;
}


/* --------------------------------------------------------------- the story

   A reading page, not a board. One sentence at display size opens it, because
   that sentence is the whole claim the rest of the site rests on.
   ======================================================================== */

.origin__lead {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.25rem, .95rem + 1.35vw, 1.9rem);
  letter-spacing: -.03em; line-height: 1.26; color: var(--ink-t);
  margin: 0 0 var(--s5); text-wrap: balance;
}
.origin p { max-width: 62ch; }

/* The slogan again, at colophon size, under the footer nameplate. */
.foot__slogan {
  margin: 0 0 var(--s4); max-width: 32ch;
  font-family: var(--display); font-weight: 500; font-size: var(--t-base);
  letter-spacing: -.022em; line-height: 1.3; color: #fff;
}
.foot__slogan span[lang="ar"] {
  display: block; margin-top: var(--s2); text-align: left;
  font-family: "IBM Plex Sans Arabic", var(--body); font-weight: 500;
  font-size: .9rem; line-height: 1.9; letter-spacing: 0; color: var(--saffron-l);
}

/* ----------------------------------------------------------------- tables

   No box around it. A table is a set of columns, and hairlines plus a hard
   rule under the head is all it takes to say so. Figures are mono, so they
   line up whatever the row.
   ======================================================================== */

.tablewrap {
  overflow-x: auto; background: transparent;
  -webkit-overflow-scrolling: touch;
  scroll-padding-inline: var(--s4);
}
/* The mask is painted on the scroller's own box, not on what is inside it,
   so the fade stays at the edge while the content runs under it. 56px is about
   a column of figures going soft — enough to read as "there is more" and not
   so much that a number is lost in it.

   Keyed on the attribute alone, so the photograph rails get it too. */
[data-fade="end"]   { mask-image: linear-gradient(to right, #000 calc(100% - 56px), transparent); }
[data-fade="start"] { mask-image: linear-gradient(to left,  #000 calc(100% - 56px), transparent); }
[data-fade="both"]  { mask-image: linear-gradient(to right, transparent, #000 56px, #000 calc(100% - 56px), transparent); }
.tablewrap:focus-visible,
[data-rail]:focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }
table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.tablewrap > table { min-width: 640px; }
.tablewrap--tight > table { min-width: 0; }

thead th {
  background: transparent; font-size: var(--t-2xs); letter-spacing: .14em; color: var(--slate-2);
  text-align: start; padding: 0 var(--s4) var(--s3); white-space: nowrap;
  border-bottom: 1.5px solid var(--ink-t);
}
thead th:first-child { padding-inline-start: 0; }
tbody td, tbody th {
  padding: var(--s4); text-align: start; vertical-align: top;
  border-bottom: 1px solid var(--line); font-weight: 400;
}
tbody th:first-child, tbody td:first-child { padding-inline-start: 0; }
tbody tr:last-child td, tbody tr:last-child th { border-bottom: 1px solid var(--line); }
tbody tr { transition: background .15s var(--ease); }
tbody tr:hover td, tbody tr:hover th { background: var(--saffron-w); }

.cell-n { text-align: end; white-space: nowrap; font-family: var(--mono); }
.cell-n b { font-family: var(--display); font-weight: 700; color: var(--ink-t); font-size: var(--t-md); letter-spacing: -.03em; }

/* The rate and the band are context for the total above them, not two more
   figures to weigh. One quiet line, wrapping only when it has to. */
.cell-n .cell-meta {
  display: block; margin-top: 2px;
  font-family: var(--mono); font-size: var(--t-2xs); color: var(--slate);
}
/* Not every course carries a band, and a separator with nothing after it
   reads as a missing figure. Both halves have to be there to earn one. */
.cell-n .cell-meta > :empty { display: none; }
.cell-n .cell-meta > :not(:empty) + :not(:empty)::before {
  content: " · "; color: var(--line-2);
}
.cell-n .cell-meta .cell-sub {
  display: inline; margin: 0; font-size: inherit;
  /* Break between the rate and the band, never inside either of them. */
  white-space: nowrap;
}

.cell-n .native, .cell-n .qualifier {
  display: block; white-space: normal; font-weight: 400; text-align: end;
  font-size: var(--t-2xs); line-height: 1.4; margin-top: 3px;
}
.cell-n .native { color: var(--slate-2); }
.cell-n .qualifier { color: var(--slate); font-family: var(--body); }
.rowhead { font-weight: 600; color: var(--ink-t); font-family: var(--body); }
.rowhead small { display: block; font-weight: 400; color: var(--slate-2); margin-top: 2px; line-height: 1.4; }
.void { color: var(--slate-2); }

/* The comparison. Fixed layout so the five school columns are equal, and
   each column wears its school's colour under the name. */
.vs { border-top: 1px solid var(--line); padding-top: var(--s5); }
/* 168px of row heading plus a 190px column per school. Raise this when a
   school joins, or the columns shrink below their own min-width and the
   table quietly gets harder to read on a phone. */
.vs > table { min-width: 1118px; width: 100%; table-layout: fixed; }
.vs thead th:first-child, .vs tbody th:first-child {
  position: sticky; inset-inline-start: 0; z-index: 2; width: 168px; min-width: 168px;
}
.vs .cell-n, .vs .cell-n .native, .vs .cell-n .qualifier { text-align: start; }
.vs thead th:first-child { background: var(--paper); }
.vs tbody th:first-child {
  background: var(--paper); font-family: var(--body); font-weight: 600;
  color: var(--ink-t); font-size: var(--t-sm); border-inline-end: 1px solid var(--line);
  padding-inline-end: var(--s4);
}
.vs tbody tr:hover th:first-child { background: var(--saffron-w); }
.vs thead th:not(:first-child) {
  font-family: var(--display); font-size: var(--t-md); font-weight: 600;
  text-transform: none; letter-spacing: -.02em; color: var(--ink-t);
  vertical-align: bottom; min-width: 190px;
  border-bottom-width: 3px;
}
.vs thead th:nth-child(2) { border-bottom-color: var(--sc-1); }
.vs thead th:nth-child(3) { border-bottom-color: var(--sc-2); }
.vs thead th:nth-child(4) { border-bottom-color: var(--sc-3); }
.vs thead th:nth-child(5) { border-bottom-color: var(--sc-4); }
.vs thead th:nth-child(6) { border-bottom-color: var(--sc-5); }
.vs thead th a { text-decoration: none; color: inherit; }
.vs thead th a:hover { color: var(--cyan-t); }
.vs thead th .tiny { font-family: var(--mono); font-weight: 400; letter-spacing: .06em; text-transform: none; }

/* Where the table sits straight on the bone ground, the sticky head column
   needs that ground rather than white. */
.band:not(.band--paper) .vs thead th:first-child,
.band:not(.band--paper) .vs tbody th:first-child { background: var(--bone); }

/* ------------------------------------------------------------ fx switcher */

.fxbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s5);
  justify-content: space-between; margin-bottom: var(--s5);
}
.fxbar > .tiny { max-width: 62ch; }
/* On the home page the argument opens the section the cards are in, so the
   switch needs air under it rather than butting up against the last claim. */
.argue + .fxbar { margin-top: clamp(var(--s7), 5vw, var(--s8)); }
.switch { display: inline-flex; background: var(--paper); border: 1px solid var(--line-2); border-radius: 0; }
.switch button {
  font-size: var(--t-2xs); letter-spacing: .14em;
  color: var(--slate); background: none; border: 0;
  border-inline-start: 1px solid var(--line-2); padding: .8rem 1.2rem;
  cursor: pointer; min-height: 42px; transition: background .2s var(--ease), color .2s var(--ease);
}
.switch button:first-child { border-inline-start: 0; }
.switch button[aria-pressed="true"] { background: var(--cyan); color: #fff; border-inline-start-color: var(--cyan); }
.switch button:not([aria-pressed="true"]):hover { color: var(--ink-t); background: var(--bone); }

/* -------------------------------------------------------------- callouts */

.callout {
  border-inline-start: 2px solid var(--cyan); background: var(--cyan-w);
  padding: var(--s5); border-radius: 0; font-size: var(--t-sm); color: var(--slate);
}
.callout strong { color: var(--ink-t); }
.callout--wait { border-left-color: var(--flag); background: var(--flag-wash); }
.plate .callout, .band--ink .callout {
  background: rgba(255,255,255,.05); border-left-color: var(--saffron); color: var(--fog);
}
.plate .callout strong, .band--ink .callout strong { color: #fff; }

/* A link inherits the site's cyan, which on the deep ground is all but
   invisible -- there was no prose link on a dark band until the legal pages
   put a phone number and an address on one. White with the saffron rule
   under it, the way the footer already does it. Buttons keep their own.
   */
.band--ink a:not(.btn), .plate a:not(.btn) {
  color: #fff; text-decoration-color: var(--saffron); text-underline-offset: 4px;
}
.band--ink a:not(.btn):hover, .plate a:not(.btn):hover { color: var(--saffron-l); }

.ticks { list-style: none; margin: 0; padding: 0; }
.ticks li { position: relative; padding-inline-start: 1.7rem; margin-bottom: var(--s2); font-size: var(--t-sm); line-height: 1.55; }
.ticks li::before {
  content: ""; position: absolute; inset-inline-start: 0; top: .42em; width: 11px; height: 7px;
  border-left: 2px solid var(--sage); border-bottom: 2px solid var(--sage); transform: rotate(-45deg);
}

/* ============================== the record ==============================

   Terms and a privacy policy are the two pages on the site nobody opens for
   pleasure and everybody opens with a question: which clause says that. So
   the clause number is treated as an address rather than an ornament -- its
   own column, in the same mono every label on the site uses, with the text
   at a reading measure beside it instead of across the full board.

   These two pages open on type rather than on a photograph. Every other
   inside page opens on one so the masthead has something to sit over; here
   the front matter of the document -- effective date, entity, place -- does
   that job, and it is the sort of stamped, checkable block the rest of the
   site is built out of anyway.
   ======================================================================== */

.pagehead--doc { min-height: clamp(280px, 34vh, 360px); }
.pagehead--doc::before {
  background: linear-gradient(172deg, var(--ink) 0%, var(--abyss) 72%);
}
.pagehead--doc h1 { font-size: clamp(2rem, 1.2rem + 2.8vw, 3.4rem); max-width: 20ch; }

/* The contents. Seventeen clauses is too many to scroll blind, and the list
   doubles as the shape of the document before you start reading it. */
.contents {
  counter-reset: toc;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(var(--s5), 4vw, var(--s8)); row-gap: 0;
}
@media (max-width: 760px) { .contents { grid-template-columns: 1fr; } }

.contents a {
  counter-increment: toc;
  display: grid; grid-template-columns: 3rem minmax(0, 1fr); align-items: baseline;
  padding: var(--s3) 0; border-top: 1px solid var(--line);
  color: var(--ink-t); text-decoration: none; font-size: var(--t-sm); line-height: 1.4;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.contents a::before {
  content: counter(toc, decimal-leading-zero);
  font-family: var(--mono); font-weight: 500; font-size: var(--t-2xs);
  letter-spacing: .14em; color: var(--saffron-t);
}
.contents a:hover { color: var(--cyan-t); border-top-color: var(--line-2); }

/* The clauses themselves. */
.clauses { counter-reset: clause; }
.clause {
  counter-increment: clause;
  display: grid; grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: 0 var(--s4);
  padding-block: clamp(var(--s6), 3.5vw, var(--s7));
  border-top: 1px solid var(--line);
  scroll-margin-top: calc(var(--mast) + var(--s6));
}
.clause:first-child { border-top: 0; padding-top: 0; }

.clause__no {
  font-family: var(--mono); font-weight: 500; font-size: 1.125rem;
  color: var(--saffron-t); line-height: 1.15; letter-spacing: .02em;
}
.clause__no::before { content: counter(clause, decimal-leading-zero); }

.clause h2 {
  font-size: clamp(1.2rem, 1rem + .8vw, 1.55rem);
  letter-spacing: -.028em; line-height: 1.12; margin: 0 0 var(--s4); max-width: 26ch;
}
.clause p, .clause li { max-width: 68ch; }
.clause p { margin-bottom: var(--s4); }
.clause p:last-child, .clause ul:last-child { margin-bottom: 0; }

/* A bulleted limb of a clause is part of the sentence above it, so it is set
   tight against that sentence and marked with a rule rather than a disc. */
.clause ul { list-style: none; margin: 0 0 var(--s4); padding: 0; }
.clause li {
  position: relative; padding-left: var(--s5); margin-bottom: var(--s3);
  line-height: 1.6;
}
.clause li:last-child { margin-bottom: 0; }
.clause li::before {
  content: ""; position: absolute; left: 0; top: .72em;
  width: 10px; height: 1px; background: var(--line-2);
}

@media (max-width: 620px) {
  .clause { grid-template-columns: 1fr; gap: var(--s3); }
  .clause__no { font-size: var(--t-sm); }
}

/* --------------------------------------------------------- school detail */

.crumbs { font-family: var(--mono); font-size: var(--t-2xs); letter-spacing: .1em; color: var(--slate-2); margin-bottom: var(--s4); }
.crumbs a { color: var(--slate-2); text-decoration: none; }
.crumbs a:hover { color: var(--ink-t); }
.crumbs span { margin-inline: .5rem; color: var(--line-2); }

.dossier {
  background: var(--abyss);
  color: var(--fog); padding-block: calc(var(--s8) + var(--mast)) var(--s8);
  margin-top: calc(var(--mast) * -1); position: relative; overflow: hidden;
}
.dossier .shell { position: relative; z-index: 2; }
.dossier h1 { color: #fff; font-size: var(--t-2xl); margin-bottom: var(--s3); }
.dossier .lede { color: rgba(255,255,255,.84); }
.dossier .kicker { color: var(--saffron-l); }
.dossier .kicker::after { background: rgba(255,255,255,.16); }
.dossier .crumbs, .dossier .crumbs a { color: var(--fog); }
.dossier .crumbs a:hover { color: var(--saffron-l); }
.dossier .crumbs span { color: var(--fog-2); }
.dossier .tiny { color: rgba(255,255,255,.72); }
.dossier .tag--plain { background: rgba(255,255,255,.08); color: var(--fog); border-color: rgba(255,255,255,.16); }

.vitals {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; margin-top: var(--s6);
  background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.14);
}
@media (max-width: 720px) { .vitals { grid-template-columns: repeat(2, 1fr); } }
.vital { background: var(--abyss); padding: var(--s4); }
.vital b {
  display: block; font-family: var(--display); font-size: var(--t-lg); font-weight: 700;
  color: #fff; line-height: 1.2; letter-spacing: -.035em; margin-bottom: 2px;
}
.vital span { font-family: var(--mono); font-size: var(--t-2xs); color: var(--fog-2); letter-spacing: .12em; text-transform: uppercase; }

#dossier h2 { font-size: var(--t-xl); letter-spacing: -.03em; }
#dossier > * + * { margin-top: var(--s8); }

.coursecard { display: flex; flex-direction: column; }
.coursecard h3 { font-size: var(--t-base); margin-bottom: var(--s1); }
.coursecard__detail { font-size: var(--t-sm); color: var(--slate-2); margin-bottom: var(--s4); }
.coursecard .tablewrap { margin-top: auto; }

/* ---------------------------------------------------- the package builder */

.builder__grid {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(300px, 380px);
  gap: clamp(var(--s5), 3vw, var(--s7)); align-items: start;
}
@media (max-width: 900px) { .builder__grid { grid-template-columns: 1fr; } }

.builder__pick { padding: clamp(var(--s5), 3vw, var(--s6)); }
.builder__sum { padding: var(--s5); box-shadow: var(--lift-2); border-top: 2px solid var(--ink-t); }
.builder__sumhead { font-size: var(--t-base); margin-bottom: var(--s2); }

@media (min-width: 901px) {
  .builder__sum { position: sticky; top: calc(var(--mast) + 16px); max-height: calc(100vh - var(--mast) - 40px); }
  .builder__sum .tally { overflow-y: auto; min-height: 0; }
  .builder__sum .tally__sum, .builder__sum > .btn { flex: none; }
}

/* Which row of the school's own table the current length lands on. */
.bandnote {
  margin: 0; font-family: var(--mono); font-size: var(--t-2xs); color: var(--cyan-d);
  background: var(--cyan-w); border-inline-start: 2px solid var(--cyan);
  padding: var(--s3); line-height: 1.5;
}

/* ------------------------------------------------------- tick-box options */

/* The top rule is there for when a list of options follows other fields, as
   the room supplements do. Directly under a legend it draws a second line a
   centimetre below the one the legend already draws. */
.opts { border-top: 1px solid var(--line); }
legend + .opts { border-top: 0; }
.opt {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s4) 0; border-bottom: 1px solid var(--line);
}
.opt input[type="checkbox"] {
  width: 18px; height: 18px; min-height: 0; margin-top: .22rem;
  flex: none; accent-color: var(--cyan);
}
.opt__body { flex: 1 1 auto; min-width: 0; margin: 0; cursor: pointer; }
.opt__name {
  display: block; font-family: var(--body); font-size: var(--t-sm);
  font-weight: 600; color: var(--ink-t); line-height: 1.35;
}
.opt__note { display: block; font-size: var(--t-xs); color: var(--slate-2); line-height: 1.45; margin-top: 2px; }
.opt__amt {
  flex: none; padding-top: .15rem; font-family: var(--mono); font-size: var(--t-xs); color: var(--slate);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ---------------------------------------------------------------- the sum */

.tally { border-top: 1px solid var(--line); }
.tally__head {
  font-size: var(--t-2xs); letter-spacing: .14em; color: var(--cyan-t);
  padding: var(--s4) 0 var(--s2);
}
.tally__head:first-child { padding-top: var(--s3); }
.tally__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); padding: var(--s3) 0; }
.tally__row + .tally__row { border-top: 1px solid var(--line); }
.tally__what { min-width: 0; }
.tally__name { display: block; font-size: var(--t-sm); color: var(--ink-t); line-height: 1.4; }
.tally__sub { display: block; font-size: var(--t-xs); color: var(--slate-2); margin-top: 2px; }
.tally__amt {
  font-family: var(--mono); font-size: var(--t-sm); font-weight: 500; color: var(--ink-t);
  white-space: nowrap;
}
.tally__row--save .tally__name, .tally__row--save .tally__amt { color: var(--sage-d); }

.tally__sum {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4);
  margin-top: var(--s4); padding-top: var(--s4); border-top: 2px solid var(--ink-t);
}
.tally__sum span { font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--slate); }
.tally__sum b {
  font-family: var(--display); font-size: var(--t-xl); font-weight: 700; color: var(--ink-t);
  letter-spacing: -.04em;
}

.tally__caveats { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--line); max-width: 78ch; }
.tally__empty { margin: 0; padding: var(--s5) 0; font-size: var(--t-sm); color: var(--slate-2); }

/* ------------------------------------------------------------ the stay

   One question, asked before any price is shown. It earns the top of the
   page because length is the only answer that moves every other number.
   ======================================================================== */

.stay {
  display: grid; grid-template-columns: minmax(0,1fr) auto; gap: var(--s5) var(--s7);
  align-items: end; background: var(--paper); border: 1px solid var(--line);
  border-top: 3px solid var(--cyan); padding: var(--s6);
}
@media (max-width: 860px) { .stay { grid-template-columns: 1fr; align-items: start; } }
.stay .kicker { margin-bottom: var(--s3); }
.stay h2 { font-size: var(--t-xl); margin-bottom: var(--s2); }
.stay__note { font-size: var(--t-sm); color: var(--slate-2); margin: 0; max-width: 46ch; }
.stay__set { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4); }

.stay__chips { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--line-2); background: var(--paper); }
.stay__chips button {
  font-size: var(--t-xs); letter-spacing: .04em; text-transform: none;
  color: var(--slate); background: none; border: 0; border-inline-start: 1px solid var(--line);
  padding: .7rem 1rem; min-height: 46px; cursor: pointer;
  font-variant-numeric: tabular-nums; transition: background .18s var(--ease), color .18s var(--ease);
}
.stay__chips button:first-child { border-inline-start: 0; }
.stay__chips button[aria-pressed="true"] { background: var(--cyan); color: #fff; }
.stay__chips button:not([aria-pressed="true"]):hover { background: var(--cyan-w); color: var(--cyan-d); }

@media (max-width: 560px) {
  .stay__chips { display: grid; grid-template-columns: repeat(3, 1fr); width: 100%; }
  .stay__chips button { border-inline-start: 1px solid var(--line); }
  .stay__chips button:nth-child(3n+1) { border-inline-start: 0; }
  .stay__chips button:nth-child(n+4) { border-top: 1px solid var(--line); }
}

/* One control, not two. Minus and plus sit on the field so the number and
   the presets above it read as the same answer rather than two questions. */
.stay__num {
  display: inline-flex; align-items: stretch;
  border: 1px solid var(--line-2); background: var(--paper);
}
.stay__step {
  flex: 0 0 auto; width: 46px; min-height: 46px;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: var(--t-md); line-height: 1;
  color: var(--cyan-d); background: none; border: 0; cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.stay__step:hover:not(:disabled) { background: var(--cyan-w); }
.stay__step:active:not(:disabled) { background: var(--cyan); color: #fff; }
.stay__step:disabled { color: var(--line-2); cursor: default; }
.stay__step:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; }

.stay__numfield {
  display: inline-flex; align-items: baseline; gap: .4rem;
  padding-inline: var(--s3); border-inline: 1px solid var(--line);
}
.stay__num input {
  width: 2.5rem; font-family: var(--mono); font-size: var(--t-md); font-weight: 500;
  color: var(--ink-t); text-align: center; background: none;
  border: 0; padding: 0; min-height: 44px;
  -moz-appearance: textfield;
}
.stay__num input::-webkit-outer-spin-button,
.stay__num input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stay__num input:focus { outline: none; }
.stay__num:focus-within { border-color: var(--cyan); box-shadow: 0 0 0 3px var(--cyan-w); }
.stay__unit { font-family: var(--mono); font-size: var(--t-2xs); letter-spacing: .12em; text-transform: uppercase; color: var(--slate); }


/* ---------------------------------------------------------- the planner

   Four decisions in a row: length, course, room, and the sum of them. They
   used to be four different-looking things - one card, two bare sections
   and a panel - so nothing said they were one task. Same surface, same
   header, numbered, so a student can see where they are in it.
   ======================================================================== */

.plan__step {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--line);
  border-top: 3px solid var(--cyan);
  padding: var(--s6);
  margin-bottom: var(--s5);
}
.plan__step > h2,
.plan__step > .stay__ask > h2 {
  font-size: var(--t-xl);
  margin: 0 0 var(--s2);
  padding-inline-start: calc(var(--s6) + var(--s2));
}
/* The number is drawn from the section's own attribute, so a step is one
   class and one number in the markup and nothing to keep in sync here. */
.plan__step::before {
  content: attr(data-step);
  position: absolute;
  inset-inline-start: var(--s6);
  top: var(--s6);
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: .1em;
  color: var(--cyan-t);
  padding-top: .45em;
}
/* The sentence under a step is context, not the instruction. It reads
   smaller than the question and never runs past a comfortable measure. */
.plan__step > .lede,
.plan__step > .stay__ask .stay__note {
  font-size: var(--t-sm);
  color: var(--slate-2);
  max-width: 54ch;
  margin-bottom: var(--s5);
}

/* Step one keeps its own set, but joins the same chrome. Its kicker said
   "Your stay" above a heading that asks how long the stay is, which the
   number now covers, so it goes rather than sit under the number. */
.stay.plan__step { display: block; }
.stay.plan__step .stay__ask { margin-bottom: var(--s5); }
.stay.plan__step .kicker { display: none; }

@media (max-width: 720px) {
  .plan__step { padding: var(--s5) var(--s4); }
  .plan__step > h2,
  .plan__step > .stay__ask > h2 { padding-inline-start: calc(var(--s5) + var(--s1)); }
  .plan__step::before { inset-inline-start: var(--s4); top: var(--s5); }
}

/* -- step one: one row of presets, and the stepper that names the unit */

.stay__chips { gap: 0; }
@media (max-width: 560px) {
  .stay__chips {
    display: flex; width: 100%;
  }
  .stay__chips button {
    flex: 1 1 0; min-width: 0; padding-inline: 0;
    border-inline-start: 1px solid var(--line);
    border-top: 0;
  }
  .stay__chips button:first-child { border-inline-start: 0; }
  .stay__set { gap: var(--s3); }
  .stay__num { width: 100%; }
  .stay__numfield { flex: 1 1 auto; justify-content: center; }
}

/* Until the length is given, step one is the only one with anything to
   answer. The step below it still shows every course and every weekly
   rate - those are true without a length - and only the totals wait. */
.plan__step[data-asked="false"] { border-top-color: var(--saffron); }
.plan__step[data-asked="false"] .stay__note { color: var(--saffron-t); }
.plan__step[data-asked="false"] .stay__num { border-color: var(--saffron-l); }
.plan__step[data-asked="false"] .stay__num input::placeholder { color: var(--fog); opacity: 1; }

.plan__step[data-asked="false"] [data-your] { color: var(--saffron-t); }
.plan__step[data-asked="false"] .cell-n b { color: var(--fog); font-weight: 400; }

/* A step is a thing you do. The caveats around it are worth reading and
   are not the instruction, so inside a step they are set as a note rather
   than a panel, and the first choice comes up the page to meet the eye. */
.plan__step .callout {
  font-size: var(--t-xs);
  line-height: 1.6;
  padding: var(--s3) var(--s4);
  border-inline-start-width: 2px;
}
.plan__step .roomgroup__name {
  font-size: var(--t-base);
  margin-bottom: var(--s1);
}
.plan__step .roomgroup > .tiny,
.plan__step .roomgroup > p {
  font-size: var(--t-xs);
  margin-bottom: var(--s3);
}

/* -- steps two and three: a table is a poor shape for a phone

   Each row is one choice, so below the breakpoint it becomes a card: the
   name and the figure on one line, the detail under them, the whole card
   the target. The radio stays for the keyboard and the screen reader and
   stops taking a column of its own. */

@media (max-width: 720px) {
  .picktable, .picktable tbody, .picktable tr, .picktable td, .picktable th {
    display: block;
  }
  .picktable thead { display: none; }
  .picktable tbody tr {
    position: relative;
    border: 1px solid var(--line);
    border-radius: var(--r);
    padding: var(--s4);
    margin-bottom: var(--s3);
    background: var(--paper);
    transition: border-color .18s var(--ease), background .18s var(--ease);
  }
  .picktable tbody tr > td,
  .picktable tbody tr > th {
    border: 0; padding: 0; background: none;
  }
  /* The radio is the control; the card is the target. Kept reachable and
     announced, taken out of the layout. */
  .picktable .pickrow__box {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); padding: 0; margin: 0;
  }
  .picktable .rowhead { padding-inline-end: 0; }
  .picktable .cell-n {
    text-align: start;
    margin-top: var(--s3);
    padding-top: var(--s3);
    border-top: 1px dashed var(--line) !important;
  }
  .picktable .cell-n b { font-size: var(--t-lg); }
  .picktable .cell-n .cell-was { display: inline; margin-inline-start: var(--s2); }
  .picktable .cell-n .cell-meta { margin-top: var(--s1); }
  /* The second rate column is a duplicate of the line inside the cell. */
  .picktable tbody td.cell-n:last-child:empty { display: none; }

  .pickrow:hover td, .pickrow:hover th { background: none; }
  .pickrow[data-on="true"] { border-color: var(--cyan); background: var(--cyan-w); }
  .pickrow[data-on="true"] td, .pickrow[data-on="true"] th { background: none; }
  .pickrow[data-on="true"] .pickrow__box { box-shadow: none; }
  .tablewrap { overflow: visible; }
  .tablewrap > .picktable { min-width: 0; }
}

/* --------------------------------------------------------- pick tables

   The table is the selector. Choosing happens where the information already
   is, so nothing has to be carried in the head to a form further down.
   ======================================================================== */

.tablewrap > .picktable { min-width: 520px; }
.picktable thead th[data-your], .picktable thead th[data-yourstay] { color: var(--cyan-t); }
.picktable .pickrow__box { width: 44px; padding-inline: 0 var(--s2); text-align: start; vertical-align: middle; }
.picktable tbody th, .picktable tbody td { vertical-align: middle; }
.picktable tbody th:first-child, .picktable tbody td:first-child { padding-inline-start: 0; }

.pickrow { cursor: pointer; }
.pickrow input[type="radio"] { width: 18px; height: 18px; accent-color: var(--cyan); cursor: pointer; margin: 0; }
.pickrow label { cursor: pointer; }
.pickrow .rowhead small { margin-top: 3px; }
.pickrow:hover td, .pickrow:hover th { background: var(--cyan-w); }

/* Selected reads as a state, not a hover: a left rule and a wash that hover
   cannot produce, so the row you chose is still obvious under the cursor. */
.pickrow[data-on="true"] td, .pickrow[data-on="true"] th { background: var(--cyan-w); }
.pickrow[data-on="true"] .pickrow__box { box-shadow: inset 3px 0 0 var(--cyan); }
.pickrow[data-on="true"] .rowhead label { color: var(--cyan-d); }
.pickrow[data-on="true"] .cell-n b { color: var(--cyan-d); }

.cell-n .cell-sub {
  display: block; font-family: var(--body); font-size: var(--t-2xs); font-weight: 400; color: var(--slate-2);
  line-height: 1.4; margin-top: 2px; white-space: normal;
}
.cell-n .cell-was {
  display: block; font-size: var(--t-2xs); font-weight: 400; color: var(--sage-d);
  line-height: 1.4; margin-top: 2px; text-decoration: line-through; white-space: nowrap;
}
.cell-n .cell-was:empty { display: none; }
.cell-sub--narrow { display: none; }

/* Two kinds of stay, side by side. Where a student sleeps is a decision they
   make once — with a family or the school's own residence, against a place of
   their own — and running both down one list made it a decision they had to
   reconstruct from twenty room names. Two columns state it instead.

   The rate column goes while they are side by side, exactly as it does on a
   phone: half the measure will not carry a total and a rate, and the rate is
   already under the figure it belongs to. */
.roomsplit { display: grid; gap: var(--s6); align-items: start; }
.roomgroup__name {
  font-size: var(--t-md); margin: 0 0 var(--s1);
  padding-bottom: var(--s3); border-bottom: 2px solid var(--cyan);
}
.roomgroup__note {
  font-size: var(--t-sm); color: var(--slate-2); line-height: 1.5;
  margin: var(--s3) 0 var(--s5); max-width: 46ch;
}
/* Set like the callout it is, so an empty column reads as an answer rather
   than as the space where a table should have been. */
.roomgroup__none {
  margin: 0; padding: var(--s4) var(--s5); max-width: 46ch;
  border-inline-start: 3px solid var(--line); background: var(--paper);
  font-size: var(--t-sm); color: var(--slate-2); line-height: 1.55;
}

@media (min-width: 1000px) {
  .roomsplit { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s7); }
  .roomsplit .tablewrap > .picktable { min-width: 0; }
  .roomsplit .picktable thead th:last-child,
  .roomsplit .picktable tbody tr > *:last-child { display: none; }
  .roomsplit .cell-sub--narrow { display: block; }
}

/* A phone has no room for a rate column beside a total. Rather than scroll
   the table sideways — which is where people give up — the rate moves under
   the figure it belongs to and the column goes. */
@media (max-width: 700px) {
  .tablewrap > .picktable { min-width: 0; }
  .picktable thead th:last-child, .picktable tbody tr > *:last-child { display: none; }
  .cell-sub--narrow { display: block; }
  .picktable .pickrow__box { width: 32px; }
  .picktable tbody th, .picktable tbody td { padding-inline: var(--s2); }
}

/* -------------------------------------------------------------- location */

.locale {
  display: grid; grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
  gap: 0; border: 1px solid var(--line); background: var(--paper);
}
@media (max-width: 760px) { .locale { grid-template-columns: 1fr; } }

.locale__frame { position: relative; aspect-ratio: 16 / 10; background: var(--bone); }
@media (max-width: 760px) { .locale__frame { aspect-ratio: 4 / 3; } }

/* Behind the iframe, not inside it: an iframe paints nothing until its
   document lands, so this shows through the wait — and stays put if the map
   is blocked, where a bare frame would leave a white hole. */
.locale__wait {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--s2);
  color: var(--slate-2); text-align: center; padding: var(--s4);
}
.locale__pin { color: var(--line-2); }
.locale__pin svg { width: 30px; height: 30px; display: block; }
.locale__map { position: relative; width: 100%; height: 100%; border: 0; display: block; }

.locale__aside {
  padding: var(--s5); border-inline-start: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: flex-start;
}
@media (max-width: 760px) {
  .locale__aside { border-inline-start: 0; border-top: 1px solid var(--line); }
}
.locale__addr {
  font-family: var(--body); font-size: var(--t-md); font-weight: 600;
  font-style: normal; color: var(--ink-t); line-height: 1.4;
  margin: var(--s4) 0 var(--s3);
}
.locale__tabs { margin-top: var(--s3); }
.locale__note:empty { display: none; }
.locale__note { color: var(--slate); margin-bottom: var(--s3); }
.locale__aside .btn-set { margin-top: auto; padding-top: var(--s5); }
@media (max-width: 760px) { .locale__aside .btn-set { width: 100%; } }

/* ------------------------------------------------------ school: the rail

   A row of photographs read left to right, not a grid. A grid would set its
   own height from however many pictures a school happened to send, and three
   schools with three different counts would each break the page rhythm
   differently. A rail is one band tall whatever is in it.

   Squared, because these are printed matter in the site's terms — the same
   register as the cards and the boards, not the full-bleed photographic
   bands. Each picture is captioned, so each is evidence rather than filler.
   ======================================================================== */

.shots { margin-bottom: var(--s7); }
.shots__rail {
  display: flex; align-items: flex-start;
  gap: var(--s4); overflow-x: auto; overscroll-behavior-inline: contain;
  -webkit-overflow-scrolling: touch;
  /* Each picture comes to rest against the edge rather than halfway across,
     so a flick lands on a photograph instead of between two. */
  scroll-snap-type: inline mandatory; scroll-padding-inline: 0;
  padding-bottom: var(--s2);
}
/* Sized by height, not by a column width, so a rail can hold whatever shape
   the school sent. UCT photographed its rooms on a phone held upright; Good
   Hope sent a set shot sideways. Fixing a single frame ratio would have cut
   the ceiling off one or the sides off the other, and cropping somebody's
   photograph to fit a grid is the sort of thing that shows.

   The minimum is for the caption rather than the picture: a narrow portrait
   frame would otherwise leave its sentence in a two-word column. */
.shots__one {
  margin: 0; scroll-snap-align: start; flex: none;
  min-width: clamp(170px, 19vw, 210px); max-width: min(82vw, 420px);
}
/* The picture is the button. No chrome on it until you point at it, and then
   only enough to say it does something. */
.shots__open {
  display: block; width: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in; position: relative; overflow: hidden;
}
.shots__open::after {
  content: ""; position: absolute; inset: 0; background: var(--ink);
  opacity: 0; transition: opacity .22s var(--ease); pointer-events: none;
}
.shots__open:hover::after, .shots__open:focus-visible::after { opacity: .14; }
.shots__open img { transition: transform .6s var(--ease-o); }
.shots__open:hover img { transform: scale(1.04); }
.shots__open:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .shots__open img, .shots__open:hover img { transition: none; transform: none; }
}
/* Portrait, because these are photographs somebody took on their phone while
   walking through the school, and 3:4 is the shape they came in. Forcing a
   landscape crop on them would cut the ceiling off every room. */
.shots__one img {
  display: block; height: clamp(215px, 27vw, 310px); width: auto; max-width: 100%;
  object-fit: cover; background: var(--shell); border: 1px solid var(--line);
}
.shots__one figcaption {
  margin-top: var(--s3); font-size: var(--t-xs); color: var(--slate-2);
  line-height: 1.5;
}
@media (max-width: 580px) {
  .shots__one { min-width: min(58vw, 220px); max-width: 86vw; }
  .shots__one img { height: 260px; }
}
/* The rail is the one thing on the page that is meant to be dragged, so it
   keeps its scrollbar where the tables hide theirs behind the fade. */
.shots__rail::-webkit-scrollbar { height: 3px; }
.shots__rail::-webkit-scrollbar-thumb { background: var(--line-2); }
.shots__rail::-webkit-scrollbar-track { background: var(--line); }

/* ----------------------------------------------------------- the viewer

   The photograph, opened. On the abyss rather than on paper: this is the one
   place the site asks you to look at an image and nothing else, and a dark
   ground is what gets the page out of the way. Everything in here is chrome
   for the picture, so it is all one weight and one colour, and the caption
   that was under the thumbnail is still under it here.
   ======================================================================== */

.viewer {
  position: fixed; inset: 0; z-index: 95;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(var(--s4), 3vw, var(--s7));
  background: rgba(8, 22, 29, .93);
  animation: viewer-in .18s var(--ease);
}
.viewer[hidden] { display: none; }
@keyframes viewer-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .viewer { animation: none; } }

.viewer__sheet {
  position: relative; display: flex; flex-direction: column; gap: var(--s4);
  max-width: min(100%, 1080px); max-height: 100%;
}
.viewer__frame {
  display: flex; align-items: center; justify-content: center;
  min-height: 0; flex: 1 1 auto;
  /* Sideways is ours — that is the swipe between photographs. Up, down and
     pinch stay with the browser, so a closer look still works. */
  touch-action: pan-y pinch-zoom;
}
.viewer__frame img {
  display: block; max-width: 100%; max-height: calc(100vh - 11rem);
  width: auto; height: auto; object-fit: contain;
  background: var(--ink-2);
  /* Only for the slide back when a swipe did not travel far enough; while a
     thumb is down the script turns this off so the picture tracks it. */
  transition: transform .26s var(--ease-o), opacity .26s var(--ease);
  user-select: none; -webkit-user-drag: none;
}
@media (prefers-reduced-motion: reduce) { .viewer__frame img { transition: none; } }
.viewer__bar {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s5); flex: none;
}
.viewer__says { margin: 0; color: #fff; font-size: var(--t-sm); line-height: 1.5; max-width: 62ch; }
.viewer__count {
  margin: 0; flex: none; font-family: var(--mono); font-size: var(--t-2xs);
  letter-spacing: .12em; color: var(--fog-2);
}

/* Over the picture on a wide screen, where there is room beside it; below it
   on a phone, where there is not and a thumb cannot reach the middle anyway. */
.viewer__step, .viewer__shut {
  position: absolute; display: flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; padding: 0; cursor: pointer;
  color: #fff; background: rgba(8, 22, 29, .55); border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 50%; transition: background .18s var(--ease), border-color .18s var(--ease);
}
.viewer__step svg, .viewer__shut svg { width: 18px; height: 18px; }
.viewer__step:hover, .viewer__shut:hover { background: var(--cyan); border-color: var(--cyan); }
.viewer__step:focus-visible, .viewer__shut:focus-visible { outline: 2px solid var(--cyan-l); outline-offset: 3px; }
.viewer__step--prev { inset-inline-start: var(--s4); top: calc(50% - 3rem); translate: 0 -50%; }
.viewer__step--next { inset-inline-end: var(--s4); top: calc(50% - 3rem); translate: 0 -50%; }
/* The arrows point along the rail, and in Arabic the rail runs the other way. */
[dir="rtl"] .viewer__step svg { scale: -1 1; }
/* Pinned to the window rather than to the sheet. Hung off the top of the
   sheet it rides up with a tall photograph, and on a short window it goes off
   the top of the screen — taking the way out with it. */
.viewer__shut { position: fixed; top: var(--s4); inset-inline-end: var(--s4); }
.viewer:focus { outline: none; }
body[data-viewer="open"] .wa, body[data-viewer="open"] .totebar { display: none; }

@media (max-width: 720px) {
  .viewer__sheet { gap: var(--s3); padding-bottom: 68px; }
  .viewer__frame img { max-height: calc(100vh - 16rem); }
  .viewer__bar { flex-direction: column; align-items: flex-start; gap: var(--s1); }
  /* Both arrows within one thumb's reach, side by side under the caption,
     and the way out at the far end of the same row. */
  .viewer__step { top: auto; translate: none; bottom: 0; }
  .viewer__step--prev { inset-inline-start: 0; }
  .viewer__step--next { inset-inline-start: 60px; inset-inline-end: auto; }
  .viewer__shut { position: absolute; top: auto; bottom: 0; inset-inline-end: 0; }
}

/* ---------------------------------------------------------------- folds */

.fold { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); }
.fold > summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s4);
  padding: var(--s4) var(--s5); cursor: pointer; list-style: none;
  transition: background .18s var(--ease);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:hover { background: var(--bone); }
.fold__name { font-family: var(--body); font-size: var(--t-md); font-weight: 600; color: var(--ink-t); }
.fold__hint { font-size: var(--t-2xs); letter-spacing: .1em; color: var(--slate-2); }
.fold > summary::after {
  content: ""; margin-inline-start: auto; width: 8px; height: 8px; flex: none;
  border-right: 1.5px solid var(--slate); border-bottom: 1.5px solid var(--slate);
  transform: rotate(45deg); transition: transform .2s var(--ease); align-self: center;
}
.fold[open] > summary::after { transform: rotate(-135deg); }
.fold[open] > summary { border-bottom: 1px solid var(--line); }
/* Any author display on the body outranks the UA rule that hides a closed
   details, and .cols sets one — so the collapse has to be stated outright. */
.fold:not([open]) > .fold__body { display: none; }
.fold__body { padding: var(--s5); }
.fold__body > h3 { font-size: var(--t-base); }

/* ------------------------------------------------------------ tote bar

   The total follows the student down the page, so what they picked is never
   off screen and the estimate is never something they have to go and find.
   ======================================================================== */

.totebar {
  position: fixed; inset-inline: 0; inset-block-end: 0; z-index: 60;
  background: var(--ink); color: #fff; border-top: 2px solid var(--saffron);
  box-shadow: 0 -8px 32px rgba(8,22,29,.3);
  animation: totein .28s var(--ease-o);
}
@keyframes totein { from { transform: translateY(100%); } to { transform: none; } }
.totebar__in {
  display: flex; align-items: center; gap: var(--s4) var(--s5);
  padding-block: var(--s3); min-height: 72px;
}
.totebar__what { min-width: 0; flex: 1 1 auto; }
.totebar__pick {
  display: block; font-family: var(--body); font-size: var(--t-sm); font-weight: 600;
  color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.totebar__sub {
  display: block; font-size: var(--t-xs); color: var(--fog);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.totebar__money { text-align: end; flex: none; }
.totebar__label { display: block; font-size: var(--t-2xs); letter-spacing: .14em; color: var(--fog-2); }
.totebar__amt {
  display: block; font-family: var(--display); font-size: var(--t-lg); font-weight: 700;
  color: #fff; letter-spacing: -.04em;
}
.totebar .btn { flex: none; }
/* On the dark bar the paper-grey fill would disappear, so the secondary
   borrows the treatment the hero uses over a photograph. */
.totebar .btn--line { color: #fff; background: rgba(255,255,255,.16); }
.totebar .btn--line:hover { color: var(--ink-t); background: #fff; }
.totebar .btn--line:focus-visible { box-shadow: inset 0 0 0 2px #fff, inset 0 0 0 4px var(--cyan); }

body[data-totebar="true"] { padding-bottom: calc(var(--totebar-h, 84px) + var(--s3)); }
body[data-totebar="true"] .wa { inset-block-end: calc(var(--totebar-h, 84px) + var(--s4)); }

@media (max-width: 720px) {
  .totebar__in { flex-wrap: wrap; gap: var(--s2) var(--s4); padding-block: var(--s3); }
  .totebar__what { flex: 1 1 60%; }
  .totebar__money { text-align: end; }
  .totebar__more { display: none; }
  .totebar .btn--go { flex: 1 1 100%; justify-content: center; }
  body[data-totebar="true"] { padding-bottom: calc(var(--totebar-h, 132px) + var(--s3)); }
  body[data-totebar="true"] .wa { inset-block-end: calc(var(--totebar-h, 132px) + var(--s3)); }
}

@media (prefers-reduced-motion: reduce) { .totebar { animation: none; } }

/* ------------------------------------------------------- school chooser */

.chooser { margin-bottom: var(--s7); }
.chooser .stay { margin-bottom: var(--s5); }
.chooser .stay h2 { font-size: var(--t-lg); }

/* On the school page the stay strip is a panel. Here it is a section heading
   above three cards, so it drops the panel chrome. */
.chooser .stay {
  grid-template-columns: 1fr; align-items: start; gap: var(--s5);
  background: none; border: 0; padding: 0;
}
.chooser .stay__ask { max-width: 62ch; }
.chooser .stay__note { max-width: none; }
.chooser .stay h2 { margin-bottom: var(--s2); }

.picks { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--s4); }
@media (max-width: 860px) { .picks { grid-template-columns: 1fr; } }

/* The chooser card. Note the specificity: .pick is also the home page's
   section wrapper, so everything here is scoped under .picks. */
.picks > .pick {
  display: flex; flex-direction: column; background: var(--paper);
  border: 1px solid var(--line); border-top: 3px solid var(--sc, var(--cyan)); border-radius: 0;
  padding: var(--s5); text-decoration: none; color: inherit;
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease), transform .25s var(--ease);
}
.picks > .pick:nth-child(1) { --sc: var(--sc-1); }
.picks > .pick:nth-child(2) { --sc: var(--sc-2); }
.picks > .pick:nth-child(3) { --sc: var(--sc-3); }
.picks > .pick:hover { box-shadow: var(--lift-2); transform: translateY(-3px); color: inherit; }
.pick__place { font-size: var(--t-2xs); letter-spacing: .16em; color: var(--sc, var(--cyan)); }
.picks .pick__name { font-size: var(--t-md); margin: var(--s2) 0 var(--s4); }
.pick__amt {
  font-family: var(--display); font-size: var(--t-xl); font-weight: 700;
  color: var(--ink-t); letter-spacing: -.04em; line-height: 1.1;
}
.pick__amt small {
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--slate-2);
  margin-inline-end: .5rem;
}
.pick__basis { font-family: var(--mono); font-size: var(--t-2xs); color: var(--slate-2); margin-top: 4px; line-height: 1.4; }
.pick__rooms { font-size: var(--t-xs); color: var(--slate); margin-top: var(--s3); line-height: 1.4; }
.picks .pick__go {
  margin-top: auto; padding-top: var(--s4);
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--cyan-t);
}
.picks > .pick:hover .pick__go { color: var(--ink-t); }

.chooser__skip { display: inline-block; margin-top: var(--s4); font-size: var(--t-sm); }

/* ------------------------------------------------------- carried package

   Sage, because this is status and not an action: it confirms something the
   student has already done. The form below drops every question this card
   answers, so the two can never disagree about what was chosen.
   ======================================================================== */

.docket {
  background: var(--sage-w); border: 1px solid var(--sage);
  border-radius: 0; padding: var(--s5); margin-bottom: var(--s6);
}
.docket__top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s3);
}
.docket__kicker {
  display: flex; align-items: center; gap: var(--s3);
  font-size: var(--t-2xs); letter-spacing: .16em; color: var(--sage-d);
}
.docket__kicker::before {
  content: ""; flex: none; width: 11px; height: 7px; margin-top: -4px;
  border-left: 2px solid var(--sage-d); border-bottom: 2px solid var(--sage-d);
  transform: rotate(-45deg);
}
.docket__change { font-size: var(--t-sm); color: var(--sage-d); white-space: nowrap; }
.docket__change:hover { color: var(--ink-t); }
.docket__school { font-family: var(--display); font-size: var(--t-md); font-weight: 600; color: var(--sage-d); margin-bottom: var(--s4); }

.docket__lines {
  display: grid; grid-template-columns: auto minmax(0,1fr);
  gap: var(--s2) var(--s5); margin: 0; font-size: var(--t-sm);
}
.docket__lines dt { font-family: var(--mono); font-size: var(--t-2xs); letter-spacing: .1em; text-transform: uppercase; color: var(--sage-d); padding-top: 3px; }
.docket__lines dd { margin: 0; font-weight: 600; color: var(--ink-t); }

.docket__sum {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4);
  margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--sage);
  font-family: var(--mono); font-size: var(--t-2xs); letter-spacing: .12em; text-transform: uppercase; color: var(--sage-d);
}
.docket__sum b {
  font-family: var(--display); font-size: var(--t-lg); font-weight: 700;
  color: var(--ink-t); letter-spacing: -.03em; text-transform: none; letter-spacing: -.03em;
}
.docket__note { margin: var(--s3) 0 0; font-size: var(--t-xs); color: var(--sage-d); line-height: 1.55; }

/* ------------------------------------------------------------------ forms */

.formgrid { display: grid; grid-template-columns: minmax(0,1.25fr) minmax(0,.75fr); gap: clamp(var(--s6), 5vw, var(--s8)); align-items: start; }
@media (max-width: 980px) { .formgrid { grid-template-columns: 1fr; } }

.formcard {
  background: var(--paper); border: 1px solid var(--line); border-radius: 0;
  border-top: 3px solid var(--cyan);
  padding: clamp(var(--s5), 3vw, var(--s7)); box-shadow: var(--lift-1);
}

fieldset { border: 0; margin: 0 0 var(--s6); padding: 0; }
fieldset:last-of-type { margin-bottom: var(--s5); }
legend {
  font-size: var(--t-2xs); letter-spacing: .16em; color: var(--cyan-t);
  padding: 0 0 var(--s3); margin-bottom: var(--s5);
  width: 100%; border-bottom: 1px solid var(--line);
}

.fld { margin-bottom: var(--s4); }
.fld > label { display: block; font-size: var(--t-sm); font-weight: 600; color: var(--ink-t); margin-bottom: var(--s2); }
.fld .req { color: var(--cyan-t); }
.fld .help { display: block; font-weight: 400; font-size: var(--t-xs); color: var(--slate-2); margin-top: 2px; }
.fld input, .fld select, .fld textarea {
  width: 100%; font-family: var(--body); font-size: var(--t-base); color: var(--ink-t);
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--r);
  padding: .8rem .9rem; min-height: 50px;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
/* Safari on iOS sizes date, number and file inputs from their own shadow
   content and treats `width: 100%` as a suggestion, so a field that fits on
   every other browser hangs over the edge of the card on a phone. Stripping
   the native appearance is what makes the declared width the real one; the
   min-width floor is what stops the intrinsic width winning back inside a
   grid track, where `auto` means "no narrower than your content".

   Checkboxes and radios are left alone — they have no appearance of their
   own to fall back on, and `appearance: none` would erase them. */
.fld input:not([type="checkbox"]):not([type="radio"]),
.fld select,
.fld textarea {
  -webkit-appearance: none; appearance: none;
  min-width: 0; max-width: 100%;
}
.fld input[type="number"]::-webkit-outer-spin-button,
.fld input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.fld input[type="number"] { -moz-appearance: textfield; }
/* Stripped of its appearance, an iOS date field centres its own value and
   keeps a picker button sized to the old control. Both are put back where
   the rest of the form has them. */
.fld input[type="date"] { text-align: start; }
/* The min-height is a guard, not a style: with no date chosen the value part
   is empty, and on iOS an empty one collapses the field to a slot. */
.fld input[type="date"]::-webkit-date-and-time-value { text-align: start; margin: 0; min-height: 1.2em; }
.fld input[type="date"]::-webkit-datetime-edit { padding: 0; }
.fld input[type="date"]::-webkit-calendar-picker-indicator { flex: none; margin-inline-start: auto; opacity: .55; cursor: pointer; }
.fld input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }
/* The longest option decides a select's intrinsic width, and one of them is a
   school name plus its suburb. It has to be allowed to clip. */
.fld select { text-overflow: ellipsis; }
.fld textarea { min-height: 116px; resize: vertical; line-height: 1.6; }

/* The passport upload. A file input is a button and a filename sharing one
   box, and neither of them wraps — a photo off a phone camera is named
   IMG_20260918_113244.HEIC and pushes the box off the card. The box holds
   the width and the name is what gives way, so the button a student has to
   find stays put. */
.fld input[type="file"] {
  display: flex; align-items: center; gap: var(--s3);
  padding: .55rem .6rem; overflow: hidden; cursor: pointer;
  font-size: var(--t-sm); color: var(--slate);
}
.fld input[type="file"]::file-selector-button {
  flex: none; margin-inline-end: var(--s3); border: 0; border-radius: var(--r);
  background: var(--ink); color: #fff; cursor: pointer;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase;
  padding: .7rem 1.1rem; min-height: 38px;
  transition: background .18s var(--ease);
}
.fld input[type="file"]:hover::file-selector-button { background: var(--cyan); }

.fld input:hover, .fld select:hover, .fld textarea:hover { border-color: var(--slate-2); }
.fld input:focus, .fld select:focus, .fld textarea:focus { border-color: var(--cyan); box-shadow: 0 0 0 3px var(--cyan-w); outline: none; }
.fld select {
  appearance: none; padding-inline-end: 2.5rem; background-repeat: no-repeat;
  background-position: right .95rem center; background-size: 12px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%23495661' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
}
/* An answer the student already gave, standing where the question used to be.
   Quiet enough not to read as a field they must fill, definite enough to
   confirm the choice, with the only way to change it pointing back at the
   builder where it was made. */
.fld--locked { }
.locked__label {
  display: block; font-family: var(--display); font-size: var(--t-sm);
  font-weight: 600; color: var(--ink); margin-bottom: var(--s2);
}
.locked__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4);
  background: var(--bone); border: 1px solid var(--line);
  border-inline-start: 3px solid var(--sage); border-radius: var(--r);
  padding: .75rem .9rem; min-height: 48px;
}
.locked__value { font-family: var(--body); font-weight: 400; color: var(--ink); line-height: 1.4; }
.locked__change { font-size: var(--t-sm); white-space: nowrap; flex: none; }

/* minmax(0,…) rather than a bare 1fr: an `auto` minimum lets a native control
   that will not shrink set the column width, which is the overflow again by
   another route. */
.pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s4); }
.pair[data-solo] { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 580px) { .pair { grid-template-columns: minmax(0, 1fr); } }

.pair > * { min-width: 0; }

/* Two questions side by side, and one of them carries a line of help that
   wraps to a second line. Left to themselves the two boxes then start at
   different heights and the row stops reading as a row.

   Subgrid is what fixes it properly: the pair owns the three rows — label,
   box, whatever the field has to say afterwards — and both fields hang their
   parts on the same ones, so the labels share a height and the boxes sit on
   one line however long the help runs. Below the breakpoint the pair is a
   single column and there is nothing left to line up against. */
@supports (grid-template-rows: subgrid) {
  @media (min-width: 581px) {
    .pair { grid-template-rows: auto auto auto; align-items: start; margin-bottom: var(--s4); }
    .pair > .fld { display: grid; grid-row: span 3; grid-template-rows: subgrid; margin-bottom: 0; }
    .pair > .fld > label { align-self: start; }
  }
}

/* A school's minimum age is a hard stop, not a suggestion — it reads as the
   flag it is rather than as another grey hint. */
.agewarn { color: var(--flag); font-weight: 500; }

/* Said next to the field that caused it, in the flag colour the site already
   uses for "this needs attention". */
.fld__error {
  margin: var(--s2) 0 0; font-size: var(--t-sm); font-weight: 500;
  color: var(--flag); line-height: 1.45;
}
.fld [aria-invalid="true"] { border-color: var(--flag); }
.fld [aria-invalid="true"]:focus {
  border-color: var(--flag); box-shadow: 0 0 0 4px var(--flag-wash); outline: none;
}
.agree [aria-invalid="true"] { outline: 2px solid var(--flag); outline-offset: 2px; }

/* Seeing the photograph before it is sent is what catches a thumb over the
   corner — the commonest reason a school rejects one. */
.uploadprev { margin-top: var(--s4); }
.uploadprev img {
  display: block; width: 100%; max-height: 340px; object-fit: contain;
  background: var(--bone); border: 1px solid var(--line);
}

.agree { display: flex; gap: var(--s3); align-items: flex-start; font-size: var(--t-sm); line-height: 1.55; }
.agree input { width: 18px; height: 18px; min-height: 0; margin-top: .22rem; flex: none; accent-color: var(--cyan); }

.done { display: none; background: var(--sage-w); border: 1px solid var(--sage); border-radius: 0; padding: var(--s6); }
.done[data-shown="true"] { display: block; }
.done h2 { color: var(--sage-d); font-size: var(--t-lg); margin-bottom: var(--s2); }
.done p { color: var(--sage-d); font-size: var(--t-sm); }

/* Shown only when the enquiry did not reach the database. Flag rather than
   sage: something needs doing, and it is ours to fix. */
.failed { display: none; background: var(--flag-wash); border-inline-start: 2px solid var(--flag); padding: var(--s5); margin-bottom: var(--s5); }
.failed[data-shown="true"] { display: block; }
.failed h3 { color: var(--flag); font-size: var(--t-base); margin-bottom: var(--s2); }
.failed p { color: var(--slate); font-size: var(--t-sm); margin: 0; }

/* ----------------------------------------------------------------- footer */

.foot {
  background: var(--abyss); color: var(--fog);
  padding-block: var(--s8) var(--s5); font-size: var(--t-sm);
  position: relative; overflow: hidden;
}
/* The call. A rule under it separates the invitation from the directory. */
.foot__call {
  display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: var(--s5) clamp(var(--s6), 6vw, var(--s9)); align-items: end;
  padding-bottom: var(--s7); margin-bottom: var(--s7);
  border-bottom: 1px solid rgba(255,255,255,.14);
}
@media (max-width: 800px) { .foot__call { grid-template-columns: 1fr; align-items: start; } }

.foot__call .kicker { color: var(--saffron-l); margin-bottom: var(--s4); }
.foot__call .kicker::after { background: rgba(255,255,255,.16); }

.foot__mail {
  display: inline-block; font-family: var(--display); font-weight: 700;
  font-size: clamp(1.15rem, .72rem + 1.5vw, 2rem);
  letter-spacing: -.035em; line-height: 1.1; color: #fff;
  text-decoration: none; overflow-wrap: break-word;
  background-image: linear-gradient(var(--saffron), var(--saffron));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 100% 2px;
  transition: background-size .45s var(--ease-o), color .25s var(--ease);
}
.foot__mail:hover { color: var(--saffron-l); background-size: 100% 2px; }

.foot__ar { margin: 0; font-size: var(--t-sm); color: var(--fog); max-width: 44ch; line-height: 1.6; }
.foot__ar .arabic { display: block; margin-bottom: var(--s2); font-size: 15px; }

.foot .nameplate { margin-bottom: var(--s4); }
.foot h4 { color: var(--fog-2); font-size: var(--t-2xs); letter-spacing: .16em; margin-bottom: var(--s4); }
.foot a { color: rgba(255,255,255,.8); text-decoration: none; }
.foot a:hover { color: var(--saffron-l); }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: var(--s3); }
/* Nine links in a column, each one a line of type tall, is a column you tap
   twice to land on once. On a touch screen the row is opened up and the link
   is made to fill it, so the gap between two of them is dead space rather
   than the wrong destination. */
@media (pointer: coarse) {
  .foot li { margin-bottom: 0; }
  .foot li > a { display: flex; align-items: center; min-height: 44px; }
  .foot__legal { gap: var(--s2) var(--s5); }
  .foot__legal a { display: inline-flex; align-items: center; min-height: 44px; }
}
.foot .tiny { color: var(--fog-2); }
.foot__grid { display: grid; grid-template-columns: 1.7fr repeat(3, 1fr); gap: var(--s7) var(--s5); }
@media (max-width: 900px) { .foot__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 540px) { .foot__grid { grid-template-columns: 1fr; } }
.foot__base {
  margin-top: var(--s7); padding-top: var(--s5); border-top: 1px solid rgba(255,255,255,.12);
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5); justify-content: space-between;
  font-size: var(--t-xs); color: var(--fog-2); line-height: 1.6;
}
/* The legal pages sit here rather than in the index overlay: the overlay is
   the four things a student came to do, and terms are what you go looking for.
   */
.foot__legal { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); }
.foot__legal a { color: var(--fog-2); }
.foot__legal a:hover { color: var(--saffron-l); }

/* -------------------------------------------------------------- whatsapp

   The one rounded thing on the site, and deliberately so. Everything else is
   squared because it is printed matter — bands, boards, cards, the table. This
   is not on the page, it floats over it, and the pill says so at a glance.
   On a phone the label goes and what is left is the mark alone, so the shape
   closes into a circle rather than sitting as a rounded-off square.
   ======================================================================== */

.wa {
  position: fixed; inset-inline-end: var(--s5); inset-block-end: var(--s5); z-index: 50;
  display: inline-flex; align-items: center; gap: .6rem;
  background: var(--cyan); color: #fff; text-decoration: none;
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  padding: 1rem 1.5rem; border-radius: 999px;
  letter-spacing: .14em; text-transform: uppercase;
  box-shadow: 0 10px 30px rgba(8,22,29,.3);
  transition: background .2s var(--ease), transform .2s var(--ease), box-shadow .2s var(--ease);
}
.wa:hover { background: var(--cyan-d); color: #fff; transform: translateY(-3px); box-shadow: 0 14px 36px rgba(2,128,144,.4); }

/* The pick tables scroll sideways under a fixed button, and the column it
   covers is the price. While the planner is on screen the totebar already
   carries a way to get in touch, so this one stands down. */
body[data-planning="true"] .wa {
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity .2s var(--ease), transform .2s var(--ease), visibility 0s .2s;
}
@media (prefers-reduced-motion: reduce) {
  body[data-planning="true"] .wa { transition: none; }
}
.wa svg { width: 19px; height: 19px; flex: none; }
.wa:focus-visible { border-radius: 999px; }

@media (max-width: 580px) {
  .wa span { display: none; }
  /* Sized, not padded: a circle needs the two axes to agree, and padding
     around an icon leaves them to the line box to settle. */
  .wa {
    width: 56px; height: 56px; padding: 0; gap: 0;
    border-radius: 50%; justify-content: center;
    inset-inline-end: var(--s4); inset-block-end: var(--s4);
  }
  .wa svg { width: 24px; height: 24px; }
  .wa:focus-visible { border-radius: 50%; }
}

/* ============================================================== PAGE HEAD
   The hero's short cousin. Every inside page opens on a photograph so the
   masthead has something to sit over and the site reads as one thing.
   ======================================================================== */

.pagehead {
  position: relative; isolation: isolate; overflow: hidden;
  min-height: clamp(360px, 48vh, 480px);
  margin-top: calc(var(--mast) * -1); padding-top: var(--mast);
  display: flex; align-items: flex-end;
  background: var(--abyss);
}
.pagehead__media { position: absolute; inset: -18% 0; z-index: 0; }
.pagehead__media img { width: 100%; height: 100%; object-fit: cover; }
.pagehead::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, rgba(8,22,29,.7) 0%, rgba(8,22,29,.24) 30%, transparent 54%),
    linear-gradient(8deg, rgba(8,22,29,.92) 0%, rgba(8,22,29,.56) 40%, rgba(8,22,29,.1) 78%);
}
.pagehead .shell { position: relative; z-index: 2; padding-block: clamp(var(--s6), 5vh, var(--s8)); }

.pagehead__eyebrow {
  display: block; color: rgba(255,255,255,.74);
  font-size: var(--t-2xs); letter-spacing: .26em; margin-bottom: var(--s4);
}
.pagehead h1 {
  color: #fff; margin: 0; max-width: 17ch;
  font-size: clamp(2.2rem, 1.2rem + 3.6vw, 4.1rem);
  letter-spacing: -.045em; line-height: 1;
}
.pagehead .lede { color: rgba(255,255,255,.84); max-width: 56ch; margin-top: var(--s5); }

/* The same device the hero uses, at the size an inside page can carry: a
   line of mono facts on a rule, so every page opens on something checkable
   rather than on a photograph and a promise. */
.pagehead__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s6);
  margin: var(--s6) 0 0; padding-top: var(--s4);
  border-top: 1px solid rgba(255,255,255,.22);
  font-family: var(--mono); font-size: var(--t-2xs); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: rgba(255,255,255,.72);
}
.pagehead__meta .stamp { color: var(--sage); }
.pagehead .crumbs, .pagehead .crumbs a { color: rgba(255,255,255,.66); }
.pagehead .crumbs a:hover { color: #fff; }
.pagehead .crumbs span { color: rgba(255,255,255,.34); }

/* The school dossier gets that school's own photograph behind it. */
.dossier__media { position: absolute; inset: -16% 0; z-index: 0; }
.dossier__media img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%; }
.dossier::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, rgba(8,22,29,.72) 0%, rgba(8,22,29,.32) 34%, transparent 62%),
    linear-gradient(100deg, rgba(8,22,29,.94) 0%, rgba(8,22,29,.86) 46%, rgba(8,22,29,.66) 100%);
}

@media (max-width: 900px) {
  .switch button { min-height: 44px; padding-inline: 1.1rem; }
}

/* ==================================================== motion + reveal ==== */

[data-reveal] { opacity: 0; transform: translateY(20px); }
[data-reveal].is-in {
  opacity: 1; transform: none;
  transition: opacity .7s var(--ease-o), transform .7s var(--ease-o);
  transition-delay: var(--delay, 0ms);
}

/* -------------------------------------------------------------- hero type

   The headline is split into per-letter boxes by initHeroType() so it can
   rise a letter at a time. The word stays in an aria-label, so it is still
   announced as one word.
   ======================================================================== */

.hero__word .wd { display: inline-block; white-space: nowrap; }
.hero__word .ch { display: inline-block; overflow: hidden; vertical-align: bottom; padding-bottom: .06em; }
.hero__word .ch__i { display: inline-block; transform: translateY(115%); }
.hero__word.is-split { animation: none; opacity: 1; transform: none; }
/* The word comes up to the waterline and stops there, half under. The rest
   of the lift is the reader's: --rise is set from the scroll, a third of a
   screen takes the word clear of the water, and it sinks again on the way
   back up. */
.hero__word.is-split .ch__i {
  transform: translateY(calc(46% * (1 - var(--rise, 0))));
  transition: transform 1.15s cubic-bezier(.16, 1, .3, 1);
  transition-delay: calc(var(--i) * 78ms + 260ms);
}
.hero__word.is-split.is-armed .ch__i { transform: translateY(115%); transition: none; }

/* Once the staggered entry has run, the letters answer the scroll directly:
   a long ease here would land them a second after the page had moved. */
.hero__word.is-split.is-live .ch__i {
  transition: transform .14s linear; transition-delay: 0s;
}

/* -------------------------------------------------------- the waterline

   The headline is masked at its own baseline, so that edge can be a surface:
   a line of light sits on it, a haze of the same light lies under it, and the
   letters come up through both one at a time. The word surfaces rather than
   fades in. */
.hero__word { position: relative; width: -moz-fit-content; width: fit-content; }
.hero__word::before,
.hero__word::after {
  content: ""; position: absolute; left: 0; pointer-events: none;
  animation: surfaceIn 1.4s var(--ease-o) .5s both;
}
.hero__word::after {
  bottom: .02em; width: 100%; height: 2px;
  background: linear-gradient(90deg, rgba(255,255,255,.62), rgba(255,255,255,.12) 64%, transparent);
}
.hero__word::before {
  top: calc(100% + .02em); width: 100%; height: .26em;
  background: radial-gradient(64% 100% at 26% 0%, rgba(255,255,255,.14), transparent 72%);
  filter: blur(6px);
}
@keyframes surfaceIn { from { opacity: 0; transform: scaleX(.2); transform-origin: left }
                       to   { opacity: 1; transform: none } }


/* -------------------------------------------------------- shaped reveals

   A wipe reads as something being made; a fade reads as something loading.
   The panel still fades, so the two arrive together but differently. */

.pick__shot img {
  clip-path: inset(0 0 101% 0);
  transition:
    transform 1s var(--ease-o) 0s,
    clip-path 1.15s var(--ease-o) calc(var(--delay, 0ms) + 120ms);
}
.pick__card.is-in .pick__shot img { clip-path: inset(0 0 0 0); }

.pick__card .pick__rule { transform: scaleX(0); transform-origin: left; }
.pick__card.is-in .pick__rule {
  transform: scaleX(1);
  transition: transform .7s var(--ease-o) calc(var(--delay, 0ms) + 340ms), width .5s var(--ease-o);
}

/* Pointer parallax: the photograph leans a few pixels toward the cursor. */
@media (hover: hover) and (pointer: fine) {
  .pick__shot img {
    transform: translate3d(calc(var(--px, 0) * 8px), calc(var(--py, 0) * 8px), 0) scale(1.03);
  }
  .pick__card:hover .pick__shot img {
    transform: translate3d(calc(var(--px, 0) * 8px), calc(var(--py, 0) * 8px), 0) scale(1.06);
  }
}

/* ------------------------------------------------------- scroll progress

   The masthead rule fills as you go. It is the one piece of chrome on screen
   the whole time, so it is the honest place to put this. */

.masthead::before {
  content: ""; position: absolute; z-index: 2; inset-inline-start: 0; bottom: -1px; height: 2px;
  width: 100%; transform-origin: left; transform: scaleX(var(--read, 0));
  background: var(--saffron); pointer-events: none;
}

/* ------------------------------------------------------------- calm */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal] { opacity: 1; transform: none; }
  .hero__word .ch__i,
  .hero__word.is-split .ch__i { transform: none !important; }
  .hero__media img, .hero__eyebrow, .hero__word, .hero__sub, .hero__acts,
  .hero__word::before, .hero__word::after { animation: none !important; transform: none !important; }
  .pick__shot img { clip-path: none !important; transform: none !important; }
  .pick__card .pick__rule { transform: scaleX(1) !important; }
  .hero__inner { opacity: 1 !important; transform: none !important; }
  .masthead::before { display: none; }
  .masthead::after { transition: none; }
  .tag--live::before { animation: none; }
  .nameplate:hover .mark__land { transform: none; }
  .nameplate:hover .nameplate__mark::after { animation: none; }
}

@media print {
  .masthead, .foot, .wa, .btn-set, .skip, .totebar, .index { display: none; }
  body { background: #fff; }
  .dossier, .figures, .band--ink, .plate { background: #fff !important; color: #000; }
  .dossier h1, .figure b, .band--ink h2 { color: #000; }
  /* A printed map tile is a grey rectangle; the address above it is the part
     a student can carry to a taxi driver. */
  .locale__frame { display: none; }
  .locale { grid-template-columns: 1fr; }
}

/* ------------------------------------------------- the upload page only */

/* A transactional page, not a marketing one: the name is here so the student
   can see whose site is asking for their passport, and nothing else is, so
   there is nothing to click that would take the token out of the address bar. */
/* .pagehead pulls itself up under the floating masthead by --mast. This one
   does not float, so that pull has to be undone or the hero swallows the
   nameplate — which is the one thing on the page that has to be seen. */
.masthead--plain {
  position: static; background: var(--paper); border-bottom: 1px solid var(--line);
}

/* The rest of the site gets its light ground from the canvas, which this
   stripped-down layout does not inherit — the page rendered on the dark
   default instead, white card floating on near-black. It states its own. */
.masthead--plain ~ main { background: var(--bone); }
.masthead--plain ~ main .band { background: var(--bone); }
.masthead--plain + main .pagehead { margin-top: 0; padding-top: 0; }
.masthead--plain .masthead__bar { min-height: 68px; }
/* The name is white everywhere else because everywhere else it sits on a
   photograph. On this white bar it has to be ink -- and the mark beside it is
   the ink copy of the logo, set in the markup of this page alone. */
.masthead--plain .nameplate { color: var(--ink); cursor: default; }
/* Shows the language you would get, not the one you are in. A toggle that
   names its current state tells you nothing about what pressing it does. */
/* Saffron is the one warm colour in this bar — the sea line under the ridge,
   the rule beneath Get a quote, and the "we speak Arabic" badge standing
   immediately to the left of this button. The toggle is the control that
   badge promises, so it is lettered to match.

   It used to take colour: inherit, which on a near-black masthead resolved to
   the body's slate: a dark grey on a dark ground, at three-quarters opacity.
   Present in the DOM, invisible on the page. */
.langtoggle {
  font-family: var(--display); font-size: var(--t-2xs); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; line-height: 1;
  color: var(--sage); cursor: pointer;
  background: rgba(82, 183, 136, .1);
  border: 1px solid rgba(82, 183, 136, .55); border-radius: 0;
  padding: .45rem .75rem;
  transition: background .18s var(--ease), border-color .18s var(--ease),
              color .18s var(--ease);
}
.langtoggle:hover,
.langtoggle:focus-visible {
  background: var(--sage); border-color: var(--sage); color: var(--abyss);
}
.langtoggle[lang="ar"] { letter-spacing: 0; font-size: var(--t-xs); }

/* The upload page's bar is bone rather than abyss, and sage at full lightness
   would vanish into it, so the darker leg of the green carries the lettering. */
.masthead--plain .langtoggle {
  color: var(--sage-d);
  background: rgba(82, 183, 136, .1);
  border-color: rgba(43, 109, 76, .4);
}
.masthead--plain .langtoggle:hover,
.masthead--plain .langtoggle:focus-visible {
  background: var(--sage-d); border-color: var(--sage-d); color: #fff;
}

.masthead__step {
  margin-inline-start: auto;
  font-family: var(--display); font-size: 10px; font-weight: 600;
  letter-spacing: .22em; text-transform: uppercase; color: var(--slate-2);
}
/* .foot is white-on-dark everywhere else, so the text colour has to come with
   the background or it is fog grey on bone — technically present, unreadable. */
.foot--plain {
  background: var(--bone); border-top: 1px solid var(--line);
  padding-block: var(--s6); color: var(--slate-2);
}
.foot--plain .tiny { max-width: 62ch; color: var(--slate-2); }
.foot--plain a { color: var(--cyan-t); }

/* .loading came from the admin's stylesheet, which this page does not load, so
   it was bare text sitting on the left. It only shows for a moment, but a
   moment of looking broken is the first thing a student sees. */
.masthead--plain ~ main .loading {
  padding: var(--s5) 0; text-align: center;
  font-size: var(--t-sm); color: var(--slate-2);
}


/* ==========================================================================
   Right to left
   --------------------------------------------------------------------------
   The layout mirrors itself: everything above is written in logical
   properties, so inline-start becomes the right-hand edge and the page turns
   round on its own. What cannot mirror itself is anything drawn with a
   transform, because a transform is measured in screen pixels and knows
   nothing about the writing direction. Those are the rules below.
   ========================================================================== */

/* Arrows point the way you are going, so in Arabic they point left. The
   down-arrows are symmetrical about the vertical, so the same flip leaves
   them exactly as they were. */
[dir="rtl"] .btn svg,
[dir="rtl"] .pull svg,
[dir="rtl"] .pick__go svg,
[dir="rtl"] .schoolcard__go svg { transform: scaleX(-1); }

/* The nudge on hover follows the arrow. */
[dir="rtl"] .btn:hover svg,
[dir="rtl"] .pull:hover svg,
[dir="rtl"] .pick__card:hover .pick__go svg,
[dir="rtl"] .panel--tap:hover .schoolcard__go svg { transform: scaleX(-1) translateX(4px); }
[dir="rtl"] .nameplate:hover .sea { transform: translateX(-3px); }
[dir="rtl"] .index__row:focus-visible .index__name { transform: translateX(-10px); }
[dir="rtl"] .sheet__close:hover { transform: translateX(3px); }

/* The school sheet slides in from the other side. */
[dir="rtl"] .sheet__shot { transform: translateX(101%); }
[dir="rtl"] .sheet__panel { transform: translateX(-101%); }
[dir="rtl"] .sheet.is-open .sheet__shot,
[dir="rtl"] .sheet.is-open .sheet__panel { transform: none; }

/* The rule marking a chosen row sits on the side the eye starts from. */
[dir="rtl"] .pickrow[data-on="true"] .pickrow__box { box-shadow: inset -3px 0 0 var(--cyan); }

/* Prices, dates and reference codes stay left to right inside a right to
   left line — a figure is read the same way in both languages. */
[dir="rtl"] .cell-n,
[dir="rtl"] .tally__amt,
[dir="rtl"] .tally__sum b,
[dir="rtl"] .totebar__amt,
[dir="rtl"] .docket__sum b { direction: ltr; }

/* Arabic letters join to each other, and letter-spacing prises them apart —
   the tracked small-caps labels and the tight headline tracking are Latin
   typography and have no business here. Arabic also carries marks above and
   below the line, so it wants more room than the English setting leaves.

   The !important is deliberate: a few of the tracking rules are written
   against #ids, and there is no Arabic case where letter-spacing is wanted,
   so losing on specificity here would only ever be a bug. */
[dir="rtl"] * { letter-spacing: normal !important; }
[dir="rtl"] body { line-height: 1.78; }
[dir="rtl"] h1, [dir="rtl"] h2, [dir="rtl"] h3, [dir="rtl"] h4 { line-height: 1.32; }

/* The display face is a Latin one, and asked for Arabic it hands the word to
   a fallback a glyph at a time: كيبواي came out with the dots of its final ي
   adrift below the rest of the word. The Arabic headline is set in the same
   Arabic face the rest of the Arabic page uses. */
.hero__word[dir="rtl"] {
  font-family: "IBM Plex Sans Arabic", var(--body);
  font-weight: 700; letter-spacing: 0; line-height: 1.05;
}

/* The hero's letter boxes clip what falls below the baseline, and Arabic puts
   more there than Latin does — the tail of a ي or a ن would be shaved off. */
.hero__word[dir="rtl"] .ch { padding-bottom: .34em; }

/* The motto is written twice in the markup so it can be read in both
   languages under the English headline. Under the Arabic one the English
   half is not a second voice, it is the untranslated one, so it goes. */
[dir="rtl"] .hero__sub span[dir="ltr"],
[dir="rtl"] .foot__slogan span[dir="ltr"] { display: none; }
[dir="rtl"] .hero__sub span[lang="ar"],
[dir="rtl"] .foot__slogan span[lang="ar"] { margin-top: 0; text-align: start; }
