/* StemSkills components (c-*). Token-driven. Built once, reused by every template.
   Order: core set, then page components, then global chrome. Nothing here knows
   which page it is on: a component must look right with zero template CSS. */

/* ===========================================================================
   SPECIFICITY NOTE, read before editing anything in this file.
   base.css writes .ssl-scope h1/h2/h3/p/a at (0,1,1), which BEATS a plain
   .c-x__title at (0,1,0). So every rule here that sets font-family, font-size,
   font-weight, colour or max-width on a heading, paragraph or anchor uses the
   compound form .ssl-scope .c-x__title. That is the one sanctioned deviation
   from single-class selectors. Never reach for !important.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   c-btn, AND THE GUTENBERG BUTTON BRIDGE. Read this before touching any selector.

   A core wp:button block renders our className on the WRAPPER, never on the control:
       <div class=wp-block-button c-btn c-btn--primary>
         <a class=wp-block-button__link wp-element-button>Label</a>
       </div>
   So a plain `.c-btn{...}` paints a div and leaves every editable patterns call to
   action as a default link. Moving the classes onto the anchor in saved block markup is
   NOT the fix: WordPress validates saved markup against the blocks save output and
   would flag every one of those blocks invalid, which is a worse failure than a styling
   one. The bridge therefore lives here: neutralise the wrapper when it carries our
   class, and paint the anchor inside it.

   The BARE form is equally real: hand-coded chrome emits it (header CTA, hero actions,
   mobile-nav foot, the sticky CTA bar), the footer Subscribe is a <button>, and the CF7
   submit is an <input>. Both forms ship, so every rule below states the pair ONCE, in
   one selector list. Do not simplify it to a single selector and do not copy the
   declarations into a second block: that is how the two of them drift apart.

   Emil craft: exact properties (never `all`), scale-on-press feedback, strong ease-out,
   hover gated to real pointers so touch taps dont false-fire. */
.wp-block-button.c-btn{
  background:none; border:0; padding:0; border-radius:0; box-shadow:none;
}
.c-btn:not(.wp-block-button),
.c-btn.wp-block-button > .wp-block-button__link{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--ssl-s-2);
  font-family:var(--ssl-sans); font-weight:700; font-size:var(--ssl-fs-1);
  /* 14px text plus s-3 block padding lands near 41px, under the 44px minimum */
  min-height:var(--ssl-tap);
  padding:var(--ssl-s-3) var(--ssl-s-5); border-radius:var(--ssl-radius-pill);
  border:1px solid transparent;
  cursor:pointer; text-decoration:none;
  transition:transform var(--ssl-dur-press) var(--ssl-ease-out),
             background-color var(--ssl-dur-ui) var(--ssl-ease-out),
             border-color var(--ssl-dur-ui) var(--ssl-ease-out);
}
/* instant press feedback: the UI is listening */
.c-btn:not(.wp-block-button):active,
.c-btn.wp-block-button > .wp-block-button__link:active{ transform:scale(.97); }
.c-btn--primary:not(.wp-block-button),
.c-btn--primary.wp-block-button > .wp-block-button__link{
  background:var(--ssl-primary); color:var(--ssl-surface);
}
.c-btn--secondary:not(.wp-block-button),
.c-btn--secondary.wp-block-button > .wp-block-button__link{
  /* --ssl-line-strong, not --ssl-line: this border is the buttons only affordance, so it is
     a UI component boundary under WCAG 1.4.11 and needs 3:1, not the hairline token. See the
     measurements on the token. */
  background:transparent; color:var(--ssl-ink); border-color:var(--ssl-line-strong);
}
/* warm CTA: dark ink on amber, NEVER white (WCAG rule, ADR-0007) */
.c-btn--warm:not(.wp-block-button),
.c-btn--warm.wp-block-button > .wp-block-button__link{
  background:var(--ssl-accent); color:var(--ssl-ink);
}
@media (hover:hover) and (pointer:fine){
  /* base.css underlines every anchor on hover at (0,2,1); a button is not a link, and
     these pairs outrank it, so the label stays clean under the pointer. */
  .c-btn:not(.wp-block-button):hover,
  .c-btn.wp-block-button > .wp-block-button__link:hover{ text-decoration:none; }
  .c-btn--primary:not(.wp-block-button):hover,
  .c-btn--primary.wp-block-button > .wp-block-button__link:hover{ background:var(--ssl-primary-700); }
  .c-btn--secondary:not(.wp-block-button):hover,
  .c-btn--secondary.wp-block-button > .wp-block-button__link:hover{ border-color:var(--ssl-slate); }
}
@media (max-width:480px){
  /* the wrapper has to stretch too, or a full-width anchor sits in a shrink-wrapped div */
  .wp-block-button.c-btn{ width:100%; }
  .c-btn:not(.wp-block-button),
  .c-btn.wp-block-button > .wp-block-button__link{ width:100%; }
}
@media (prefers-reduced-motion:reduce){
  .c-btn:not(.wp-block-button):active,
  .c-btn.wp-block-button > .wp-block-button__link:active{ transform:none; }
}

/* card */
.c-card{
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius); padding:var(--ssl-s-5); box-shadow:var(--ssl-shadow);
}
/* A photograph at the top of a card needs air under it. c-figure zeroes its own margin,
   because it is normally the only thing inside its container and the container spaces it.
   In a card it is followed immediately by the h3 it introduces, and the two rendered flush:
   measured 0px between the image edge and the heading on all three campus-achievements
   cards. Put the space back here rather than in c-figure, so a figure that IS alone in its
   container keeps its clean margin. */
.c-card > .c-figure{ margin-block-end:var(--ssl-s-4); }

/* The hexagon-and-node marker, on LIST cards only.
   §7 sanctions the logos hexagon plus node as a quiet recurring section marker and as a
   list bullet, and this is that. It runs on the three card grids whose items are a single
   short line each (campus-what-we-teach, campus-how-it-works, for-schools-what): five, four
   and six cards stacked, which without a marker read as an undifferentiated wall of boxes.
   Scoped to `li.c-card` deliberately. An `article.c-card` (programs-levels, for-schools
   partnership) leads with its own h3, and a bullet in front of a heading is noise.
   Built from clip-path and tokens rather than a data-URI SVG, so both colours stay in
   tokens.css. A data URI would have had to hardcode the indigo and the amber, which is
   exactly the swap-the-palette-in-one-file property tokens.css exists to protect.
   Decorative, so it is a pseudo element and never announced. */
.t-card-grid > li.c-card{
  position:relative;
  padding-inline-start:calc(var(--ssl-s-5) + var(--ssl-s-6));
}
.t-card-grid > li.c-card::before,
.t-card-grid > li.c-card::after{ content:""; position:absolute; }
/* the hexagon */
.t-card-grid > li.c-card::before{
  inset-block-start:var(--ssl-s-5); inset-inline-start:var(--ssl-s-5);
  width:var(--ssl-s-5); height:var(--ssl-s-5);
  background:var(--ssl-primary-tint);
  clip-path:polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
}
/* the node: the one amber moment in the card, centred in the hexagon */
.t-card-grid > li.c-card::after{
  inset-block-start:calc(var(--ssl-s-5) + var(--ssl-s-2));
  inset-inline-start:calc(var(--ssl-s-5) + var(--ssl-s-2));
  width:var(--ssl-s-2); height:var(--ssl-s-2);
  border-radius:50%; background:var(--ssl-accent-strong);
}

/* chip. The pill is indigo-on-tint (9.9:1) at every size, so it is legible on the
   paper, surface and indigo grounds without a per-ground variant. */
.c-chip{
  display:inline-block; background:var(--ssl-primary-tint); color:var(--ssl-primary);
  font-size:var(--ssl-fs-1); font-weight:700;
  padding:var(--ssl-s-1) var(--ssl-s-3); border-radius:var(--ssl-radius-pill);
}
/* the flex wrap that holds them. Resets the core wp-block-list bullet and indent,
   since the patterns ship this as a wp:list. */
.c-chip-list{
  list-style:none; margin:var(--ssl-s-5) 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-3);
}

/* stat. The number is real text carrying the proof claim (70+, 1,390+), not
   decoration, so it must clear WCAG 1.4.3. It stays ink (16.0:1 on paper) rather than amber:
   --ssl-accent-strong now clears the 3:1 large-text threshold (3.80:1 on paper,
   4.06:1 on surface, see tokens.css) but a proof claim is not the place to spend
   12 points of contrast, and amber stays graphical (house rule 4). Size + weight
   carry the emphasis. */
.c-stat__num{ font-weight:800; color:var(--ssl-ink); font-size:var(--ssl-fs-5);
  font-variant-numeric:tabular-nums; }
.c-stat__label{ color:var(--ssl-slate); font-size:var(--ssl-fs-1); }

/* ---------------------------------------------------------------------------
   c-band. the full-bleed feature/CTA ground, and the only component in the system
   that ships on TWO grounds. One element set serves both: every element rule below
   inherits its colour from the band, and the only hardcoded text colours in the whole
   block are the two ground rules themselves. Add a colour to an element here and you
   have silently broken one of the two grounds.
   Spelling is --amber-tint, per stage-3 §4 and §5, which is the authority.        */
.c-band{ padding-block:var(--ssl-s-8); }
.c-band--indigo{ background:var(--ssl-primary); color:var(--ssl-surface); }      /* 11.4:1 */
.c-band--amber-tint{ background:var(--ssl-accent-tint); color:var(--ssl-ink); }  /* 14.9:1 */

/* base.css paints links indigo, and indigo on the indigo ground is 1.0:1. The indigo
   focus ring vanishes there too. Same two fixes the dark footer uses: paper links
   (10.7:1) and an amber ring (5.3:1; a ring is graphical, so amber is allowed).
   Both rules match base.csss specificity and win on source order alone. */
.c-band--indigo a{ color:var(--ssl-paper); text-decoration:underline; }
.c-band--indigo :focus-visible{ outline-color:var(--ssl-accent); }
/* .ssl-eyebrow sets color:var(--ssl-slate) ON THE ELEMENT, which beats the colour
   inherited from the band, so a slate eyebrow on indigo measures 1.33:1 and is
   unreadable. Every slate small print that can land inside the indigo ground is
   switched to paper here. Do not fix this by dropping .ssl-eyebrow from the markup:
   the class is what makes an eyebrow an eyebrow everywhere else.
   The compound .ssl-scope form is deliberate: some of these are already declared at
   (0,2,0) further down this file, so a (0,2,0) rule here would lose on source order. */
.ssl-scope .c-band--indigo .ssl-eyebrow,
.ssl-scope .c-band--indigo .c-stat__label,
.ssl-scope .c-band--indigo .c-field__hint{ color:var(--ssl-paper); }
/* Headings and ledes get their ink from base.css AT THE ELEMENT, which beats the colour
   inherited from the band ground, exactly like the eyebrow case above. The go-live audit
   measured the blog and about CTA band h2s at 1.15:1 (ink on indigo). Same fix, same
   specificity reasoning: name them explicitly on the indigo ground. */
.ssl-scope .c-band--indigo .c-band__title,
.ssl-scope .c-band--indigo .c-band__lede,
.ssl-scope .c-band--indigo .c-band__dek{ color:var(--ssl-surface); }

/* Element set. These are paragraphs and a heading, so they need the compound
   .ssl-scope form to outrank base.css (see the specificity note at the top). */
.ssl-scope .c-band__eyebrow{
  margin:0 0 var(--ssl-s-3); max-width:none;
  font-size:var(--ssl-fs-1); font-weight:700; letter-spacing:.14em; text-transform:uppercase;
}
.ssl-scope .c-band__title{ margin:0 0 var(--ssl-s-4); }
.ssl-scope .c-band__lede{ margin:0 0 var(--ssl-s-5); max-width:60ch; font-size:var(--ssl-fs-3); }
/* symmetric margins so the body reads the same whether it sits above or below the
   quote grid; adjacent block margins collapse, so this never double-counts. */
.ssl-scope .c-band__body{ margin:var(--ssl-s-5) 0; }
/* the line that introduces the quotes (What past learners say:). Weight, not colour:
   a dimmed grey is exactly what fails on the indigo ground. */
.ssl-scope .c-band__label{ margin:0 0 var(--ssl-s-4); max-width:none; font-weight:700; }
.c-band__actions{
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-4); margin-block-start:var(--ssl-s-6);
}
/* the secondary action is a question or a soft offer, so it is a link, not a second
   button: one button keeps a single visual priority in the band. */
.ssl-scope .c-band__aside{ margin:var(--ssl-s-4) 0 0; max-width:none; font-weight:700; }
/* the asides LINK needs the same treatment .c-hero__aside gets: without it the anchor is
   a bare inline box roughly 20px tall, well under --ssl-tap, and it is the softest action
   on the page (the cohort waitlist) so it is the one most worth being able to hit. */
.ssl-scope .c-band__aside a{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap);
}
@media (min-width:1024px){
  .ssl-scope .c-band__title{ font-size:var(--ssl-fs-6); letter-spacing:var(--ssl-track-6); }
}

/* ---------------------------------------------------------------------------
   c-quote. real peoples words, and it ships on both the indigo band and the paper
   results section, so it carries no ground of its own: no background, no box, no
   radius, no decorative quotation glyph (the marks are in the copy, and a CSS glyph
   is announced by some screen readers). Hierarchy inside is size and weight only,
   never a dimmed grey, because grey is what fails on indigo. Never restyle the
   wording, only the type.                                                       */
.c-quote{ display:flex; flex-direction:column; gap:var(--ssl-s-3); }
.ssl-scope .c-quote__text{
  margin:0; max-width:none;
  font-family:var(--ssl-serif); font-size:var(--ssl-fs-3); line-height:var(--ssl-lh-body);
}
.ssl-scope .c-quote__cite{ margin:0; max-width:none; font-size:var(--ssl-fs-1); font-weight:700; }

/* ---------------------------------------------------------------------------
   c-card--interactive. the ONE interaction layer for clickable cards.
   c-router-card is named alongside it (not given its own copy) because its
   markup does not carry .c-card, and hover/focus must not be written twice.
   The stretched link makes the whole card the target; the ring moves to the
   card so a keyboard user sees the real hit area, not a three-word title.
   Transitions are transform + border-color ONLY: box-shadow is expensive to
   composite and a grid of cards would shimmer. translateY caps at 2px.       */
.c-card--interactive,
.c-router-card{
  position:relative;
  transition:transform var(--ssl-dur-ui) var(--ssl-ease-out),
             border-color var(--ssl-dur-ui) var(--ssl-ease-out);
}
/* The overlay has to sit on the ANCHOR. If the link is ever authored as a core
   wp:button the class lands on the wrapper div instead, and an overlay on a div is not
   a link: the card would look clickable and do nothing. Same bridge as c-btn, so both
   forms are covered here rather than in the markup. The audience router deliberately
   ships the bare-anchor form in a wp:html block (see the patterns own note), which is
   why the bare selector must keep working. */
.c-card__link::after,
.c-router-card__link:not(.wp-block-button)::after,
.c-router-card__link.wp-block-button > .wp-block-button__link::after{
  content:""; position:absolute; inset:0;
}
/* card titles read as ink headings; the affordance is the card state, plus an
   underline on hover, so nothing is carried by colour alone. */
.ssl-scope .c-card__link{ color:inherit; }
@media (hover:hover) and (pointer:fine){
  .c-card--interactive:hover{ transform:translateY(-2px); border-color:var(--ssl-slate); }
  .c-card--interactive:hover .c-card__link{ text-decoration:underline; }
}
.c-card--interactive:active,
.c-router-card:active{ transform:scale(.99); }
.c-card--interactive:focus-within,
.c-router-card:focus-within{ outline:2px solid var(--ssl-primary); outline-offset:2px; }
.c-card--interactive:focus-within .c-card__link:focus-visible,
.c-router-card:focus-within .c-router-card__link:focus-visible{ outline:none; }
@media (prefers-reduced-motion:reduce){
  .c-card--interactive:active,
  .c-router-card:active{ transform:none; }
}

/* ---------------------------------------------------------------------------
   c-hero. the LCP element. Text only: no image, no background-image, no JS
   gate, no entrance animation, no IntersectionObserver. It must paint on the
   first frame (the old JS-gated carousel hero is the documented 5.2s LCP).
   The art starts display:none, so hidden below 768 costs no extra query.   */
.c-hero{ padding-block:var(--ssl-s-7); }
.c-hero__inner{ display:grid; gap:var(--ssl-s-6); }
/* grid child: without min-width:0 a long unbreakable token (a tool name, a URL in the
   dek) blows the text column past its track instead of wrapping inside it. */
.c-hero__body{ min-width:0; }
.ssl-scope .c-hero__eyebrow{ margin:0 0 var(--ssl-s-3); }
.ssl-scope .c-hero__title{ font-size:var(--ssl-fs-5); letter-spacing:var(--ssl-track-5); margin:0 0 var(--ssl-s-4); }
.ssl-scope .c-hero__dek{ font-size:var(--ssl-fs-3); color:var(--ssl-slate); margin:0; }
/* the campus answer capsule: the 40 to 60 word direct answer that AEO extracts. It is
   set apart by the hairline (ADR-0004), not by a tinted box, and keeps ink text rather
   than the deks slate because it is the thing most likely to be quoted. */
.ssl-scope .c-hero__capsule{
  margin:var(--ssl-s-5) 0 0; max-width:60ch;
  border-inline-start:1px solid var(--ssl-line); padding-inline-start:var(--ssl-s-4);
}
/* the single proof line, used where the campus hero has no stat row. Sits where the
   stat row would, so both hero variants have the same rhythm below the actions. */
.ssl-scope .c-hero__proof{
  margin:var(--ssl-s-7) 0 0; max-width:none; font-weight:700;
}
.c-hero__actions{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:var(--ssl-s-4); margin-block-start:var(--ssl-s-6);
}
/* the second home action is a question, and a question is a link, not a button.
   One button keeps a single visual priority in the hero. */
.ssl-scope .c-hero__aside{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap); font-weight:700;
}
/* stats: a wrapping row at every width rather than a 1-col list below 480, so
   the component needs no max-width query and no raw breakpoint of its own. */
.c-hero__stats{
  list-style:none; margin:var(--ssl-s-7) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-5) var(--ssl-s-6);
  /* end, not start: the approved proof line ends on Small batches, max 10, which is a
     label with no number in front of it. Top-aligned, that item floats level with the big
     numerals and reads as a stray caption. Bottom-aligned, every label sits on one baseline
     and the numbers rise above it, which is what the row is meant to look like. */
  align-items:end;
}
.c-stat{ display:flex; flex-direction:column; gap:var(--ssl-s-1); }
/* campus hero: chips instead of stats */
.c-hero__chips{
  list-style:none; margin:var(--ssl-s-6) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-3);
}
/* the motif is decoration and nothing else: aria-hidden in the markup, hidden below 768
   here, and NEVER animated (it would compete with the LCP heading for the first frame).
   The linework inherits indigo and dials back with opacity alone, so the drawing stays
   one colour and one token.
   THE NODE IS THE EXCEPTION, and it is deliberate. Brand guideline §1 and §7 both put the
   hub node in --accent, and it is the logos own amber node recurring as the section motif.
   Painting it currentColor made the entire first screen of the homepage amber-free: the
   stat numbers are ink by ADR-0015 (amber failed contrast at that size), so with an indigo
   node there was no accent above the fold at all on the sites most visited page. At this
   scale the node is a graphical mark, not text, so --accent-strong is the right token and
   the AA text rules do not apply to it. */
.c-hero__art{ display:none; color:var(--ssl-primary); }
.c-hero__art-line{ opacity:.22; }   /* the hexagon outlines and connectors: quietest */
.c-hero__art-node{ color:var(--ssl-accent-strong); opacity:1; }  /* the one amber moment */
.c-hero__art-dot{ opacity:.35; }    /* the two satellite dots sit between the two */
@media (min-width:768px){
  .c-hero{ padding-block:var(--ssl-s-8); }
  /* tracking follows the size up: the same value at 33px and 52px is wrong at one of them */
  .ssl-scope .c-hero__title{ font-size:var(--ssl-fs-6); letter-spacing:var(--ssl-track-6); }
  .c-hero__art{ display:block; }
  .c-hero__art-svg{ display:block; width:100%; height:auto; }
}
/* the two hero variants. stage-3 §6 specifies the campus hero as text-led: an answer
   capsule and chips, no line-art. So the art slot is suppressed there at every breakpoint,
   and the two-column split belongs to --home, the only variant that has something to put
   in the second column. Binding the split to the variant rather than to .c-hero__inner is
   what gives the campus capsule the full measure without a second override to undo. */
.c-hero--campus .c-hero__art{ display:none; }

/* THE HERO PHOTOGRAPH. The campus hero was specified text-led and carried no second column
   at all; Shiva asked for the branch photograph in it, so --has-image gives it one.
   Bound to the class the template sets, not to :has(.c-hero__figure), so the column exists
   from the first frame rather than after the image resolves.
   DOM order is text then figure and stays that way: on a phone the photograph must not push
   the H1 and the CTA down the page, and reordering it in CSS would split visual order from
   focus order, which is the same rule c-router already carries. */
.c-hero__figure{ margin:0; min-width:0; }
.c-hero__img{
  display:block; width:100%; height:auto; aspect-ratio:4/3; object-fit:cover;
  /* RIGHT, and the value was measured rather than guessed. The three things that say WHO,
     WHAT and WHERE all sit right of centre: the STEMSKILLS INSTITUTE science fair poster, the
     molecular models on the table, the hillside through the corridor. On desktop the figure
     stretches to the text columns height, so the box goes to about 1.03 aspect against a
     1.333 source and only 77.5% of the width survives. Solving for the window: 62% showed
     x 0.140..0.914 and sliced the poster, which is the one element that answers the criticism
     that killed the previous hero (just students, could be from anywhere). Only 100% reaches
     x 0.225..1.000 and keeps the poster whole. It costs the leftmost student, which is the
     right trade: she is the most replaceable thing in the frame and the branding is not.
     On mobile the box is 4:3 like the source, so nothing is cropped and this has no effect. */
  object-position:100% 50%;
  border-radius:var(--ssl-radius);
  /* the photograph is a real edge against --paper, and this is the one place a shadow is
     doing work rather than decorating: it separates a light image from a light ground. */
  box-shadow:var(--ssl-shadow-panel);
}
@media (min-width:1024px){
  .c-hero{ padding-block:var(--ssl-s-9); }
  .c-hero--home .c-hero__inner{
    grid-template-columns:minmax(0,7fr) minmax(0,5fr);
    gap:var(--ssl-s-7); align-items:center;
  }
  /* 6fr/5fr, not the home heros 7fr/5fr: this heros text column is now a short dek rather
     than a 60-word capsule, so it needs less width, and the photograph earns the difference. */
  .c-hero--has-image .c-hero__inner{
    grid-template-columns:minmax(0,6fr) minmax(0,5fr);
    gap:var(--ssl-s-7);
    /* STRETCH, not centre, and the image fills the row it is given.
       At 16:9 beside a text column running eyebrow to buttons, the photograph came out about
       340px tall in a 575px row and sat marooned in the middle with 120px of dead space above
       and below it. It read as an afterthought dropped next to the copy rather than half of a
       composition. Letting the figure take the full row height and cropping with object-fit
       turns it into a portrait-shaped panel that squares off against the text.
       This crops rather than resamples, so it costs no quality: the browser takes a 720x900
       window out of the 1600x900 file for a slot about 460px wide, which is still a downscale.
       A purpose-cut portrait derivative would have been WORSE, because the original behind
       this frame is only 1280x575 and a 4:5 cut of it is 460x575, with nothing left over. */
    align-items:stretch;
  }
  .c-hero--has-image .c-hero__figure{ display:flex; }
  .c-hero--has-image .c-hero__img{ height:100%; aspect-ratio:auto; }
  .ssl-scope .c-hero__title{ font-size:var(--ssl-fs-7); letter-spacing:var(--ssl-track-7); }
}

/* ---------------------------------------------------------------------------
   c-router. the audience router. 1 col, 2x2 at 768, 4 across at 1024.
   DOM order is fixed (online audiences first, ADR-0009) and must never be
   reordered in CSS: that would split visual order from focus order.          */
.c-router{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr; gap:var(--ssl-s-4);
}
@media (min-width:768px){
  .c-router{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--ssl-s-5); }
}
@media (min-width:1024px){
  .c-router{ grid-template-columns:repeat(4,minmax(0,1fr)); }
}
.c-router-card{
  display:flex; flex-direction:column; gap:var(--ssl-s-3); height:100%;
  background:var(--ssl-surface);
  border:1px solid var(--ssl-line);
  border-block-start:var(--ssl-s-1) solid var(--ssl-primary);
  border-radius:var(--ssl-radius); padding:var(--ssl-s-5);
}
/* the top rule is decorative only (amber for the two online audiences, indigo
   for the two physical ones). The audience is named in the h3, so nothing is
   carried by colour alone and the rule needs no aria. */
.c-router-card--postgraduate{ border-block-start-color:var(--ssl-accent); }
.c-router-card--researcher{ border-block-start-color:var(--ssl-accent); }
.c-router-card--school-student{ border-block-start-color:var(--ssl-primary); }
.c-router-card--institution{ border-block-start-color:var(--ssl-primary); }
/* hover moves the sides and the base only, so the decorative rule keeps its colour */
@media (hover:hover) and (pointer:fine){
  .c-router-card:hover{
    transform:translateY(-2px);
    border-inline-color:var(--ssl-slate); border-block-end-color:var(--ssl-slate);
  }
}
.ssl-scope .c-router-card__title{ font-size:var(--ssl-fs-3); }
.ssl-scope .c-router-card__body{ margin:0; max-width:none; color:var(--ssl-slate); }
.ssl-scope .c-router-card__link{
  margin-block-start:auto; display:inline-flex; align-items:center;
  gap:var(--ssl-s-2); min-height:var(--ssl-tap); font-weight:700;
}
/* arrow drawn in CSS so no > character ever lands in the accessible name */
.c-router-card__arrow{
  flex:none; width:var(--ssl-s-2); height:var(--ssl-s-2);
  border-block-start:1px solid currentColor; border-inline-end:1px solid currentColor;
  transform:rotate(45deg);
}

/* ---------------------------------------------------------------------------
   c-split. two equal panels. No featured half, no background fill: the depth
   model is the 1px hairline (ADR-0004). The separator is horizontal when the
   panels stack and vertical when they sit side by side.                      */
.c-split{ display:grid; grid-template-columns:1fr; gap:var(--ssl-s-6); }
.c-split__panel + .c-split__panel{
  border-block-start:1px solid var(--ssl-line); padding-block-start:var(--ssl-s-6);
}
.ssl-scope .c-split__title{ font-size:var(--ssl-fs-4); margin:0 0 var(--ssl-s-3); }
/* drop the 70ch measure, or one column ends short and the pair reads ragged */
.ssl-scope .c-split__body{ max-width:none; }
/* one action per panel, and the two panels must agree, so the row is defined here
   rather than left to the core wp-block-buttons gap. The panel is a column and the
   action row takes the leftover space above it, so the two buttons land on the same
   line even when one panels heading wraps to two lines and the others does not.
   Stacked, each panel is its own grid row, there is no leftover space, and the auto
   margin resolves to zero, so this costs nothing on mobile. */
.c-split__panel{ display:flex; flex-direction:column; }
.c-split__actions{
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-4);
  margin-block-start:auto; padding-block-start:var(--ssl-s-5);
}
@media (min-width:768px){
  .c-split{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--ssl-s-7); }
  .c-split__panel + .c-split__panel{
    border-block-start:0; padding-block-start:0;
    border-inline-start:1px solid var(--ssl-line); padding-inline-start:var(--ssl-s-7);
  }
}
@media (min-width:1024px){
  .c-split{ gap:var(--ssl-s-8); }
  .c-split__panel + .c-split__panel{ padding-inline-start:var(--ssl-s-8); }
}

/* ---------------------------------------------------------------------------
   c-profile. mentors and coaching faculty. 1 col, 2 at 768, 3 at 1024.
   NOT clickable as a whole: only the ORCID link is interactive, so
   c-card--interactive must never be applied here. A four-item set wraps 3 + 1
   and the orphan stays in column 1 (align-items:start keeps rows honest when
   the teacher variant has no photo).                                         */
.c-profile-grid{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr; gap:var(--ssl-s-5); align-items:start;
}
/* TEACHER CARDS GO TWO ACROSS ON THE SMALLEST SCREENS. A mentor card is a name, a role, a
   research line and an ORCID row, and it needs the full width to hold that. A teacher card is
   a photograph, a name and one word of subject. In a single column the six of them measured
   2012px on a 375px viewport: two and a half screens of scrolling, on the section a parent
   scrutinises hardest, to read six names. Two across halves it and lets the whole team be
   taken in at once, which is the point of the section. Scoped by :has() so the homepage
   mentor grid keeps its single column. */
.c-profile-grid:has(.c-profile--teacher){ grid-template-columns:repeat(2,minmax(0,1fr)); }
@media (min-width:768px){
  .c-profile-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (min-width:1024px){
  .c-profile-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--ssl-s-6); }
  /* The coaching faculty is a set of FOUR, and four in a three-column grid strands the
     last teacher alone on row two. On the section a parent scrutinises hardest (who will
     teach my child?), that reads as an afterthought, and it puts a spotlight on the one
     person without a Dr. title that an even row would have carried quietly. A teacher card
     is a name plus a subject line with no photo, so it needs far less width than a mentor
     card: 2x2 fits comfortably and keeps the set reading as one team. Scoped by :has() to
     the teacher variant so the three-mentor homepage grid is untouched. */
  /* six teachers now, not four, so three across leaves two clean rows with no orphan.
     The 2x2 that was here was correct for the four-name set it was written against. */
  .c-profile-grid:has(.c-profile--teacher){ grid-template-columns:repeat(3,minmax(0,1fr)); }
}
.c-profile{ display:flex; flex-direction:column; gap:var(--ssl-s-2); }
/* NEITHER VARIANT CARRIES A PHOTO ANY MORE (2026-07-26). The mentor card used to sit
   under a full-width square, and while headshots stayed blocked that square rendered as
   an empty grey box. Three empty boxes in a row read as these people do not exist,
   which is the opposite of what a credibility section is for, so the slot was removed and
   the card now leads with clickable researcher IDs instead. See patterns/home-mentors.php.
   Consequence for layout: with the photo gone, a mentor card has no top edge, and three
   of them in a row read as loose text rather than as cards. That is exactly the problem
   the teacher variant already solved, so it takes the same hairline (ADR-0004: depth is a
   1px rule, never a box). */
.c-profile--mentor{
  gap:var(--ssl-s-3);
  border-block-start:1px solid var(--ssl-line); padding-block-start:var(--ssl-s-4);
}
/* Teacher: the branch faculty, and as of 2026-08-03 they HAVE photographs, cropped from
   the staff ID cards. That reverses the old reasoning here. The hairline existed only
   because there was no photo and four names in a row read as loose text; a portrait
   supplies its own edge, so the rule would now be a second edge doing nothing. */
.c-profile--teacher{ text-align:center; }
/* The source photographs are circular on white (see build-faculty-photos.py: the ID cards
   hexagon frame made any square crop catch its chevron). Matching that here with a round
   frame means the image and its container agree instead of a circle sitting inside a
   rounded square. */
.c-profile--teacher .c-profile__photo{
  border-radius:50%; max-width:180px; margin-inline:auto;
}
.c-profile__photo{
  width:100%; height:auto; aspect-ratio:1; object-fit:cover;
  border-radius:var(--ssl-radius); margin-block-end:var(--ssl-s-3);
}
/* photos are blocked on Shiva. The placeholder is a plain div, never an image element
   with empty alt, so nothing at all is announced. Hexagon motif from the logo. */
.c-profile__photo--placeholder{
  background:var(--ssl-primary-tint); display:grid; place-items:center;
}
.c-profile__photo--placeholder::before{
  content:""; width:40%; aspect-ratio:1; background:var(--ssl-primary); opacity:.16;
  clip-path:polygon(50% 0%,93% 25%,93% 75%,50% 100%,7% 75%,7% 25%);
}
.ssl-scope .c-profile__name{ font-size:var(--ssl-fs-3); }
.ssl-scope .c-profile__role{ margin:0; max-width:none; color:var(--ssl-slate); }
.ssl-scope .c-profile__meta{
  margin:0; max-width:none; color:var(--ssl-slate); font-size:var(--ssl-fs-1);
}
/* a bare 14px word is not a tap target */
.ssl-scope .c-profile__link{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap); font-weight:700;
}

/* ---------------------------------------------------------------------------
   c-pub-row. the research wall. The plainest component in the system by
   design: no background, no border box, no radius, no shadow. The only
   separator is the 1px hairline, and density is the point. The title has to be
   reset off base.css or it renders as a 21px serif h3 and the density dies.   */
.c-pub-list{ list-style:none; margin:0; padding:0; }
.c-pub-row{ padding-block:var(--ssl-s-4); }
.c-pub-row + .c-pub-row{ border-block-start:1px solid var(--ssl-line); }
.ssl-scope .c-pub-row__title{
  font-family:var(--ssl-sans); font-size:var(--ssl-fs-2); font-weight:700;
  line-height:var(--ssl-lh-body); margin:0 0 var(--ssl-s-1);
}
/* the whole row is deliberately NOT stretched-clickable: rows are dense and
   adjacent full-row targets would be ambiguous. The title is the link. */
.ssl-scope .c-pub-row__authors{
  margin:0 0 var(--ssl-s-1); max-width:none;
  color:var(--ssl-slate); font-size:var(--ssl-fs-1);
}
/* ours is carried by <strong>, by weight and semantics, never by colour */
.c-pub-row__author--ours{ color:var(--ssl-ink); }
.ssl-scope .c-pub-row__meta{
  margin:0; max-width:none; color:var(--ssl-slate); font-size:var(--ssl-fs-1);
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--ssl-s-3);
}
.ssl-scope .c-pub-row__doi{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap); font-weight:700;
}
@media (min-width:1024px){
  .ssl-scope .c-pub-row__meta{ gap:var(--ssl-s-4); }
}
/* genuinely tabular presentation only, and always inside .ssl-scroll-x (base.css)
   so the page body never scrolls sideways. Element selectors on th/td are
   unavoidable: cell markup is generated by the editor, not authored by us. */
.c-pub-table{ width:100%; border-collapse:collapse; font-size:var(--ssl-fs-1); }
.c-pub-table th,
.c-pub-table td{
  padding:var(--ssl-s-3) var(--ssl-s-4); text-align:start; vertical-align:top;
  border-block-end:1px solid var(--ssl-line);
}
.c-pub-table th{ font-weight:700; }

/* ---------------------------------------------------------------------------
   c-blog-card. composes .c-card .c-card--interactive. Padding drops to 0 so
   the thumb bleeds to the card edge; __body carries the padding back.        */
.c-blog-grid{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr; gap:var(--ssl-s-5);
}
@media (min-width:768px){
  .c-blog-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (min-width:1024px){
  .c-blog-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--ssl-s-6); }
}
.c-blog-card{ display:flex; flex-direction:column; height:100%; padding:0; overflow:hidden; }
.c-blog-card__thumb{
  width:100%; height:auto; aspect-ratio:16/9; object-fit:cover;
  border-radius:var(--ssl-radius) var(--ssl-radius) 0 0;
}
.c-blog-card__body{
  flex:1; display:flex; flex-direction:column;
  gap:var(--ssl-s-3); padding:var(--ssl-s-5);
}
.ssl-scope .c-blog-card__cat{ margin:0; max-width:none; }
.ssl-scope .c-blog-card__title{ font-size:var(--ssl-fs-3); margin:0; }
.ssl-scope .c-blog-card__dek{ margin:0; max-width:none; color:var(--ssl-slate); }
/* date is pushed to the baseline so cards in a row line up along the bottom */
.ssl-scope .c-blog-card__date{
  margin:0; margin-block-start:auto; max-width:none;
  color:var(--ssl-slate); font-size:var(--ssl-fs-1);
}

/* ---------------------------------------------------------------------------
   c-faq. native <details>/<summary>, ZERO JavaScript. The element already
   gives the disclosure role, the expanded state, Enter and Space, correct
   focus, and browser find-in-page can open a collapsed answer. A button plus
   aria-expanded would need JS to do worse.
   No name attribute on <details>: an exclusive accordion collapses the item
   the user just read and jumps the scroll position. Multiple open is fine.
   The question is a <span> inside <summary>, never a heading: a heading nested
   in summary is announced inconsistently, and the sections own h2 sits above.
   Height is never animated (the motion law bans it), so an open item appears
   instantly; only the answers opacity is eased in.
   The root takes the outer hairlines so the stack has a top and a bottom edge; the
   item rule below only has to draw the ones between.                          */
.c-faq{ border-block:1px solid var(--ssl-line); }
.c-faq__item + .c-faq__item{ border-block-start:1px solid var(--ssl-line); }
.c-faq__q{
  list-style:none; display:flex; align-items:center; justify-content:space-between;
  gap:var(--ssl-s-4); min-height:var(--ssl-tap); padding-block:var(--ssl-s-4);
  cursor:pointer; font-weight:700; font-size:var(--ssl-fs-2); color:var(--ssl-ink);
}
.c-faq__q::-webkit-details-marker{ display:none; }
/* chevron: the 45deg lives on the pseudo element, so the open state can be a
   plain rotate(180deg) on the box without unwinding the arrow. */
.c-faq__icon{
  position:relative; flex:none; width:var(--ssl-s-5); height:var(--ssl-s-5);
  transition:transform var(--ssl-dur-ui) var(--ssl-ease-out);
}
.c-faq__icon::before{
  content:""; position:absolute; inset-block-start:50%; inset-inline-start:50%;
  width:var(--ssl-s-2); height:var(--ssl-s-2);
  border-block-end:1px solid currentColor; border-inline-end:1px solid currentColor;
  transform:translate(-50%,-50%) rotate(45deg);
}
/* the rotation is decoration: the state is carried by the native expanded flag
   and by the answer becoming visible, never by the chevron alone. */
.c-faq__item[open] .c-faq__icon{ transform:rotate(180deg); }
.c-faq__a{ padding-block-end:var(--ssl-s-4); }
.c-faq__item[open] .c-faq__a{ animation:ssl-faq-in var(--ssl-dur-ui) var(--ssl-ease-out) both; }
@keyframes ssl-faq-in{ from{ opacity:0; transform:translateY(-2px); } to{ opacity:1; transform:none; } }
@media (min-width:1024px){
  .c-faq__q{ font-size:var(--ssl-fs-3); }
  .c-faq__a{ padding-block-end:var(--ssl-s-5); }
}

/* ---------------------------------------------------------------------------
   c-field. the only form primitive in batch 1. Written once here because the
   footer newsletter and the campus counsellor both need an input and would
   otherwise ship two different ones. Label always visible, always tied by
   for/id; a placeholder never carries the label. The --ssl-surface fill with
   ink text is kept even on the dark footer, which solves value and placeholder
   contrast in one move instead of needing a dark-ground variant.             */
.c-field{ display:flex; flex-direction:column; gap:var(--ssl-s-2); }
.c-field__label{ font-size:var(--ssl-fs-1); font-weight:700; }
/* .ssl-scope prefix is LOAD-BEARING, not style. MasterStudy ships
   `input[type=email], input[type=text], textarea { background:#F0F2F5 }` at (0,1,1),
   which beats a bare class: on the Submit Opportunity form every text/email input rendered
   grey while the url input (absent from their list) stayed white. (0,2,0) restores one look
   for all five. Measured on the rendered page, not inferred. */
.ssl-scope .c-field__input{
  width:100%; min-height:var(--ssl-tap); padding:var(--ssl-s-3) var(--ssl-s-4);
  background:var(--ssl-surface); color:var(--ssl-ink);
  border:1px solid var(--ssl-line); border-radius:var(--ssl-radius); font:inherit;
}
.ssl-scope .c-field__hint{
  margin:0; max-width:none; font-size:var(--ssl-fs-1); color:var(--ssl-slate);
}
/* Validation states, added for the Submit Opportunity form (ADR-0026). The error is a
   MESSAGE, never colour alone: the border thickens and reddens AND a role=alert line
   appears under the field, so the state survives colour-blindness and screen readers.
   1.5px, from the comp: at 1px the red border reads as a focus quirk, at 2px it shouts. */
.ssl-scope .c-field--invalid .c-field__input{ border:1.5px solid var(--ssl-error); }
.ssl-scope .c-field__error{
  margin:0; max-width:none; font-size:var(--ssl-fs-1); color:var(--ssl-error);
}
/* optional-marker beside a label, so optional never masquerades as part of the name */
.c-field__opt{ font-weight:400; color:var(--ssl-slate); font-size:var(--ssl-fs-1); }

/* ---------------------------------------------------------------------------
   c-counsellor. campus pages only. Intro, form and map stack on mobile; at
   1024 intro and form sit side by side with the map spanning both. This
   component is never position:sticky itself: the mobile sticky action is
   c-cta-bar--campus, a separate component.                                   */
.c-counsellor{ display:grid; gap:var(--ssl-s-6); }
.ssl-scope .c-counsellor__body{ color:var(--ssl-slate); }
.c-counsellor__actions{
  list-style:none; margin:0 0 var(--ssl-s-5); padding:0;
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-3);
}
.c-counsellor__places{ list-style:none; margin:0; padding:0; display:grid; gap:var(--ssl-s-3); }
.c-counsellor__place-name{ display:block; font-weight:700; }
.c-counsellor__place-addr{ color:var(--ssl-slate); }
.c-counsellor__map{ aspect-ratio:4/3; }
/* the embed markup is Googles, so the iframe carries no class of ours */
.c-counsellor__map iframe{ width:100%; height:100%; border:0; border-radius:var(--ssl-radius); }
/* SANCTIONED DESCENDANT-SELECTOR EXCEPTION, and the only one in this file:
   Contact Form 7 emits its own markup that we do not control, so the fields
   cannot be given c-field classes. Values are kept identical to c-field. */
.c-counsellor__form label{
  display:block; font-size:var(--ssl-fs-1); font-weight:700; margin-block-end:var(--ssl-s-2);
}
.c-counsellor__form input:not([type="submit"]),
.c-counsellor__form select,
.c-counsellor__form textarea{
  width:100%; min-height:var(--ssl-tap); padding:var(--ssl-s-3) var(--ssl-s-4);
  background:var(--ssl-surface); color:var(--ssl-ink);
  border:1px solid var(--ssl-line); border-radius:var(--ssl-radius); font:inherit;
}
/* the submit button is NOT styled here. CF7 takes classes in the form tag, so
   the shortcode carries [submit class:c-btn class:c-btn--primary ...] and the
   real c-btn rules apply. One button definition, not two. */
@media (min-width:1024px){
  .c-counsellor{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--ssl-s-7); }
  .c-counsellor__map{ grid-column:1 / -1; aspect-ratio:21/9; }
}

/* ===========================================================================
   GLOBAL CHROME. Every root below sits outside <main>, so each one carries
   .ssl-scope in its own markup. Miss that and the chrome silently loses
   tokens, focus rings and reduced-motion handling.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   c-site-header. sticky, and that is all it does. No shrink-on-scroll, no
   hide-on-scroll, no scroll listener: it is seen on every page on every
   scroll, and things seen that often do not animate.
   The full nav appears at 1024 ONLY. Below that it is the burger, one nav
   pattern instead of two, which is fewer moving parts across four builds.
   position:sticky makes the header the containing block for c-mega.          */
/* A MATERIAL, NOT A STRIP. Apples guidance for floating chrome (WWDC): build sticky bars as a
   translucent layer with content scrolling underneath, and use a soft edge where content meets
   the chrome rather than a hard divider. An opaque white bar with a 1px rule reads as a lid
   bolted on top of the page; a material reads as glass the page passes behind.
   The alpha stays high (.82) rather than fashionably low: nav labels and the wordmark sit on
   this, and §12s vibrancy rule is that text over a translucent surface must not lose contrast.
   The divider drops from --ssl-line to a barely-there rule, because the blur now does the
   separating. No scroll listener is involved, which keeps the no-JS-chrome rule intact. */
.c-site-header{
  position:sticky; inset-block-start:0; z-index:var(--ssl-z-header);
  background:color-mix(in srgb, var(--ssl-surface) 82%, transparent);
  -webkit-backdrop-filter:blur(20px) saturate(180%);
  backdrop-filter:blur(20px) saturate(180%);
  border-block-end:1px solid color-mix(in srgb, var(--ssl-line) 55%, transparent);
}
/* Both of these are real user settings, and translucency has to answer to them. Without the
   first, someone who asked the OS to reduce transparency still gets glass; without the second,
   a low-contrast reader gets a soft edge where they asked for a defined one. */
@media (prefers-reduced-transparency:reduce){
  .c-site-header{
    background:var(--ssl-surface);
    -webkit-backdrop-filter:none; backdrop-filter:none;
    border-block-end:1px solid var(--ssl-line);
  }
}
@media (prefers-contrast:more){
  .c-site-header{
    background:var(--ssl-surface);
    -webkit-backdrop-filter:none; backdrop-filter:none;
    border-block-end:1px solid var(--ssl-ink);
  }
}
.c-site-header__inner{
  display:flex; align-items:center; gap:var(--ssl-s-5);
  min-height:var(--ssl-header-h-sm);
}
/* the three logo slots are all a link wrapping one mark, so they share the box.
   ssl_theme_logo() emits the same pair everywhere: a link_class on the anchor and a class
   on the image element (or, until the file lands, on the .c-logo-text fallback span). */
/* the logo is a link to home, so it is a tap target like any other. The wordmark itself
   is only ~27px tall, so without a min-height the whole go home control sits under the
   44px minimum on touch. Height only: the width is already well past the minimum. */
.c-site-header__logo,
.c-site-footer__logo-link,
.c-mobile-nav__logo-link{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap);
}
.c-site-header__logo{ margin-inline-end:auto; }
/* capping by height keeps the mark token-driven whatever its aspect ratio */
.c-site-header__logo-img{
  display:block; width:auto; max-height:calc(var(--ssl-header-h-sm) - var(--ssl-s-5));
}
/* wordmark fallback while assets/img/logo.svg is missing. No colour: it inherits ink in
   the header and paper in the footer, so one rule is correct on both grounds. */
.c-logo-text{
  font-family:var(--ssl-serif); font-weight:700; font-size:var(--ssl-fs-2);
  letter-spacing:-.01em; white-space:nowrap;
}
.c-site-header__nav{ display:none; }
.c-nav{ list-style:none; margin:0; padding:0; display:flex; align-items:center; gap:var(--ssl-s-5); }
.c-nav__item{ display:flex; align-items:center; }
/* NOT position:relative, and that is load-bearing. c-mega is absolutely positioned with
   inset-inline:0 against the sticky header, which is the nearest positioned ancestor.
   Give this item a position and the panel collapses to the width of the word Programs. */
.c-nav__item--has-mega{ position:static; }
.ssl-scope .c-nav__link{
  display:inline-flex; align-items:center; gap:var(--ssl-s-2);
  min-height:var(--ssl-tap); color:var(--ssl-ink);
  font-family:inherit; font-size:var(--ssl-fs-1); font-weight:700;
  background:none; border:0; cursor:pointer;
}
/* Programs is a <button>, so aria-expanded is the single source of truth for
   the panel state. Driving the chevron off that attribute keeps it that way. */
.c-nav__chev{
  flex:none; width:var(--ssl-s-2); height:var(--ssl-s-2);
  border-block-end:1px solid currentColor; border-inline-end:1px solid currentColor;
  transform:rotate(45deg);
  transition:transform var(--ssl-dur-ui) var(--ssl-ease-out);
}
.c-nav__toggle[aria-expanded="true"] .c-nav__chev{ transform:rotate(225deg); }
.c-site-header__actions{ display:flex; align-items:center; gap:var(--ssl-s-3); }
.ssl-scope .c-site-header__phone{ display:none; }
/* campus template only; visible from 375 up there. nowrap because a bare phone number
   broken across three lines is unreadable and it drags the whole header row taller. */
.c-site-header--campus .c-site-header__phone{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap); font-weight:700;
  white-space:nowrap;
  transition:transform var(--ssl-dur-press) var(--ssl-ease-out);
}
/* Press feedback on the highest-intent action the school side has. A tel: link hands off
   to the dialer, and on a low-end Android that handoff can take several hundred ms during
   which nothing at all confirms the tap landed. Same scale and duration as c-btn, so the
   whole site presses identically. */
.c-site-header--campus .c-site-header__phone:active{ transform:scale(.97); }
@media (prefers-reduced-motion:reduce){
  .c-site-header--campus .c-site-header__phone:active{ transform:none; }
}
/* below 768 the sticky c-cta-bar carries this same action, so the header
   button would be a duplicate competing for a narrow row.
   SPECIFICITY: the element also carries .c-btn, and the Gutenberg bridge above states
   the buttons look through `.c-btn:not(.wp-block-button)` (0,2,0). A plain
   `.c-site-header__cta` (0,1,0) therefore loses and the button never hides. Matching
   both classes puts this back on equal footing, where source order decides and this
   rule, being later, wins. */
.c-site-header__cta.c-btn{ display:none; }
.c-site-header__burger{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:var(--ssl-tap); min-height:var(--ssl-tap);
  background:none; border:0; cursor:pointer; color:var(--ssl-ink);
  transition:transform var(--ssl-dur-press) var(--ssl-ease-out);
}
.c-site-header__burger:active{ transform:scale(.97); }
.c-site-header__bars{
  position:relative; display:block; width:var(--ssl-s-5); height:1px; background:currentColor;
}
.c-site-header__bars::before,
.c-site-header__bars::after{
  content:""; position:absolute; inset-inline:0; height:1px; background:currentColor;
}
.c-site-header__bars::before{ inset-block-start:calc(var(--ssl-s-2) * -1); }
.c-site-header__bars::after{ inset-block-start:var(--ssl-s-2); }
@media (min-width:768px){
  .c-site-header__inner{ min-height:var(--ssl-header-h); }
  .c-site-header__logo-img{ max-height:calc(var(--ssl-header-h) - var(--ssl-s-6)); }
  .c-site-header__cta.c-btn{ display:inline-flex; } /* both classes: see the hide rule above */
}
@media (min-width:1024px){
  .c-site-header__nav{ display:block; }
  .c-site-header__burger{ display:none; }
}
@media (prefers-reduced-motion:reduce){
  .c-site-header__burger:active{ transform:none; }
}

/* ---------------------------------------------------------------------------
   c-mega. a DISCLOSURE over a list of links, not an application menu. No
   role=menu, no roving tabindex, no arrow keys: those override link
   semantics and break Tab in a nav bar. No focus trap either.
   Click and keyboard only, never hover-open: hover-open needs intent timers,
   false-fires on touch, and gives keyboard and pointer users different
   behaviour. Closed state is opacity + visibility + inert, never the hidden
   attribute (an element cannot transition out of display:none in one frame).
   Column headings are <p>, so a nav panel never enters the page outline.
   Exists at 1024 and up only; below that the links re-render in c-mobile-nav
   from the same PHP source, so the two copies cannot drift.                  */
.c-mega{ display:none; }
@media (min-width:1024px){
  .c-mega{
    display:block;
    position:absolute; inset-inline:0; inset-block-start:100%;
    background:var(--ssl-surface); border-block-end:1px solid var(--ssl-line);
    box-shadow:var(--ssl-shadow-panel); z-index:var(--ssl-z-mega);
    transform-origin:top center;
    opacity:0; visibility:hidden; transform:translateY(-4px) scale(.98);
    /* the closed rule IS the exit, so it runs at the shorter press duration:
       exits leave faster than enters. Never enters from scale(0). */
    transition:opacity var(--ssl-dur-press) var(--ssl-ease-out),
               transform var(--ssl-dur-press) var(--ssl-ease-out),
               visibility 0s linear var(--ssl-dur-press);
  }
  .c-mega.is-open{
    opacity:1; visibility:visible; transform:none;
    transition:opacity var(--ssl-dur-ui) var(--ssl-ease-out),
               transform var(--ssl-dur-ui) var(--ssl-ease-out),
               visibility 0s linear 0s;
  }
  .c-mega__inner{
    display:grid; grid-template-columns:repeat(4,minmax(0,1fr));
    gap:var(--ssl-s-6); padding-block:var(--ssl-s-6);
  }
  /* grid child. The column headings are long (Institutional Partnerships), and without
     min-width:0 the longest one sets the track and the four columns stop being equal. */
  .c-mega__col{ min-width:0; }
  .ssl-scope .c-mega__heading{
    margin:0 0 var(--ssl-s-3); max-width:none;
    font-size:var(--ssl-fs-1); font-weight:700; letter-spacing:.14em;
    text-transform:uppercase; color:var(--ssl-slate);
  }
  .c-mega__list{ list-style:none; margin:0; padding:0; }
  /* ink, not indigo, so a panel of twenty links stays calm */
  .ssl-scope .c-mega__link{
    display:flex; align-items:center; min-height:var(--ssl-tap); color:var(--ssl-ink);
  }
}

/* ---------------------------------------------------------------------------
   c-mobile-nav. a modal full-screen sheet from 375 to 1023. The focus trap is
   the inert attribute set by JS on the header, #main and the footer, not Tab
   keydown maths: inert removes those subtrees from focus order and from the
   accessibility tree in one attribute and cannot drift out of sync.
   A visible close button inside the panel is mandatory; the burger sits behind
   the overlay and cannot be the only way out.
   The Programs accordion inside is native <details> and needs no JS at all.   */
.c-mobile-nav{
  position:fixed; inset:0; z-index:var(--ssl-z-nav);
  background:var(--ssl-paper); overflow-y:auto; overscroll-behavior:contain;
  opacity:0; visibility:hidden; transform:translateY(8px);
  transition:opacity var(--ssl-dur-press) var(--ssl-ease-out),
             transform var(--ssl-dur-press) var(--ssl-ease-out),
             visibility 0s linear var(--ssl-dur-press);
}
.c-mobile-nav.is-open{
  opacity:1; visibility:visible; transform:none;
  transition:opacity var(--ssl-dur-ui) var(--ssl-ease-out),
             transform var(--ssl-dur-ui) var(--ssl-ease-out),
             visibility 0s linear 0s;
}
/* background scroll lock, set by JS on <body> alongside the inert attributes */
.ssl-nav-open{ overflow:hidden; }
.c-mobile-nav__inner{
  display:flex; flex-direction:column; min-height:100%; padding-block-end:var(--ssl-s-7);
}
.c-mobile-nav__top{
  display:flex; align-items:center; justify-content:space-between; gap:var(--ssl-s-4);
  min-height:var(--ssl-header-h-sm); padding-inline:var(--ssl-s-4);
  border-block-end:1px solid var(--ssl-line);
}
.c-mobile-nav__logo{
  display:block; width:auto; max-height:calc(var(--ssl-header-h-sm) - var(--ssl-s-5));
}
/* the button is the 44px target; the X is drawn on the inner span, exactly as the
   burger draws its bars, so the glyph can never be mistaken for the hit area. */
.c-mobile-nav__close{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:var(--ssl-tap); min-height:var(--ssl-tap);
  background:none; border:0; cursor:pointer; color:var(--ssl-ink);
  transition:transform var(--ssl-dur-press) var(--ssl-ease-out);
}
.c-mobile-nav__close-icon{
  position:relative; display:block; width:var(--ssl-s-5); height:var(--ssl-s-5);
}
.c-mobile-nav__close-icon::before,
.c-mobile-nav__close-icon::after{
  content:""; position:absolute; inset-block-start:50%; inset-inline:0;
  height:1px; background:currentColor;
}
.c-mobile-nav__close-icon::before{ transform:rotate(45deg); }
.c-mobile-nav__close-icon::after{ transform:rotate(-45deg); }
.c-mobile-nav__close:active{ transform:scale(.97); }
.c-mobile-nav__nav{ flex:1; }
.c-mobile-nav__list{ list-style:none; margin:0; padding:0; }
.ssl-scope .c-mobile-nav__link,
.c-mobile-nav__summary{
  display:flex; align-items:center; justify-content:space-between; gap:var(--ssl-s-4);
  min-height:var(--ssl-tap); padding:var(--ssl-s-3) var(--ssl-s-4);
  border-block-end:1px solid var(--ssl-line);
  color:var(--ssl-ink); font-size:var(--ssl-fs-3); font-weight:700;
  list-style:none; cursor:pointer;
}
.c-mobile-nav__summary::-webkit-details-marker{ display:none; }
.c-mobile-nav__icon{
  position:relative; flex:none; width:var(--ssl-s-5); height:var(--ssl-s-5);
  transition:transform var(--ssl-dur-ui) var(--ssl-ease-out);
}
.c-mobile-nav__icon::before{
  content:""; position:absolute; inset-block-start:50%; inset-inline-start:50%;
  width:var(--ssl-s-2); height:var(--ssl-s-2);
  border-block-end:1px solid currentColor; border-inline-end:1px solid currentColor;
  transform:translate(-50%,-50%) rotate(45deg);
}
.c-mobile-nav__group[open] .c-mobile-nav__icon{ transform:rotate(180deg); }
/* nested <details> is banned: the four Programs groups are plain labelled
   lists inside the single disclosure, so any link is two taps away. */
.c-mobile-nav__panel{
  background:var(--ssl-surface); border-block-end:1px solid var(--ssl-line);
  padding:var(--ssl-s-2) var(--ssl-s-4) var(--ssl-s-5);
}
/* The chevron rotated but the panel itself teleported open. The FAQ, which is the same
   native <details> pattern, already had a bridge (ssl-faq-in), so this was one interaction
   pattern with two treatments in one codebase. Reusing that exact keyframe rather than
   writing a second one: same easing, same duration, nothing new to keep in sync.
   Reduced motion is already handled by the base layer, which zeroes animation-duration
   inside .ssl-scope. */
.c-mobile-nav__group[open] .c-mobile-nav__panel{
  animation:ssl-faq-in var(--ssl-dur-ui) var(--ssl-ease-out) both;
}
.ssl-scope .c-mobile-nav__heading{
  margin:var(--ssl-s-4) 0 var(--ssl-s-2); max-width:none;
  font-size:var(--ssl-fs-1); font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ssl-slate);
}
.c-mobile-nav__sublist{ list-style:none; margin:0; padding:0; }
.ssl-scope .c-mobile-nav__sublink{
  display:flex; align-items:center; min-height:var(--ssl-tap); color:var(--ssl-ink);
}
.c-mobile-nav__foot{
  display:flex; flex-direction:column; align-items:flex-start;
  gap:var(--ssl-s-4); padding:var(--ssl-s-5) var(--ssl-s-4);
}
.ssl-scope .c-mobile-nav__phone{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap); font-weight:700;
}
@media (min-width:1024px){ .c-mobile-nav{ display:none; } }
@media (prefers-reduced-motion:reduce){ .c-mobile-nav__close:active{ transform:none; } }

/* ---------------------------------------------------------------------------
   c-site-footer. dark ground, and the three overrides below are the most
   likely accessibility failure in this build, so they are stated explicitly.
   CONTRAST: paper #F7F7FB on ink #1A1A2E measures about 16:1, far past the AA
   4.5:1 minimum, so every line of footer text sits at ONE colour and hierarchy
   is carried by size and weight, never by a dimmed grey.
   Both this rule and base.csss .ssl-scope are one class, so components.css
   wins on source order alone. No !important anywhere.
   Columns stack as plain lists on mobile rather than accordions: four short
   lists are about twenty links, an accordion adds state and taps for nothing,
   and the footer is the one place users scroll to on purpose.                */
.c-site-footer{ background:var(--ssl-ink); color:var(--ssl-paper); padding-block:var(--ssl-s-8); }
/* (1) base.css paints links indigo, and #312E81 on ink is about 1.4:1,
   unreadable. Same (0,1,1) specificity, so source order carries it. */
.c-site-footer a{ color:var(--ssl-paper); }
/* (2) the indigo focus ring is invisible on ink. Amber on ink is about 7.9:1,
   and a focus ring is graphical, so the amber-is-never-text rule holds. */
.c-site-footer :focus-visible{ outline-color:var(--ssl-accent); }
/* (3) hairlines switch to the dark token; --ssl-line reads as a white stripe. */
.c-site-footer__inner{ display:grid; gap:var(--ssl-s-6); }
.c-site-footer__logo{ display:block; width:auto; max-height:calc(var(--ssl-header-h) - var(--ssl-s-6)); }
.c-site-footer__cols{ display:grid; grid-template-columns:1fr; gap:var(--ssl-s-6); }
/* grid child. Foundation (Class 10 and 12) is the longest link in the footer and
   would set the track width for all four columns without this. */
.c-site-footer__col{ min-width:0; }
/* headings are <p>, never h2/h3: a footer must not add to the page outline.
   Each list is named with aria-labelledby pointing at its heading id instead. */
.ssl-scope .c-site-footer__heading{
  margin:0 0 var(--ssl-s-3); max-width:none;
  font-size:var(--ssl-fs-1); font-weight:700; letter-spacing:.14em; text-transform:uppercase;
}
.c-site-footer__list{ list-style:none; margin:0; padding:0; }
.ssl-scope .c-site-footer__link{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap);
}
/* the newsletter input keeps the c-field --ssl-surface fill with ink text, which
   solves value and placeholder contrast on ink without a dark-ground variant.
   Subscribe is c-btn--warm: ink on amber (about 8.5:1), never white on amber,
   and it is the single amber moment in the footer. */
.c-site-footer__newsletter{ display:grid; gap:var(--ssl-s-3); justify-items:start; }
/* the address block. It carries the visible [address and email] placeholder until the
   real details are supplied, so it must sit at full footer contrast like every other
   line here, never dimmed to look like a stub. */
.ssl-scope .c-site-footer__address{ margin:0 0 var(--ssl-s-2); max-width:none; }
.c-site-footer__social{
  list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:var(--ssl-s-2);
}
/* icon-only, so every link carries an aria-label naming the network AND the
   brand; the inline glyph is aria-hidden and carries .c-icon. */
.ssl-scope .c-site-footer__social-link{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:var(--ssl-tap); min-height:var(--ssl-tap);
  border:1px solid var(--ssl-line-dark); border-radius:var(--ssl-radius);
  transition:transform var(--ssl-dur-press) var(--ssl-ease-out),
             border-color var(--ssl-dur-ui) var(--ssl-ease-out);
}
.c-icon{ width:var(--ssl-s-5); height:var(--ssl-s-5); fill:currentColor; }
.c-site-footer__social-link:active{ transform:scale(.97); }
@media (hover:hover) and (pointer:fine){
  .c-site-footer__social-link:hover{ border-color:var(--ssl-paper); }
}
.c-site-footer__legal{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:var(--ssl-s-4);
  border-block-start:1px solid var(--ssl-line-dark); padding-block-start:var(--ssl-s-5);
}
.ssl-scope .c-site-footer__copy{ margin:0; max-width:none; font-size:var(--ssl-fs-1); }
.c-site-footer__legal-links{
  list-style:none; margin:0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-4); font-size:var(--ssl-fs-1);
}
@media (min-width:768px){
  .c-site-footer__cols{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  /* field and Subscribe sit in a row from here up; the heading spans both */
  .c-site-footer__newsletter{ grid-template-columns:minmax(0,1fr) auto; align-items:end; }
  .c-site-footer__newsletter .c-site-footer__heading{ grid-column:1 / -1; }
}
@media (min-width:1024px){
  .c-site-footer__cols{ grid-template-columns:repeat(4,minmax(0,1fr)); }
  /* brand, newsletter and contact form a left rail; the four link columns
     sit to the right; social and legal run the full width. */
  .c-site-footer__inner{ grid-template-columns:minmax(0,1fr) minmax(0,2fr); gap:var(--ssl-s-7); }
  .c-site-footer__brand{ grid-column:1; grid-row:1; }
  .c-site-footer__cols{ grid-column:2; grid-row:1 / span 3; }
  .c-site-footer__newsletter{ grid-column:1; grid-row:2; }
  .c-site-footer__contact{ grid-column:1; grid-row:3; }
  .c-site-footer__social{ grid-column:1 / -1; }
  .c-site-footer__legal{ grid-column:1 / -1; }
}
@media (prefers-reduced-motion:reduce){ .c-site-footer__social-link:active{ transform:none; } }

/* ---------------------------------------------------------------------------
   c-cta-bar. mobile only, and purely in CSS. No scroll listener, no
   reveal-on-scroll, no entrance animation: it is present from first paint and
   simply stays. It ships LAST in the DOM, after the footer, so it never
   interrupts the reading or focus order of the page.
   It duplicates an action that also exists in the page body, which is
   intended, so both copies must carry the same label text.
   Placed after c-site-footer in this file on purpose: the footer clearance
   rule below has to win on source order.

   IT NOW STANDS DOWN while that body copy is actually on screen, which is the
   one thing duplicates an action, and that is intended did not survive
   contact with. Measured on /himachal/ at 375px: the heros own full-width
   Book a free class sits about 30px above the bars, so the first screen of
   the page showed the same primary button twice, and the closing band did it
   again at the foot. Duplication across a long scroll is useful; duplication
   inside one screen just looks like the page has lost count of itself.
   ssl-chrome.js adds .is-stood-down via an IntersectionObserver, so this is
   still not a scroll listener and the bar degrades to its old always-on
   behaviour with JS disabled.                                                */
.c-cta-bar{ display:none; }
@media (max-width:767px){
  .c-cta-bar{
    display:flex; gap:var(--ssl-s-3);
    position:fixed; inset-inline:0; inset-block-end:0; z-index:var(--ssl-z-sticky);
    background:var(--ssl-surface); border-block-start:1px solid var(--ssl-line);
    padding:var(--ssl-s-3) var(--ssl-s-4);
    padding-bottom:calc(var(--ssl-s-3) + env(safe-area-inset-bottom));
    transition:transform .22s ease-out, opacity .22s ease-out, visibility .22s;
  }
  /* visibility, not just transform: a bar parked off-screen must also leave the tab order and
     the accessibility tree, or a keyboard user tabs into two buttons they cannot see. The
     global reduced-motion rule in base.css already collapses the transition to nothing. */
  .c-cta-bar.is-stood-down{
    transform:translateY(100%); opacity:0; visibility:hidden;
  }
  /* the bar must never cover the legal row */
  .c-site-footer{ padding-block-end:calc(var(--ssl-s-8) + var(--ssl-s-5)); }
}
/* The bars own actions. This overrides c-btns width:100% below 480, or the campus
   bars two buttons stack and double the bar height. c-btn sets that at (0,2,0) via the
   :not() bridge, so the parent class is part of the selector to match it and win on
   source order, which this block has because it is last in the file. */
.c-cta-bar .c-cta-bar__action{ width:auto; flex:1 1 50%; }
.c-cta-bar--home .c-cta-bar__action{ flex-basis:100%; }
/* the campus bar carries TWO actions on a 375px row, one of them a phone number, so it
   trades horizontal padding for label room. Wrapping to a second line is the intended
   failure mode here; overflowing the viewport is not. */
.c-cta-bar--campus .c-cta-bar__action{ padding-inline:var(--ssl-s-3); }
/* the bar can never sit on top of the open mobile nav */
.ssl-nav-open .c-cta-bar{ display:none; }

/* ---------------------------------------------------------------------------
   c-cl-*. CAMPUS LIFE: the school sides activity feed.
   Rendered by inc/ssl-redesign-campus-life.php (ssl_cl_card / ssl_cl_strip) and by
   archive-campus_life.php / single-campus_life.php.

   The card is a BLOCK LINK, not the overlay pattern that c-card--interactive uses:
   ssl_cl_card() wraps the photo, meta line, title and dek in one <a>, so the anchor
   already is the whole hit area and there is nothing to overlay. That also means one
   tab stop per card instead of four, which matters on an archive of twelve.
   Consequence: focus styling goes on the anchor itself, and the hover lift goes on
   the article via :has(), with a plain :hover fallback for older Safari.             */
.c-cl-grid{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr; gap:var(--ssl-s-5);
}
@media (min-width:768px){
  .c-cl-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (min-width:1024px){
  .c-cl-grid{ grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--ssl-s-6); }
}

.c-cl-card{
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius); box-shadow:var(--ssl-shadow);
  overflow:hidden; position:relative;
  transition:transform var(--ssl-dur-ui) var(--ssl-ease-out),
             border-color var(--ssl-dur-ui) var(--ssl-ease-out);
}
.c-cl-card:has(.c-cl-card__link:hover){ transform:translateY(-2px); border-color:var(--ssl-primary); }
.c-cl-card:has(.c-cl-card__link:active){ transform:scale(.99); }
.c-cl-card:has(.c-cl-card__link:focus-visible){ outline:2px solid var(--ssl-primary); outline-offset:2px; }
/* The anchor keeps no ring of its own once the card carries one, or the two nest. */
.c-cl-card:has(.c-cl-card__link:focus-visible) .c-cl-card__link{ outline:none; }
@media (prefers-reduced-motion:reduce){
  .c-cl-card{ transition:none; }
  .c-cl-card:has(.c-cl-card__link:hover),
  .c-cl-card:has(.c-cl-card__link:active){ transform:none; }
}

.ssl-scope .c-cl-card__link{
  display:flex; flex-direction:column; height:100%;
  color:inherit; text-decoration:none;
}
.ssl-scope .c-cl-card__link:hover,
.ssl-scope .c-cl-card__link:focus-visible{ text-decoration:none; }
/* The title is the only part that should read as a link on hover. */
.ssl-scope .c-cl-card__link:hover .c-cl-card__title{ text-decoration:underline; }

.c-cl-card__media{ margin:0; }
.c-cl-card__img{
  display:block; width:100%; height:auto; aspect-ratio:4/3; object-fit:cover;
}
.c-cl-card__body{
  flex:1; display:flex; flex-direction:column;
  gap:var(--ssl-s-2); padding:var(--ssl-s-5);
}
/* The meta line carries type, date and place, joined with a middot in PHP. Slate on
   surface measures 8.0:1, so it clears AA at this size. */
.ssl-scope .c-cl-card__meta{
  margin:0; max-width:none;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate);
}
.ssl-scope .c-cl-card__title{
  margin:0; font-size:var(--ssl-fs-3); color:var(--ssl-ink);
}
.ssl-scope .c-cl-card__dek{
  margin:0; margin-block-start:auto; max-width:none;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate);
}

/* An upcoming activity is the one a parent can still act on, so it is marked.
   This WAS a 3px amber edge on one side. Changed for two reasons. ADR-0004 locks the depth
   model to the 1px hairline plus tonal ground, and a thick bar on one edge is a fifth
   device that belongs to neither. And a coloured left bar on a card is the single most
   recognisable tell of generated UI, which is a poor look on a site whose whole argument
   is that real people made it.
   The card already carries `border:1px solid var(--ssl-line)`, so recolouring that hairline
   marks the card using the systems own vocabulary and costs one property. Not a tinted
   ground: these cards sit on both paper and surface and a tint only works on one. */
.c-cl-card--upcoming{ border-color:var(--ssl-accent-strong); }

/* --- the archives type filter -------------------------------------------- */
/* Chips, not a <select>: there are seven fixed types, they are all worth seeing, and
   each is a real URL that can rank and be linked to. */
.c-cl-filter{
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-2);
  margin-block-end:var(--ssl-s-6);
}
.ssl-scope .c-cl-filter__chip{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap);
  padding:var(--ssl-s-2) var(--ssl-s-4);
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius-pill);
  font-size:var(--ssl-fs-1); font-weight:700;
  color:var(--ssl-primary); text-decoration:none;
  transition:background-color var(--ssl-dur-ui) var(--ssl-ease-out);
}
.ssl-scope .c-cl-filter__chip:hover{ background:var(--ssl-primary-tint); text-decoration:none; }
/* The current filter is indigo-on-tint at 9.9:1, matching c-chip, and also carries
   aria-current=page in the markup, so the state is not colour-only. */
.ssl-scope .c-cl-filter__chip.is-current{
  background:var(--ssl-primary-tint); border-color:var(--ssl-primary); 
}
@media (prefers-reduced-motion:reduce){
  .ssl-scope .c-cl-filter__chip{ transition:none; }
}

/* --- a single entry ------------------------------------------------------- */
/* Held to a reading measure rather than the full 1200px grid: these entries are a few
   sentences and a photograph, and full-width body text at 1200px is unreadable. */
.c-cl-single__head,
.c-cl-single__body{ max-width:70ch; }
.ssl-scope .c-cl-single__flag{
  margin:0 0 var(--ssl-s-2); max-width:none;
  font-size:var(--ssl-fs-1); font-weight:700;
  text-transform:uppercase; letter-spacing:.06em;
  color:var(--ssl-accent-strong);
}
.ssl-scope .c-cl-single__title{ margin:0 0 var(--ssl-s-3); }
.ssl-scope .c-cl-single__meta{
  margin:0; max-width:none;
  font-size:var(--ssl-fs-2); color:var(--ssl-slate);
}
.c-cl-single__media{ margin:var(--ssl-s-6) 0 0; }
.c-cl-single__img{
  display:block; width:100%; height:auto;
  border-radius:var(--ssl-radius);
}
.ssl-scope .c-cl-single__caption{
  margin:var(--ssl-s-3) 0 0; max-width:70ch;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate);
}
.c-cl-single__body{ margin-block-start:var(--ssl-s-6); }
/* Images inside the entry body: the team pastes a gallery, so cap and round them
   rather than letting a 4000px phone photo blow out the measure. */
.c-cl-single__body img{
  max-width:100%; height:auto; border-radius:var(--ssl-radius);
}

/* ---------------------------------------------------------------------------
   c-pagination. Cores the_posts_pagination() markup, restyled. Numbers are
   44px targets because this is the archives main control on a phone.          */
.c-pagination{ margin-block-start:var(--ssl-s-8); }
.c-pagination .nav-links{
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-2); align-items:center;
}
.ssl-scope .c-pagination .page-numbers{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:var(--ssl-tap); min-height:var(--ssl-tap);
  padding-inline:var(--ssl-s-3);
  border:1px solid var(--ssl-line); border-radius:var(--ssl-radius);
  background:var(--ssl-surface); color:var(--ssl-primary);
  font-weight:700; text-decoration:none;
}
.ssl-scope .c-pagination .page-numbers:hover{ background:var(--ssl-primary-tint); text-decoration:none; }
.ssl-scope .c-pagination .page-numbers.current{
  background:var(--ssl-primary); border-color:var(--ssl-primary); color:var(--ssl-surface);
}
.ssl-scope .c-pagination .page-numbers.dots{ border-color:transparent; background:none; }

/* ---------------------------------------------------------------------------
   c-ladder. The /programs/ offer ladder: four stages from free to paid, in order.
   An <ol> because the ORDER is the message (each stage assumes the one before),
   and screen readers announce the position for free. The number is drawn from a
   CSS counter rather than typed into the markup, so re-ordering the list in the
   block editor renumbers it and cannot go stale.                                */
.c-ladder{
  list-style:none; margin:0; padding:0;
  counter-reset:ssl-ladder;
  display:grid; grid-template-columns:1fr; gap:var(--ssl-s-5);
}
@media (min-width:1024px){
  .c-ladder{ grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--ssl-s-6); }
}
.c-ladder__step{
  counter-increment:ssl-ladder;
  position:relative;
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius); box-shadow:var(--ssl-shadow);
  padding:var(--ssl-s-5);
  display:flex; flex-direction:column; gap:var(--ssl-s-3);
}
.c-ladder__step::before{
  content:counter(ssl-ladder);
  display:flex; align-items:center; justify-content:center;
  inline-size:2rem; block-size:2rem;
  border-radius:var(--ssl-radius-pill);
  background:var(--ssl-primary-tint); color:var(--ssl-primary);
  font-family:var(--ssl-sans); font-weight:700; font-size:var(--ssl-fs-2);
}
.ssl-scope .c-ladder__title{ margin:0; font-size:var(--ssl-fs-3); }
.ssl-scope .c-ladder__who{
  margin:0; max-width:none;
  font-size:var(--ssl-fs-1); font-weight:700; color:var(--ssl-primary);
}
.ssl-scope .c-ladder__body{ margin:0; max-width:none; color:var(--ssl-slate); }
.ssl-scope .c-ladder__link{ margin:0; margin-block-start:auto; max-width:none; font-weight:700; }
/* The paid stages are marked so the ladder reads as a progression to something, not four
   equivalent options. Same change and the same reasoning as c-cl-card--upcoming above: the
   3px one-sided bar became a recoloured 1px hairline, because ADR-0004 owns the depth model
   and a coloured side bar is the classic generated-UI tell.
   Colour is never the only signal here anyway: every step already states its price in
   c-ladder__who (Free, Paid, limited seats, Paid, by enquiry), so the hairline
   reinforces a distinction the text already makes. */
.c-ladder__step--paid{ border-color:var(--ssl-accent-strong); }

/* A .c-card that carries a HEADING needs the heading separated from its body. base.css
   sets `h3{margin:0}` globally, which is right for a hero or a section head but wrong
   inside a card, and the Batch-1 cards never showed it because they were each a single
   paragraph of text with no heading at all. The /programs/ and /for-schools/ cards are
   the first heading-plus-body cards in the build.
   Scoped to a direct child of .c-card so it cannot reach c-cl-card or c-ladder__step,
   both of which space themselves with flex gap and would end up double-spaced. */
.ssl-scope .c-card > h3{ margin-block-end:var(--ssl-s-3); }
/* The last paragraph must not push the cards own bottom padding open. (0,2,1) to beat
   `.ssl-scope p` at (0,1,1). */
.ssl-scope .c-card > p:last-child{ margin-block-end:0; }

/* A bare heading in PROSE FLOW needs its own spacing. base.css zeroes every heading
   margin, which is correct everywhere a flex or grid gap already owns the rhythm:
   t-section__head, c-card, c-ladder__step and c-split__panel all do exactly that.
   A heading dropped straight into .ssl-wrap has no such owner and collides with the
   paragraph under it. /for-schools/ is the first page in the build to do that, with its
   How long does it take to start? and Who do I talk to? questions.
   The DIRECT-CHILD combinator is load bearing: it keeps this away from every heading
   that lives inside a component, which is all of the others. */
.ssl-scope .ssl-wrap > h2,
.ssl-scope .ssl-wrap > h3{ margin-block:var(--ssl-s-6) var(--ssl-s-3); }
.ssl-scope .ssl-wrap > h2:first-child,
.ssl-scope .ssl-wrap > h3:first-child{ margin-block-start:0; }

/* ---------------------------------------------------------------------------
   c-profile__ids. The verifiable-credential row on a mentor card: ORCID and
   Google Scholar, as real outbound links the reader can click and check.

   This is what replaced the headshot. For an MSc or PhD reader, a third-party
   researcher ID is a stronger trust signal than a photograph, because it cannot
   be faked and it can be checked in one click. A card renders however many IDs
   that person actually has, and none at all if their ID is unconfirmed, which is
   why this is a list rather than a fixed two-column row.                       */
.c-profile__ids{
  list-style:none; margin:var(--ssl-s-1) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--ssl-s-2);
}
/* 44px minimum, because a 14px word is not a tap target. The existing rule
   immediately above this block makes the same point about c-profile links. */
.ssl-scope .c-profile__id{
  display:inline-flex; align-items:center; min-height:var(--ssl-tap);
  padding-inline:var(--ssl-s-3);
  border:1px solid var(--ssl-line); border-radius:var(--ssl-radius-pill);
  background:var(--ssl-surface);
  font-size:var(--ssl-fs-1); font-weight:700;
  text-decoration:none;
}
.ssl-scope .c-profile__id:hover,
.ssl-scope .c-profile__id:focus-visible{
  background:var(--ssl-primary-tint); border-color:var(--ssl-primary);
  text-decoration:none;
}
/* The outbound arrow is decorative and generated, so it is never announced and
   never ends up in a copied selection. */
.ssl-scope .c-profile__id::after{
  content:"\2197"; margin-inline-start:var(--ssl-s-2); opacity:.55; font-weight:400;
}

/* ---------------------------------------------------------------------------
   c-figure. THE image slot. One component for every content image on the site,
   so that adding a picture to a section is never a bespoke piece of CSS.

   Every image the site shows falls into one of two jobs, and they want opposite
   treatment, which is the whole reason this component has two modifiers:
     --evidence  a scientific render, plot or screenshot. Contained, on a tinted
                 ground, with a caption that says what it is. It is data, so it
                 must not be cropped to fit a shape.
     --photo     a photograph of people or a place. Cropped to a fixed ratio so a
                 grid of them stays even, because a row of phone photos at mixed
                 aspect ratios is the fastest way to make a page look amateur.

   ALT TEXT IS NOT OPTIONAL on either. A decorative image belongs in CSS, not in
   a c-figure. If a c-figure has no alt text, that is a bug, not a style choice. */
.c-figure{ margin:0; }
.c-figure__img{
  display:block; width:100%; height:auto;
  border-radius:var(--ssl-radius);
}
/* Evidence: never cropped. object-fit is deliberately absent. A docking pose with
   its binding site cut off is not evidence of anything. The tint gives a white or
   transparent scientific render an edge without a heavy border. */
.c-figure--evidence .c-figure__img{
  background:var(--ssl-primary-tint);
  border:1px solid var(--ssl-line);
}
/* Photo: cropped, because evenness matters more than any one photos framing. */
.c-figure--photo .c-figure__img{
  aspect-ratio:4/3; object-fit:cover;
}
.c-figure--photo.c-figure--wide .c-figure__img{ aspect-ratio:16/9; }
/* ---------------------------------------------------------------------------
   c-video. A YouTube facade: a poster and a button, with no Google request until
   somebody presses play. See the handler in ssl-chrome.js for why.
   The button is a real <button>, so it is keyboard reachable and announced as a
   control for free; the poster inside it is decorative (alt=) because the
   buttons own accessible name already says what pressing it does.             */
/* s-6 below, because the video is followed by the Instagram strip and the two are separate
   thoughts. Without it the strip butts against the caption and reads as part of the video. */
.c-video{ margin:0 0 var(--ssl-s-6); }
.c-video__play{
  display:block; position:relative; width:100%; padding:0; border:0; cursor:pointer;
  background:var(--ssl-ink); border-radius:var(--ssl-radius); overflow:hidden;
  aspect-ratio:16/9;
}
.c-video__poster{
  display:block; width:100%; height:100%; object-fit:cover;
  /* the poster dims so the play badge stays legible over a bright frame, and lifts on
     hover so the whole tile reads as one control rather than a picture with a button. */
  opacity:.82; transition:opacity var(--ssl-dur-ui) var(--ssl-ease-out);
}
.c-video__play:hover .c-video__poster,
.c-video__play:focus-visible .c-video__poster{ opacity:1; }
.c-video__badge{
  position:absolute; inset-block-start:50%; inset-inline-start:50%;
  translate:-50% -50%;
  display:grid; place-items:center;
  width:64px; height:64px; border-radius:50%;
  background:var(--ssl-surface); color:var(--ssl-primary);
  box-shadow:var(--ssl-shadow-panel);
  transition:scale var(--ssl-dur-press) var(--ssl-ease-out);
}
.c-video__play:hover .c-video__badge{ scale:1.06; }
.c-video__play:active .c-video__badge{ scale:.97; }
/* The triangle is drawn here rather than shipped as inline SVG in the pattern: kses strips
   <svg> out of post_content, so the markup version rendered as an empty white disc. The
   3px inline offset is optical centring, because a triangles visual centre of mass sits
   left of its bounding box. */
.c-video__badge::before{
  content:""; width:24px; height:24px; background:currentColor;
  clip-path:polygon(8% 0%, 100% 50%, 8% 100%);
  margin-inline-start:3px;
}
.c-video__frame{ display:block; width:100%; aspect-ratio:16/9; border:0; border-radius:var(--ssl-radius); }
.ssl-scope .c-video__caption{
  margin:var(--ssl-s-3) 0 0; max-width:100%;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate);
}
@media (prefers-reduced-motion:reduce){
  .c-video__play:hover .c-video__badge,
  .c-video__play:active .c-video__badge{ scale:1; }
}

/* ---------------------------------------------------------------------------
   c-social-strip. Three photographs standing in for a social account, above a
   link to it. NOT a feed and must never be captioned as one: nothing here is
   fetched, so calling these latest posts would be false the next day.
   Square, because that is the shape the eye reads as an Instagram account
   and because it makes a 16:9 frame and a 4:3 frame sit in one even row.

   THREE ACROSS AT EVERY WIDTH, including 375px, and the first version of this
   was wrong about that. It dropped to one column below 480 on the reasoning
   that three squares would be too small to see. Measured on the live page,
   that produced three 343px squares stacked: over 1000px of scrolling, on a
   phone, for a strip whose entire job is to say we are on Instagram. It was
   the single largest thing on the page and the least important.
   Three across at 375px gives about 105px each, which is almost exactly what
   Instagrams own profile grid shows on the same phone. That is the shape
   people already recognise, so it reads as a feed at the size they expect it,
   and it costs a tenth of the scroll. The gap tightens on small screens for
   the same reason: a 3-up grid of photographs wants to read as one block.    */
.c-social-strip{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--ssl-s-2);
}
@media (min-width:768px){ .c-social-strip{ gap:var(--ssl-s-4); } }
.c-social-strip .c-figure__img{ aspect-ratio:1; object-fit:cover; }

/* max-width is 100%, NOT the 70ch measure used for body text. A caption belongs to its
   figure, so it must wrap inside the figures column; at 70ch it escapes a half-width
   media-split column and runs under the text beside it, which is how it first shipped. */
.ssl-scope .c-figure__caption{
  margin:var(--ssl-s-3) 0 0; max-width:100%;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate);
}
/* On the indigo band the caption has to flip to a paper-coloured token, or it is
   slate on indigo at 1.33:1. Same fix ADR-0015 applied to the eyebrow. */
.c-band--indigo .ssl-scope .c-figure__caption,
.c-band--indigo .c-figure__caption{ color:var(--ssl-paper); }
.c-band--indigo .c-figure--evidence .c-figure__img{
  background:transparent; border-color:var(--ssl-line-dark);
}

/* c-media-split. A section that pairs one c-figure with a block of text.
   Text first in the DOM on every one of them, so the reading order is right and
   so a screen reader is never handed a figure before the sentence explaining it;
   the --figure-first modifier only changes the VISUAL order, and only from 768.  */
.c-media-split{
  display:grid; grid-template-columns:1fr; gap:var(--ssl-s-6);
  align-items:center;
}
@media (min-width:768px){
  .c-media-split{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--ssl-s-7); }
  .c-media-split--figure-first .c-media-split__figure{ order:-1; }
}
.c-media-split__body > :last-child{ margin-block-end:0; }
/* The split is a block inside a band that continues below it, so it owns the gap to
   whatever follows. Without this the figures caption collides with the next heading. */
.c-media-split{ margin-block-end:var(--ssl-s-7); }

/* ---------------------------------------------------------------------------
   c-crumb. Breadcrumb for pages that live under a section. First use:
   /submit-opportunity/ under /opportunities/ (ADR-0026). A nav landmark with
   aria-label, the current page as text not a link, and the separator as a
   glyph the screen reader skips.                                             */
.c-crumb{ font-size:var(--ssl-fs-1); color:var(--ssl-slate); }
.c-crumb a{ color:var(--ssl-primary); text-decoration:none; }
.c-crumb a:hover{ text-decoration:underline; }
.c-crumb__sep{ margin-inline:var(--ssl-s-2); }

/* ---------------------------------------------------------------------------
   c-notice. A status card: the outcome of something the visitor just did.
   Base is a surface card; the modifier paints a 3px top border in the status
   colour, which is the comps device for this card IS the verdict without
   tinting the whole ground and wrecking text contrast on it.                 */
.c-notice{
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius); padding:var(--ssl-s-6);
  box-shadow:var(--ssl-shadow);
}
.c-notice--ok{ border-block-start:3px solid var(--ssl-ok); }
.c-notice--error{ border-block-start:3px solid var(--ssl-error); }
.c-notice__pill{
  display:inline-block; font-size:11px; line-height:1; font-weight:700;
  letter-spacing:.12em; text-transform:uppercase;
  padding:5px 10px; border-radius:var(--ssl-radius-pill);
}
.c-notice--ok .c-notice__pill{ color:var(--ssl-ok); background:var(--ssl-ok-tint); }
.c-notice--error .c-notice__pill{ color:var(--ssl-error); background:var(--ssl-error-tint); }
.ssl-scope .c-notice__title{ font-size:var(--ssl-fs-3); letter-spacing:var(--ssl-track-3); margin:var(--ssl-s-3) 0 0; }
.ssl-scope .c-notice__body{ margin:var(--ssl-s-2) 0 0; max-width:54ch; }

/* ---------------------------------------------------------------------------
   c-checklist. Do/do-not list with a leading glyph per row. The glyph is
   aria-hidden decoration; the sentence carries the meaning, so the list
   still reads correctly where the colours do not survive.                    */
.c-checklist{ list-style:none; margin:var(--ssl-s-3) 0 0; padding:0; display:grid; gap:var(--ssl-s-3); }
.c-checklist li{ display:flex; gap:var(--ssl-s-3); font-size:15px; line-height:var(--ssl-lh-body); }
.c-checklist__glyph{ font-weight:700; flex:none; }
.c-checklist li[data-ok] .c-checklist__glyph{ color:var(--ssl-ok); }
.c-checklist li[data-no] .c-checklist__glyph{ color:var(--ssl-error); }

/* ---------------------------------------------------------------------------
   c-pill. The status word of an opportunity: Open, Closing soon, Closed,
   Rolling. Colour never carries the meaning alone; the WORD is the meaning
   and the colour restates it. All four pairs measured AA on their grounds
   (see tokens.css).                                                          */
.c-pill{
  display:inline-block; font-size:11px; line-height:1; font-weight:700;
  letter-spacing:.12em; text-transform:uppercase;
  padding:5px 10px; border-radius:var(--ssl-radius-pill); white-space:nowrap;
}
.c-pill--open{ color:var(--ssl-ok); background:var(--ssl-ok-tint); }
.c-pill--soon{ color:var(--ssl-warn); background:var(--ssl-accent-tint); }
.c-pill--closed{ color:var(--ssl-slate); background:var(--ssl-slate-tint); }
.c-pill--rolling{ color:var(--ssl-primary); background:var(--ssl-primary-tint); }

/* ---------------------------------------------------------------------------
   c-opp-row. One opportunity in a list: directory rows and the record pages
   related list. An <a> that is one grid; the title is the links visual
   anchor, the deadline column right-aligns from 768.                          */
.c-opp-list{
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius); overflow:hidden;
}
.c-opp-row{
  display:grid; grid-template-columns:1fr;
  grid-template-areas:"pill" "title" "date" "meta" "fund";
  gap:3px; padding:var(--ssl-s-4);
  text-decoration:none; color:var(--ssl-ink);
  border-block-start:1px solid var(--ssl-line);
}
.c-opp-list > .c-opp-row:first-child{ border-block-start:none; }
.c-opp-row:hover{ background:#F5F5FA; }
.c-opp-row--closed{ opacity:.6; }
.c-opp-row__pill{ grid-area:pill; margin-block-end:2px; }
.c-opp-row__title{
  grid-area:title; font-weight:700; font-size:var(--ssl-fs-2); line-height:1.35;
  color:var(--ssl-primary);
}
.c-opp-row:hover .c-opp-row__title{ text-decoration:underline; text-underline-offset:3px; }
.c-opp-row__meta{ grid-area:meta; font-size:var(--ssl-fs-1); color:var(--ssl-slate); }
.c-opp-row__fund{ grid-area:fund; font-size:var(--ssl-fs-1); color:var(--ssl-ink); margin-block-start:2px; }
.c-opp-row__date{ grid-area:date; }
.c-opp-row__when{
  font-weight:700; font-size:var(--ssl-fs-2); line-height:1.3;
  font-variant-numeric:tabular-nums;
}
.c-opp-row--soon .c-opp-row__when{ color:var(--ssl-warn); }
.c-opp-row--closed .c-opp-row__when{ color:var(--ssl-slate); }
.c-opp-row__rel{ display:block; font-size:var(--ssl-fs-1); color:var(--ssl-slate); }
.c-opp-row--soon .c-opp-row__rel{ color:var(--ssl-warn); font-weight:700; }
@media (min-width:768px){
  .c-opp-row{
    grid-template-columns:minmax(0,1fr) 230px;
    grid-template-areas:"pill date" "title date" "meta date" "fund date";
    gap:3px var(--ssl-s-5); padding:var(--ssl-s-4) var(--ssl-s-5);
  }
  .c-opp-row__date{ text-align:end; }
}

/* ---------------------------------------------------------------------------
   c-post-row / c-post-list. One guide in the blog listing comp: whole row is
   the link, grid minmax(0,1fr) 170px from 768, date right-aligned tabular,
   green Updated pill only when true. This block was the one Autoptimize CSSmin
   silently dropped from the minified output (gotcha 25); the fix is the AO
   exclusion in inc/ssl-redesign-enqueue.php, not anything in this file.      */
.c-post-list{
  background:var(--ssl-surface); border:1px solid var(--ssl-line);
  border-radius:var(--ssl-radius); overflow:hidden;
}
.c-post-row{
  display:grid; grid-template-columns:1fr; gap:4px;
  padding:var(--ssl-s-4);
  text-decoration:none; color:var(--ssl-ink);
  border-block-start:1px solid var(--ssl-line);
}
.c-post-list > .c-post-row:first-child{ border-block-start:none; }
.c-post-row:hover{ background:#F5F5FA; }
.c-post-row__main{ min-width:0; display:grid; gap:4px; }
.c-post-row__cat{
  font-size:11px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--ssl-slate);
}
.c-post-row__title{
  font-weight:700; font-size:var(--ssl-fs-2); line-height:1.35; color:var(--ssl-primary);
}
.c-post-row:hover .c-post-row__title{ text-decoration:underline; text-underline-offset:3px; }
.c-post-row__dek{ font-size:var(--ssl-fs-1); line-height:1.55; color:var(--ssl-slate); }
.c-post-row__by{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate); margin:2px 0 0;
}
.c-post-row__date{
  display:flex; flex-direction:column; gap:6px; align-items:flex-start;
  font-size:var(--ssl-fs-1); color:var(--ssl-slate);
  font-variant-numeric:tabular-nums;
}
@media (min-width:768px){
  .c-post-row{
    grid-template-columns:minmax(0,1fr) 170px;
    gap:4px var(--ssl-s-5); padding:var(--ssl-s-4) var(--ssl-s-5);
  }
  .c-post-row__date{ align-items:flex-end; text-align:end; }
}

/* the byline avatar: initials on primary tint, no raster asset to load */
.c-avatar{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:28px; height:28px; border-radius:999px;
  background:var(--ssl-primary-tint); color:var(--ssl-primary);
  font-size:11px; font-weight:700; letter-spacing:.04em;
}
.c-avatar--lg{ width:48px; height:48px; font-size:15px; }

/* ---------------------------------------------------------------------------
   c-whatsapp-fab. The researchers-community join button, site-wide parity with
   production. WhatsApp brand green with the white glyph (the universal affordance;
   the glyph is graphical, aria-label carries the meaning). Offset upward on small
   screens so it never sits on the sticky mobile CTA bar.                      */
.c-whatsapp-fab{
  position:fixed; inset-inline-end:16px; inset-block-end:84px; z-index:60;
  display:flex; align-items:center; justify-content:center;
  width:56px; height:56px; border-radius:999px;
  background:#25D366; color:#fff; box-shadow:0 4px 14px rgba(26,26,46,.25);
}
.c-whatsapp-fab:hover{ background:#1DA851; color:#fff; }
.c-whatsapp-fab:focus-visible{ outline:2px solid var(--ssl-primary); outline-offset:3px; }
@media (min-width:768px){ .c-whatsapp-fab{ inset-block-end:24px; } }

/* The footer newsletter block is now the newsletter plugin's own form (.ssl-news),
   which styles itself for a LIGHT page. Re-ground it for the dark footer. */
.c-site-footer .ssl-news{
  margin:0; padding:0; border:0; background:transparent; border-radius:0;
}
.c-site-footer .ssl-news__kicker{ display:none; }
.c-site-footer .ssl-news__h{ color:var(--ssl-paper); font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; margin:0 0 var(--ssl-s-3); }
.c-site-footer .ssl-news__p{ color:var(--ssl-paper); opacity:.75; font-size:var(--ssl-fs-1); max-width:44ch; }
.c-site-footer .ssl-news__input{ background:var(--ssl-surface); border:1px solid var(--ssl-line); border-radius:var(--ssl-radius); color:var(--ssl-ink); min-height:var(--ssl-tap); }
.c-site-footer .ssl-news__btn{ background:var(--ssl-accent); color:var(--ssl-ink); border:0; border-radius:var(--ssl-radius-pill); font-weight:700; min-height:var(--ssl-tap); padding-inline:24px; cursor:pointer; }
.c-site-footer .ssl-news__btn:hover{ background:var(--ssl-accent-strong); color:var(--ssl-ink); }
.c-site-footer .ssl-news__fine{ color:var(--ssl-paper); opacity:.65; font-size:var(--ssl-fs-1); }
.c-site-footer .ssl-news__msg{ color:var(--ssl-paper); }

/* ---------------------------------------------------------------------------
   Footer, to the handoff SiteFooter comp (2026-08-23): brand column with blurb,
   social TILES and contact; link columns; the newsletter as the right-hand
   column with a dark input and a full-width warm Subscribe; a hairline bottom
   bar carrying the copyright and the brand line. Grid 1.4fr / links / 1.2fr.  */
@media (min-width:1024px){
  .c-site-footer__inner{
    grid-template-columns:1.4fr 2.6fr 1.2fr; align-items:start; column-gap:var(--ssl-s-6);
  }
  /* grid-row:1 on every cell is load-bearing: the earlier 1024 block above places
     __newsletter in row 2 and spans __cols over three rows, and without the reset
     the newsletter column starts a full brand-column height below the others. */
  .c-site-footer__brand{ grid-column:1; grid-row:1; }
  .c-site-footer__cols{ grid-column:2; grid-row:1; }
  .c-site-footer__newsletter{ grid-column:3; grid-row:1; }
}
/* The comp's dark footer carries the INVERSE mark; the site only has the colour
   asset, whose dark script drowns on ink. A white silhouette is the standard
   inverse treatment and needs no second logo file. */
.c-site-footer .c-site-footer__logo{ filter:brightness(0) invert(1); }
.ssl-scope .c-site-footer__blurb{
  max-width:34ch; margin:var(--ssl-s-4) 0 0; font-size:var(--ssl-fs-1); line-height:1.6;
  color:#C4CCD8;
}
/* social tiles, comp-style: 34px rounded squares on translucent white */
.c-site-footer__social{ display:flex; gap:10px; margin:var(--ssl-s-4) 0 0; padding:0; list-style:none; }
.c-site-footer__social-link{
  display:flex; align-items:center; justify-content:center;
  width:34px; height:34px; border-radius:8px; background:rgba(255,255,255,.08); color:#fff;
}
.c-site-footer__social-link:hover{ background:rgba(255,255,255,.16); color:#fff; }
.c-site-footer__contact{ margin:var(--ssl-s-4) 0 0; }
.ssl-scope .c-site-footer__contact p{ margin:0 0 6px; max-width:none; font-size:var(--ssl-fs-1); }
/* the newsletter column: dark translucent input + warm full-width subscribe (comp) */
.c-site-footer .ssl-news__input{
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.18); color:#fff;
}
.c-site-footer .ssl-news__input::placeholder{ color:#8B94A3; }
.c-site-footer .ssl-news__btn{ width:100%; justify-content:center; display:flex; }
/* bottom bar: hairline, (c) left + slogan right */
.c-site-footer__legal{
  display:flex; justify-content:space-between; flex-wrap:wrap; gap:8px;
  border-block-start:1px solid rgba(255,255,255,.12);
  padding-block-start:var(--ssl-s-4); margin-block-start:var(--ssl-s-6);
}
.ssl-scope .c-site-footer__copy,
.ssl-scope .c-site-footer__slogan{ margin:0; max-width:none; font-size:13px; color:#8B94A3; }
