/* ============================================================================
   MyESO palette override
   ----------------------------------------------------------------------------
   The base stylesheets (redesign.css + warm-theme.css) are inherited from the
   sibling Explorer sites and are built on a turquoise/gold ramp. Rather than
   fork ~93 KB of CSS, this file retargets the handful of custom properties the
   whole theme is expressed through onto the Earth Explorer brand colours
   sampled from the live site:

       #F8AD36  amber      primary accent / CTAs
       #EDFF2B  bright lime  highlights
       #E4FF9A  pale lime   section washes
       #2A5B39  deep green  headings and dark blocks
       #917B29  gold        secondary accent

   Loaded last in the layout so it wins on specificity ties.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   SELECTOR NOTE — read before editing either token block below.

   warm-theme.css declares its palette under `:root[data-theme="warm"]`, which is
   specificity (0,2,0). A bare `[data-theme="warm"]` is (0,1,0) and LOSES to it no
   matter which file the browser loads last. Both blocks here therefore have to
   carry `:root` as well, or they are dead rules that compute to turquoise.

   This is not hypothetical: it is exactly what shipped. Until the audit, --brand
   computed to #0d867b, --text to #123b3a and every border to teal, so the Earth
   palette only ever reached the components that set literal hex values.
   --------------------------------------------------------------------------- */
:root,
:root[data-theme="warm"] {
    /* brand ramp — amber through to the deep green the site uses for headings */
    --brand: #f8ad36;
    --brand-100: #fff4dd;
    --brand-200: #ffe6b4;
    --brand-300: #fcd486;
    --brand-400: #fabf58;
    --brand-500: #f8ad36;
    --brand-600: #d98d18;
    --brand-700: #a86a10;
    --brand-800: #6d4a12;
    --brand-900: #2a5b39;

    /* accents */
    --accent: #2a5b39;
    --accent-2: #7a6722;   /* darkened from #917b29 — that was 4.14:1 on white, under AA */
    --accent-3: #edff2b;
    --lime: #e4ff9a;
    --lime-soft: #f2ffd0;

    /* Link ink. Amber is a FILL colour on this brand, never a text colour:
       --brand-600 is 2.70:1 on white and --brand is 1.91:1, both far under the
       4.5:1 AA floor. Body links and the active nav item run in the brand deep
       green instead, which measures 7.91:1 and is already in the palette. */
    --link: #2a5b39;
    --link-hover: #1c4527;

    /* Inherited names that only warm-theme.css declares, retargeted here so no
       rule downstream can reach back into the turquoise ramp. */
    --teal: #f8ad36;                      /* accent hairlines, ring progress arc */
    --gold: #917b29;
    --violet: #2a5b39;                    /* kept name → deep green */
    --pink: #d98d18;
    --muted: #6e6858;                     /* was #4f6e6c teal; also raised to 5.2:1 */
    --brand-glow: rgba(60, 48, 12, .14);  /* neutral warm shadow, not a teal halo */

    /* Gradients. --grad-title is what paints the second half of every section
       heading ("Intro|duction"), via `.section-title .grad` in redesign.css at
       (0,2,0) — which outranks this file's own `.grad` rule, so the token is the
       only place the fix can live. */
    --grad-brand: linear-gradient(135deg, #f8ad36 0%, #d98d18 100%);
    --grad-gold:  linear-gradient(135deg, #f8ad36, #917b29);
    --grad-text:  linear-gradient(92deg, #d98d18 0%, #f8ad36 45%, #2a5b39 100%);
    --grad-title: linear-gradient(92deg, #d98d18 0%, #f8ad36 45%, #2a5b39 100%);
}

/* Footer. warm-theme.css ends the page on a teal ink panel with a turquoise
   accent — the sibling sites' colours, and the last thing a visitor sees. On the
   Earth palette it is bedrock green with the amber accent, so the page closes in
   the same colours it opened in.

   Set through `:root[data-theme="warm"]`, not the plain `[data-theme="warm"]`
   above: warm-theme.css declares these under that same compound selector, which
   out-specifies a bare attribute selector no matter which file loads last. */
:root[data-theme="warm"] {
    --footer-bg: linear-gradient(165deg, #24512f 0%, #17391f 100%);
    --footer-text: #f3fbec;
    --footer-muted: #bed4b2;
    --footer-border: rgba(255, 255, 255, .14);
    --footer-accent: #f8ad36;
}

/* Light theme — a warm, earthy wash instead of the aqua one.
   `:root` prefix required; see the selector note above. */
:root[data-theme="warm"] {
    --bg: #fffdf6;
    --bg-2: #fdf6e4;
    --surface: #ffffff;
    --surface-2: #fdf7e8;
    --surface-3: #f7f0dc;

    /* The --bg-9xx ramp is warm-theme's aqua-white stack, and redesign.css builds
       the fallback page backdrop out of it. Retargeted to the cream wash so the
       backdrop is warm even before earth-theme's own body rules apply. */
    --bg-950: #fffdf6;
    --bg-900: #f9f3e3;
    --bg-850: #f3ecd8;

    --text: #1c1a12;
    --text-soft: #5c5647;
    --text-mute: #6e6858;   /* was #857e6c — 4.04:1 on white, under the AA floor */

    --border: rgba(42, 91, 57, .14);
    --border-strong: rgba(42, 91, 57, .28);

    --shadow-sm: 0 2px 8px rgba(60, 48, 12, .07);
    --shadow: 0 10px 28px rgba(60, 48, 12, .11);
    --shadow-md: 0 14px 34px rgba(60, 48, 12, .12);
    --shadow-lg: 0 24px 60px rgba(60, 48, 12, .15);
}

/* The page backdrop: pale lime settling into warm cream, so the earth/plant
   palette reads across the whole page rather than only on the accents. */
[data-theme="warm"] body {
    background:
        radial-gradient(1200px 600px at 15% -10%, #eaffb8 0%, rgba(234, 255, 184, 0) 60%),
        radial-gradient(900px 500px at 95% 5%, #fff0cd 0%, rgba(255, 240, 205, 0) 55%),
        linear-gradient(180deg, #fffdf6 0%, #f7f2e2 100%);
}

/* …and the sheet that was covering it.

   warm-theme.css paints `body::before` — fixed, inset:0, z-index:-3, opacity:1 —
   with four turquoise/aqua blooms over an opaque aqua-white gradient. That is a
   LAYER ABOVE the body's own background, so the warm wash directly above was
   computed correctly and then hidden on every page. It is the single reason the
   site read as turquoise despite an amber-and-green component set.

   Repainted rather than removed: the ambient field is what stops the page feeling
   like a flat wall, and it is worth keeping — in the right colours. Topsoil lime
   at the top corners, warm gold low and right, over the cream base. The 40s drift
   animation is inherited and already suspended under prefers-reduced-motion. */
[data-theme="warm"] body::before {
    background:
        radial-gradient(900px 700px at 84% -8%, rgba(228, 255, 154, .55), transparent 62%),
        radial-gradient(780px 640px at  2% 22%, rgba(234, 255, 184, .42), transparent 60%),
        radial-gradient(860px 660px at 98% 56%, rgba(248, 173,  54, .16), transparent 62%),
        radial-gradient(900px 720px at 10% 88%, rgba(145, 123,  41, .10), transparent 62%),
        linear-gradient(180deg, #fffdf6 0%, #fbf6e6 48%, #f5eeda 100%);
    background-repeat: no-repeat;
}

/* Gradient text used for the second half of most headings. */
.grad-text,
.grad {
    background: linear-gradient(92deg, #d98d18 0%, #f8ad36 45%, #2a5b39 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}

/* Primary CTA — amber fill, deep-green text, which is how the live buttons read. */
[data-theme="warm"] .btn-primary {
    background: linear-gradient(135deg, #f8ad36 0%, #e89a1c 100%);
    border-color: #d98d18;
    color: #26210d;
}

[data-theme="warm"] .btn-primary:hover {
    background: linear-gradient(135deg, #ffbe55 0%, #f0a323 100%);
    color: #1c1a12;
}

/* The accent line along the top edge of the header. The base stylesheet paints it
   with --grad-brand, which this file never retargets (the primary CTA is overridden
   directly instead), so it was still running the inherited turquoise — identical to
   MyBO's, since both sites inherit the same ramp. Retargeted here onto the Earth
   palette: the strata ramp the hero uses, topsoil green through gold to bedrock
   amber, so the two sites are told apart at the very first pixel of the page. */
[data-theme="warm"] .site-header::before {
    background: linear-gradient(135deg, #2a5b39 0%, #917b29 45%, #f8ad36 100%);
}

/* ---- Navigation chrome ------------------------------------------------------
   warm-theme.css paints all four of these in `rgba(245,252,251)` / `#f6fdfc` — a
   turquoise-tinted white. Against the cream page above they read visibly cool, so
   they are restated here on the warm white the rest of the site uses. */
[data-theme="warm"] .site-header { background: rgba(255, 253, 246, .78); border-bottom-color: var(--border); }
[data-theme="warm"] .site-header.scrolled { background: rgba(255, 253, 246, .96); box-shadow: var(--shadow-sm); }
[data-theme="warm"] .dropdown { background: rgba(255, 253, 246, .99); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
@media (max-width: 1024px) {
    /* the open burger menu is a full overlay panel — fully opaque, or the hero
       ghosts through it */
    [data-theme="warm"] .nav-links { background: #fffdf6; border-bottom-color: var(--border-strong); }
}

/* Scroll-to-top. This kept warm-theme's flat turquoise (#12a89b) — it was the last
   piece of the sibling palette still visible on every page after a scroll. */
[data-theme="warm"] .to-top {
    background: linear-gradient(140deg, #f8ad36, #e89a1c);
    border-color: #d98d18;
    color: #26210d;
    box-shadow: var(--shadow-md);
}
[data-theme="warm"] .to-top:hover { background: linear-gradient(140deg, #ffbe55, #f0a323); }

/* ---- Link ink --------------------------------------------------------------
   Every one of these rules previously resolved to --brand-600. While that token
   was still the inherited teal it passed contrast by accident; retargeting it to
   the Earth gold (#d98d18, 2.70:1 on white) would have made every body link and
   the active nav item fail WCAG AA. So text links take --link (the brand deep
   green, 7.91:1) and amber stays a fill colour.

   Selectors mirror warm-theme.css exactly rather than out-specifying it — this
   file loads last, so an equal-specificity rule wins, and keeping them identical
   means a future edit there is easy to find here. */
[data-theme="warm"] .nav-links a:hover { color: var(--link-hover); background: var(--surface-2); }
[data-theme="warm"] .nav-links a.active { color: var(--link); }
[data-theme="warm"] .gx-glass a:not(.btn) { color: var(--link); }
[data-theme="warm"] .gx-glass.prose a:not(.btn),
[data-theme="warm"] .gx-glass .prose a:not(.btn) { color: var(--link); }
[data-theme="warm"] .step-row a { color: var(--link); }
[data-theme="warm"] .prose a:not(.btn) { color: var(--link); }
[data-theme="warm"] .prose a:not(.btn):hover { color: var(--link-hover); }
/* the small-caps tagline under the wordmark — amber at 10px was the worst
   offender on the page for size and contrast together */
[data-theme="warm"] .brand-sub { color: var(--accent); }

/* The eyebrow chip above each page title. */
[data-theme="warm"] .eyebrow {
    background: var(--lime-soft);
    border-color: rgba(42, 91, 57, .22);
    color: #2a5b39;
}

/* Section headings sit in the deep green rather than the inherited teal. */
[data-theme="warm"] h1,
[data-theme="warm"] h2,
[data-theme="warm"] h3 {
    color: #23301f;
}
/* …but not on the dark surfaces. The rule above is a bare element selector, so it
   ties with `.footer h2` on specificity and wins only because this file loads
   last — which painted EXPLORE / GET IN TOUCH / FOLLOW US deep green on the dark
   footer panel, where they were all but invisible. The footer keeps its own ink. */
[data-theme="warm"] .footer h2,
[data-theme="warm"] .footer h3 { color: var(--footer-text); }

/* Glass panels pick up the warm cream instead of the aqua tint. */
[data-theme="warm"] .gx-glass {
    background: linear-gradient(180deg, rgba(255, 255, 255, .92) 0%, rgba(253, 247, 232, .92) 100%);
    border-color: var(--border);
}

/* The icon chips on tiles. */
[data-theme="warm"] .gx-icon {
    background: linear-gradient(140deg, var(--a, #f8ad36) 0%, rgba(42, 91, 57, .85) 130%);
    color: #fff;
}

/* ---- Page-level helpers shared by the MyESO pages -------------------------- */

/* Two-column "label | value" tables used for the competition format, categories
   and award bands. Plain definition rows, not a full table widget. */
.kv-list { display: grid; gap: 10px; margin: 0; }
.kv-row {
    display: grid;
    grid-template-columns: minmax(140px, 34%) 1fr;
    gap: 8px 18px;
    padding: 12px 0;
    border-bottom: 1px solid var(--border);
}
.kv-row:last-child { border-bottom: 0; }
.kv-row dt { font-weight: 700; color: #2a5b39; margin: 0; }
.kv-row dd { margin: 0; color: var(--text-soft); }
@media (max-width: 560px) {
    .kv-row { grid-template-columns: 1fr; gap: 2px; padding: 10px 0; }
}

/* Numbered step list (registration procedure) — Galaxy's timeline, in Earth's
   colours: numbered discs threaded on a vertical rail, one plain paragraph of
   text hanging off each.

   Everything the earlier version did to a step is gone. No bold title with a
   muted detail beside or beneath it: at two weights and two columns, a step
   with no detail read as a different kind of row from one that had a detail,
   and the panel filled with ragged half-width blocks. Set as one sentence at
   one weight, every row is the same shape and the rail carries the sequence. */
.step-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 20px;
    counter-reset: step;
    line-height: 1.65;
    color: var(--text-soft);
}
.step-list li {
    counter-increment: step;
    position: relative;
    padding-left: 52px;
    min-height: 32px;
}
.step-list li::before {
    content: counter(step);
    position: absolute;
    left: 0; top: -3px;    /* centres the disc on the first line of text */
    display: grid;
    place-items: center;
    width: 32px; height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, #f8ad36 0%, #e89a1c 100%);
    color: #26210d;
    font-weight: 800;
    font-size: .95rem;
}
/* the two things a step marks up: the buttons and menu items to look for, and
   the address to go to. Both are darker than the surrounding line, since that
   line is set in the soft body colour. */
.step-list strong { color: var(--text); font-weight: 700; }
.step-list a {
    color: #2a5b39;
    font-weight: 700;
    text-decoration: underline;
    text-decoration-thickness: 1.5px;
    text-underline-offset: 3px;
    transition: color .2s ease;
}
.step-list a:hover, .step-list a:focus-visible { color: #d8901a; }

/* sub-actions inside a step: own line, arrow hanging in the margin so a wrapped
   one lines up with the text above it rather than with the arrow */
.step-list .step-sub {
    display: block;
    margin-top: 2px;
    padding-left: 1.35em;
    text-indent: -1.35em;
}
.step-list .step-sub::before {
    content: "→";
    display: inline-block;
    width: 1.35em;
    text-indent: 0;
    color: #d8901a;
}

/* the rail: disc centre is 16px in, so a 2px line sits at 15px. It runs from
   the bottom of this disc to the top of the next one (20px gap, less the 3px
   the disc is raised by), and the last step has none to run to. */
.step-list li:not(:last-child)::after {
    content: "";
    position: absolute;
    left: 15px;
    top: 34px;
    bottom: -17px;
    width: 2px;
    border-radius: 1px;
    background: linear-gradient(180deg, rgba(248, 173, 54, .45), rgba(248, 173, 54, .14));
}

/* the panel around it: capped so the lines keep a readable measure, centred in
   the section, and — with the heading gone — holding only the steps */
.step-panel { max-width: 880px; margin-inline: auto; }

/* The mark over a results-page hero. redesign.css sets .badge-mini to 88px, which
   suits the sibling sites' round badges and the Earth Explorer one — but MyESO's
   own mark is a wordmark carrying a strapline, and at 88px that line is a smudge.
   --wide gives it the width the lettering needs, capped against the viewport. */
.page-hero .badge-mini { height: auto; }
.page-hero .badge-mini--wide { width: min(260px, 66vw); }

/* ---- Explorer → MyESO lockup ----------------------------------------------
   The competition badge feeding the olympiad it leads into, over the copy that
   describes that route — Biology's .about-lockup, in Earth's colours. */
.about-lockup {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(14px, 2.6vw, 34px);
    margin-bottom: clamp(22px, 3vw, 34px);
}
.al-item { flex: 0 0 auto; margin: 0; text-align: center; }
/* One shared plate height, so the square badge and the wide wordmark sit on the
   same line. One box for both, not one per artwork: the plates used to be sized
   height-led with width:auto, so each came out as wide as its own logo — a
   square badge in a narrow plate beside a 2:1 wordmark in a wide one, which read
   as two different kinds of card rather than as a pair.

   The logo is fitted inside the box instead (max-width and max-height, never
   stretched), so the square one fills the height, the wide one fills the width,
   and both sit on identical plates. */
.al-plate {
    width: clamp(190px, 21vw, 264px);
    height: clamp(120px, 13vw, 168px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(14px, 1.8vw, 22px);
    border-radius: 18px;
    background: linear-gradient(180deg, #fff 0%, #fdf7e8 100%);
    border: 1px solid rgba(42, 91, 57, .12);
    box-shadow: 0 14px 34px rgba(60, 48, 12, .16);
}
.al-plate img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
}
.al-cap {
    margin: 10px 0 0;
    font-weight: 700;
    font-size: .72rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--text-soft);
}

/* the connector: a dashed trail running toward MyESO, ending in an amber chip */
.al-arrow {
    flex: 1 1 auto;
    max-width: 190px;
    min-width: 54px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.al-trail {
    flex: 1 1 auto;
    height: 3px;
    border-radius: 999px;
    background: repeating-linear-gradient(90deg, #d98d18 0 9px, transparent 9px 18px);
    background-size: 18px 100%;
    animation: al-flow 1.1s linear infinite;
}
.al-tip {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    /* dark ink on amber, as everywhere else on this palette — white on #f8ad36 is
       nowhere near readable at an 18px glyph */
    color: #26210d;
    background: linear-gradient(135deg, #f8ad36 0%, #e89a1c 100%);
    box-shadow: 0 6px 16px rgba(176, 110, 12, .4);
    animation: al-nudge 1.7s ease-in-out infinite;
}
@keyframes al-flow  { to { background-position: 18px 0; } }
@keyframes al-nudge { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(4px); } }
@keyframes al-flow-down  { to { background-position: 0 18px; } }
@keyframes al-nudge-down { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
/* the motion is pure decoration — nothing is lost by holding it still */
@media (prefers-reduced-motion: reduce) {
    .al-trail, .al-tip { animation: none; }
}
@media (max-width: 760px) {
    /* stack the lockup and turn the arrow to point down the column */
    .about-lockup { flex-direction: column; }
    /* stacked, the plates have the column's full width to share, so they can be
       squarer without either logo shrinking — but still the same box as each other */
    .al-plate { width: min(260px, 78vw); height: 132px; }
    .al-arrow { flex: 0 0 auto; max-width: none; min-width: 0; flex-direction: column; height: 74px; }
    .al-trail {
        width: 3px;
        height: auto;
        background: repeating-linear-gradient(180deg, #d98d18 0 9px, transparent 9px 18px);
        background-size: 100% 18px;
        animation-name: al-flow-down;
    }
    .al-tip { animation-name: al-nudge-down; }
    .al-tip .gx-ico { transform: rotate(90deg); }
}

/* Justified body copy — the About page's introduction and every write-up under
   it, the same treatment the home page's introduction gets.

   max-width is released because .prose p is otherwise held to a reading measure,
   and justifying a column that stops short of the panel edge just looks like a
   ragged block that missed. No hyphenation, so words stay whole; text-wrap:pretty
   keeps the closing lines from stranding a single word on its own.

   Lists come with it: a justified paragraph beside a ragged list reads as an
   inconsistency rather than a distinction. */
.eso-justify p,
.eso-justify li {
    max-width: none;
    hyphens: none;
    text-wrap: pretty;
}
/* Justify only where the measure can absorb it. With hyphenation deliberately off,
   the browser has nothing but the word gaps to stretch — so on a narrow column it
   opens rivers of white space, and below about 45em a line holds so few words that
   the gaps between them stop looking like typography and start looking like a bug.
   Above that width the same setting reads as a clean, squared-off block, which is
   what it was added for. Ragged-right is the safe default, so it is what we opt
   OUT of rather than into. */
@media (min-width: 45em) {
    .eso-justify p,
    .eso-justify li { text-align: justify; }
}

/* ---- Fee tiers ------------------------------------------------------------
   The live site's registration-fee cards: a filled header naming the tier, the
   figure large underneath it, then the two facts that decide the tier — who it
   is for and the window it is open — and the button.

   Not filled cards with the price on a coloured ground, which is what these
   were: at two tiers, colour has nothing left to encode, and a rate is easier
   to read as dark text on white than as white text on gold. The colour moves to
   the header band, where it separates the tiers without touching the figures. */
.fee-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
    gap: clamp(16px, 2vw, 26px);
    /* stretch, not start: a closed tier carries no Register button and so came out
       visibly shorter than the open one beside it. Two rates are two alternatives,
       and drawing one of them as a smaller card says something about them that is
       not true — it is closed, not lesser. */
    align-items: stretch;
}
.fee-card {
    position: relative;
    display: flex;
    flex-direction: column;
    /* clips the ribbon's tails to the card's corner */
    overflow: hidden;
    border-radius: 16px;
    border: 1px solid var(--border);
    background: #fffdf8;
    box-shadow: 0 12px 30px rgba(60, 48, 12, .12);
    transition: transform .3s ease, box-shadow .3s ease;
}
.fee-card:hover { transform: translateY(-4px); box-shadow: 0 20px 44px rgba(60, 48, 12, .2); }

/* ---- header: the tier's name on brand gold -------------------------------- */
.fee-card-head {
    padding: 15px 18px;
    background: linear-gradient(160deg, #a86a10 0%, #8a5610 100%);
    text-align: center;
}
/* both selectors: warm-theme.css paints every h3 inside a .gx-glass in the dark
   body colours, and this one sits on the gold band — see the same note on the
   video playlist header */
.fee-card-head h3,
[data-theme="warm"] .gx-glass .fee-card-head h3 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: .01em;
    color: #fff;
}

/* the corner ribbon on the tier that closes. Rotated through the card's clipped
   corner, so it reads as a band laid over the card rather than a label stuck to
   it. Bedrock green: the header is gold, and the ribbon has to carry across it.

   Enlarged from .64rem (~10.2px). This is the only thing on the page telling a
   teacher the RM40 rate expires, and at 10px uppercase, tracked out and rotated
   45°, it was the least readable text on the site — the exact inverse of its
   importance. .8rem is the floor for anything carrying words rather than acting
   as a mark; the band grows with it. */
.fee-flag {
    position: absolute;
    top: 18px;
    right: -42px;
    width: 168px;
    padding: 6px 0;
    transform: rotate(45deg);
    background: linear-gradient(135deg, #2a6444 0%, #1d4a31 100%);
    color: #fff;
    font-size: .8rem;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
    text-align: center;
    box-shadow: 0 3px 10px rgba(18, 56, 35, .35);
}

/* Which rate applies today. Exactly one card is ever "current"; a closed one is
   drained of colour rather than hidden, so the price history still reads. */
.fee-pill {
    align-self: center;
    margin-bottom: 12px;
    padding: 4px 12px;
    border-radius: 999px;
    background: rgba(42, 91, 57, .1);
    border: 1px solid rgba(42, 91, 57, .28);
    color: #2a5b39;
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.fee-pill--muted {
    background: rgba(60, 48, 12, .07);
    border-color: rgba(60, 48, 12, .16);
    color: var(--text-soft);
}
.fee-card.is-ended { opacity: .72; }
.fee-card.is-ended .fee-card-head { background: linear-gradient(160deg, #8d8168 0%, #746a55 100%); }
.fee-card.is-ended:hover { transform: none; box-shadow: 0 12px 30px rgba(60, 48, 12, .12); }

/* ---- body: figure, then what the figure buys ------------------------------ */
.fee-card-body {
    /* takes the height the card was stretched to, so the header bands stay level
       across the row and the difference falls at the foot of the card */
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: clamp(20px, 2.4vw, 28px) clamp(16px, 2vw, 24px);
    text-align: center;
}
/* the button sits on the card's floor rather than directly under the dates, so the
   two cards' buttons line up whatever is written above them */
.fee-card-body .btn { margin-top: auto; }
.fee-price,
[data-theme="warm"] .gx-glass .fee-price {
    margin: 0;
    font-size: clamp(2.6rem, 4.4vw, 3.4rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -.02em;
    color: var(--text);
}
/* the currency rides small and high against the figure, the way a price is set */
.fee-cur {
    font-size: .38em;
    font-weight: 800;
    vertical-align: .95em;
    margin-right: .12em;
    letter-spacing: 0;
}
.fee-note,
[data-theme="warm"] .gx-glass .fee-note {
    margin: 8px 0 0;
    font-size: .92rem;
    color: var(--text-soft);
}

/* the two facts that pick the tier, ruled apart so neither is skimmed past */
.fee-points {
    list-style: none;
    width: 100%;
    margin: 20px 0 22px;
    padding: 0;
    display: grid;
    gap: 10px;
}
.fee-points li {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
    font-size: .95rem;
    color: var(--text-soft);
}
.fee-points li:first-child { padding-top: 0; border-top: 0; }
.fee-points .gx-ico { flex: 0 0 auto; width: 19px; height: 19px; color: #2a5b39; }

/* Stat counters ("previous competition"). */
/* ============================================================================
   SCALE — deliberately none
   ----------------------------------------------------------------------------
   This file used to carry a density pass: section padding, panel padding, the
   section title and the ring counters were all retargeted smaller, on the
   argument that MyESO's home page runs twelve sections where the inherited
   engine assumes three or four.

   It is gone, and nothing here should bring it back. The three Explorer sites
   are meant to read as one family, and every value it overrode is identical in
   MyESO's own redesign.css and in Biology's:

       .section        clamp(32px, 5vw, 64px)      both
       .section--tight clamp(22px, 3.5vw, 40px)    both
       .gx-pad         clamp(24px, 3.4vw, 44px)    both
       .section-title  clamp(1.9rem, 4.2vw, 3.1rem) both (--fs-h2)

   So the fix for "MyESO looks smaller than Biology" is always to delete an
   override here, never to add one. If the page ever needs to be shorter, cut
   sections or content — not the scale everything is drawn at.
   ========================================================================== */

/* the heading block above each section's body: eyebrow chip over title. Matches
   Biology's .gx-head (margin 0 auto clamp(26px, 4vw, 44px); eyebrow 14px). */
.eso-head-text .eyebrow { margin-bottom: 14px; }
.eso-head + .lead { margin-bottom: 12px; text-align: center; margin-inline: auto; }
/* A sentence set apart inside a lead. The lead is deliberately soft, so its bold
   has to be both heavier and darker than the line around it to read as emphasis
   rather than as a rendering accident. */
.lead strong { font-weight: 800; color: var(--text); }

/* ---- Section head: eyebrow + title ----------------------------------------
   The eyebrow chip above each title is the sibling sites' pattern, so a section
   is labelled by kind ("Timeline", "Eligibility") before its name is read. */
.eso-head-text { min-width: 0; }
.eso-head-text .eyebrow { margin-bottom: 10px; }

/* ---- Introduction panel ----------------------------------------------------
   Paragraph and objectives side by side. Stacked, this was the tallest section
   on the page for the least content: five one-line objectives boxed as cards
   ran to two rows of panels under a full-width paragraph. In two columns the
   section is about half the height and the objectives sit where the paragraph's
   closing line ("with the following objectives:") points. */
.eso-intro { padding: clamp(20px, 2.6vw, 36px) clamp(18px, 2.4vw, 34px); }
.eso-intro-split {
    display: grid;
    grid-template-columns: minmax(0, .88fr) minmax(0, 1.12fr);
    gap: clamp(22px, 3.4vw, 48px);
    align-items: stretch;   /* so the left column can push its card to the bottom */
}
/* the left column is a stack: paragraph at the top, infopack card pinned to the
   bottom, so it ends level with the objectives list rather than trailing off */
.eso-intro-side { display: flex; flex-direction: column; gap: 22px; height: 100%; }
.eso-intro-lead p {
    max-width: none;
    margin: 0;
    font-size: clamp(1rem, 1.15vw, 1.08rem);
    line-height: 1.75;
    color: var(--text-soft);
    /* Ragged right, deliberately. This paragraph shares the Introduction panel with
       the objectives list, so its column is only ~330px on desktop and ~300px on a
       phone — around 45 characters. Justified at that measure (and with hyphenation
       off, as it must be here) the word gaps stretch to several times their natural
       width and the block fills with rivers. It was the most visible typographic
       fault on the home page. text-wrap:pretty still keeps the last line from
       stranding a single word. */
    hyphens: none;
    text-wrap: pretty;
}
.eso-intro-lead p + p { margin-top: .8em; }

/* ---- Introduction ornament -------------------------------------------------
   A cross-section of the Earth inside the dashed orbit the ring counters use:
   topsoil lime down through gold and amber to bedrock green, the same strata
   ramp as the hero. Decoration only — it fills the foot of the paragraph column
   without asking anyone to read anything.

   `margin: auto` on a column flex item splits the leftover space above and
   below, so the ornament floats in the middle of whatever the paragraph leaves
   rather than being pinned to an edge. */
.eso-intro-art {
    position: relative;
    margin: auto;
    width: clamp(124px, 13vw, 158px);
    aspect-ratio: 1;
    display: grid; place-items: center;
}
/* the dashed orbit, and a second fainter ring outside it for depth */
.eso-intro-art::before {
    content: "";
    position: absolute; inset: 0; border-radius: 50%;
    border: 1px dashed rgba(42, 91, 57, .26);
}
.eso-intro-art::after {
    content: "";
    position: absolute; inset: -13px; border-radius: 50%;
    border: 1px solid rgba(42, 91, 57, .10);
}
/* the moon rides the orbit: the ring rotates, the dot sits on its edge, so one
   animation carries both and the dot always tracks the circle exactly */
.eso-intro-art-orbit {
    position: absolute; inset: 0;
    animation: eso-ring-spin 14s linear infinite;
}
.eso-intro-art-orbit i {
    position: absolute; top: -7px; left: 50%;
    width: 14px; height: 14px; margin-left: -7px;
    border-radius: 50%;
    background: linear-gradient(140deg, #ffc76b, #e89a1c);
    border: 1px solid rgba(255, 255, 255, .9);
    box-shadow: 0 3px 8px rgba(60, 48, 12, .28);
}
/* sparkles around the planet, twinkling out of step with each other */
.eso-intro-art-spark {
    position: absolute;
    color: #f8ad36;
    animation: eso-twinkle 3.2s ease-in-out infinite;
}
.eso-intro-art-spark.s1 { top: -4%;  right: -10%; }
.eso-intro-art-spark.s2 { bottom: 4%; left: -13%; color: #2a5b39; animation-delay: 1.1s; }
.eso-intro-art-spark.s3 { bottom: -8%; right: 6%;  color: #917b29; animation-delay: 2s; }
@keyframes eso-twinkle {
    0%, 100% { opacity: .35; transform: scale(.85); }
    50%      { opacity: 1;   transform: scale(1.1); }
}
/* the planet itself — the drawn Earth fills the disc, so no tile behind it */
.eso-intro-art-core {
    display: grid; place-items: center;
    width: 74%; height: 74%; border-radius: 50%;
    box-shadow: 0 10px 24px rgba(60, 48, 12, .18);
}
.eso-earth { display: block; width: 100%; height: 100%; overflow: visible; }
@media (prefers-reduced-motion: reduce) {
    .eso-intro-art-orbit, .eso-intro-art-spark { animation: none; }
    .eso-intro-art-spark { opacity: .8; }
}

/* The objectives column carries a hairline on its inner edge, so the split reads
   as one panel divided rather than as two panels pushed together. */
.eso-obj { border-left: 1px solid var(--border); padding-left: clamp(20px, 2.6vw, 40px); }
.eso-obj-label {
    display: flex; align-items: center; gap: 12px;
    max-width: none;          /* a <p> in a glass panel inherits the 66ch measure */
    margin: 0 0 6px;
    font-size: .76rem; font-weight: 700;
    letter-spacing: .16em; text-transform: uppercase;
    color: #2a5b39;
}
.eso-obj-label::after {
    content: ""; flex: 1; height: 1px;
    background: linear-gradient(90deg, rgba(42, 91, 57, .26), transparent);
}

/* ---- Objectives ------------------------------------------------------------
   Icon rows on a hairline rather than five cards: the objectives are one line
   each, and a card apiece gave every sentence the footprint of a feature panel
   plus a stretched empty half. */
.eso-features { list-style: none; margin: 0; padding: 0; display: grid; }
.eso-feature {
    display: grid;
    grid-template-columns: 42px 1fr;
    gap: 14px;
    align-items: center;
    padding: 13px 0;
    border-bottom: 1px solid rgba(42, 91, 57, .10);
}
.eso-feature:last-child { border-bottom: 0; padding-bottom: 0; }
.eso-feature p {
    margin: 0;
    max-width: none;
    color: var(--text-soft);
    font-size: 1rem;
    line-height: 1.55;
}
.eso-feature-ico {
    display: grid; place-items: center;
    width: 42px; height: 42px; border-radius: 13px;
    background: linear-gradient(135deg, rgba(248, 173, 54, .26), rgba(228, 255, 154, .5));
    border: 1px solid rgba(42, 91, 57, .16);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .8);
    color: #2a5b39;
    transition: transform .3s ease, border-color .3s ease;
}
.eso-feature:hover .eso-feature-ico {
    transform: scale(1.07);
    border-color: rgba(248, 173, 54, .55);
}

/* One column below 900px: at that width the two columns are each too narrow to
   hold a sentence without breaking it into three or four words a line. */
@media (max-width: 900px) {
    .eso-intro-split { grid-template-columns: 1fr; gap: clamp(18px, 3vw, 26px); }
    .eso-obj { border-left: 0; padding-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .eso-feature-ico { transition: none; }
    .eso-feature:hover .eso-feature-ico { transform: none; }
}

/* ---- Competition format ----------------------------------------------------
   Five specs, a handful of words each. As full-width label/value rows they made
   a panel that was mostly rule and empty space; as cells they fit two or three
   to a row and the section is about a third of the height. */
/* Each cell sizes to its own content, not to a third of the row. Equal thirds
   meant "Online" got the same box as "Stable internet connection and scientific
   calculator" and sat in a pool of empty space; content-sized, the specs pack
   like chips and the row wraps where it needs to. Capped so one long value
   cannot take a whole row to itself. */
.eso-format {
    display: flex; flex-wrap: wrap; justify-content: center;
    gap: 14px;
}
.eso-fmt {
    flex: 0 1 auto;
    max-width: min(100%, 420px);
    display: grid;
    grid-template-columns: 46px minmax(0, 1fr);
    align-items: center;
    gap: 14px;
    padding: 14px 18px;
    border-radius: 16px;
}
/* Content-sized cells only hold up while three fit across. Below that they pack
   one or two to a row at whatever width their text happens to be, and the run
   turns into a ragged stack of different-width cards centred on nothing. From
   here down they are equal halves, then full width. */
@media (max-width: 1150px) {
    .eso-fmt { flex: 1 1 calc((100% - 14px) / 2); max-width: none; }
}
@media (max-width: 560px) { .eso-fmt { flex: 1 1 100%; } }
.eso-fmt-ico {
    display: grid; place-items: center;
    width: 46px; height: 46px; border-radius: 13px;
    background: linear-gradient(140deg, rgba(248, 173, 54, .18), rgba(228, 255, 154, .38));
    border: 1px solid rgba(42, 91, 57, .14);
}
.eso-fmt-text { min-width: 0; }
/* set at the same size as the stage labels, so the two card sets on this page
   name themselves at one size rather than two */
.eso-fmt-label {
    display: block;
    font-size: .9rem; font-weight: 800;
    letter-spacing: .1em; text-transform: uppercase;
    color: #2a5b39;
    margin-bottom: 3px;
}
[data-theme="warm"] .eso-fmt p {
    margin: 0; max-width: none;
    color: var(--text); font-weight: 600; font-size: 1.04rem; line-height: 1.4;
}

/* ---- Category cards --------------------------------------------------------
   Artwork on the left, the levels on the right. The two categories are the
   page's fork in the road, so each one gets a drawn planet: whole Earth for
   Primary, cut away to the core for Secondary. */
/* One card per row, not two side by side. Side by side, each card got about a
   third of the page for its text, and every line broke — "Standard 4, 5, and 6"
   left "6" alone on a second line, and Secondary's "Year 7, 8, 9, 10, 11, and 12"
   was worse. Full width, each school and its levels sit on one line. */
/* One column sized to the widest card's content, centred in the panel — so each
   card wraps its planet and levels instead of stretching the full width and
   carrying empty space on both sides. max-content on the column (rather than a
   width on each card) is what keeps the two cards the same width, so the planets
   line up; minmax(0, …) lets them shrink on a phone rather than overflow. */
.eso-cats {
    display: grid; gap: 18px;
    grid-template-columns: minmax(0, 1fr);
}
/* Side by side once there is room for it. The two categories are the page's fork in
   the road — a visitor belongs to exactly one of them — and stacked they read as a
   sequence rather than a choice, with the section running to twice the height it
   needs.

   One pair of equal cards, like the fee tiers: 1fr columns rather than max-content,
   and stretched rather than aligned to the top. Sized to their own content they came
   out visibly different — Secondary carries three rows and a line that wraps, Primary
   two — so a choice between two equal options was drawn as one big box and one small
   one. Equal cards say the two are alternatives; unequal cards say one matters more. */
@media (min-width: 900px) {
    .eso-cats {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: stretch;
        gap: clamp(18px, 2.2vw, 28px);
    }
}
.eso-cat {
    display: grid;
    /* planet, then the text taking whatever is left. 1fr rather than max-content now
       that the cards are equal: the rules stretch to the card's edge instead of
       stopping at the longest line and leaving a ragged gap beside them. */
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: clamp(14px, 2.4vw, 26px);
    padding: clamp(18px, 2.4vw, 26px);
    border-radius: 20px;
    transition: transform .3s ease, box-shadow .3s ease, border-color .3s ease;
}
.eso-cat:hover {
    transform: translateY(-4px);
    box-shadow: 0 18px 40px rgba(60, 48, 12, .13);
    border-color: rgba(248, 173, 54, .45);
}
.eso-cat-art {
    display: grid; place-items: center;
    filter: drop-shadow(0 10px 18px rgba(60, 48, 12, .22));
}
.eso-cat-art .eso-planet { display: block; width: clamp(84px, 11vw, 112px); height: auto; }
.eso-cat-body { min-width: 0; }
.eso-cat h3 { margin: 0 0 12px; font-size: clamp(1.25rem, 2.2vw, 1.6rem); }
.eso-cat ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.eso-cat li {
    max-width: none;
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr);
    gap: 9px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
}
.eso-cat li:last-child { padding-bottom: 0; border-bottom: 0; }
/* the icon is the only thing on the row that is not text, so it aligns to the
   text baseline rather than the row box */
.eso-cat li svg { justify-self: center; color: #2a5b39; position: relative; top: 3px; }
.eso-cat-txt { line-height: 1.5; }
.eso-cat li b { color: #23301f; }
.eso-cat-txt span { color: var(--text-soft); }
@media (max-width: 520px) {
    /* stacked: at this width the planet and a wrapped school name fight for room */
    .eso-cat { grid-template-columns: 1fr; justify-items: center; text-align: center; }
    .eso-cat-body { width: 100%; }
    .eso-cat li { text-align: left; }
}
@media (prefers-reduced-motion: reduce) {
    .eso-cat { transition: none; }
    .eso-cat:hover { transform: none; }
}

/* ---- Ring counters ---------------------------------------------------------
   The figure sits inside a dashed orbit, as on the sibling sites, so last
   cycle's reach reads as an achievement rather than as another spec row. */
/* Geometry matched to Biology's .gx-count: same ring diameter, same gap, same
   label and figure sizes — only the colours differ, because the palette is what
   is supposed to tell the two sites apart, not the scale. */
.eso-counters {
    display: flex; justify-content: center;
    gap: clamp(28px, 7vw, 90px); flex-wrap: wrap;
}
.eso-count { text-align: center; }
.eso-ring {
    position: relative;
    width: clamp(130px, 20vw, 168px); aspect-ratio: 1;
    margin: 0 auto 8px;
    display: grid; place-items: center; border-radius: 50%;
    /* a near-white disc under the figure, as on the sibling sites: it separates the
       ring from the page wash, which is what the travelling arc needs to read against */
    background: radial-gradient(circle at 50% 42%, rgba(248, 173, 54, .22), rgba(255, 255, 255, .9) 66%);
    border: 1px dashed rgba(42, 91, 57, .42);
}
/* The travelling arc. At 1px in pale amber it was invisible against a pale
   background — the ring looked static. It is now 3px, in the darker end of the
   brand ramp, and runs over two borders so the arc is a quarter of the circle
   rather than a tick: a longer, darker mark is what makes the rotation readable
   at a glance. The track it runs on is darkened to match. */
.eso-ring::before {
    content: ""; position: absolute; inset: -9px; border-radius: 50%;
    border: 3px solid rgba(42, 91, 57, .16);
    border-top-color: #d98d18;
    border-right-color: rgba(217, 141, 24, .55);
    filter: drop-shadow(0 0 4px rgba(217, 141, 24, .35));
    animation: eso-ring-spin 12s linear infinite;
}
@keyframes eso-ring-spin { to { rotate: 1turn; } }
@media (prefers-reduced-motion: reduce) { .eso-ring::before { animation: none; } }
.eso-ring b {
    font-family: var(--font-display); font-weight: 800;
    font-size: clamp(2.4rem, 5.5vw, 3.4rem); line-height: 1;
    background: linear-gradient(92deg, #d98d18, #2a5b39);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
.eso-count > span {
    display: block; margin-top: 10px;
    font-size: .86rem; font-weight: 600;
    letter-spacing: .14em; text-transform: uppercase; color: var(--text-soft);
}

/* ---- Timeline --------------------------------------------------------------
   The dates are a sequence, so they run as cards along a rule rather than as
   stacked label/value pairs. Competition day is the one a visitor must not miss,
   so it is pulled out with the amber wash. */
.eso-rail {
    list-style: none; margin: 0; padding: 0;
    position: relative;
    display: grid;
    grid-template-columns: repeat(var(--stations, 4), minmax(0, 1fr));
    gap: 18px;
}
/* the rail: one line threaded behind the discs, stopped short of the first and
   last so it does not dangle past the run */
.eso-rail::before {
    content: "";
    position: absolute; z-index: 0;
    left: 10%; right: 10%; top: 37px; height: 3px;   /* half the disc */
    border-radius: 2px;
    background: linear-gradient(90deg, rgba(42, 91, 57, .35), #917b29 45%, #f8ad36);
    opacity: .5;
}
.eso-station { position: relative; z-index: 1; text-align: center; }

/* the station disc: icon, with the step number tucked into its corner. Kept white
   rather than tinted — the icons inside are full-colour illustrations, and a tinted
   disc muddies them. */
.eso-station-node {
    position: relative;
    display: grid; place-items: center;
    width: 74px; height: 74px; margin: 0 auto 14px;
    border-radius: 50%;
    background: #fff;
    border: 1px solid rgba(42, 91, 57, .2);
    /* the cream halo punches the rail out behind each disc */
    box-shadow: 0 0 0 7px var(--bg), 0 0 0 8px rgba(42, 91, 57, .12), 0 8px 18px rgba(60, 48, 12, .1);
    color: #2a5b39;
    transition: transform .3s ease, box-shadow .3s ease;
}
.eso-station:hover .eso-station-node {
    transform: translateY(-4px);
    box-shadow: 0 0 0 7px var(--bg), 0 0 0 8px rgba(248, 173, 54, .45), 0 14px 26px rgba(60, 48, 12, .16);
}
.eso-station h3 {
    margin: 0 0 8px; font-size: .98rem; line-height: 1.35;
    color: #23301f; text-wrap: balance;
}
.eso-station-date {
    display: inline-block; margin: 0;
    padding: 5px 12px; border-radius: 999px;
    background: var(--lime-soft);
    border: 1px solid rgba(42, 91, 57, .16);
    font-size: .86rem; font-weight: 700; color: #2a5b39;
}

/* competition day: the one date a visitor must not miss. Marked with an amber ring
   and glow rather than an amber fill — a fill would sit under the illustration and
   flatten it. */
.eso-station.is-hl .eso-station-node {
    border: 2px solid #e89a1c;
    box-shadow: 0 0 0 7px var(--bg), 0 0 0 9px rgba(217, 141, 24, .4), 0 10px 26px rgba(217, 141, 24, .3);
}
/* the date itself, not just the disc: a tinted pill sat too close to the pale-lime
   ones beside it to register as the date that matters, so this one is a solid amber
   chip with the CTA's own fill */
.eso-station.is-hl .eso-station-date {
    background: linear-gradient(135deg, #f8ad36, #e89a1c);
    border-color: #d98d18;
    color: #26210d;
    font-weight: 800;
    box-shadow: 0 6px 16px rgba(217, 141, 24, .34);
}
.eso-station.is-hl h3 { color: #2a5b39; font-weight: 800; }

/* Below 780px the rail turns vertical: four stations across a phone leaves each
   title three words wide. Same run, rotated — not a different component. */
@media (max-width: 780px) {
    .eso-rail {
        grid-template-columns: 1fr; gap: 4px;
        /* the run is only as wide as a disc plus a title, so left-aligned it left
           half the panel empty and read as pushed to one side. Capped and centred
           instead, so it sits in the middle of the panel at any width. */
        max-width: 470px; margin-inline: auto;
    }
    /* One line down the whole list overshot the last disc and dangled. Drawn per
       station instead: each segment runs between two discs, so the run starts at
       the first disc's centre and stops at the last one's. */
    .eso-rail::before { display: none; }
    .eso-station::before {
        content: "";
        position: absolute; z-index: 0;
        left: 35.5px; width: 3px;
        top: -4px; bottom: -4px;          /* -4px = the row gap, so segments meet */
        border-radius: 2px;
        background: rgba(145, 123, 41, .42);
    }
    .eso-station:first-child::before { top: 50%; }
    .eso-station:last-child::before { bottom: 50%; }
    .eso-station:only-child::before { display: none; }
    .eso-station {
        display: grid;
        grid-template-columns: 74px 1fr;
        gap: 16px;
        align-items: center;
        text-align: left;
        padding: 10px 0;
    }
    .eso-station-node { margin: 0; }
    .eso-station h3 { margin: 0 0 6px; grid-column: 2; }
    /* grid blockifies inline-block children, which stretched the pill across the
       whole column — justify-self pulls it back to its own width */
    .eso-station-date { grid-column: 2; justify-self: start; }
}
@media (prefers-reduced-motion: reduce) {
    .eso-station-node { transition: none; }
    .eso-station:hover .eso-station-node { transform: none; }
}

/* ============================================================================
   PREPARATORY WORKSHOP — the one section that is not on the page field
   ----------------------------------------------------------------------------
   Everything else on this page is a panel on the cream wash. The workshop is the
   only paid add-on and the only thing besides registering that a visitor can act
   on, so it gets the page's one strong visual break: a deep-green band, its own
   panel, and amber cards inside it. That is the whole point of keeping the rest
   of the page quiet — a page where every section shouts has no way to say
   "this one is different".
   ========================================================================== */
.eso-workshop {
    position: relative;
    margin: clamp(18px, 3vw, 36px) 0;
    padding-block: clamp(34px, 5vw, 62px);
    background:
        radial-gradient(120% 90% at 12% -10%, rgba(248, 173, 54, .20), transparent 58%),
        radial-gradient(110% 90% at 92% 4%, rgba(237, 255, 43, .14), transparent 55%),
        linear-gradient(165deg, #1f4a2c 0%, #2a5b39 46%, #21482b 100%);
    border-block: 1px solid rgba(248, 173, 54, .35);
    color: #eef7e6;
}
/* the sediment seam the other bands use, in the light ink this one needs */
.eso-workshop::before {
    content: "";
    position: absolute; inset-inline: 0; top: 0; height: 6px;
    background-image: radial-gradient(circle at 3px 3px, rgba(248, 173, 54, .45) 1px, transparent 1.2px);
    background-size: 12px 6px;
    pointer-events: none;
}
/* Head: on the dark band the deep-green heading colour is invisible, and the
   gradient half of the title reads as a smudge, so both are set in light ink. */
.eso-workshop .eso-head .section-title,
[data-theme="warm"] .eso-workshop .eso-head .section-title { color: #fff; }
.eso-workshop .section-title .grad {
    background: linear-gradient(92deg, #ffd98a 0%, #f8ad36 50%, #edff2b 100%);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
[data-theme="warm"] .eso-workshop .eyebrow {
    background: rgba(255, 255, 255, .12);
    border-color: rgba(248, 173, 54, .5);
    color: #ffd98a;
}
/* .eso-badge is kept legible on the band in case the eligibility chip is ever put
   back, but nothing renders it on this page today. */
.eso-workshop .eso-badge {
    background: linear-gradient(135deg, #f8ad36, #e89a1c);
    border-color: #d98d18;
    color: #26210d;
}
/* The panel is a light sheet laid on the dark band, not another translucent green
   layer. Two tones, not one: the band alone carries the "this section is
   different" signal, and the reading happens on cream — the workshop is the
   longest run of text on the page, and it was all set on dark green. */
[data-theme="warm"] .eso-workshop .gx-glass {
    background: linear-gradient(180deg, rgba(255, 255, 255, .97) 0%, rgba(253, 247, 232, .97) 100%);
    border-color: rgba(255, 255, 255, .45);
    box-shadow: 0 24px 60px rgba(10, 30, 18, .4);
    /* the band sets light ink for the heading above; the panel has to set it back,
       or list items — which no base rule colours — inherit white onto white */
    color: var(--text-soft);
}
[data-theme="warm"] .eso-workshop .gx-glass p,
[data-theme="warm"] .eso-workshop .gx-glass li { max-width: none; color: var(--text-soft); }

/* Tabs: the underline treatment is built for a light panel and vanishes on the
   band, so on this section they become pills — amber for the tabs you can go to,
   dark for the one you are on, which is how the workshop's own material sets
   them out. Centred, because the panel below is symmetrical. */
.eso-workshop .eso-tabs {
    justify-content: center; gap: 10px;
    border-bottom: 0;
    margin-bottom: clamp(18px, 2.4vw, 26px);
}
.eso-workshop .eso-tab {
    border-radius: 999px;
    padding: 10px 20px;
    background: linear-gradient(150deg, #fcbf58, #f0a323);
    border: 1px solid #d98d18;
    color: #26210d;
}
.eso-workshop .eso-tab:hover {
    background: linear-gradient(150deg, #ffd07a, #f8ad36);
    color: #1c1a12;
}
/* Solid, not translucent: this was tuned when the panel behind it was dark green,
   and on the cream panel a translucent fill washes out to grey. */
.eso-workshop .eso-tab[aria-selected="true"] {
    background: linear-gradient(150deg, #2f6640, #23502f);
    border-color: #2a5b39;
    color: #fff;
}
.eso-workshop .eso-tab:focus-visible { outline: 2px solid #ffd98a; outline-offset: 2px; }

/* Blocks inside the panel — the workshop's own material runs as titled blocks,
   not as loose prose. */
.eso-wk-block {
    padding: clamp(16px, 2vw, 24px);
    border-radius: 16px;
    background: #fff;
    border: 1px solid rgba(42, 91, 57, .14);
    box-shadow: 0 6px 18px rgba(60, 48, 12, .05);
}
.eso-wk-block + .eso-wk-block,
.eso-wk-facts + .eso-wk-block,
.eso-wk-block + .eso-wk-facts { margin-top: 16px; }
.eso-wk-block h3 { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; font-size: 1.15rem; }
.eso-wk-block h4 { display: flex; align-items: center; gap: 9px; margin: 18px 0 8px; font-size: 1rem; }
.eso-wk-block h3 + p, .eso-wk-block h4 + ul { margin-top: 0; }
.eso-wk-block ul { margin: 0; padding-left: 1.15em; }
.eso-wk-block li { margin-bottom: .4em; }
.eso-wk-note {
    margin: 14px 0 0; padding: 10px 14px;
    border-left: 3px solid #f8ad36; border-radius: 0 10px 10px 0;
    background: rgba(248, 173, 54, .12);
    font-size: .92rem; font-style: italic;
}
.eso-wk-cta { margin: 18px 0 0; }
/* the Infopack tab is a centred sheet of posters, so its button centres with them */
.eso-wk-posters + .eso-wk-cta { text-align: center; margin-top: 22px; }

/* Date / Time / Venue and Dates / Fees — amber cards, the one place on the page
   that colour is used as a fill rather than an accent. */
.eso-wk-facts {
    display: grid; gap: 14px; margin-top: 16px;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.eso-wk-facts--two { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.eso-wk-fact {
    padding: 16px 18px;
    border-radius: 14px;
    background: linear-gradient(155deg, #fcbf58, #f0a323);
    border: 1px solid #d98d18;
    color: #26210d;
}
/* .gx-glass is an ancestor of these cards, so the panel's light-ink rules above
   out-specify a plain `.eso-wk-fact p`. Matched through the panel here, which is
   what keeps the ink dark on the amber fill. */
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact h4 { margin: 0 0 8px; color: #26210d; font-size: .95rem; }
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact p { margin: 0; color: #2c2611; font-weight: 600; }
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact li { color: #2c2611; }
.eso-wk-fact dl { margin: 0; }
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact dt { margin-top: 8px; font-weight: 700; color: #26210d; }
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact dt:first-child { margin-top: 0; }
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact dd { margin: 0 0 0 14px; color: #35301a; }
[data-theme="warm"] .eso-workshop .gx-glass .eso-wk-fact b { color: #26210d; }

/* Contact + social, two columns inside one block */
.eso-wk-contact { display: grid; gap: clamp(14px, 3vw, 40px); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.eso-wk-contact h4:first-child { margin-top: 0; }
[data-theme="warm"] .eso-wk-contact li span { color: var(--text-mute); }

/* Infopack posters. Capped and centred rather than stretched to the panel: they
   are portrait, and auto-fit with one or two posters blew a single sheet up to
   the full width of the section. */
.eso-wk-posters {
    display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fit, minmax(210px, 300px));
    justify-content: center;
}
.eso-wk-posters a {
    display: block; border-radius: 14px; overflow: hidden;
    border: 1px solid rgba(42, 91, 57, .18);
    box-shadow: 0 12px 26px rgba(60, 48, 12, .14);
    transition: transform .3s ease;
}
.eso-wk-posters a:hover { transform: translateY(-4px); }
.eso-wk-posters img { display: block; width: 100%; height: auto; }
@media (prefers-reduced-motion: reduce) {
    .eso-wk-posters a { transition: none; }
    .eso-wk-posters a:hover { transform: none; }
}

/* ---- Workshop tabs ---------------------------------------------------------
   The workshop carries more content than the rest of the page combined, so it is
   split across three panels rather than run as one column. */
.eso-tabs {
    display: flex; gap: 6px; flex-wrap: wrap;
    margin: 0 0 18px;
    border-bottom: 1px solid rgba(42, 91, 57, .18);
}
.eso-tab {
    appearance: none; background: transparent; cursor: pointer;
    border: 0; border-bottom: 2px solid transparent; border-radius: 10px 10px 0 0;
    font-family: var(--font-display); font-weight: 600; font-size: .95rem;
    padding: 9px 15px;
    color: var(--text-soft);
    transition: color .2s, background .2s, border-color .2s;
}
.eso-tab:hover { color: var(--accent); background: rgba(248, 173, 54, .10); }
.eso-tab[aria-selected="true"] { color: #2a5b39; background: rgba(248, 173, 54, .14); border-bottom-color: #f8ad36; }
.eso-tab:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
/* !important: [hidden] loses to any display rule that lands on the panel later —
   the popup overlay was invisible-but-clickable for exactly this reason. */
.eso-panel[hidden] { display: none !important; }

/* ---- Syllabus --------------------------------------------------------------
   Two full-width blocks rather than two columns: Primary is seven keywords and
   Secondary is five subjects with sub-topics, so side by side the left column
   ran out a third of the way down. */
.eso-syl { display: grid; gap: 16px; }
.eso-syl-block { padding: clamp(18px, 2.2vw, 26px); border-radius: 18px; }
.eso-syl-block h3 { display: flex; align-items: center; gap: 10px; margin: 0 0 14px; }
/* Secondary's subjects in two columns — one column made a tall thin list against
   a band of chips. Each subject is kept whole across the break. */
.eso-syl-cols ul { columns: 2; column-gap: clamp(24px, 3vw, 48px); margin: 0; padding-left: 18px; }
.eso-syl-cols li { break-inside: avoid; margin-bottom: .55em; max-width: none; }
.eso-syl-cols li:last-child { margin-bottom: 0; }
@media (max-width: 760px) {
    .eso-syl-cols ul { columns: 1; }
}

/* ---- Syllabus chips --------------------------------------------------------
   Subject keywords, not a sequence — chips let the eye count the ground covered. */
.eso-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.eso-chips li {
    max-width: none;
    padding: 5px 12px; border-radius: 999px;
    background: var(--lime-soft);
    border: 1px solid rgba(42, 91, 57, .18);
    font-size: .88rem; font-weight: 600; color: #2a5b39;
}

/* ---- Quick facts bar -------------------------------------------------------
   The four-up strip under the hero, as on both sibling sites. Hairline dividers
   between cells rather than four separate cards: it is one fact read four ways,
   not four unrelated tiles. The amber rule along the top ties it to the header's
   accent line. */
.eso-facts {
    position: relative;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    overflow: hidden;
}
.eso-facts::after {
    content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
    background: linear-gradient(135deg, #f8ad36 0%, #917b29 55%, #2a5b39 100%);
}
/* Padding left at the sibling sites' value, not tightened with the rest of the page:
   this bar is the first thing under the hero and carries only four figures, so it is
   the one place on the page where the air is doing work.

   The side padding is the exception — 12px was not enough for the longest label
   ("Objective Questions"), which ran within a few pixels of the hairline divider and
   made a four-up bar read as cramped. It scales with the viewport now, so the gutter
   holds at the widths where the labels are closest to wrapping. */
.eso-fact { position: relative; text-align: center; padding: clamp(18px, 2.4vw, 26px) clamp(16px, 1.8vw, 28px); }
.eso-fact + .eso-fact::before {
    content: ""; position: absolute; left: 0; top: 22%; height: 56%; width: 1px;
    background: rgba(42, 91, 57, .16);
}
.eso-fact b {
    display: block;
    font-family: var(--font-display); font-weight: 800;
    font-size: clamp(1.5rem, 2.8vw, 2.1rem); line-height: 1.1;
    color: #2a5b39;
}
/* Its own class rather than `.eso-fact span`. That selector is (0,1,1) and used to also
   match the icon's span at (0,1,0), overriding it and turning the disc into a block —
   which is why the icons once sat hard left of centred figures. The icons are gone now,
   but the class stays: naming the label directly is what stops the next element added
   to this cell from inheriting label styling by accident. */
.eso-fact-label {
    display: block; margin-top: 8px;
    font-size: .8rem; font-weight: 600; letter-spacing: .02em;
    color: var(--text-soft);
    text-wrap: balance;
}
@media (max-width: 700px) {
    .eso-facts { grid-template-columns: repeat(2, 1fr); }
    /* with two per row, cells 1 and 3 start a row and must not carry a divider */
    .eso-fact:nth-child(odd)::before { display: none; }
}

/* ---- Centred card ----------------------------------------------------------
   The closing-CTA pattern: a glass card whose heading, copy and button all sit on
   the centre line.

   The second rule is the one that matters. redesign.css gives every paragraph in a
   glass card `max-width: var(--measure)` (66ch) to keep long prose readable. That
   constrains the box but does not move it, so in a centred card the words inside
   were centring while the box itself stayed flush left — the copy and the button
   ended up left of the heading, which is full-width and genuinely centred. Putting
   the box back on the centre line is what margin-inline does here.

   redesign.css already carries `.books-empty p, .pyq-empty p { margin-inline: auto }`
   for the same reason; those two classes do not exist on this site, so this is the
   general form rather than a third one-off.

   NOT written as `.eso-card-center :where(p, .prose)`. :where() contributes zero
   specificity, which put that selector at (0,1,0) — under `.prose p` at (0,1,1), whose
   `margin: 0 0 1rem` SHORTHAND resets margin-inline back to 0. Richtext copy (which is
   always wrapped in .prose) therefore stayed flush left while plain <p> in the same
   card centred fine. Each selector below is deliberately specific enough to win on its
   own rather than on file order. */
.eso-card-center { text-align: center; }
.eso-card-center p,
.eso-card-center .prose,
.eso-card-center .prose p { margin-inline: auto; }

/* A list is the one thing that should NOT have its items centred: bullets have to line
   up under each other or the list stops reading as one. The block is centred and the
   items inside it stay left-aligned. Neither card carries a list today — this is here
   because the copy is editor-written richtext, so one can appear without a deploy. */
.eso-card-center .prose :is(ul, ol) {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
    text-align: left;
}

/* ---- Quick links -----------------------------------------------------------
   Three onward routes above the closing CTA. Two-up, with the third centred
   across the full width rather than left hanging in an empty second column. */
.eso-links { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.eso-links > :last-child:nth-child(odd) { grid-column: 1 / -1; justify-self: center; max-width: 520px; width: 100%; }
.eso-link { text-align: center; }
.eso-link h3 { margin: 0 0 14px; font-size: var(--fs-h4); color: var(--accent); }
@media (max-width: 700px) {
    .eso-links { grid-template-columns: minmax(0, 1fr); }
    .eso-links > :last-child:nth-child(odd) { max-width: none; }
}

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 18px; }
.stat { text-align: center; padding: 22px 16px; }
.stat-num {
    display: block;
    font-size: clamp(2.2rem, 6vw, 3.4rem);
    font-weight: 800;
    line-height: 1;
    background: linear-gradient(92deg, #d98d18, #2a5b39);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
.stat-label { display: block; margin-top: 8px; font-size: .9rem; letter-spacing: .1em;
    text-transform: uppercase; font-weight: 600; color: var(--text-soft); }

/* Generic responsive tile grid used by categories, stages, disciplines, awards. */
.tile-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.tile { padding: 20px 22px; }
/* flex so a leading icon sits on the text baseline row rather than pushing the
   heading onto two lines */
.tile h3 { display: flex; align-items: center; gap: 9px; margin: 0 0 8px; font-size: 1.05rem; }
.tile h3 .gx-ico { flex: 0 0 auto; color: var(--accent); }
/* text-wrap: pretty as the tile default — these are the narrowest text column on the
   site (240px minimum in the auto-fit grid), so they are where a wrapping line is most
   likely to strand its last word. The address overrides this to `balance` just below;
   see there for why the two differ. */
.tile p, .tile li { color: var(--text-soft); text-wrap: pretty; }
.tile ul { margin: 0; padding-left: 18px; }

/* The postal address. Its longest line is 41 characters and this tile's text column
   holds about 32 (the "Taman Danau Kota…" line fits exactly), so the street line
   cannot fit and will wrap whatever we do here — the only question is where.
   `pretty` optimises against a stranded last word, which broke it as
   "…Off Jalan / Genting Klang," and split the road name. `balance` evens the two
   halves instead, which puts the break before "Jalan" and keeps "Jalan Genting Klang"
   whole. Balanced per segment, so the hard <br> breaks the editor typed still stand.

   Written as `.tile p.eso-addr` on purpose: `.tile p` above is (0,1,1), so a bare
   `.eso-addr` at (0,1,0) would lose to it and keep inheriting `pretty`. */
.tile p.eso-addr { text-wrap: balance; }

/* ---- The five IESO disciplines --------------------------------------------
   Three across, then the last two centred underneath.

   Five is the count that breaks an ordinary auto-fit grid: at four columns the
   fifth card sits alone against the left edge and reads as an afterthought
   rather than as the fifth of five, and at five columns every card is too
   narrow to hold its blurb without wrapping every few words.

   So the columns are stated rather than fitted, and the row is a centred flex
   wrap rather than a grid — a grid cannot centre a part-full last row, which is
   the one thing this layout needs it to do. Nothing is hidden behind a scroll
   or a click: all five are the answer to what MyESO covers, and the reader
   should be able to count them at a glance. */
.eso-disciplines {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: clamp(16px, 2vw, 22px);
}
.eso-disciplines > .tile {
    /* a third of the row, less its share of the two gaps */
    flex: 0 1 calc((100% - 2 * clamp(16px, 2vw, 22px)) / 3);
    max-width: 340px;
    display: flex;
    flex-direction: column;
    padding: 16px 18px 20px;
}
.eso-disciplines > .tile p { margin: 0; }
/* a touch shorter than the shared 168px: at a third of the panel these cards are
   wide, and a full-height photo over three lines of text top-heavies them */
.eso-disciplines .media-tile img { height: 152px; }

@media (max-width: 900px) {
    /* 2 + 2 + 1, still centred */
    .eso-disciplines > .tile { flex-basis: calc((100% - clamp(16px, 2vw, 22px)) / 2); }
}
@media (max-width: 560px) {
    .eso-disciplines > .tile { flex-basis: 100%; max-width: none; }
}

/* Discipline / partner imagery. */
.media-tile img { width: 100%; height: 168px; object-fit: cover; border-radius: 12px; margin-bottom: 14px; }
/* Partner logos. One box per logo rather than a shared height cap: the marks run
   from a 3.2:1 wordmark (Universiti Malaya) to a near-square crest (USM), and
   capping height alone gave the wide ones three times the width of the narrow
   ones. That is what pushed the seventh partner onto a row of its own — the row
   was 6 + 1 not because seven is too many, but because two of the seven were
   taking the space of six.

   Boxed and gapped as below, all seven fit the panel's 1044px at full width with
   room to spare (7 x 128 + 6 x 20 = 1016). Narrower than that it wraps centred,
   which is the right behaviour once one row genuinely cannot hold them. */
.logo-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: clamp(14px, 1.6vw, 20px) clamp(14px, 1.6vw, 20px);
}
.logo-strip img {
    flex: 0 0 auto;
    width: clamp(100px, 10vw, 128px);
    height: clamp(44px, 4.2vw, 56px);
    /* contain, so a logo is fitted into its box rather than cropped to it */
    object-fit: contain;
    opacity: .85;
}

/* Download / link rows (reading materials, past year papers, results). */
.link-list { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.link-row {
    display: flex; align-items: center; gap: 12px;
    padding: 13px 16px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface);
    text-decoration: none;
    color: inherit;
    transition: border-color .2s, box-shadow .2s, transform .2s;
}
.link-row:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.link-row svg { flex: 0 0 auto; color: #d98d18; }
.link-row span { font-weight: 600; font-size: .95rem; }

/* Video tutorial grid. */
/* ---- Tutorial playlist ----------------------------------------------------
   Player and playlist as one unit: a single rounded, clipped block with the
   list flush against the player, its own titled header and a count. The list
   is set on bedrock green — the footer's colour — so the panel reads as one
   object against the cream card rather than as a player with some links next
   to it, and the playing entry can be picked out in amber against it.

   The list scrolls to the player's height rather than growing the section, so
   a sixth tutorial costs the page no height at all. */
.video-playlist {
    display: grid;
    grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
    border-radius: 18px;
    overflow: hidden;
    box-shadow: 0 18px 44px rgba(30, 40, 25, .22);
}
.video-stage iframe {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    background: #121a14;
}

.video-side {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: linear-gradient(170deg, #24512f 0%, #17391f 100%);
}
.video-side-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 16px 16px 12px;
    border-bottom: 1px solid rgba(255, 255, 255, .12);
}
/* both selectors: warm-theme.css colours every h3 inside a .gx-glass dark, and
   this one sits on the dark panel — see the .fee-card note above */
.video-side-head h3,
[data-theme="warm"] .gx-glass .video-side-head h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    color: #fff;
}
.video-count {
    flex: 0 0 auto;
    padding: 4px 11px;
    border-radius: 999px;
    background: rgba(248, 173, 54, .16);
    border: 1px solid rgba(248, 173, 54, .42);
    color: #ffd98a;
    font-size: .74rem;
    font-weight: 800;
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.video-list {
    list-style: none;
    margin: 0;
    padding: 8px;
    display: grid;
    gap: 6px;
    align-content: start;
    overflow-y: auto;
    /* the player's height, so the two columns end level */
    max-height: min(46vw, 400px);
    scrollbar-width: thin;
    scrollbar-color: rgba(248, 173, 54, .5) transparent;
}
.video-item {
    display: grid;
    grid-template-columns: 92px minmax(0, 1fr) auto;
    gap: 11px;
    align-items: center;
    width: 100%;
    padding: 7px;
    border: 0;
    border-left: 3px solid transparent;
    border-radius: 10px;
    background: transparent;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background .2s ease, border-color .2s ease;
}
.video-item:hover { background: rgba(255, 255, 255, .08); }
.video-item.is-playing {
    background: rgba(248, 173, 54, .17);
    border-left-color: #f8ad36;
}
.video-thumb {
    display: block;
    width: 92px;
    height: 52px;
    object-fit: cover;
    border-radius: 7px;
}
.video-name {
    font-size: .88rem;
    font-weight: 700;
    line-height: 1.35;
    color: rgba(255, 255, 255, .86);
}
.video-item.is-playing .video-name { color: #fff; }
/* the run time, where one is written — right-aligned so the titles keep a
   straight edge whether or not their neighbour has one */
.video-time {
    font-size: .78rem;
    font-weight: 700;
    color: rgba(255, 255, 255, .6);
    white-space: nowrap;
}

@media (max-width: 900px) {
    .video-playlist { grid-template-columns: 1fr; }
    .video-stage iframe { height: auto; }
    .video-list { max-height: 300px; }
}

/* Map embed on the contact page. */
.map-embed { width: 100%; aspect-ratio: 16 / 8; border: 0; border-radius: 14px; }
@media (max-width: 640px) { .map-embed { aspect-ratio: 4 / 3; } }

/* ============================================================================
   HOME HERO
   ----------------------------------------------------------------------------
   A geological cross-section: four strata bands stacked behind a two-column
   split (copy left, key visual right). The bands are gradients rather than an
   image, so they scale to any viewport and cost nothing to load.
   ========================================================================== */

.eso-hero {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: clamp(28px, 5vw, 68px) 0 clamp(34px, 6vw, 80px);
    margin-bottom: clamp(10px, 2vw, 24px);
}

/* The strata. Each band is a horizontal slab with a slight tilt, stacked from
   topsoil green at the top down to bedrock amber — the order a cross-section
   diagram uses, so it reads as ground rather than as decoration. */
.eso-hero-strata { position: absolute; inset: 0; z-index: -1; }
.eso-hero-strata span {
    position: absolute;
    left: -8%; right: -8%;
    transform: rotate(-1.6deg);
    border-radius: 40%;
}
.eso-hero-strata span:nth-child(1) { top: 4%;  height: 32%; background: linear-gradient(180deg, rgba(228,255,154,.55), rgba(228,255,154,0)); }
.eso-hero-strata span:nth-child(2) { top: 38%; height: 26%; background: linear-gradient(180deg, rgba(248,173,54,.22), rgba(248,173,54,.04)); }
.eso-hero-strata span:nth-child(3) { top: 62%; height: 22%; background: linear-gradient(180deg, rgba(145,123,41,.20), rgba(145,123,41,.03)); }
.eso-hero-strata span:nth-child(4) { top: 80%; height: 26%; background: linear-gradient(180deg, rgba(42,91,57,.20), rgba(42,91,57,.02)); }

.eso-hero-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    gap: clamp(24px, 4vw, 56px);
    align-items: center;
}

.eso-hero-copy h1 { font-size: clamp(2.1rem, 5.2vw, 3.9rem); margin: 10px 0 14px; line-height: 1.06; }
.eso-hero-copy .lead { margin: 0 0 22px; max-width: 52ch; }
.eso-hero-copy .hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 0; }

/* The workshop ribbon — a live-announcement chip, so it gets a pulsing dot
   rather than looking like a third button competing with the two CTAs. */
.eso-ribbon {
    display: inline-flex; align-items: center; gap: 10px;
    margin-top: 22px; padding: 10px 18px;
    border-radius: 999px;
    background: linear-gradient(135deg, rgba(237,255,43,.30), rgba(228,255,154,.55));
    border: 1px solid rgba(42,91,57,.28);
    color: #2a5b39;
    font-weight: 700; font-size: .92rem;
    text-decoration: none;
    transition: transform .2s, box-shadow .2s;
}
.eso-ribbon:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(42,91,57,.18); }
.eso-ribbon-dot {
    width: 9px; height: 9px; border-radius: 50%;
    background: #d98d18;
    box-shadow: 0 0 0 0 rgba(217,141,24,.6);
    animation: eso-pulse 2.2s infinite;
}
@keyframes eso-pulse {
    70% { box-shadow: 0 0 0 10px rgba(217,141,24,0); }
    100% { box-shadow: 0 0 0 0 rgba(217,141,24,0); }
}

/* Key visual — the competition mark, drifting on a long, slow bob. */
.eso-hero-art { position: relative; min-height: clamp(240px, 34vw, 430px); }
.eso-hero-globe {
    display: block; width: 100%; height: auto;
    filter: drop-shadow(0 26px 50px rgba(60,48,12,.24));
    animation: eso-bob 9s ease-in-out infinite;
}

@keyframes eso-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-12px); }
}

@media (max-width: 900px) {
    .eso-hero-grid { grid-template-columns: minmax(0, 1fr); text-align: center; }
    .eso-hero-copy .lead { margin-inline: auto; }
    .eso-hero-copy .hero-actions { justify-content: center; }
    /* The lockup's hard break exists for the two-column hero, where the copy has only
       half the width. Once the hero is a single centred column the title has the whole
       container, so the break is dropped and it sets on one line — falling back to a
       natural wrap on a phone, where one line no longer fits. */
    .eso-hero-copy h1 br { display: none; }
    /* art first would push the copy below the fold on a phone */
    .eso-hero-art { order: 2; max-width: 460px; margin: 0 auto; }
}

/* Motion is decorative here — none of it conveys information. */
@media (prefers-reduced-motion: reduce) {
    .eso-hero-globe { animation: none; }
    .eso-ribbon-dot { animation: none; }
}

/* ---- Galleries ------------------------------------------------------------ */

/* ---- Table cells: whole words -------------------------------------------
   redesign.css sets `overflow-wrap: anywhere` on every td and th, to stop a
   pasted URL pushing a table wider than its card. It has a second effect that is
   not wanted here: `anywhere` counts toward a cell's MIN-CONTENT width, so the
   browser is told a column can be one character wide. In an auto-layout table it
   then squeezes the columns toward equal widths and breaks words to fit —
   "Stage" set as "Sta / ge", "Registration" as "Registratio / n".

   `break-word` is the same protection without that: it breaks a word only when
   the word genuinely cannot fit, and it does NOT shrink min-content. So a column
   is at least as wide as its longest word, and the columns size to what they
   hold. The overflow-x on .faq-answer stays as the backstop for anything wider. */
.prose :is(td, th) { overflow-wrap: break-word; }

/* Captioned photo grid (About page camp / IESO write-ups). */
.photo-grid {
    display: grid; gap: 16px; margin-top: 26px;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.photo-grid figure { margin: 0; }
.photo-grid img {
    width: 100%; height: 190px; object-fit: cover;
    border-radius: 12px;
    border: 1px solid var(--border);
    transition: transform .25s, box-shadow .25s;
}
.photo-grid figure:hover img { transform: translateY(-3px); box-shadow: var(--shadow); }
.photo-grid figcaption { margin-top: 8px; font-size: .86rem; color: var(--text-soft); }

/* ---- Photo lightbox -------------------------------------------------------
   Every gallery photo is wrapped in a link to its full-size file; the script in
   partials/lightbox.blade.php shows it in place instead. zoom-in is the cursor
   that says what the click does before it is made. */
.ph-link { display: block; cursor: zoom-in; border-radius: 12px; }
.ph-link:focus-visible { outline: 2px solid #d98d18; outline-offset: 3px; }
/* the strip's flex item is the link now, not the image inside it */
.faces-strip .ph-link { flex: 0 0 auto; scroll-snap-align: start; }
/* the discipline card's photo keeps its own bottom margin on the link that now
   wraps it, so the heading below does not ride up against the image */
.eso-disciplines .ph-link { margin-bottom: 14px; }
.eso-disciplines .ph-link img { margin-bottom: 0; }

.lightbox {
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    margin: 0;
    padding: clamp(14px, 3vw, 40px);
    border: 0;
    background: transparent;   /* the ::backdrop is the surface, so a click beside
                                  the photo lands on the dialog and closes it */
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: clamp(8px, 2vw, 20px);
    overflow: hidden;
}
/* Display belongs ONLY on [open]. The browser hides a closed dialog with
   `dialog:not([open]) { display: none }` in its own stylesheet, and any author
   `display` on the element beats that no matter how specific it is — set it here
   and the arrows and the counter stay on the page after the photo is closed. */
.lightbox[open] { display: grid; }
.lightbox::backdrop { background: rgba(18, 26, 20, .88); backdrop-filter: blur(3px); }

.lb-figure {
    grid-column: 2;
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    min-height: 0;
}
.lb-img {
    max-width: 100%;
    /* leaves room for the caption under it and the dialog's own padding */
    max-height: calc(100vh - 150px);
    object-fit: contain;
    border-radius: 12px;
    box-shadow: 0 24px 70px rgba(0, 0, 0, .6);
}
.lb-cap {
    margin: 0;
    max-width: 62ch;
    text-align: center;
    font-size: .92rem;
    color: rgba(255, 255, 255, .88);
}

.lb-btn {
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, .28);
    border-radius: 50%;
    background: rgba(255, 255, 255, .12);
    color: #fff;
    cursor: pointer;
    transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.lb-btn:hover {
    background: linear-gradient(135deg, #f8ad36, #e89a1c);
    border-color: #d98d18;
    color: #26210d;
}
.lb-prev { grid-column: 1; }
.lb-next { grid-column: 3; }
.lb-close { position: absolute; top: 14px; right: 14px; }
.lb-count {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    margin: 0;
    font-size: .82rem;
    font-weight: 700;
    letter-spacing: .06em;
    color: rgba(255, 255, 255, .72);
}

@media (max-width: 620px) {
    /* the arrows move off the sides and onto the bottom corners, so the photo
       gets the full width rather than a third of it back in gutters */
    .lightbox { grid-template-columns: minmax(0, 1fr); padding: 12px; }
    .lb-figure { grid-column: 1; }
    .lb-img { max-height: calc(100vh - 190px); }
    .lb-prev, .lb-next { position: absolute; bottom: 44px; grid-column: 1; }
    .lb-prev { left: 18px; }
    .lb-next { right: 18px; }
}

/* An eligibility / constraint chip. Deep green rather than the amber used for
   invitations, because it narrows who something is for rather than inviting. */
.eso-badge {
    display: inline-block;
    margin-left: 8px;
    padding: 5px 12px;
    border-radius: 99px;
    background: #2a5b39;
    color: #eaffb8;
    font-size: .76rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    vertical-align: middle;
}

/* ============================================================================
   HOME POPUP POSTER
   ----------------------------------------------------------------------------
   Opens over the hero on arrival. The overlay is display:grid so the dialog
   centres, which means the [hidden] attribute alone would not hide it — grid
   beats hidden's implicit display:none — hence the explicit [hidden] rule.
   ========================================================================== */

/* The overlay is a real <dialog> opened with showModal(), so the browser puts it in
   the top layer and paints the dim behind it as ::backdrop. That means: no z-index
   to maintain, no position:fixed, no [hidden] handling (a closed <dialog> is
   display:none by default), and nothing left to swallow clicks on the page beneath.
   The UA's own centring and border are reset here; the frame is .popup-dialog. */
.popup-overlay {
    display: grid;
    place-items: center;
    max-width: 100vw;
    max-height: 100vh;
    width: 100%;
    height: 100%;
    padding: 24px;
    border: 0;
    background: transparent;
    overflow: visible;
    opacity: 0;
    transition: opacity .3s ease;
}
.popup-overlay::backdrop {
    background: rgba(20, 33, 15, .72);
    backdrop-filter: blur(3px);
    opacity: 0;
    transition: opacity .3s ease;
}
.popup-overlay.open,
.popup-overlay.open::backdrop { opacity: 1; }
/* warm-theme.css fills .popup-overlay with a teal dim at (0,2,0), written when this
   was a plain positioned div. The dimming is ::backdrop's job now, so the element
   itself has to stay transparent or the page is dimmed twice. */
[data-theme="warm"] .popup-overlay { background: transparent; }

/* Framed like the sibling sites: the poster is inset inside a gradient border, so
   the announcement reads as a mounted card rather than a bare image floating on the
   dimmed page. Same construction as MyAO and MyBO — 10px of padding, the frame drawn
   as the dialog's own background.

   The colour half lives in the [data-theme="warm"] rule below, not here. warm-theme.css
   already paints this frame turquoise/gold at [data-theme="warm"] .popup-dialog —
   specificity (0,2,0) — which an unqualified .popup-dialog (0,1,0) cannot beat no
   matter that this file loads later. Same trap as the header accent line. */
.popup-dialog {
    position: relative;
    max-width: min(92vw, 560px);
    max-height: 86vh;
    padding: 10px;
    border-radius: calc(var(--r-md) + 10px);
    transform: scale(.94);
    transition: transform .3s cubic-bezier(.2, .9, .3, 1);
}
/* Earth's ramp — amber through gold to deep green — where MyAO runs blue → violet →
   gold and MyBO turquoise → gold. Literal hex, not var(--brand): warm-theme.css
   re-points those tokens under :root[data-theme="warm"] (0,1,1), which outranks this
   file's :root, [data-theme="warm"] block (0,1,0), so --brand here is still turquoise.
   That is why the rest of this stylesheet hard-codes its colours too. */
[data-theme="warm"] .popup-dialog {
    background: linear-gradient(135deg, #f8ad36 0%, #917b29 55%, #2a5b39 100%);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .5),
                0 30px 80px rgba(28, 26, 18, .45),
                0 0 60px rgba(248, 173, 54, .28);
}
.popup-overlay.open .popup-dialog { transform: scale(1); }

.popup-link { display: block; cursor: pointer; }
/* The white keyline separates the poster from the frame behind it — without it a
   poster whose own edge is dark or amber bleeds into the gradient. The drop shadow
   that used to sit here is gone: the frame now carries the depth. */
.popup-posters img {
    display: block;
    width: 100%;
    height: auto;
    max-height: calc(86vh - 20px);
    object-fit: contain;
    border-radius: var(--r-md);
    border: 2px solid rgba(255, 255, 255, .9);
}

/* More than one poster: a scroll-snapping strip, so all of them are reachable
   without a carousel library. Scrolls with the wheel, swipes on touch, and is
   keyboard-focusable via tabindex on the container. */
.popup-posters.is-strip {
    display: flex;
    gap: 14px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    padding-bottom: 10px;
    -webkit-overflow-scrolling: touch;
}
.popup-posters.is-strip .popup-link {
    flex: 0 0 100%;
    scroll-snap-align: center;
}
.popup-posters.is-strip:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; }
.popup-posters.is-strip::-webkit-scrollbar { height: 8px; }
.popup-posters.is-strip::-webkit-scrollbar-thumb {
    background: rgba(248, 173, 54, .55);
    border-radius: 99px;
}

.popup-close {
    position: absolute;
    top: -14px;
    right: -14px;
    z-index: 1;
    width: 40px;
    height: 40px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    cursor: pointer;
    /* white ring, as on the sibling sites — the button now sits on the gradient
       frame rather than on the dimmed page, and amber on gold needs the separation */
    border: 2px solid rgba(255, 255, 255, .85);
    background: #f8ad36;
    color: #26210d;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
    transition: background .2s, transform .2s;
}
.popup-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* Scoped to beat warm-theme.css, which paints this button turquoise at
   [data-theme="warm"] .popup-close — see the note on .popup-dialog above. */
[data-theme="warm"] .popup-close { background: #f8ad36; border-color: rgba(255, 255, 255, .85); }
[data-theme="warm"] .popup-close:hover { background: #ffbe55; transform: scale(1.06); }

@media (max-width: 560px) {
    .popup-overlay { padding: 18px; }
    .popup-close { top: -10px; right: -10px; width: 34px; height: 34px; }
}

/* Someone who asked for less motion still gets the popup — it carries real
   information — just without the scale-in. */
@media (prefers-reduced-motion: reduce) {
    .popup-overlay,
    .popup-dialog { transition: none; }
    .popup-dialog { transform: none; }
}

/* ============================================================================
   SECTION RHYTHM
   ----------------------------------------------------------------------------
   The inherited engine gives every section the same treatment — glass panel,
   centred head, tile grid — so a long page reads as one texture repeated a
   dozen times and the eye stops finding the boundaries.

   These classes break that up along an Earth-Science line: sections alternate
   between an open "surface" treatment and a tinted "seam" band, separated by a
   strata rule. The page then reads as a descent through layers, which is the
   same idea the hero opens with.
   ========================================================================== */

/* A tinted band. Used on every other section so the page alternates instead of
   repeating one surface. Full-bleed via the padding trick rather than a
   negative-margin hack, so it never causes a horizontal scrollbar. */
.eso-band {
    position: relative;
    background: linear-gradient(180deg, rgba(228, 255, 154, .28) 0%, rgba(253, 247, 232, .55) 100%);
    border-block: 1px solid rgba(42, 91, 57, .10);
}
.eso-band--deep {
    background: linear-gradient(180deg, rgba(248, 173, 54, .13) 0%, rgba(145, 123, 41, .09) 100%);
}

/* Strata rule — a hairline seam with a sediment dot pattern beneath it, the way
   a bedding plane shows in a cross-section. Sits at the top edge of a band. */
.eso-band::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    top: 0;
    height: 6px;
    background-image: radial-gradient(circle at 3px 3px, rgba(42, 91, 57, .16) 1px, transparent 1.2px);
    background-size: 12px 6px;
    pointer-events: none;
}

/* Centred on the page field, above the panel — the sibling sites' arrangement. */
.eso-head {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    max-width: 720px;
    margin: 0 auto clamp(26px, 4vw, 44px);   /* Biology's .gx-head, to the pixel */
    text-align: center;
}
.eso-head .section-title { margin: 0; }

@media (max-width: 560px) {
    .eso-head { gap: 10px; }
}

/* ---- Stages: two by two ---------------------------------------------------
   Four cards on a 2×2 grid, both rows read left to right. Snaking back along
   the second row would halve the height too, but it runs against the way the
   eye scans: the reader lands on the bottom-left card before the arrow tells
   them it is the fourth stage, and reads the two out of order.

   The funnel is carried by the count chips rather than by the card widths, so
   "top 10% → 15–20 → 4" still reads straight down the section. */
.eso-stages {
    list-style: none; margin: 0; padding: 0;
    /* tighter than it was: the gutter no longer has to hold an arrow */
    display: grid; gap: clamp(16px, 1.8vw, 22px);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.eso-stage {
    position: relative;
    padding: clamp(16px, 2vw, 22px) clamp(18px, 2.2vw, 26px);
    border-radius: 18px;
    background: linear-gradient(180deg, #fffef8, #fff);
    border: 1px solid rgba(42, 91, 57, .16);
    border-left: 5px solid var(--depth, #f8ad36);
    box-shadow: 0 8px 22px rgba(60, 48, 12, .07);
    transition: transform .3s ease, box-shadow .3s ease;
}
.eso-stage:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(60, 48, 12, .13); }

/* each stage sits deeper: topsoil lime, gold, amber, bedrock green */
.eso-stage:nth-child(1) { --depth: #9ccc65; }
.eso-stage:nth-child(2) { --depth: #f8ad36; }
.eso-stage:nth-child(3) { --depth: #a98f31; }
.eso-stage:nth-child(4) { --depth: #2a5b39; }

/* No flow arrows. On a 2×2 they cannot tell the truth: the wrap from stage 2 to
   stage 3 travels down AND back to the left, so a down arrow under stage 2 sits
   directly above stage 4 and reads as 2 → 4. Drawing the real path would mean an
   L-shaped connector threading between the cards, which is more line than the
   four cards are worth. The STAGE 1–4 labels carry the order on their own. */

.eso-stage-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.eso-stage-ico {
    display: grid; place-items: center;
    width: 40px; height: 40px; border-radius: 12px;
    background: color-mix(in srgb, var(--depth) 22%, #fff);
    border: 1px solid color-mix(in srgb, var(--depth) 45%, #fff);
    color: #23301f;
    flex: 0 0 auto;
}
/* The label is what tells a reader which stage they are on, and with no flow
   arrows it is the only thing carrying the order — so it is set at reading size
   in the deep green rather than as a muted micro-caption. */
.eso-stage-label {
    font-size: .92rem; font-weight: 800;
    letter-spacing: .1em; text-transform: uppercase;
    color: #2a5b39;
}
/* the figure the funnel is drawn from — pushed to the right so the four line up
   as a column and can be read down the section on their own */
.eso-stage-count {
    margin-left: auto;
    padding: 6px 14px; border-radius: 999px;
    background: color-mix(in srgb, var(--depth) 20%, #fff);
    border: 1px solid color-mix(in srgb, var(--depth) 50%, #fff);
    font-family: var(--font-display);
    font-weight: 800; font-size: 1.02rem; line-height: 1.2;
    color: #23301f; white-space: nowrap;
}

.eso-stage-body { min-width: 0; }
.eso-stage-body p:first-child { margin-top: 0; }
.eso-stage-body p:first-child strong { font-size: 1.08rem; color: #23301f; }
.eso-stage-body p { margin-bottom: .4rem; }
.eso-stage-body ul { margin: 6px 0 0; padding-left: 18px; }
.eso-stage-body li { color: var(--text-soft); margin-bottom: .25em; }

@media (max-width: 860px) {
    /* one column: two cards side by side leaves each stage a column too narrow
       for its own title */
    .eso-stages { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
    .eso-stage { transition: none; }
    .eso-stage:hover { transform: none; }
}


/* ---- Awards: show the percentile split, don't just state it ---------------
   Top 10% / next 40% / remaining 50% is a proportion, so the bars are sized to
   it. Reading the shape tells you the split before you read the words. */

.eso-awards { display: grid; gap: 14px; }
/* The icon column is a fixed width for every row, so the bars still start at the
   same x and their widths stay comparable — which is the whole point of sizing
   them to the percentage. */
.eso-award {
    display: grid;
    grid-template-columns: 46px minmax(0, var(--w)) 1fr;
    gap: 16px;
    align-items: center;
}
.eso-award-ico {
    display: grid; place-items: center;
    width: 46px; height: 46px; border-radius: 13px;
    background: linear-gradient(140deg, rgba(255, 255, 255, .9), rgba(253, 247, 232, .85));
    border: 1px solid rgba(42, 91, 57, .14);
}
.eso-award-bar {
    display: grid;
    place-items: center;
    min-height: 62px;
    padding: 10px 14px;
    border-radius: 12px;
    font-weight: 800;
    font-size: clamp(1rem, 2.2vw, 1.35rem);
    color: #26210d;
    white-space: nowrap;
}
.eso-award:nth-child(1) .eso-award-bar { background: linear-gradient(135deg, #ffd487, #f8ad36); }
.eso-award:nth-child(2) .eso-award-bar { background: linear-gradient(135deg, #e8dd9a, #b8c860); }
.eso-award:nth-child(3) .eso-award-bar { background: linear-gradient(135deg, #cfe3cd, #a8c4ac); }
.eso-award p { margin: 0; color: var(--text-soft); }

/* Below ~640px the 10% bar would be a sliver too narrow to hold its label, so
   the bars go full width and stack above their text. */
@media (max-width: 640px) {
    /* icon beside the bar, text beneath both */
    .eso-award { grid-template-columns: 46px minmax(0, 1fr); gap: 8px 14px; }
    .eso-award p { grid-column: 1 / -1; }
    .eso-award-bar { min-height: 48px; }
}

/* MyESO Faces — a scroll-snapping strip. No carousel JS: it swipes on touch and
   scrolls with the wheel or arrow keys (tabindex="0" makes it keyboard-focusable),
   which is everything a photo strip actually needs. */
.faces-strip {
    display: flex; gap: 16px;
    overflow-x: auto;
    /* proximity, not mandatory: mandatory re-snaps during a flick, so a fast swipe
       is dragged back to the nearest photo instead of coasting, and the last photo
       is awkward to rest on because it can never align to the start. */
    scroll-snap-type: x proximity;
    /* a swipe that runs past the end stays in the strip rather than turning into
       the browser's back gesture */
    overscroll-behavior-x: contain;
    padding-bottom: 10px;
    scrollbar-width: thin;
}
.faces-strip img {
    flex: 0 0 auto;
    width: clamp(200px, 26vw, 280px);
    /* One ratio at every width. Width and height were clamped independently, and
       their clamps bit at different viewports — the photo was 4:3 on a phone and
       1.4:1 on a desktop, so object-fit: cover cropped a different part of the
       same picture on each. Deriving the height from the width fixes the crop. */
    aspect-ratio: 4 / 3;
    height: auto;
    object-fit: cover;
    border-radius: 12px;
    border: 1px solid var(--border);
    scroll-snap-align: start;
}
.faces-strip:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 12px; }

/* ---- Closing CTA ----------------------------------------------------------
   The last panel on the home page. Its wording is a full sentence, so it is set
   a step below the section titles and capped at ~46ch: three readable lines
   rather than four display-sized ones spanning the whole panel. The amber wash
   and the glowing CTA are what carry the weight here, not the type size. */

.eso-closing {
    text-align: center;
    background: linear-gradient(135deg, var(--brand-100) 0%, var(--lime-soft) 100%);
    border-color: rgba(217, 141, 24, .25);
}
.eso-closing h2 {
    max-width: 46ch;
    margin: 0 auto;
    font-family: var(--font-display);
    font-weight: var(--fw-bold);
    font-size: clamp(1.3rem, 2.3vw, 1.8rem);
    line-height: 1.3;
    text-wrap: balance;
    color: var(--accent);
}
/* margin-inline auto because .gx-glass p inherits a 66ch measure — without it the
   button would centre inside that box rather than inside the panel. */
.eso-closing p { max-width: none; margin: clamp(20px, 3vw, 28px) auto 0; }
.eso-closing .btn { padding: 16px 34px; font-size: 1.05rem; }

/* ============================================================================
   FLORA AND SCREE — leaves, branches (ranting) and rocks
   ----------------------------------------------------------------------------
   Everything in this section is BACKGROUND. Not one rule here adds a box to the
   flow, and not one of these figures can be reached by a pointer or a screen
   reader: they are either the fixed gutter layer at z-index -1 (a sibling plane
   to #space-bg), a background-image on the page backdrop, or a pseudo-element
   painted underneath the section's own content. Delete the whole block and the
   layout is byte-for-byte what it was.

   Why it exists. The Earth palette landed on the colours — amber, lime, bedrock
   green — but every ornament on the site was still a circle, a ring or a
   gradient band inherited from the deep-space siblings. Nothing was shaped like
   Earth Science. These are the shapes: a leaf blade, a branch, a scree pile.

   Six surfaces carry them, chosen so that every page gets some without any page
   template being touched:

       page backdrop   a whisper-faint leaf-and-pebble scatter, tiled
       .eso-flora      branches, clumps and rock piles in the side margins
       .page-hero      a sprig either side of the hero on every inner page
       .eso-hero       a scree line along the foot of the home page's strata
       .eso-head       a twig-and-leaf rule under every section title
       .eso-band       a scree line along the foot of every tinted band
       .site-footer    grass growing out of the top edge

   The artwork is held in custom properties so a tile used in two places (the
   scree line runs along both the home hero and every band) exists once. Property
   names are new, so none of the specificity traps documented at the top of this
   file apply — nothing else declares them at any weight.

   Encoding note: `#` is percent-escaped to %23 inside every data URI. It is the
   one character that MUST be, since a bare # ends the URL and starts a fragment
   — an un-escaped colour turns the whole tile into a 404. Angle brackets are
   left literal; they are legal inside a quoted url() and the file is served
   as-authored, with no minifier in the path to disagree.
   ========================================================================== */

:root {
    /* A twig broken by a leaf pair over an amber berry — the rule under a section
       title. Two instances of the same blade the rest of the site is cut from,
       one at rotate(0) and one at rotate(-90), which is what opens them into a
       symmetric V: the blade points up-and-right at 45° untouched, and a quarter
       turn anticlockwise puts its twin at 135°. Drawn 200x20 and used at ~200px,
       so the hairline lands on one physical pixel. */
    --eso-twig-rule: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 20'><path d='M6 13h70M124 13h70' stroke='%23a88a3c' stroke-width='1.6' stroke-linecap='round' opacity='.6'/><path d='M100 13v5' stroke='%237a6722' stroke-width='1.8' stroke-linecap='round' opacity='.7'/><g fill='%232a5b39'><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(100 13) rotate(-90) scale(.55) translate(0 -20)' opacity='.75'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(100 13) scale(.55) translate(0 -20)' opacity='.55'/></g><circle cx='100' cy='18' r='2' fill='%23f8ad36'/></svg>");

    /* Scree: cobbles and pebbles on a shared ground line, tiled horizontally
       along the foot of a band. The seam is at x=0/220, where nothing sits.

       Every stone is wider than it is tall, and no peak sits over its own
       centre. Both rules are load-bearing: the first draft used symmetric
       shapes about half as wide as this and the row came out as a line of
       molehills — rocks read as rocks because they are lying down and because
       one shoulder is longer than the other. */
    --eso-scree-tile: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 22'><g fill='%236e6858'><path d='M6 22 11 13l8-4 11 3 3 10Z' opacity='.34'/><path d='M44 22 48 16l6-4 7 3 2 7Z' opacity='.26'/><path d='M70 22a11 5 0 0 1 22 0Z' opacity='.22'/><path d='M100 22 106 11l10-5 12 4 3 12Z' opacity='.34'/><path d='M142 22 146 17l5-3 6 3 2 5Z' opacity='.26'/><path d='M170 22a9 4 0 0 1 18 0Z' opacity='.2'/><path d='M196 22 201 14l7-4 8 4 2 8Z' opacity='.3'/></g></svg>");

    /* The hero sprig: an arching twig with six leaves. Drawn facing up and to
       the right; the left-hand copy is mirrored in CSS rather than redrawn. */
    --eso-sprig-art: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 80'><g fill='none' stroke='%237a6722' stroke-width='2.6' stroke-linecap='round' opacity='.75'><path d='M5 77C24 71 42 58 56 42 68 28 79 17 95 7'/><path d='M31 65 24 52M50 50 44 36M68 33 64 20M40 58l12-9M59 42l13-7'/></g><g fill='%232a5b39' opacity='.7'><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(24 52) rotate(-35) scale(.55) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(44 36) rotate(-30) scale(.6) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(64 20) rotate(-25) scale(.5) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(52 49) rotate(40) scale(.55) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(72 35) rotate(45) scale(.5) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(95 7) rotate(-8) scale(.55) translate(0 -20)'/></g></svg>");

    /* Blades of grass rising from a baseline, tiled along the footer's top edge
       so the dark panel reads as the ground the page settles into.

       Bedrock green, NOT the footer's own pale ink. The blades are rooted on the
       footer's top border but grow upward out of it, so every pixel of them is
       drawn on the cream section above — in --footer-muted (#bed4b2, chosen for
       dark ground) they came out as faint scratches on nothing. */
    --eso-grass-tile: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 18'><g fill='none' stroke='%232a5b39' stroke-width='2' stroke-linecap='round' opacity='.34'><path d='M12 18C12 12 11 8 9 3M26 18C26 12 28 7 31 2M44 18C44 13 43 9 41 5M62 18C62 11 64 6 68 1M82 18C82 12 81 8 79 3M100 18C100 11 103 6 107 2M120 18C120 13 119 9 117 5M138 18C138 11 141 7 145 2M154 18C154 14 153 11 151 8'/></g></svg>");

    /* The backdrop scatter: three leaves, a twig and three pebbles on a 440px
       tile. Deliberately sparse and almost transparent — at ~.05 alpha it
       registers as texture in the paper, not as a pattern anyone is meant to
       read. This is the layer that puts the motif on EVERY pixel of the site.

       440, not the 320 it was first drawn at. The shapes are the right size at
       320 but the repeat put ~50 of them on a 1600x1100 screen, and earth-bg.js
       is already drawing contours and mineral crystals on the canvas above this.
       Widening the tile without touching a coordinate keeps every figure exactly
       the size it was and drops the count by ~45%. If this ever needs thinning
       again, grow the viewBox and the background-size together — never scale the
       shapes, or the leaves start competing with the ones in the margins. */
    --eso-ground-tex: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 440 440'><g fill='%232a5b39' opacity='.055'><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(52 96) rotate(-52) scale(1.5) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(318 368) rotate(-18) scale(1.2) translate(0 -20)'/><path d='M0 20A20 20 0 0 1 20 0A20 20 0 0 1 0 20Z' transform='translate(368 178) rotate(-96) scale(1.35) translate(0 -20)'/></g><g fill='none' stroke='%237a6722' stroke-width='2.4' stroke-linecap='round' opacity='.05'><path d='M162 294C176 286 186 274 192 258M170 280l-14-6M184 266l14-4'/></g><g fill='%236e6858' opacity='.05'><path d='M256 64 259 52l7-4 7 5 2 11Z'/><path d='M96 338a8 5 0 0 1 16 0Z'/><path d='M400 288 403 280l5-3 5 4 1 7Z'/></g></svg>");
}

/* ---- The page backdrop -----------------------------------------------------
   body::before is the site's ambient wash: fixed, inset 0, z-index -3, four
   radial blooms over an opaque cream base (see the block near the top of this
   file). The scatter is prepended to that stack, so it lies ON the wash and
   UNDER everything else on the page, and inherits the wash's 40s drift for free.

   The whole existing stack is restated rather than layered on with a second
   rule: setting background-image alone would win the image list but leave the
   inherited `background` shorthand's other components in place, and the two
   would then disagree about how many layers there are — exactly the shorthand
   trap this file warns about further up. One declaration, one source of truth.
   Keep this in step with the [data-theme="warm"] body::before block above. */
[data-theme="warm"] body::before {
    background:
        var(--eso-ground-tex),
        radial-gradient(900px 700px at 84% -8%, rgba(228, 255, 154, .55), transparent 62%),
        radial-gradient(780px 640px at  2% 22%, rgba(234, 255, 184, .42), transparent 60%),
        radial-gradient(860px 660px at 98% 56%, rgba(248, 173,  54, .16), transparent 62%),
        radial-gradient(900px 720px at 10% 88%, rgba(145, 123,  41, .10), transparent 62%),
        linear-gradient(180deg, #fffdf6 0%, #fbf6e6 48%, #f5eeda 100%);
    /* the scatter tiles; every gradient under it stays a single sheet */
    background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
    background-size: 440px 440px, auto, auto, auto, auto, auto;
}

/* ---- The gutter layer ------------------------------------------------------
   Fixed, so the margins keep their planting while the page scrolls past. Same
   plane as #space-bg (z-index -1) and placed after it in the layout, so the
   branches sit over the contour canvas and under every section.

   --gut is the width of one empty margin. Resolving `100%` against a fixed
   inset:0 box gives the viewport's *client* width, which excludes the scrollbar
   — `100vw` includes it, and would have pushed every piece ~8px inward on
   Windows. Percentages inside a custom property resolve at use, and both
   properties it is used in (width, and left/right) resolve against that box. */
.eso-flora {
    --gut: calc((100% - var(--container)) / 2);
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
    /* Below the breakpoint further down there is no margin to plant in, and a
       branch would land on the text. The in-page motifs carry the theme there. */
    display: none;
}
/* <defs> renders nothing, but an inline <svg> is still a replaced element and
   would take a line box in the flow if left to itself. */
.eso-flora-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.eso-flora-piece {
    position: absolute;
    /* never wider than the margin it stands in, less a 14px kerb, and never so
       wide on an ultrawide monitor that it becomes the subject of the page */
    width: min(calc(var(--gut) - 14px), 118px);
    height: auto;
    transform-origin: bottom center;
}
/* Two inks: living things are bedrock green, stone is the muted earth grey the
   body copy uses. Both sit far enough back that they never read as content. */
.eso-flora--leaf { color: #2a5b39; opacity: .13; }
.eso-flora--rock { color: #6e6858; opacity: .17; }

/* Left margin. The branch is rooted below the foot of the viewport so it reads
   as growing up out of the bottom edge rather than floating. */
.f-branch-l { left: 8px;  bottom: -26px; }
.f-fronds-l { left: 12px; top: 15%; }
.f-scree-l  { left: 4px;  top: 58%; }

/* Right margin. `scale` — the independent property, not a transform function —
   is what mirrors these: the sway animation below owns `transform` outright, and
   a flip written there would be wiped the moment the animation started. The two
   compose, in the order translate, rotate, scale, then transform. The branch is
   turned twice (-1 -1) so it hangs down from the top edge instead of standing. */
.f-branch-r { right: 10px; top: -30px; scale: -1 -1; }
.f-scree-r  { right: 6px;  top: 42%;   scale: -1 1; }
.f-fronds-r { right: 12px; bottom: 7%; scale: -1 1; }

/* Stone does not move. Only the branches and clumps breathe, each on its own
   phase so the two margins never sway in step. */
.eso-flora--leaf { animation: eso-sway 11s ease-in-out infinite; }
.f-fronds-l { animation-duration: 9s;  animation-delay: -3s; }
.f-branch-r { animation-duration: 13s; animation-delay: -6s; }
.f-fronds-r { animation-duration: 10s; animation-delay: -1.5s; }
@keyframes eso-sway {
    0%, 100% { transform: rotate(-1.8deg); }
    50%      { transform: rotate(1.8deg); }
}

/* 1360px is where one margin first reaches ~90px — enough for a branch to be a
   branch rather than a smudge. Below it the layer is off entirely, so phones
   never composite any of it. */
@media (min-width: 1360px) {
    .eso-flora { display: block; }
}

/* ---- Hero sprigs -----------------------------------------------------------
   Every inner page opens with `section.container.page-hero`, so one pair of
   pseudo-elements here reaches About, Contact, FAQs, Registration, Reading
   Materials, Past-Year Questions, both results pages and the 404.

   They are pinned to the container's outer edges. The hero's text is centred and
   measure-capped well inside that, so the sprigs sit over the 24px padding and
   the slack beside it — they cannot reach the title at any width where they are
   visible. */
.page-hero::before,
.page-hero::after {
    content: "";
    position: absolute;
    top: 0;
    z-index: 0;              /* under the hero's text, which follows in the flow */
    width: clamp(72px, 8vw, 116px);
    aspect-ratio: 100 / 80;
    background: var(--eso-sprig-art) center / contain no-repeat;
    opacity: .5;
    pointer-events: none;
}
.page-hero::before { left: 2px; scale: -1 1; }   /* mirrored, so the pair opens outward */
.page-hero::after  { right: 2px; }
/* Under ~1000px the hero's copy starts to use the full container and the sprigs
   would crowd it. They go, and the section divider below carries the motif. */
@media (max-width: 1000px) {
    .page-hero::before, .page-hero::after { display: none; }
}

/* ---- Hero ground -----------------------------------------------------------
   The home page has no .page-hero — it opens on .eso-hero, four strata bands in
   cross-section. Sprigs in its corners would fight the globe artwork, so it gets
   the scree line instead: stones lying on the topmost stratum, which is the one
   thing that cross-section was missing. .eso-hero already clips its overflow and
   isolates its stacking context, so this stays inside both. */
.eso-hero::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 30px;
    background: var(--eso-scree-tile) bottom center / 300px 30px repeat-x;
    opacity: .55;
    pointer-events: none;
}

/* ---- Section divider -------------------------------------------------------
   .eso-head is the eyebrow-over-title block above every section on every page,
   and it is a column flex container — so an ::after simply becomes its last
   item, centred, with no positioning needed.

   This is the one motif that repeats often enough to set a rhythm: on the home
   page it marks all twelve section boundaries, which is precisely the "one
   texture repeated a dozen times" problem the SECTION RHYTHM block above was
   written to break up. */
.eso-head::after {
    content: "";
    width: min(200px, 64%);
    height: 20px;              /* the tile's own 200x20, so `contain` scales 1:1 */
    margin-top: 10px;
    background: var(--eso-twig-rule) center / contain no-repeat;
}
/* The preparatory-workshop section runs on a dark green panel — the same twig in
   the same ink would vanish into it. Flattened to white and dropped back, which
   reads as the lit edge of a leaf against dark ground. */
.eso-workshop .eso-head::after {
    filter: brightness(0) invert(1);
    opacity: .45;
}

/* ---- Band scree ------------------------------------------------------------
   A tinted band already carries a strata seam at its top edge (.eso-band::before
   above). This closes the other edge with the debris that collects at the foot
   of an exposed face. It lives in the band's bottom padding — clamp(32px, 5vw,
   64px) — so there is always room for 22px of stones under the last element.

   `.section > .container` is z-index 1, so a band's content already sits above
   anything the band itself paints. */
.eso-band::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 22px;
    background: var(--eso-scree-tile) bottom center / 220px 22px repeat-x;
    opacity: .75;
    pointer-events: none;
}

/* ---- Footer edge -----------------------------------------------------------
   Grass along the top of the footer. Offset by its own height so the blades grow
   OUT of the dark panel into the section above rather than standing inside it —
   which needs the footer to be a positioning context, and redesign.css leaves it
   static. The 18px it borrows falls in the preceding section's bottom padding,
   and that section's .container is z-index 1, so any content there still wins. */
.site-footer { position: relative; }
.site-footer::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    top: -18px;
    height: 18px;
    background: var(--eso-grass-tile) bottom center / 160px 18px repeat-x;
    pointer-events: none;
}

/* The sway is decoration and carries nothing; the figures themselves stay. */
@media (prefers-reduced-motion: reduce) {
    .eso-flora-piece { animation: none; }
}
