/* ═══════════════════════════════════════════════════════════════════════
   MeducateED — Homepage: "The blueprint of a physician."
   Opener · blueprint specimen hero · HUD · ticker · scroll chapters · outro.
   Idle loops key off body.is-idle / body.is-scrolling (MedFX.scrollState).
   ═══════════════════════════════════════════════════════════════════════ */

/* ── OPENER ─────────────────────────────────────────────────────────── */
.op {
    position: fixed;
    inset: 0;
    z-index: 999;
    pointer-events: none;
}

.op-panel {
    position: absolute;
    left: 0;
    width: 100%;
    height: 50.5%;
    background: var(--ink2);
}

.op-top {
    top: 0;
    box-shadow: 0 1px 0 var(--border-dark);
}

.op-bottom {
    bottom: 0;
}

.op-core {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    gap: 1.2rem;
    width: min(560px, 86vw);
    margin: 0 auto;
}

.op-ecg {
    width: 100%;
    height: 90px;
}

.op-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
}

.op-label {
    font-size: .6rem;
    letter-spacing: .3em;
    color: var(--gold-lt);
}

.op-count {
    font-size: 1.5rem;
    color: var(--gold-live);
    letter-spacing: .08em;
}

.op.done {
    display: none;
}

body.no-opener .op {
    display: none;
}

/* ── INTRO — Dr. Atef & the sphere of knowledge ─────────────────────── */
body.home-page {
    background: #020F24;
}

/* The header stays out of the theatre until the intro has played through.
   On the homepage it is fixed (not sticky) so it takes no flow space and
   the intro owns the full viewport from y = 0. */
body.home-page .site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
}

.site-header {
    transition: transform .6s cubic-bezier(.65, 0, .35, 1);
}

body.intro-active .site-header {
    transform: translateY(-101%);
}

.hm-intro {
    position: relative;
    height: 100vh;
    overflow: hidden;
    background: #020F24;
    color: var(--paper);
    display: grid;
    align-items: center;
    justify-items: center;
}

.intro-grain {
    position: absolute;
    inset: -20%;
    pointer-events: none;
    z-index: 1;
    opacity: .5;
    background: url('/assets/images/ui/grainbg.webp') repeat;
    background-size: 480px;
    mix-blend-mode: overlay;
    animation: grainShift 1.1s steps(3) infinite;
}

@keyframes grainShift {
    0% {
        transform: translate(0, 0);
    }

    33% {
        transform: translate(-3%, 2%);
    }

    66% {
        transform: translate(2%, -3%);
    }

    100% {
        transform: translate(0, 0);
    }
}

.intro-stage {
    position: relative;
    z-index: 2;
    height: 100%;
    width: 100%;
    display: grid;
    place-items: center;
    will-change: transform;
}

.intro-atef {
    height: min(70vh, 640px);
    width: auto;
    position: relative;
    z-index: 2;
    filter: drop-shadow(0 34px 60px rgba(0, 0, 0, .6));
}

body.is-idle .intro-atef {
    animation: atefIntroFloat 5.6s ease-in-out infinite;
}

@keyframes atefIntroFloat {

    0%,
    100% {
        transform: translateY(0) rotate(-1.4deg);
    }

    50% {
        transform: translateY(-16px) rotate(1.2deg);
    }
}

/* The sphere layer is FULL-BLEED — the notes are free to storm across the
   entire viewport when they detonate. The sphere itself is positioned in
   world-space over Atef's raised palm by home-intro.js. */
.sphere-holder {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
}

#sphereCanvas {
    width: 100%;
    height: 100%;
    display: block;
}

.intro-copy {
    position: absolute;
    z-index: 4;
    bottom: 9vh;
    left: 0;
    right: 0;
    text-align: center;
    /* The mascot stands directly behind this block, so the copy has to make
       its own contrast rather than hope for a dark pixel. */
    isolation: isolate;
}

/* A soft pool of the ground colour behind the copy. Radial, not a flat bar,
   so it reads as depth-of-field rather than a panel stuck on the scene. */
.intro-copy::before {
    content: '';
    position: absolute;
    inset: -16vh -8vw -24vh;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(58% 62% at 50% 58%,
        rgba(2, 15, 36, .90) 0%,
        rgba(2, 15, 36, .72) 42%,
        rgba(2, 15, 36, 0) 74%);
}

.intro-kicker {
    font-size: .6rem;
    letter-spacing: .34em;
    color: var(--gold-lt);
    margin-bottom: var(--s-3);
}

.intro-word {
    color: var(--paper);
    /* NOT the display face. Oi has almost no counter space and at this size,
       on a dark ground, "Knowledge, in hand." read as a solid block — Mo:
       "still to bold try to make it less bold to be readable". Zain 700 keeps
       the presence and is legible. (fonts.css:19 already made the same call
       for the logo.) */
    font-family: var(--font-body);
    font-weight: 700;
    letter-spacing: .01em;
    font-size: clamp(1.7rem, 3.6vw, 2.7rem);
    line-height: 1.2;
}

/* ── HOLD TO BEGIN — the way out of the intro ─────────────────────────────
   The ring fills with a CSS transition, not requestAnimationFrame: a
   transition is compositor-driven, survives a throttled tab, and needs no
   frame loop to stay in sync with the setTimeout that fires the launch.
   r=46 → circumference 2πr = 289.03, which is the dasharray below. */
.launch {
    --hold: 1100ms;                      /* MUST match HOLD_MS in home-intro.js */
    margin-top: var(--s-6);
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: .55rem;
    background: none;
    border: 0;
    padding: 6px;
    color: var(--paper);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;          /* no 500ms tap delay, no pan hijack */
    user-select: none;
}

.launch-dial {
    position: relative;
    width: 74px;
    height: 74px;
    display: grid;
    place-items: center;
}

.launch-ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);           /* start the fill at 12 o'clock */
    overflow: visible;
}
.launch-ring circle {
    fill: none;
    stroke-width: 3;
    stroke-linecap: round;
}
.lr-track { stroke: rgba(236, 164, 36, .22); }
.lr-fill {
    stroke: var(--gold-live);
    stroke-dasharray: 289.03;
    stroke-dashoffset: 289.03;
    /* Springs back faster than it fills, so an accidental tap feels answered. */
    transition: stroke-dashoffset .28s ease-out;
    filter: drop-shadow(0 0 6px rgba(236, 164, 36, .55));
}
.launch.is-holding .lr-fill {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset var(--hold) linear;
}

.launch-pulse {
    width: 30px;
    height: 26px;
    fill: none;
    stroke: var(--gold-live);
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: .9;
}
body.is-idle .launch:not(.is-holding) .launch-pulse { animation: launchBeat 2.6s ease-in-out infinite; }
@keyframes launchBeat {
    0%, 72%, 100% { transform: scale(1);    opacity: .85; }
    80%           { transform: scale(1.14); opacity: 1; }
}

.launch-label {
    font-size: .64rem;
    letter-spacing: .28em;
    color: var(--gold-live);
}

.launch:hover .lr-track { stroke: rgba(236, 164, 36, .38); }
.launch:focus-visible { outline: 2px solid var(--gold-live); outline-offset: 6px; border-radius: 12px; }
.launch.is-holding .launch-label { color: var(--gold-live); }

/* Once the intro has played, it leaves the document entirely — that is what
   removes the 1,903px of pinned dead scroll the old version needed. */
.hm-intro.intro-done { display: none; }

@media (prefers-reduced-motion: reduce) {
    .lr-fill { transition: none; }
    body.is-idle .launch .launch-pulse { animation: none; }
}

.intro-darken {
    position: absolute;
    inset: 0;
    z-index: 6;
    pointer-events: none;
    background: #010810;
    opacity: 0;
}

@media (max-width: 760px) {
    .intro-atef {
        height: min(56vh, 480px);
    }

    .intro-copy {
        bottom: 9vh;
    }
}

/* ── HERO ───────────────────────────────────────────────────────────── */
.hm-hero {
    position: relative;
    min-height: 100vh;
    background: var(--ink);
    color: var(--paper);
    overflow: hidden;
    display: flex;
    align-items: center;
}

.hm-grid {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image:
        linear-gradient(rgba(236, 164, 36, .05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(236, 164, 36, .05) 1px, transparent 1px);
    background-size: 64px 64px;
    mask-image: radial-gradient(ellipse at 60% 40%, #000 30%, transparent 78%);
}

.hm-particles {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.hm-frame {
    position: absolute;
    inset: 22px;
    pointer-events: none;
}

.fc {
    position: absolute;
    width: 26px;
    height: 26px;
    border: 1.5px solid var(--gold-lt);
    opacity: .55;
}

.fc-tl {
    top: 0;
    left: 0;
    border-right: 0;
    border-bottom: 0;
}

.fc-tr {
    top: 0;
    right: 0;
    border-left: 0;
    border-bottom: 0;
}

.fc-bl {
    bottom: 0;
    left: 0;
    border-right: 0;
    border-top: 0;
}

.fc-br {
    bottom: 0;
    right: 0;
    border-left: 0;
    border-top: 0;
}

.hm-hero-inner {
    position: relative;
    display: grid;
    grid-template-columns: 1.02fr .98fr;
    gap: var(--s-8);
    align-items: start;
    /* ⚠ bottom was var(--s-8) (48px). With the scroll cue removed nothing sits
       in that band any more — see the note on .hm-copy. Halved rather than
       zeroed so the hero still closes with a breath rather than a hard edge. */
    padding-block: var(--s-12) var(--s-5);
}

/* Extra scroll length so the sticky specimen drifts as you read.
   ⚠ 46vh → 18vh → 6vh. Mo, 2026-09-15: *"resize it to close this huge gap."*
     Measured at 1440x900 before this change: 210px of empty hero below the last
     button — 162px from this rule (18vh) and 48px from `.hm-hero-inner`'s
     bottom padding. The scroll cue that used to sit in that space is gone, so
     the space was reserving room for nothing.
   ⚠ It is not taken to zero: this padding is what lets the sticky specimen
     plate drift while the paragraph is read, and at 0 the plate locks the
     instant the copy ends. 6vh keeps the drift and returns ~110px. */
.hm-copy {
    padding-bottom: 6vh;
}

.hm-kicker {
    font-size: .62rem;
    letter-spacing: .3em;
    color: var(--gold-lt);
    margin-bottom: var(--s-5);
}

.hm-title {
    max-width: 15ch;
    color: var(--paper);
}

.hm-title .fx-char {
    display: inline-block;
    will-change: transform, opacity;
}

.hm-sub {
    margin-top: var(--s-5);
    max-width: 46ch;
    color: var(--peach);
    font-size: 1.12rem;
    line-height: 1.7;
}

.hm-cta {
    display: flex;
    gap: var(--s-4);
    margin-top: var(--s-8);
    flex-wrap: wrap;
}

/* ── Specimen: 3D blueprint heart ───────────────────────────────────── */
.hm-specimen {
    position: sticky;
    top: 13vh;
    height: 74vh;
    display: grid;
    place-items: center;
    perspective: 1400px;
}

/* (The blueprint-heart specimen was replaced by the Corvus X-ray below;
   its partial remains at templates/partials/heart-blueprint.php for reuse
   on the anatomy pages.) */

/* ── Corvus X-ray stage ─────────────────────────────────────────────────
   Two pixel-registered plates: flesh <img> below, skeleton revealed through
   the canvas mask above (scan band + Cappen cursor trail — xray.js).
   The plates ship with their own navy backdrop, so a radial vignette mask
   melts them into the hero. */
.xr-stage {
    /* ── Corvus tuning knobs — everything lives here ─────────────────
       --xr-opacity       flesh plate opacity (0–1)
       --xr-canvas-opacity  skeleton/X-ray canvas opacity (0–1)
       --xr-vignette-w/h  the vignette ellipse DIAMETER (width/height %)
                          — bigger % = more of the plate survives
       --xr-vignette-in   where the edge fade begins (%)
       --xr-vignette-out  where the plate is fully melted away (%)
       --xr-edge-tint     colour washed over the edges (use transparent
                          alpha, e.g. rgba(2,15,36,.55), to tint)      */
    /* Mo, 2026-09-14: sit the plate further back into the ground. These are
       the file's own documented tuning knobs, so this is a one-variable change
       — nothing about the mask, the scanline or xray.js is touched. */
    --xr-opacity: .82;
    --xr-canvas-opacity: .88;
    /* ⛔ RE-DERIVED 2026-09-14 FROM THE MEASURED GEOMETRY — do not "tidy"
       these back to rounder numbers without re-measuring.

       The plate is a 2000x2000 PNG drawn `object-fit: contain`, so the PICTURE
       is square and the box is not: at 492x477 the picture is 477 wide and its
       side edges sit at 71% of the old horizontal radius, where the old stops
       (solid 62% -> clear 80%) had only faded it about halfway. The edges were
       always half-opaque; it merely LOOKED seamless while the plate's own navy
       backdrop matched --ink (#00153D). Moving --ink to the platform's #020F24
       revealed the residue as a rectangle.

       These values make the mask reach FULL transparency inside the picture's
       own edge, so the colour behind it stops mattering and a future palette
       change cannot bring the rectangle back:
           rx = 56% x 492 = 275 ; clear at 86% -> 237px <= 238.5 ✓
           ry = 56% x 477 = 267 ; clear at 86% -> 230px <= 238.5 ✓ */
    --xr-vignette-w: 56%;
    --xr-vignette-h: 56%;
    --xr-vignette-in: 58%;
    --xr-vignette-out: 86%;
    /* A faint wash of the CURRENT ground over the outer ring — a second, cheap
       safety net so any sub-pixel residue tends toward the hero colour rather
       than toward the plate's baked-in navy. Uses the token, so it follows the
       palette automatically. */
    --xr-edge-tint: rgba(2, 15, 36, .55);

    position: absolute;
    inset: 0 0 12% 0;
    z-index: 2;
    pointer-events: auto;
    cursor: url('/assets/images/cursors/hover.webp') 2 4, crosshair;
    -webkit-mask-image: radial-gradient(ellipse var(--xr-vignette-w) var(--xr-vignette-h) at 50% 47%, #000 var(--xr-vignette-in), transparent var(--xr-vignette-out));
    mask-image: radial-gradient(ellipse var(--xr-vignette-w) var(--xr-vignette-h) at 50% 47%, #000 var(--xr-vignette-in), transparent var(--xr-vignette-out));
}

.xr-flesh,
.xr-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.xr-flesh {
    opacity: var(--xr-opacity);
}

.xr-canvas {
    opacity: var(--xr-canvas-opacity);
}

.xr-stage::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse var(--xr-vignette-w) var(--xr-vignette-h) at 50% 47%, transparent 46%, var(--xr-edge-tint) 100%);
}

/* Scanline sweeping the specimen — position driven by xray.js so the
   visible line and the skeleton reveal band never drift apart. */
.hm-scan {
    position: absolute;
    left: 6%;
    right: 6%;
    height: 2px;
    top: 6%;
    z-index: 3;
    background: linear-gradient(90deg, transparent, rgba(26, 158, 143, .9), transparent);
    filter: drop-shadow(0 0 6px rgba(26, 158, 143, .8));
    opacity: 0;
    pointer-events: none;
}

body.is-idle .hm-scan,
body.is-scrolling .hm-scan {
    opacity: .85;
}

@media (prefers-reduced-motion: reduce) {
    .hm-scan {
        opacity: 0 !important;
    }
}

/* HUD readouts */
.hm-hud {
    position: absolute;
    left: -4%;
    right: -4%;
    bottom: -6%;
    z-index: 4;
    display: flex;
    justify-content: space-between;
    gap: var(--s-3);
    border-top: 1px solid var(--border-dark);
    padding-top: .55rem;
}

.hud-cell {
    display: grid;
    gap: .15rem;
}

.hud-k {
    font-size: .5rem;
    letter-spacing: .22em;
    color: rgba(243, 217, 166, .5);
}

.hud-v {
    font-size: .72rem;
    letter-spacing: .14em;
    color: var(--gold-live);
}

.hm-atef {
    position: absolute;
    right: -16%;
    bottom: 2%;
    width: 118px;
    opacity: .96;
    z-index: 5;
    filter: drop-shadow(0 18px 30px rgba(0, 0, 0, .5));
}

body.is-idle .hm-atef {
    animation: atefBob 5.5s ease-in-out infinite;
}

@keyframes atefBob {

    0%,
    100% {
        transform: translateY(0) rotate(-2deg);
    }

    50% {
        transform: translateY(-14px) rotate(2deg);
    }
}

/* ── Full-bleed continuous ECG bands ────────────────────────────────── */
.ecg-band {
    position: relative;
    width: 100%;
    height: 64px;
    background: var(--ink);
    overflow: hidden;
    border-block: 1px solid var(--border-dark);
}

.ecg-band-paper {
    background: var(--paper);
    border-color: var(--border);
}

.ecg-line {
    display: block;
    width: 100%;
    height: 100%;
}

/* ⚠ The scroll cue and its dropping gold line were REMOVED 2026-09-15 at
   Mo's request. Its rules (.hm-scrollcue, .cue-line, @keyframes cueDrop) are
   deleted rather than commented out; the space they reserved is reclaimed in
   .hm-copy and .hm-hero-inner above. */


@media (max-width: 940px) {
    .hm-hero-inner {
        grid-template-columns: 1fr;
        padding-block: var(--s-12) var(--s-16);
    }

    .hm-copy {
        padding-bottom: 0;
        order: 1;
    }

    .hm-specimen {
        position: relative;
        top: 0;
        height: 62vh;
        order: 0;
        margin-bottom: var(--s-8);
    }

    .hb-stage {
        width: min(320px, 80%);
    }

    .co-1,
    .co-3 {
        right: -2%;
    }

    .co-2,
    .co-4 {
        left: -2%;
    }

    .hm-hud {
        left: 0;
        right: 0;
    }

    .hm-atef {
        width: 84px;
        right: -4%;
    }
}

/* ── TICKER ─────────────────────────────────────────────────────────── */
.hm-ticker {
    background: var(--ink2);
    border-block: 1px solid var(--border-dark);
    overflow: hidden;
    white-space: nowrap;
    display: flex;
}

.ticker-track {
    display: inline-flex;
    align-items: center;
    gap: 2.2rem;
    padding: .8rem 1.1rem;
    font-size: .62rem;
    letter-spacing: .26em;
    color: var(--gold-lt);
    flex-shrink: 0;
}

.ticker-track em {
    color: var(--gold-live);
    font-style: normal;
}

/* ── Chapter labels ─────────────────────────────────────────────────── */
.hm-chnum {
    font-size: .6rem;
    letter-spacing: .3em;
    color: var(--gold);
    margin-bottom: var(--s-4);
}

.on-ink .hm-chnum {
    color: var(--gold-lt);
}

/* ── CH.01 VITALS ───────────────────────────────────────────────────── */
/* The vitals chapter sits on the dark home body — its heading is paper. */
.hm-vitals h2 {
    color: var(--paper);
}

.vitals-grid {
    display: grid;
    /* FOUR figures since the sessions count returned (Mo, 2026-09-14). */
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s-5);
    margin-top: var(--s-10);
}

@media (max-width: 1100px) {
    .vitals-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
    .vitals-grid { grid-template-columns: 1fr; }
}

.vital {
    position: relative;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--s-6) var(--s-5);
    background: var(--paper);
    overflow: hidden;
    /* Centred, and tall enough that a one-line and a two-line label sit at the
       same height across the row. */
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 190px;
    /* ⭐ THE FIX. `cqi` is 1% of THIS CARD's inline size, so the number below
       scales with the COLUMN. The old `5vw` scaled with the window, which is
       why "85,000+" fitted at full width and was clipped by the card edge the
       moment the grid went 3-up. */
    container-type: inline-size;
}

.vital-num {
    /* Zain 800, not Oi — see components.css .stat-num for the reasoning; these
       two are the same component in different clothes. */
    font-family: var(--font-body);
    font-weight: 800;
    /* min 2rem so it survives a 320px phone; cqi tracks the CARD; 4rem cap
       keeps the desktop proportions the design had. 16 -> 20 because Zain is
       about 60% of Oi's width for the same digits. */
    font-size: clamp(2rem, 20cqi, 4rem);
    color: var(--gold-live);
    line-height: 1.05;
    display: block;
    max-width: 100%;
    /* A number must never hyphenate or break mid-digit — it shrinks instead. */
    white-space: nowrap;
}

.vital-label {
    display: block;
    margin-top: var(--s-3);
    font-size: .6rem;
    letter-spacing: .2em;
    color: var(--muted);
    /* Let a long label wrap onto a second line instead of forcing the card
       wider than its grid track. */
    max-width: 100%;
    overflow-wrap: anywhere;
    text-wrap: balance;
    line-height: 1.5;
}

.vital-spark {
    position: absolute;
    /* Centred under the centred content, not pinned to a corner. */
    left: 50%;
    transform: translateX(-50%);
    bottom: 0;
    width: min(120px, 60%);
    height: 22px;
    opacity: .35;
    background: repeating-linear-gradient(90deg, transparent 0 10px, var(--gold-lt) 10px 11px);
    mask-image: linear-gradient(90deg, transparent, #000 35%, #000 65%, transparent);
}

/* (the 2-up tablet step now lives in the rule above; four is an even
   count, so nothing needs to span the full row any more) */
@media (max-width: 420px) {
    .vital { min-height: 160px; padding: var(--s-5) var(--s-4); }
}

/* ── CH.02 DISCIPLINES ──────────────────────────────────────────────── */
.hm-disc {
    position: relative;
    padding-block: var(--s-12);
    overflow: hidden;
}

.disc-ghost {
    position: absolute;
    top: 6%;
    left: 0;
    width: 100%;
    overflow: hidden;
    pointer-events: none;
    opacity: .05;
}

.disc-ghost span {
    display: inline-block;
    font-family: var(--font-display);
    font-size: clamp(6rem, 18vw, 15rem);
    color: var(--paper);
    white-space: nowrap;
    will-change: transform;
}

.disc-head {
    position: relative;
    margin-bottom: var(--s-10);
}

.disc-rail {
    display: flex;
    gap: var(--s-5);
    padding: 0 max(var(--s-6), calc((100vw - var(--maxw)) / 2 + var(--s-6))) var(--s-4);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    position: relative;
}

.disc-rail::-webkit-scrollbar {
    display: none;
}

.disc-card {
    position: relative;
    flex: 0 0 min(320px, 78vw);
    scroll-snap-align: start;
    background: var(--ink3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-lg);
    padding: var(--s-6);
    display: grid;
    gap: var(--s-3);
    align-content: start;
    transition: border-color .3s, transform .3s;
    will-change: transform;
}

.disc-card:hover {
    border-color: var(--gold-live);
}

.disc-ix {
    position: absolute;
    top: var(--s-4);
    right: var(--s-5);
    font-size: .62rem;
    color: var(--gold-lt);
}

.disc-icon {
    width: 56px;
    height: 56px;
    margin-block: var(--s-2);
}

.disc-card h3 {
    color: var(--paper);
    font-size: 1.5rem;
}

.disc-card p {
    color: var(--peach);
    font-size: .92rem;
}

.disc-go {
    font-size: .58rem;
    letter-spacing: .22em;
    color: var(--gold-live);
    margin-top: var(--s-2);
}

/* ── CH.03 METHOD — the production line rides a descending curve ─────── */
.hm-method {
    background: var(--paper);
}

.method-line {
    position: relative;
    margin-top: var(--s-12);
    height: 320px;
    /* ⚠ THE LAST STATION HANGS 2px PAST THE VIEWPORT BETWEEN ~940px AND ~1024px,
       AND THAT MADE THE WHOLE PAGE SCROLL SIDEWAYS. Measured at 941px: the rail
       is 877px wide, the ninth station's centre is computed from the SVG path at
       859px, and `translate(-50%)` on a 104px box puts its right edge at 911px —
       34px past the rail, 2px past the document. Found by the overflow sweep,
       not by eye: two pixels are invisible and a page that can be swiped
       sideways into a blank column is not.
       ⭐ `clip`, NOT `hidden`. `hidden` would make this a scroll container, which
          breaks `position: sticky` on any descendant and gives the rail its own
          scrollbar; `clip` exists precisely to trim the overhang without either.
       ⚠ Only the X axis is clipped — the station icons' gold glow spills
         vertically and must keep doing so. What gets trimmed is the empty part
         of a 104px text box whose content is centred, so nothing visible is lost. */
    overflow-x: clip;
    overflow-y: visible;
}

.ml-curve {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

.ml-curve path {
    fill: none;
    stroke-linecap: round;
}

#mlTrack {
    stroke: var(--border);
    stroke-width: 2;
    stroke-dasharray: 2 9;
}

#mlProgress {
    stroke: var(--gold-live);
    stroke-width: 2.5;
    filter: drop-shadow(0 0 8px rgba(236, 164, 36, .55));
}

.ml-station {
    position: absolute;
    transform: translate(-50%, -19px);
    display: grid;
    justify-items: center;
    gap: .3rem;
    width: 104px;
    text-align: center;
}

.ml-icon {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--paper);
    border: 2px solid var(--border);
    transition: border-color .3s, box-shadow .3s, transform .3s;
}

.ml-icon svg {
    width: 18px;
    height: 18px;
    stroke: var(--muted);
    fill: none;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke .3s;
}

.ml-station.lit .ml-icon {
    border-color: var(--gold-live);
    box-shadow: 0 0 14px rgba(236, 164, 36, .55);
    transform: scale(1.08);
}

.ml-station.lit .ml-icon svg {
    stroke: var(--gold);
}

.ml-num {
    font-size: .55rem;
    color: var(--gold-lt);
    letter-spacing: .2em;
}

.ml-name {
    font-family: var(--font-subhead);
    font-size: .72rem;
    letter-spacing: .04em;
    color: var(--muted);
}

.ml-station.lit .ml-name {
    color: var(--ink);
}

.method-cta {
    margin-top: var(--s-8);
}

/* Small screens: the curve retires; stations become an icon grid.
   Raised 760 -> 820: at exactly 768 (the iPad portrait width) the last
   station on the curve still landed 6px past the viewport. */
@media (max-width: 820px) {
    .method-line {
        height: auto;
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: var(--s-6) var(--s-4);
    }

    .ml-curve {
        display: none;
    }

    .ml-station {
        position: static;
        transform: none;
        width: auto;
        min-width: 0;
        left: auto !important;
        top: auto !important;
    }
    /* "Illustration" / "Integration" do not fit an 88px cell: measured 34px
       past the viewport at 330px with three columns. */
    .ml-name {
        overflow-wrap: break-word;
        hyphens: auto;
        max-width: 100%;
    }
}

@media (max-width: 420px) {
    .method-line { grid-template-columns: repeat(2, 1fr); }
}

/* ── CH.04 STORE ────────────────────────────────────────────────────── */
.store-card {
    position: relative;
    overflow: hidden;
}

.store-ph {
    display: grid;
    place-items: center;
    height: 100%;
    color: var(--gold-lt);
    font-size: .7rem;
    letter-spacing: .3em;
}

.store-price {
    display: inline-block;
    margin-top: var(--s-3);
    font-size: .68rem;
    letter-spacing: .16em;
    color: var(--gold-live);
}

.fx-tilt {
    transition: transform .35s var(--ease);
}

.fx-tilt::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(105deg, transparent 40%, rgba(236, 164, 36, .08) var(--sheen-x, 50%), transparent 60%);
}

/* ── CH.05 FUND ─────────────────────────────────────────────────────── */
.hm-fund {
    background: var(--paper2);
}

.fund-counter {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: var(--s-3);
    margin-block: var(--s-6);
}

.fund-cur {
    font-size: 1rem;
    color: var(--gold);
    letter-spacing: .2em;
}

.fund-num {
    /* Zain 800, not Oi — the same reasoning as .vital-num and .stat-num. */
    font-family: var(--font-body);
    font-weight: 800;
    /* The 3rem FLOOR was the original problem, not the 9vw: at a 330px viewport
       the container is 266px, 9vw resolves to 30px, and clamp() then took the
       48px floor — at which "1,500,000" in Oi was wider than the container.
       On Zain the floor can come back up and still fit the longest value. */
    font-size: clamp(2.4rem, 9vw, 6.4rem);
    max-width: 100%;
    color: #ECA424;
    line-height: 1;
}

/* ── OUTRO ──────────────────────────────────────────────────────────── */
.hm-outro {
    position: relative;
    /* Was 92vh for 140px of content. It is a full-stop, not a chapter. */
    min-height: 68vh;
    background: var(--ink);
    color: var(--paper);
    display: grid;
    align-items: center;
    overflow: hidden;
}

.outro-ecg {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 140px;
    transform: translateY(-50%);
    opacity: .35;
}

.outro-inner {
    position: relative;
}

.outro-title {
    color: var(--paper);
    font-size: clamp(2.2rem, 5.4vw, 4.4rem);
    max-width: 18ch;
    margin-inline: auto;
}

.outro-title .fx-char {
    display: inline-block;
    will-change: transform, opacity;
}

.outro-foot {
    margin-top: var(--s-8);
    font-size: .58rem;
    letter-spacing: .3em;
    color: var(--gold-lt);
}

/* ── Reduced motion ─────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .op {
        display: none;
    }

    body.is-idle .hm-heart,
    .hm-scan,
    .cue-line::after,
    .hm-atef {
        animation: none !important;
    }

    .hm-scan {
        opacity: 0;
    }
}
/* ── PARTNER-UNIVERSITY MARQUEE (Mo, 2026-09-15) ──────────────────────────
   Sits between the vitals ("an institution with a pulse") and the disciplines.

   ⚠ 44px IS MEASURED, NOT CHOSEN. `.hm-ticker` renders at exactly 44px at
     1440px (12.8px padding x2 + a 9.92px line), and Mo asked for this strip to
     match the other marquees. Pinning the band and sizing the logos off it —
     rather than sizing the logos and letting the band follow — is what keeps
     the two strips identical when a logo with a different aspect ratio is
     dropped into the folder. */
.uni-marquee {
    height: 44px;
    align-items: center;
    background: var(--ink2);
}

.uni-track {
    gap: 2.6rem;
    padding-inline: 1.3rem;
    padding-block: 0;
    height: 100%;
}

/* ⚠ Sized by HEIGHT with width:auto — the eleven logos are not one aspect
   ratio (MTI is a 654-byte wordmark, MUST a 48 KB crest), so any fixed width
   would render them at visibly different sizes. `object-fit: contain` keeps a
   tall crest from being cropped by the 44px band. */
.uni-logo {
    height: 26px;
    width: auto;
    max-width: 150px;
    object-fit: contain;
    flex-shrink: 0;
    /* The logos arrive in their own brand colours against a navy band. Muting
       them to the band's own palette stops eleven competing colourways from
       pulling the eye off the sections either side; hovering restores the real
       thing, so the partner is never misrepresented, only quietened. */
    filter: grayscale(1) brightness(1.9) opacity(.62);
    transition: filter .3s var(--ease), transform .3s var(--ease);
}
.uni-logo:hover {
    filter: none;
    transform: scale(1.06);
}

@media (max-width: 560px) {
    .uni-marquee { height: 38px; }
    .uni-logo { height: 21px; max-width: 110px; }
    .uni-track { gap: 1.8rem; }
}

@media (prefers-reduced-motion: reduce) {
    .uni-logo:hover { transform: none; }
}

/* A logo supplied as a solid rectangle rather than a transparent mark. Measured:
   NMU.webp is 0% transparent where the other ten are 11-77%. Inverting turns its
   light ground dark and its dark marks light; `screen` then drops that dark
   ground into the navy band, leaving only the marks — the same silhouette the
   transparent logos give for free. */
.uni-logo-opaque {
    filter: invert(1) grayscale(1) brightness(1.25) opacity(.72);
    mix-blend-mode: screen;
}
.uni-logo-opaque:hover {
    filter: invert(1) grayscale(1) brightness(1.45);
    mix-blend-mode: screen;
}

/* ── Hero CTAs side by side ──────────────────────────────────────────────────
   Mo, 2026-09-15: *"for the hero section of the home tab make the 2 buttons in
   it beside each other in pc view instead of on top of each other."*

   ⚠ `.hm-cta` WAS ALREADY `display:flex; flex-wrap:wrap` — the buttons were not
     stacked by a rule, they were stacked because they did not FIT. Measured at
     1440×900: the copy column is 586px and the two pills are 325px + 330px +
     16px gap = 671px, so `wrap` did exactly what it is for. Changing the
     container alone would have produced a horizontal overflow instead of a fix.
   ⭐ So the buttons are what changes: tighter padding and a slightly smaller
     cap-height on wide screens, where the hero has a second column beside it
     and the CTA does not need to shout at 16px to be the loudest thing there.

   ⛔ NOT APPLIED BELOW 1150px, AND THAT IS THE POINT OF THE BREAKPOINT.
      The column narrows with the viewport; to fit two pills into ~440px at
      960px wide the type would have to drop to ~11.7px, under the 12px floor
      the responsive checker enforces. Between 941px and 1150px the layout is
      still two columns and the buttons still wrap — which is correct, because
      a wrapped pair at a readable size beats a single row nobody can read.
   ⚠ `white-space: nowrap` is load-bearing: without it the pills shrink by
     breaking "Begin your / journey" over two lines, which is the stacked look
     in a different costume. */
@media (min-width: 1150px) {
    .hm-cta {
        flex-wrap: nowrap;
        align-items: stretch;
    }

    .hm-cta .btn {
        white-space: nowrap;
        font-size: clamp(.8rem, .34rem + .62vw, .86rem);
        padding-inline: clamp(1rem, .1rem + 1.2vw, 1.45rem);
    }
}
