/* ═══════════════════════════════════════════════════════════════════════
   MeducateED — Global design system.
   Colour, typography scale, spacing, layout primitives, utilities.
   Exact tokens per master plan Ch. 25.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
    /* ── Ink (navy) ───────────────────────────────────────────────────
       The SAME three stops the platform and the finance app paint their dark
       ground with — glass-ui.css:180 and finance.css:27 both read
       `linear-gradient(160deg, #09204a 0%, #041634 45%, #020F24 100%)`.
       Darkest -> lightest is preserved, so every existing `.on-ink` surface
       keeps its relative depth; only the hue moves. */
    --ink:       #020F24;   /* platform page ground (was #00153D)       */
    --ink2:      #041634;   /* gradient mid stop    (was #0E1A2E)       */
    --ink3:      #09204a;   /* gradient top stop    (was #1C2F4A)       */
    --ink-card:  #00153D;   /* the platform's GLASS CARD navy — the old
                               --ink, kept for raised surfaces on dark    */
    /* The full platform ground, for any full-bleed dark section. */
    --ink-grad:  linear-gradient(160deg, #09204a 0%, #041634 45%, #020F24 100%);

    /* Platform accents (glass-ui.css:24-26) — available, used sparingly:
       the navy/gold mandate still governs the home site's surfaces. */
    --royal:     #556ee6;
    --violet:    #7c5cff;
    --aqua:      #21c8b7;
    /* Paper */
    --paper:     #F6F3EE;
    --paper2:    #EFEBE3;
    /* Gold */
    --gold:      #8B6914;
    --gold-lt:   #C9A84C;
    --gold-live: #ECA424;
    --peach:     #F3D9A6;
    /* Accents */
    --teal:      #0E7A6E;
    --teal-lt:   #1A9E8F;
    --red:       #8B1A1A;
    --green:     #1A6B3C;
    /* Neutrals */
    --muted:     #5A5450;
    --border:    #D9D2C5;
    --border-dark: rgba(201,168,76,.22);

    /* Spacing scale */
    --s-1: .25rem; --s-2: .5rem;  --s-3: .75rem; --s-4: 1rem;
    --s-5: 1.5rem; --s-6: 2rem;   --s-8: 3rem;   --s-10: 4rem;
    --s-12: 6rem;  --s-16: 8rem;  --s-20: 10rem;

    --maxw: 1200px;
    --maxw-narrow: 760px;
    --radius: 4px;
    --radius-lg: 10px;
    --shadow: 0 1px 3px rgba(2,15,36,.10), 0 8px 30px rgba(2,15,36,.08);
    --shadow-lg: 0 20px 60px rgba(2,15,36,.22);
    --ease: cubic-bezier(.22,.61,.36,1);
    --dur: .55s;
}

/* ── Reset ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
    font-family: var(--font-body);
    font-weight: 300;
    background: var(--paper);
    color: var(--ink);
    line-height: 1.65;
    font-size: 1.05rem;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
}
img, svg, video { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
ul, ol { list-style: none; padding: 0; }

@media (prefers-reduced-motion: reduce) {
    * { animation-duration: .001ms !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
}

/* ── Typography ──────────────────────────────────────────────────────── */
/* Oi (display) is a single-weight face with no italic — the gold em-word is
   distinguished by colour, not slant. Chango carries subheads, h4/h5, eyebrows,
   and CTAs. Zain is the body. DM Mono stays for data, IDs, and code. */
h1, h2, h3 { font-family: var(--font-display); font-weight: 400; line-height: 1.1; letter-spacing: -.01em; }
h4, h5 { font-family: var(--font-subhead); font-weight: 400; line-height: 1.2; letter-spacing: .005em; }

/* ⭐ A WORD LONGER THAN THE LINE MUST BREAK, NOT BE CLIPPED.
   Oi is a display face running ~0.8em per character, so a single long word in
   a headline ("specifically.", "Physiotherapy") is wider than a 266px phone
   column at any font-size a headline can sensibly use. Measured 2026-09-14:
   the hero h1 overflowed its own container by up to 139px at a 330px viewport
   on nearly every page. Lowering the clamp floors (below) fixes today's
   headlines; this rule is what stops the NEXT headline re-opening the bug.
   `hyphens: auto` needs a language to know where to break — templates/_meta.php
   sets <html lang="en">. */
h1, h2, h3, h4, h5 {
    overflow-wrap: break-word;
    hyphens: auto;
    max-width: 100%;
    /* Stops a one-word (or one-punctuation-mark) last line. Progressive:
       browsers without it just get the ordinary greedy wrap. */
    text-wrap: pretty;
}

/* The floors were drawn against a 1440px comp and never re-checked at the small
   end: at 330px the vw term resolves far below the floor, so the FLOOR is what
   phones actually get. Each one below is the largest that fits this face's
   longest realistic word in a 266px column. The vw term and the ceiling are
   unchanged, so nothing above ~480px moves at all. */
h1 { font-size: clamp(1.85rem, 5.6vw, 4.6rem); }
h2 { font-size: clamp(1.55rem, 4.2vw, 3.2rem); }
h3 { font-size: clamp(1.25rem, 3vw, 2.2rem); }
h4 { font-size: clamp(1rem, 1.8vw, 1.35rem); }

/* ⭐ BELOW 600px THE DISPLAY FACE CHANGES. Oi has almost no counter space — the
   holes in o, e, a, s close up as it gets smaller and the headline reads as a
   solid mass. `fonts.css:19` already made exactly this exception for the logo
   ("Oi is too heavy/wide for the small logo lockup text"); this is the same
   exception at the same cause, applied to headings on a phone.

   ⛔ ABOVE 600px NOTHING CHANGES. The desktop identity is untouched — that is
   the entire reason this is a breakpoint and not a replacement.

   ⚠ The sizes RISE here. That reads backwards against the floors above, which
   were lowered this morning to stop OI being clipped; Zain 800 needs roughly
   60% of Oi's width for the same string, so it fits whole at a larger size.
   (This block replaces an earlier <=380px step that existed only to stop Oi
   hyphenating "specifically." — with Zain the word fits, so it is gone rather
   than left in the cascade as a dead override.) */
@media (max-width: 600px) {
    h1, h2, h3 {
        font-family: var(--font-body);
        font-weight: 800;
        letter-spacing: -.005em;
        line-height: 1.18;
    }
    h1 { font-size: clamp(1.75rem, 7.4vw, 2.6rem); }
    h2 { font-size: clamp(1.5rem, 6.2vw, 2.2rem); }
    h3 { font-size: clamp(1.25rem, 5vw, 1.7rem); }
    /* The gold em-word is distinguished by colour, not weight — keep it level
       with its sentence so the swap does not introduce a second emphasis. */
    h1 em, h2 em, h3 em { font-weight: 800; }
}
h1 em, h2 em, h3 em { font-style: normal; font-weight: 400; color: var(--gold-live); }

.mono { font-family: var(--font-mono); font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; }
.eyebrow {
    font-family: var(--font-subhead);
    font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
    color: var(--gold); margin-bottom: var(--s-4); display: inline-block;
}
.lead { font-size: clamp(1.15rem, 2vw, 1.4rem); color: var(--muted); line-height: 1.6; font-weight: 300; }

/* ── Layout ──────────────────────────────────────────────────────────── */
/* `min-width: 0` because .container is almost always the child of a grid or
   flex section (.hm-outro is `display:grid`, .hm-hero-inner is flex, …) and a
   grid/flex item defaults to min-width:auto — it then refuses to shrink below
   its content's min-content width. Measured at a 330px viewport: .hm-outro's
   container reported 371px inside a 315px grid, pushing the outro headline and
   its CTA 24px past the screen edge where `overflow:hidden` cut them off.
   `.grid > *` (above) covers the utility grids; this covers everything else. */
.container { width: 100%; min-width: 0; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--s-6); }
.container-narrow { max-width: var(--maxw-narrow); }
/* MEASURED 2026-09-14, which is why these came down: at 10vw the home page's
   vitals section was 599px tall around 310px of content — 48% of it empty
   padding — and the fund and journal sections were 39% and 41%. Mo:
   "remove the unreasonable huge empty space after each section".
   ~6.5vw keeps a generous editorial rhythm without the dead screens. */
.section { padding-block: clamp(var(--s-8), 6.5vw, var(--s-12)); }
.section-sm { padding-block: var(--s-8); }

.on-ink { background: var(--ink); color: var(--paper); }
.on-ink h1, .on-ink h2, .on-ink h3, .on-ink h4 { color: var(--paper); }
.on-ink .lead, .on-ink p { color: var(--peach); }
.on-ink .eyebrow { color: var(--gold-live); }
.on-paper2 { background: var(--paper2); }

.grid { display: grid; gap: var(--s-6); }
/* ⭐ THE SINGLE HIGHEST-VALUE RESPONSIVE LINE IN THIS FILE.
   A grid (and flex) item's `min-width` defaults to `auto`, which means it
   REFUSES to shrink below its widest unbreakable content — an image's
   intrinsic width, a long word, a nowrap number. The track then grows past the
   container and the content is clipped at its edge. Every "card is cut off"
   report measured on this site (2026-09-14: the home vitals, /community/press
   stats, /academics/disciplines cards, the product page) is a variant of it.
   min-width:0 lets the track win and the content reflow instead. */
.grid > * { min-width: 0; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

.text-center { text-align: center; }
.mx-auto { margin-inline: auto; }
.mt-4 { margin-top: var(--s-4); } .mt-6 { margin-top: var(--s-6); } .mt-8 { margin-top: var(--s-8); }
.mb-6 { margin-bottom: var(--s-6); } .mb-8 { margin-bottom: var(--s-8); }
.stack > * + * { margin-top: var(--s-4); }

/* ── Section headers ─────────────────────────────────────────────────── */
.section-head { max-width: 720px; margin-bottom: var(--s-8); }
.section-head.center { margin-inline: auto; text-align: center; }
.section-head p { margin-top: var(--s-4); color: var(--muted); }
.rule { height: 1px; background: var(--border); border: 0; margin-block: var(--s-8); }
.on-ink .rule { background: var(--border-dark); }

/* ── Prose (blog / legal / rich text) ────────────────────────────────── */
.prose { max-width: 68ch; font-size: 1.15rem; line-height: 1.75; }
.prose > * + * { margin-top: 1.35em; }
.prose h2 { margin-top: 1.8em; font-size: 2rem; }
.prose h3 { margin-top: 1.6em; font-size: 1.5rem; }
.prose a { color: var(--teal); text-decoration: underline; text-underline-offset: 3px; }
.prose blockquote {
    border-left: 3px solid var(--gold-live); padding-left: var(--s-5);
    font-family: var(--font-body); font-style: italic; font-size: 1.35rem; font-weight: 400; color: var(--ink3);
}
.prose img { border-radius: var(--radius-lg); margin-block: 2em; }
.prose ul, .prose ol { padding-left: 1.3em; }
.prose ul li { list-style: disc; } .prose ol li { list-style: decimal; }
.prose li + li { margin-top: .5em; }
.prose code { font-family: var(--font-mono); background: var(--paper2); padding: .15em .4em; border-radius: 3px; font-size: .85em; }
/* ⚠ THE DROP CAP WAS A SOLID GOLD BLOB AT 4.2rem, NOT A SMALL LETTER.
   Mo, 2026-09-15: *"the first stylized oi M in the first paragraph of the about
   page needs to be way bigger because rn it's so compacted that it isn't
   readable."* Captured and zoomed before changing anything: at 67px the Oi "M"
   rendered with its counters closed — the two notches that make it an M had
   filled in, so the glyph read as a rounded rectangle.
   ⭐ THAT IS A PROPERTY OF THE FACE, NOT A BUG IN THE RULE. Oi is an ultra-heavy
      display type whose strokes are far wider than its counters; it only becomes
      legible once the counters are wide enough to survive rasterisation. The fix
      is therefore size, exactly as asked — not a weight or a colour change.
   ⚠ It scales with the viewport because a fixed 7rem that reads well on a laptop
     eats a phone's whole column; the floor is the size at which the counters
     still hold, measured, not guessed.
   ⚠ `line-height` a touch tighter than before so the taller cap still occupies
     about three lines of body text rather than pushing the paragraph around.

   🔴🔴 AND SIZE ALONE DID NOT FIX IT — `font-weight: 600` WAS THE REAL CAUSE.
   Doubling the size only turned the closed counters into hairline slits. Oi is
   loaded from `fonts.css:9` as `family=Oi` with **no weight axis**: the family
   ships 400 and nothing else. Asking for 600 therefore did not select a bolder
   cut — it made the browser SYNTHESISE one, which smears an already
   ultra-heavy poster face until the two notches that make an M are gone.
   ⭐ *A weight a font does not have is not ignored, it is faked* — and on a face
      this heavy the fake is the difference between a letter and a rectangle.
   ⚠ fonts.css already knew this shape of problem: `--font-wordmark` exists
     because *"Oi is too heavy/wide for the small logo lockup text"*, and the
     legal headings were moved off Oi for the same reason. Same face, same
     failure, third place it has bitten. */
.prose .dropcap::first-letter {
    font-family: var(--font-display); float: left;
    font-size: clamp(5rem, 3.4rem + 4.4vw, 8rem);
    line-height: .74;
    padding: .06em .1em 0 0; color: var(--gold-live);
    font-weight: 400;
    /* ⛔ Belt and braces: if a future stylesheet sets a bold weight on .prose p,
       `::first-letter` inherits it and the blob comes back. This says no. */
    -webkit-text-stroke: 0;
    font-synthesis-weight: none;
}

/* ── Accessibility ───────────────────────────────────────────────────── */
.skip-link {
    position: absolute; left: -999px; top: 0; z-index: 1000;
    background: var(--ink); color: var(--paper); padding: var(--s-3) var(--s-5);
}
.skip-link:focus { left: var(--s-4); top: var(--s-4); }
:focus-visible { outline: 2px solid var(--gold-live); outline-offset: 3px; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* 🔴 GOTCHA #32 — make the `hidden` attribute actually work (2026-09-15).

   `[hidden] { display: none }` is a USER-AGENT rule, so ANY author `display:`
   declaration beats it and `el.hidden = true` then hides nothing at all. The
   element keeps rendering and the script looks like it silently did not run.

   This is not hypothetical here: it is exactly how the platform's "Delete all
   questions" button came to do nothing (§3.611). Its two-step confirm was
   styled `display: inline-flex`, so the confirmation never hid, both halves sat
   on screen at once, and the control that reads like the delete button only
   ever revealed something already visible.

   The home site currently has one such patch (`.consent-banner[hidden]`), which
   is the same bug waiting to be rediscovered somewhere new. One rule, once,
   ahead of it. ⚠ It can only ever hide something already marked hidden. */
[hidden] { display: none !important; }

/* ── a hero that carries actions ─────────────────────────────────────────────
   `render_hero()`'s optional `$extra_html` slot (includes/page.php). Added for
   the store's Courses / Books buttons; any page hero can use it.
   ⚠ Same wrap-and-fit rule the home hero learned the hard way: two `btn-lg`
     pills are ~650px together, so they wrap rather than overflow on a narrow
     screen, and shrink slightly on desktop so they sit as one row. */
.hero-extra {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-4);
    margin-top: var(--s-6);
}

@media (min-width: 900px) {
    .hero-extra .btn {
        white-space: nowrap;
        font-size: clamp(.8rem, .34rem + .62vw, .86rem);
        padding-inline: clamp(1rem, .1rem + 1.2vw, 1.45rem);
    }
}

/* ── a hero with art beside the headline ─────────────────────────────────────
   `render_hero()`'s `$aside_html` slot. Only `.page-hero-split` heroes get any
   of this; every other hero keeps its single-column layout untouched.

   ⚠ THE ART IS SIZED AGAINST THE HEADLINE'S MEASURE, NOT THE VIEWPORT. Mo asked
     for it *"in proportionate size to the large text next to it"*, and the
     headline is itself a clamp — pinning the art to a pixel size would make it
     right at one width and wrong at every other. `min(34ch, 40vw)` tracks the
     type, because `ch` is a font-relative unit.
   ⚠ Single column below 900px, art FIRST at that size so the page still opens
     on the character rather than on a wall of text — and capped hard, because a
     590 KB animation at full phone width is the whole screen. */
.page-hero-split .container {
    display: grid;
    grid-template-columns: minmax(0, 1fr) min(34ch, 40vw);
    gap: var(--s-8);
    align-items: center;
}

.hero-aside { min-width: 0; }

.hero-art {
    width: 100%;
    aspect-ratio: 1;
    display: block;
}

.hero-art svg, .hero-art canvas { width: 100% !important; height: 100% !important; display: block; }

/* the small identity chip in the eyebrow row */
.hero-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--s-3);
    font-family: var(--font-mono);
    font-size: .68rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--peach);
}

.hero-chip img {
    width: 34px; height: 34px;
    border-radius: 50%;
    flex: 0 0 auto;
}

@media (max-width: 900px) {
    .page-hero-split .container { grid-template-columns: 1fr; }
    .page-hero-split .hero-aside { order: -1; }
    .page-hero-split .hero-art { max-width: 240px; margin-inline: auto; }
}
