/* ============================================================================
   MyCLO palette — THE DECIPHERED MANUSCRIPT
   ----------------------------------------------------------------------------
   THE PALETTE IS THE ORIGINAL SITE'S, AND THE COLOUR WORDS BELOW ARE OLDER
   THAN IT. On 2 Sep 2026 the whole site moved off the lilac/rose scheme onto the
   crimson and gold that myclo.my itself uses — the colours of the seal in the
   logo, which the old palette did not match. The mapping was:

       manuscript plum  #3d1b57  ->  heading near-black  #2e293c   (14:1, as before)
       rose             #e0447a  ->  crimson             var(--brand)
       lilac highlight  #c3a0e0  ->  gold                #f1c46a
       cipher teal      #0f6f78  ->  teal                #04788a
       lilac papers              ->  warm off-whites

   Comments further down still say "plum", "rose" and "lilac". Where they are
   describing what a thing USED to look like they are history and correct as
   written; where they name a colour a rule paints TODAY, read the mapping above.
   Two things genuinely changed rather than being renamed: the rose was 3.98:1 and
   could only ever be a fill, so links borrowed the heading ink — the crimson is
   7.49:1 and carries text, so --link is the brand colour now; and on the dark
   band and the footer the crimson falls to ~2.2:1, so the accent there is the
   gold, not a lighter red.

   Colours deliberately NOT remapped: the social icons' own brand colours, the
   red PDF file-type badge, and the grey head on an ended registration card,
   which means "this is over" by being grey.
   ----------------------------------------------------------------------------
   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 MyCLO's own identity.

   THE IDENTITY: a manuscript being read. Plum ink on pale lilac paper, rose for
   the marks a reader makes, teal for the computational half of the subject. All
   five Explorer sites share this layout engine, so the palette is the main thing
   that tells them apart, and this one is chosen to sit clear of all four
   siblings:

       MyAO   deep space — blue into violet, on dark
       MyBO   turquoise and gold
       MyESO  deep green and amber, on cream
       MyGeo  navy and orange, on parchment
       MyCLO  plum and rose, on lilac paper           <- here

   The paper stays COOL on purpose, and that is the thing keeping this clear of
   MyGeo. Both are ink-on-paper sites; MyGeo's paper is warm because an atlas is
   printed on warm stock, and this one is cool because a lilac-grey leaf is the
   ground a scholarly edition of an unread text is set on. Change that and the
   two sites blur into one.

   The violet is also a deliberate distance from MyAO, which owns violet on DARK.
   Same hue family, opposite ground: nothing here is ever plum on black.

       #2e293c  manuscript plum   headings, links, hairlines  14.1:1 on white
       var(--brand)  script violet     secondary links and rules    8.7:1
       #04788a  cipher teal       the computational side       5.9:1
       #7d1a24  rose ink          rose that has to be read     6.8:1
       var(--brand)  rose              primary accent / CTAs        4.0:1 — FILL ONLY
       #f6e2e4  lilac paper       section washes
       #f2ddd9  mauve             cards and panels
       #231f2e  deep plum         footer and dark grounds
       #f1c46a  lilac             highlights

   Contrast is load-bearing here, not decoration. Rose is a FILL colour on this
   brand and never carries text — the same rule MyGeo's orange and MyBO's amber
   both have, for the same reason. Anything that needs to be read runs in the
   plum, or in the rose ink, which is a different colour with a different job.

   Run `php docs/contrast.php` after any palette edit.

   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 on the site this was
   forked from. Until that audit --brand computed to the inherited turquoise and
   every border with it, so the site's own palette only ever reached the
   components that set literal hex values.
   --------------------------------------------------------------------------- */
:root,
:root[data-theme="warm"] {
    /* Brand ramp — the crimson of myclo.my and of the seal in the logo, running
       down to the near-black the original sets its headings in. */
    --brand: #a41e2d;
    /* THE BRAND COLOUR, ALSO AS A CHANNEL TRIPLET AND A DEEP STOP.
       ------------------------------------------------------------------------
       Every rule in this file and in clo-plate.css that used to hold #a41e2d,
       #861824 or rgba(164, 30, 45, x) as a literal now reads one of these three
       — 121 lines of them. Nothing about the site's appearance changed when they
       were introduced; what changed is that the brand hue became overridable,
       which is what lets /about-us be drawn in its own colour without a second
       copy of every component rule. See "The national page" in the README.

       --brand-rgb is space-separated channels for `rgb(var(--brand-rgb) / .2)`.
       It has to be kept in step with --brand by hand: CSS cannot derive one from
       the other, so CHANGE BOTH OR NEITHER. A mismatch is the failure mode to
       watch for — the solid marks take one colour and every tint the other, and
       it looks like a rendering bug rather than a typo.

       --brand-deep is the darker stop the gradients end on. */
    --brand-rgb: 164 30 45;
    --brand-deep: #861824;
    /* The two darker stops the brand gradients end on, and the one rose that is
       allowed to carry WORDS. --brand itself is 3.98:1 on white — a fill colour,
       not an ink — so every bolded phrase that has to be read (the cost cards'
       answers, the selection thresholds) is set in --brand-ink at 6.8:1. Any
       replacement hue has to clear 4.5:1 for that token specifically; the other
       three only ever fill a shape. */
    --brand-2: #9c1f2e;
    --brand-3: #8f202d;
    --brand-ink: #7d1a24;
    --brand-100: #fbf1f2;
    --brand-200: #f6e2e4;
    --brand-300: #e9a6ad;
    --brand-400: #c33b4a;
    --brand-500: #a41e2d;
    --brand-600: #861824;
    --brand-700: #5e1119;
    --brand-800: #3a0b10;
    --brand-900: #2e293c;

    /* accents */
    --accent: #2e293c;
    --accent-2: #04788a;   /* the original's teal, its one cool note; 5.4:1 on white */
    --accent-3: #f1c46a;   /* the original's gold — the logo's stars, and the crimson's foil */
    --water: #f6ded9;
    --water-soft: #fbf0f0;

    /* Link ink — and a constraint that LIFTED with the palette change. The rose
       this replaced was 3.98:1 on white, so it could only ever be a fill, and
       links had to run in the heading ink instead. The crimson is 7.49:1, which
       clears AA for body copy at any weight, so links can finally carry the brand
       colour the way myclo.my's own do. */
    --link: #a41e2d;
    --link-hover: #7a1622;

    /* Inherited names that only warm-theme.css declares, retargeted here so no
       rule downstream can reach back into the turquoise ramp. */
    --teal: #a41e2d;                      /* accent hairlines, ring progress arc */
    --gold: #c8912f;                      /* the name is finally honest: this is gold */
    --violet: #2e293c;                    /* kept name → the heading near-black */
    --pink: #861824;
    --muted: #6a6a6a;                     /* 5.4:1 on white, clear of the AA floor */
    --brand-glow: rgba(35, 31, 46, .14);  /* neutral shadow, not a coloured 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, var(--brand) 0%, var(--brand-deep) 100%);
    --grad-gold:  linear-gradient(135deg, var(--brand), #c8912f);
    --grad-text:  linear-gradient(92deg, var(--brand-deep) 0%, var(--brand) 45%, #2e293c 100%);
    --grad-title: linear-gradient(92deg, var(--brand-deep) 0%, var(--brand) 45%, #2e293c 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
   MyCLO palette it is deep plum with the rose 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"] {
    /* Warmed with the workshop band, and for the same reason — it is the other
       large dark field. The lower stop was already a warm near-black. */
    --footer-bg: linear-gradient(165deg, #341b1e 0%, #240a0e 100%);
    --footer-text: #fdf3f1;
    --footer-muted: #e0aeb0;
    --footer-border: rgba(255, 255, 255, .14);
    /* Gold, not the crimson: on the near-black footer the brand red measures
       2.16:1 and all but disappears, where the gold reaches 9.81:1. */
    --footer-accent: #f1c46a;
}

/* Light theme — a cool lilac-paper wash instead of the aqua one.
   `:root` prefix required; see the selector note above. */
:root[data-theme="warm"] {
    --bg: #fdfaf9;
    --bg-2: #f7edeb;
    --surface: #ffffff;
    --surface-2: #faf1ef;
    --surface-3: #f2e2df;

    /* 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 lilac wash so the
       backdrop is on-palette even before this file's own body rules apply. */
    --bg-950: #fdfaf9;
    --bg-900: #f6eae7;
    --bg-850: #efdedb;

    --text: #1a1720;
    --text-soft: #4d4d4d;
    --text-mute: #6a6a6a;   /* 5.4:1 on white — the palest ink allowed to carry copy */

    --border: rgba(46, 41, 60, .14);
    --border-strong: rgba(46, 41, 60, .28);

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

/* The page backdrop: pale lilac settling into a deeper leaf, so the manuscript-paper
   palette reads across the whole page rather than only on the accents. */
/* ============================================================================
   THE GROUND — one warm wash, one motif, and grain over both.
   ============================================================================
   ONE FLAT COLOUR, not a stack. This was two radial blooms over a vertical
   ramp, with a second sheet of five more gradients painted over it on
   body::before, and a tiled scatter on top of that. Each was defensible alone;
   together they were three grounds arguing, and no amount of retuning one of
   them could fix a problem that came from there being three.

   A flat colour cannot drift, cannot band on a wide screen, and costs nothing
   to compute. Everything that made the old ground feel like paper rather than
   a wall now comes from the grain on body::after, which is the honest way to
   do it: texture, not a gradient pretending to be light. */
[data-theme="warm"] body {
    background: #f9f2f0;
}

/* …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 orange-and-navy 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. Lilac paper
   at the top corners, a rose bloom low and right, over the pale base. The 40s
   drift is inherited and already suspended under prefers-reduced-motion. */
/* THE MOTIF LAYER, and the one thing on the ground that moves.
   ---------------------------------------------------------------------------
   This is warm-theme.css's wash sheet — fixed, z-index -3 — repurposed. It used
   to paint four turquoise blooms, which is why the rule cannot simply be
   deleted: without something here those come back.

   THE WHOLE GRID DRIFTS AS ONE SHEET. That is the difference between this and
   the canvas it replaced, which moved ~28 marks independently at random speeds
   and read as mess. Here every emblem keeps its exact position relative to every
   other; the sheet they are printed on is what moves. Regularity survives the
   motion, which was the whole reason the ground was rebuilt.

   IT LOOPS SEAMLESSLY because the travel is exactly one tile — 260px in each
   axis, the motif's own repeat — so the frame at 100% is pixel-identical to the
   frame at 0% and there is no jump. 150s for that distance is about 1.7px a
   second: nothing you can watch happening, everything you notice has happened.

   inset: -260px oversizes the layer by one tile on every side, so travelling a
   full tile down-right never drags a bare edge into view at the top-left.

   transform, not background-position. A transform is composited on the GPU and
   repaints nothing; animating background-position on a fixed, viewport-sized,
   blended layer repaints it every frame, which is the wrong bill to hand a
   phone. The audience here is schoolchildren on phones. */
[data-theme="warm"] body::before {
    background: var(--clo-motif) 0 0 repeat;
    background-size: 260px 260px;
    inset: -260px;
    /* .5, matching what this had when the emblems shared body::after with the
       grain and inherited that element's opacity. On their own layer at 1 they
       came out twice as strong — the darkest stroke measured 172 against 211
       before — which is a different, louder ornament than the one that was
       settled on. The SVG carries its own opacity .5 as well, so the effective
       strength is .25, unchanged. */
    opacity: .5;
    mix-blend-mode: multiply;
    animation: clo-ground-drift 150s linear infinite;
    will-change: transform;
}
@keyframes clo-ground-drift {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(260px, 260px, 0); }
}
/* The ground is decoration and carries nothing, so it is the first thing to go
   still. Note this also has to cancel warm-theme.css's own fieldDrift, which
   was left animating this element after its background was taken away. */
@media (prefers-reduced-motion: reduce) {
    [data-theme="warm"] body::before { animation: none; }
}

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

/* Primary CTA — amber fill, navy text, which is how the live buttons read. */
[data-theme="warm"] .btn-primary {
    background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
    border-color: var(--brand-deep);
    color: #1c1922;
}

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

/* 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 MyCLO
   palette: plum through teal into rose, the site's three inks in the order the
   palette lists them, so the sites are told apart at the first pixel of the page. */
[data-theme="warm"] .site-header::before {
    background: linear-gradient(135deg, #2e293c 0%, #04788a 45%, var(--brand) 100%);
}

/* ---- Navigation chrome ------------------------------------------------------
   warm-theme.css paints all four of these in `rgba(245,252,251)` / `#fcf8f7` — 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(253, 251, 250, .78); border-bottom-color: var(--border); }
[data-theme="warm"] .site-header.scrolled { background: rgba(253, 251, 250, .96); box-shadow: var(--shadow-sm); }
[data-theme="warm"] .dropdown { background: rgba(253, 251, 250, .99); border-color: var(--border-strong); box-shadow: var(--shadow-md); }

/* The header runs wider than the 1180px reading column.

   MyCLO's nav is comfortably inside it — four top-level items, one of them a
   menu — so the extra width is headroom rather than a fix. It is kept because
   the row it has to hold is not the row an editor leaves it as: every menu item
   is behind page_visible(), and a longer label typed at /admin lands here. The
   reading column stays at 1180: prose does not want to be wider, and only the
   header ever has this problem.

   white-space: nowrap is the other half. A flex row that has run out of width
   wraps INSIDE the words — "Registratio / n" — which is how the miss showed up
   both times it happened on the sibling site this was forked from. With nowrap a
   future miss overflows instead, which is at least legible as a bug rather than
   looking like a design. */
.site-header > .container { max-width: 1320px; }
.nav-links > li > a { white-space: nowrap; padding-inline: 12px; }
@media (min-width: 1200px) and (max-width: 1439px) {
    /* A gentle tightening, not the squeeze this inherited. On the sibling site
       the row held eight items and needed 7px padding at .86rem plus a shrunken
       CTA to fit. Four items fit here with room to spare, so this only takes the
       edge off — and it is what absorbs a longer label before the nowrap above
       has to. */
    .nav-links > li > a { padding-inline: 11px; }
}
/* A .dropdown-rule lived here on the sibling site: a hairline dividing one menu
   where it stopped listing one page's anchors and started listing another page.
   The single menu here lists six pages of one subject, so there is nothing to
   divide and the rule went with the arrangement that needed it. */
@media (max-width: 1199px) {
    /* the open burger menu is a full overlay panel — fully opaque, or the hero
       ghosts through it */
    [data-theme="warm"] .nav-links { background: #fdfaf9; border-bottom-color: var(--border-strong); }
}

/* Scroll-to-top. This kept warm-theme's flat turquoise (var(--brand-deep)) — 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, var(--brand), var(--brand-2));
    border-color: var(--brand-deep);
    color: #1c1922;
    box-shadow: var(--shadow-md);
}
[data-theme="warm"] .to-top:hover { background: linear-gradient(140deg, #d4636d, #a01d2b); }

/* ---- 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 MyCLO rose (var(--brand-deep)) would have left every body link and the active nav
   item sitting on the AA floor rather than clear of it. So text links take --link
   (the manuscript plum, 14.07:1) and rose 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); }
/* Prose links are UNDERLINED, and that is not decoration.

   The link colour is the manuscript plum (#2e293c) and body copy is --text-soft
   (#4d4d4d). Those two measure 1.48:1 against each other — WCAG asks 3:1 before
   colour alone may distinguish a link, and 1.48 is not close. Without a second
   signal the APLO and IOL links in the About introduction, and every link inside
   an FAQ answer, are invisible until the pointer happens to cross one.

   Darkening the link is not the fix: it is already nearly black, and pulling the
   body text lighter to make room would cost the paragraph's own contrast.

   The underline is drawn in ROSE under plum text, which does two things at once:
   it is unmistakable at a glance, and it uses the rose the way this palette
   allows rose to be used — as a mark rather than as type. The same reason the
   registration steps have underlined their links all along. */
[data-theme="warm"] .prose a:not(.btn) {
    color: var(--link);
    text-decoration: underline;
    text-decoration-color: rgb(var(--brand-rgb) / .55);
    text-decoration-thickness: 2px;
    /* clear of descenders — an underline through a 'g' is harder to read than
       no underline at all */
    text-underline-offset: 3px;
    transition: color .2s ease, text-decoration-color .2s ease;
}
[data-theme="warm"] .prose a:not(.btn):hover,
[data-theme="warm"] .prose a:not(.btn):focus-visible {
    color: var(--link-hover);
    text-decoration-color: var(--brand);
}
@media (prefers-reduced-motion: reduce) {
    [data-theme="warm"] .prose a:not(.btn) { transition: none; }
}

/* Widows: no heading left with one word on its last line.
   ---------------------------------------------------------------------------
   redesign.css already balances .section-title, the hero titles and the h2/h3
   inside a glass panel. That covers the headings the sibling sites had and
   misses the ones this site added: the FAQ questions (long, and many of them
   end on "…Language Explorer Competition?", which lands alone), the stage and
   legend names that end in a bracketed acronym, and the card headings on the
   home page.

   `balance` for headings — it evens the line lengths, which is what pulls the
   last word back up. `pretty` for the leads and body copy, which is the value
   built for this exact problem: it leaves the line breaking alone except to
   avoid a short final line.

   Both are progressive: a browser without them breaks lines as it always did.
   Neither can fix a heading whose last word is longer than the space left on
   the line before it — those are down to the wording. */
h1, h2, h3,
.clo-step-name, .clo-legend-name, .faq-q, summary h2 {
    text-wrap: balance;
}
p.lead, .prose p, .clo-head-text p {
    text-wrap: pretty;
}
/* Sub-headings inside rich text.

   The IOL 2025 write-up runs to three of them ("The Journey to IOL 2025" and two
   more). They are h3, one level under the h2 the write-up opens on; they were h4
   until the audit caught the skipped level.

   Both levels are listed so that moving one does not silently drop its styling —
   redesign.css sets .prose h3 in the sibling sites' navy at 1.25rem and has no
   rule for h4 at all, which meant an h4 drew at bold body size, indistinguishable
   from a paragraph that already bolds every name in it.

   Sized just under .prose h3's 1.25rem and set in the manuscript plum rather than
   that navy — the whole palette moved and these headings should not be the one
   thing still pointing back at it. */
[data-theme="warm"] .prose h3,
[data-theme="warm"] .prose h4 {
    color: var(--brand-900);
    font-size: 1.12rem;
    letter-spacing: -.005em;
    /* Asymmetric on purpose: a heading belongs to what follows it, so the space
       above it has to be clearly larger than the space below, or it reads as a
       caption on the paragraph it has just ended. */
    margin: 1.7em 0 .45em;
}
/* the small-caps tagline under the wordmark — orange 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(--water-soft);
    border-color: rgba(46, 41, 60, .22);
    color: #2e293c;
}

/* Section headings sit in the manuscript plum rather than the inherited teal. */
[data-theme="warm"] h1,
[data-theme="warm"] h2,
[data-theme="warm"] h3 {
    color: #231f2e;
}
/* …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 navy 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(252, 248, 248, .92) 100%);
    border-color: var(--border);
}

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

/* ---- Page-level helpers shared across the site ----------------------------- */

/* 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: #2e293c; 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 — Galaxy's timeline, in MyCLO's colours: numbered discs
   threaded on a vertical rail, one plain paragraph of text hanging off each.

   Nothing on this site uses it today: MyCLO has no registration page, so the
   steps it was written for live on the sibling sites. Kept rather than deleted
   because it is the shape any "do this, then this" section on this site would
   want, and re-deriving it costs more than carrying it.

   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, var(--brand) 0%, var(--brand-2) 100%);
    color: #1c1922;
    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: #2e293c;
    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: #b82f3f; }

/* 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: #b82f3f;
}

/* 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, rgb(var(--brand-rgb) / .45), rgb(var(--brand-rgb) / .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, matched to the Explorer results page.
   ---------------------------------------------------------------------------
   redesign.css sets .badge-mini to 88px WIDE, which is also 88px tall for the
   sibling sites' square badges. The MyCLO mark is not square — it is a landscape
   speech bubble, 512x381 — so the two results heroes are matched on HEIGHT, not
   width: 88px tall here, whatever width the ratio makes that (~118px). Matching
   the width instead would have left this mark two thirds the height of the
   Explorer badge and reading as the smaller of the pair.

   This replaces a 200px-wide setting, which stood so that the strapline drawn
   inside the artwork — "Malaysian Computational Linguistics Olympiad" — stayed
   legible; it stops resolving below about 180px. At 88px tall the mark is
   decoration and those words are gone. That is the cost of the two results pages
   presenting their marks identically, and it is survivable HERE specifically
   because nothing in this hero depends on that strapline being read: the alt
   text spells the name out, the eyebrow says "Results Announcement", and the h1
   under it is the full "Malaysian Computational Linguistics Olympiad (MyCLO)
   Results 2026".

   Do not copy these numbers to the About page lockup. That one has no heading
   beside it naming the olympiad, so the mark there does have to carry its own
   lettering, and it is sized on .al-plate below instead. */
.page-hero .badge-mini { height: auto; }
.page-hero .badge-mini--wide { width: auto; height: 88px; }

/* ---- Explorer → MyCLO lockup ----------------------------------------------
   The competition badge feeding the olympiad it leads into, over the copy that
   describes that route — the siblings' .about-lockup, in MyCLO's colours. */
.about-lockup {
    /* One place the plate height is written, because three rules need to agree
       on it: the plate itself, and the arrow that has to sit on the plate's
       centreline rather than on the row's. */
    --al-plate-h: clamp(120px, 13vw, 168px);
    display: flex;
    /* flex-start, not center. The two captions wrap to different numbers of
       lines — "Language Explorer Competition" fits on one, "Malaysian
       Computational Linguistics Olympiad" takes two — so centring the row
       centres two figures of different heights and the PLATES come out on
       different lines. Topped, the plates align and the captions hang below at
       whatever height they need. */
    align-items: flex-start;
    justify-content: center;
    gap: clamp(14px, 2.6vw, 34px);
    margin-bottom: clamp(22px, 3vw, 34px);
}
/* The figure is exactly as wide as its plate, and the caption wraps inside it.

   It used to be `flex: 0 0 auto` with no width, so each figure sized to its
   widest child — which is the caption, not the plate. "MALAYSIAN COMPUTATIONAL
   LINGUISTICS OLYMPIAD" is 44 characters on one line, so that figure came out
   about 480px wide holding a 264px plate, and the plate sat against its left
   edge. `text-align: center` did not help: it centres inline content, and a
   plate with a set width is a block.

   Two things came out of fixing it rather than one. The plate is centred under
   its caption, and both figures are now the same width — before, the two
   captions were different lengths, so the pair was lopsided around the arrow
   between them. */
.al-item {
    flex: 0 0 auto;
    width: clamp(190px, 21vw, 264px);
    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: 100%;
    height: var(--al-plate-h);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(14px, 1.8vw, 22px);
    border-radius: 18px;
    background: linear-gradient(180deg, #fff 0%, #faf1ef 100%);
    border: 1px solid rgba(46, 41, 60, .12);
    box-shadow: 0 14px 34px rgba(35, 31, 46, .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;
    /* Tightened from .14em. The caption now wraps to the plate's width rather
       than running on one line, and at 44 characters over two or three lines the
       wider tracking was costing a whole extra line. */
    letter-spacing: .1em;
    line-height: 1.45;
    text-transform: uppercase;
    color: var(--text-soft);
    text-wrap: balance;
}

/* the connector: a dashed trail running toward MyCLO, ending in a rose chip.

   Pushed down to the plates' centreline and then pulled back by half its own
   height, so it lands on that line whatever height it happens to be. Doing it
   with align-items on the row instead would put it in the middle of the tallest
   FIGURE — caption included — which is about 26px below the plates. */
.al-arrow {
    flex: 1 1 auto;
    max-width: 190px;
    min-width: 54px;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: calc(var(--al-plate-h) / 2);
    transform: translateY(-50%);
}
.al-trail {
    flex: 1 1 auto;
    height: 3px;
    border-radius: 999px;
    background: repeating-linear-gradient(90deg, var(--brand-deep) 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 orange, as everywhere else on this palette — white on var(--brand) is
       nowhere near readable at an 18px glyph */
    color: #1c1922;
    background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 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-item { width: min(260px, 78vw); }
    .al-plate { height: 132px; }
    /* Stacked, the arrow sits between the two plates in the flow, so the
       centreline offset above is not just unnecessary — it would push it into
       the second plate. */
    .al-arrow {
        flex: 0 0 auto; max-width: none; min-width: 0;
        flex-direction: column; height: 74px;
        margin-top: 0; transform: none;
    }
    .al-trail {
        width: 3px;
        height: auto;
        background: repeating-linear-gradient(180deg, var(--brand-deep) 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. 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.

   Justified at EVERY width, phones included. This used to be gated behind
   min-width 45em, which meant the About page's write-ups were squared off on a
   desktop and ragged on a phone — the same copy set two different ways depending
   on the device, which is the inconsistency that gate was meant to prevent.

   Hyphens stay OFF, so words are never broken. That is a deliberate choice, not
   an oversight — please do not "fix" it by switching hyphens:auto back on.

   Know the cost before touching either line. With no hyphenation the only thing
   a justified line can stretch is its word spaces, so they open wider than
   normal, and the narrower the column the more it shows. hyphens:auto would
   tighten that up (it gives the breaker somewhere to break other than a space,
   and <html lang="en"> supplies the language it needs) — it was tried here and
   rejected, because whole words were preferred to perfectly even spacing.

   So there are two supported states, and mixing them is what to avoid:
       justify + hyphens:none   <- current: whole words, looser spacing
       ragged-right             <- the fallback if the spacing is ever too loose
   text-wrap:pretty stays either way; with hyphenation gone it is the only thing
   left helping the line breaks. */
/* The measure is NOT capped: these blocks take their panel's full width.
   ---------------------------------------------------------------------------
   This carried a 75ch cap on the container for a while, on the usual
   60-75-character reading-measure argument. It was taken back out on request:
   the cap centred the copy inside the panel and left a visible margin either
   side, so the panel read as a narrow column of text in a wide box rather than
   as a filled card — and the photo grids and year headings below it, which
   always spanned the whole panel, made that mismatch obvious.

   Full width is a trade, and the cost is worth knowing before touching this:
   at 1440px a line runs to roughly 100 characters, which is past the ~80 where
   the eye starts losing its place on the return sweep, and justified text has
   no ragged edge left to navigate by. If that ever reads badly, the fix is the
   fallback already named above — ragged right — not a re-cap of the measure,
   which is what was just removed.

   The cap must not go back on `.clo-justify p` either. That was tried first and
   was worse: paragraphs centred themselves while the h3 sub-headings and the
   <ul> beside them, not being paragraphs, stayed flush to the panel padding, so
   the text came loose from its own headings. */
.clo-justify { max-width: none; }
.clo-justify p,
.clo-justify li {
    max-width: none;
    hyphens: none;
    text-align: justify;
    text-wrap: pretty;
}

/* ---- 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: #fdfaf9;
    box-shadow: 0 12px 30px rgba(35, 31, 46, .12);
    transition: transform .3s ease, box-shadow .3s ease;
}
.fee-card:hover { transform: translateY(-4px); box-shadow: 0 20px 44px rgba(35, 31, 46, .2); }

/* ---- header: the tier's name on the brand band ----------------------------
   Plum into rose, not the plum-into-teal the hue rotation produced. Both ends
   were on the palette, but the interpolation between a violet and a teal runs
   through desaturated grey at the midpoint — which is exactly where the band is
   widest, so the header read as muddy rather than as either colour. Plum and rose
   are neighbours on the wheel, so the whole band stays in the brand. */
.fee-card-head {
    padding: 15px 18px;
    background: linear-gradient(160deg, #2e293c 0%, #7a1622 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 dark brand band, where it needs the
   light ink instead */
.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. Earth brown: the header is orange, 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);
    /* Crimson, not the grey-violet this was. It is a coloured band across the
       corner of a card — one of the few saturated marks on the page — and it was
       the last visible thing still sitting in the cool ink. */
    background: linear-gradient(135deg, var(--brand-deep) 0%, #4f171b 100%);
    color: #fff;
    font-size: .8rem;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
    text-align: center;
    box-shadow: 0 3px 10px rgba(35, 31, 46, .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(46, 41, 60, .1);
    border: 1px solid rgba(46, 41, 60, .28);
    color: #2e293c;
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.fee-pill--muted {
    background: rgba(35, 31, 46, .07);
    border-color: rgba(35, 31, 46, .16);
    color: var(--text-soft);
}
.fee-card.is-ended { opacity: .72; }
/* An ended tier keeps the shape and loses the colour: the same band desaturated
   toward the page's own ink, so the row still reads as two cards of one kind
   rather than as one card and a mistake. */
.fee-card.is-ended .fee-card-head { background: linear-gradient(160deg, #6b6270 0%, #565060 100%); }
.fee-card.is-ended:hover { transform: none; box-shadow: 0 12px 30px rgba(35, 31, 46, .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: #2e293c; }

/* 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 an Explorer home page runs twelve sections where the inherited
   engine assumes three or four.

   It is gone, and nothing here should bring it back. The Explorer sites are meant
   to read as one family, and every value it overrode is identical in this site'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 "MyCLO 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). */
.clo-head-text .eyebrow { margin-bottom: 14px; }
.clo-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. */
.clo-head-text { min-width: 0; }
.clo-head-text .eyebrow { margin-bottom: 10px; }

/* ---- Introduction: THE OPENING LEAF ----------------------------------------
   The section is set as the first leaf of a scholarly edition of a text nobody
   has read yet: a ruled margin down the left with the line count struck in it,
   an initial dropped into the opening paragraph, and the objectives set beneath
   as the apparatus that follows a text rather than as a list beside it.

   It replaces the arrangement the sibling sites use — paragraph on the left with
   an ornament under it, objectives boxed on the right behind a hairline — which
   is a good layout and the wrong one here for two reasons.

   The first is that the page already runs that shape four more times. Categories,
   Stages, Syllabus and Benefits are all "a mark, then a sentence", and the
   Introduction arriving as a fifth made the top of the page read as a stack of
   near-identical cards before a visitor had read a word.

   The second is what the copy actually does. The paragraph ends "…and achieve
   the following objectives:" — it points FORWARD, at the three that follow it.
   Set beside the paragraph they were a parallel column the sentence had to reach
   sideways into; set under it they are what the sentence promised, in the order
   it promised them.

   Nothing here is a box. The whole section is one leaf, ruled and divided, which
   is the one treatment on this page that says "this is a page of a text" — and
   this is the section where that claim belongs, because it is the one explaining
   what the competition is.
   ========================================================================== */
/* The leaf is NARROWER than the container, and that is the whole point of it.

   Every other panel on this page runs the full 1180px because every other panel
   holds cards, and cards want width. This one holds a paragraph, and a paragraph
   wants a measure — at 1180px minus its margin it would run to about 110
   characters a line, which is roughly double what anybody reads comfortably.

   880px is what makes the text block right, and the side effect is the thing
   worth having: the panel stops looking like a full-width band and starts
   looking like a leaf lying on the page, which is what the section is for. */
.clo-leaf {
    max-width: 880px;
    margin-inline: auto;
    display: grid;
    grid-template-columns: 58px minmax(0, 1fr);
    gap: clamp(18px, 2.6vw, 34px);
    padding: clamp(22px, 3vw, 40px);
    border-radius: 22px;
}

/* The ruled margin. Two struck rules rather than one: a fine tick every line and
   a longer rose one every fifth, which is how a manuscript's line count is
   actually ruled and the reason this reads as a count rather than as a texture.
   The 24px pitch is arbitrary — it is a drawn margin, not a real line count, and
   matching it to the body's line-height would only look like a failed attempt to.

   currentColor on the element so the two ticks can be tinted independently
   without repeating an rgba() in both. */
.clo-leaf-margin {
    position: relative;
    border-right: 1px solid var(--border);
    color: rgba(46, 41, 60, .26);
}
.clo-leaf-margin::before,
.clo-leaf-margin::after {
    content: "";
    position: absolute;
    top: 6px;
    bottom: 6px;
    right: 11px;
}
/* every line */
.clo-leaf-margin::before {
    width: 11px;
    background: repeating-linear-gradient(180deg, currentColor 0 1.5px, transparent 1.5px 24px);
}
/* every fifth, longer and in the accent — 5 x 24px */
.clo-leaf-margin::after {
    width: 19px;
    background: repeating-linear-gradient(180deg, rgb(var(--brand-rgb) / .5) 0 1.8px, transparent 1.8px 120px);
}

/* The opening paragraph, a size above body copy: this is the first prose on the
   page and the only paragraph on it anybody reads start to finish.

   No max-width. `.prose` sets 66ch and that is right everywhere else on the site,
   but here the LEAF is what holds the measure — capping the paragraph as well
   would stop it short of the panel it sits in and leave a stripe of empty card
   down its right side, which is what a second measure inside a measured column
   always does. One thing sets the width, and it is the thing you can see. */
.clo-lede p {
    max-width: none;
    font-size: clamp(1rem, 1.05vw, 1.08rem);
    line-height: 1.75;
    color: var(--text-soft);
}
.clo-lede p + p { margin-top: .85em; }

/* The initial. A drop cap is the one typographic mark that says "manuscript"
   without drawing anything, which is why it is here and not on any other
   section — used twice it would stop being an opening and become a style.

   float, not `initial-letter`: that property is Safari-only at a usable level,
   and its fallback is a normal-sized letter, so half the visitors would see the
   paragraph simply start. This way every browser draws the same cap.

   The rose underline is the ruled seat an illuminated capital sits on. It is
   drawn with a border rather than a pseudo-element because ::first-letter takes
   a very short list of properties and a pseudo-element on a pseudo-element is
   not one of them. */
.clo-lede p:first-of-type::first-letter {
    float: left;
    font-family: var(--font-display);
    font-size: 3.5em;
    line-height: .78;
    font-weight: 800;
    margin: .06em .14em -.04em 0;
    padding-right: .06em;
    color: var(--accent);
    border-bottom: 2px solid rgb(var(--brand-rgb) / .45);
}
/* Below 560px the cap eats a third of the measure and the first three lines
   wrap to four or five words each. The paragraph reads better without it. */
@media (max-width: 560px) {
    .clo-lede p:first-of-type::first-letter {
        float: none; font-size: inherit; line-height: inherit; font-weight: inherit;
        margin: 0; padding: 0; color: inherit; border-bottom: 0;
    }
}

/* The label over the apparatus, with a hairline running off it to the right —
   the way a printed edition heads a block of notes. Not a section title: this
   sits INSIDE the leaf and must not compete with the one over it. */
.clo-aims-label {
    display: flex;
    align-items: center;
    gap: 14px;
    max-width: none;          /* a <p> in a glass panel inherits the 66ch measure */
    margin: clamp(24px, 3.2vw, 36px) 0 clamp(16px, 2vw, 22px);
    /* .9rem/.1em — see the note on .clo-spec-label. */
    font-size: .9rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--accent);
}
.clo-aims-label::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, rgba(46, 41, 60, .3), transparent);
}

/* The apparatus: numerals hanging in their own column, text in the measure.

   Three columns side by side was the first attempt and it was wrong twice over.
   It gave each objective about 196px — four words a line, and "problem-solving"
   alone on one of them — and it set the apparatus WIDER than the paragraph it
   answers, so the block got broader as it went down the leaf instead of holding
   one text block.

   Hanging numerals are what an edition actually does with a numbered note, and
   they cost nothing: the marker sits out in the gutter, every line of the note
   lines up under the first, and the measure is the paragraph's own. */
.clo-aims { list-style: none; margin: 0; padding: 0; display: grid; }
.clo-aim {
    display: grid;
    grid-template-columns: 40px minmax(0, 1fr);
    gap: 4px;
    padding: clamp(13px, 1.8vw, 17px) 0;
    border-top: 1px solid var(--border);
}
.clo-aim:first-child { border-top: 0; padding-top: 0; }
.clo-aim:last-child { padding-bottom: 0; }

/* The numeral, in lower-case roman and in the teal.

   Roman because this is apparatus, and because it is the one numbering style
   left: the Stages cards and the About legend both run 01 / 02, and a third
   block numbered the same way would read as a continuation of them. Teal
   because it is the palette's third ink and this is the only place on the home
   page that uses it for type — the objectives are the analytical half of the
   subject, which is what the teal is for. */
/* Ranged right, so the numerals align on their last character the way a
   numbered list aligns on its full stop — "i", "ii" and "iii" ranged LEFT step
   raggedly outward and read as three different indents.

   Barely any letter-spacing: at .2em "ii" reads as two separate i's, which is
   the one thing a roman numeral must not do. */
.clo-aim-num {
    padding-top: .18em;
    padding-right: 14px;
    text-align: right;
    font-family: var(--font-display);
    font-size: .9rem;
    font-weight: 700;
    letter-spacing: .02em;
    color: var(--accent-2);
}
.clo-aim p {
    margin: 0;
    max-width: none;
    color: var(--text-soft);
    line-height: 1.6;
}

/* The numeral column closes up on a phone, where 40px of gutter is a tenth of
   the measure. The numeral keeps its own line rather than shrinking. */
@media (max-width: 480px) {
    .clo-aim { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .clo-aim-num { text-align: left; padding-right: 0; }
}

/* No margin to rule below 720px, and a 58px column of ticks beside a 300px
   measure is a stripe rather than a margin. The leaf becomes a plain panel and
   the drop cap carries the motif on its own. */
@media (max-width: 720px) {
    .clo-leaf { grid-template-columns: minmax(0, 1fr); }
    .clo-leaf-margin { display: none; }
}

/* ---- Feature rows ----------------------------------------------------------
   A mark on a tinted tile, a sentence beside it, a hairline between.

   Only .clo-feature-ico is still in use, by the two "how to register" rows in
   the pricing panel. The row rules themselves are kept because they are the
   shape any "a mark, then a sentence" list on this site would want, and because
   two sections have already been built out of them and taken back out again —
   the Introduction's objectives (now the leaf's hanging numerals) and the
   Benefits section (removed: hidden upstream, see ContentSeeder). Deleting them
   would mean re-deriving them the next time. */
.clo-features { list-style: none; margin: 0; padding: 0; display: grid; }
.clo-feature {
    display: grid;
    grid-template-columns: 42px 1fr;
    gap: 14px;
    align-items: center;
    padding: 13px 0;
    border-bottom: 1px solid rgba(46, 41, 60, .10);
}
.clo-feature:last-child { border-bottom: 0; padding-bottom: 0; }
.clo-feature p {
    margin: 0;
    max-width: none;
    color: var(--text-soft);
    font-size: 1rem;
    line-height: 1.55;
}
.clo-feature-ico {
    display: grid; place-items: center;
    width: 42px; height: 42px; border-radius: 13px;
    background: linear-gradient(135deg, rgb(var(--brand-rgb) / .26), rgba(246, 226, 228, .5));
    border: 1px solid rgba(46, 41, 60, .16);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .8);
    color: #2e293c;
    transition: transform .3s ease, border-color .3s ease;
}
.clo-feature:hover .clo-feature-ico {
    transform: scale(1.07);
    border-color: rgb(var(--brand-rgb) / .55);
}

@media (prefers-reduced-motion: reduce) {
    .clo-feature-ico { transition: none; }
    .clo-feature:hover .clo-feature-ico { transform: none; }
}

/* ---- Competition format: THE SPEC CARDS ------------------------------------
   Two cards to a row, and a long value takes the row to itself.

   Three arrangements came before this. The first was a flex wrap of
   content-sized cells, which packed three then two and left the second row
   centred on nothing. The second was a full-width table of hairline rows, which
   fixed the ragged edge and introduced a worse problem: these values are mostly
   two or three words, so more than half of every row was empty card, and a page
   of hairlines directly under five saturated plates read as unfinished.

   So: cards again, but on a real grid rather than a wrap — and the width of a
   card is decided by the length of its own value, which is the thing the first
   two attempts each got half right. See the note in x-clo-spec for where the
   threshold comes from.

   The section still has a job the quick-facts bar under the hero does not. That
   bar carries four of these five values as big figures; this is the
   specification a teacher checks before the day, and it is the only place the
   fifth (Necessity) appears at all. Same values, different question — which is
   why the two are drawn differently rather than merged.
   ========================================================================== */
.clo-spec {
    max-width: 1000px;
    margin: clamp(20px, 3vw, 30px) auto 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(12px, 1.4vw, 16px);
    /* Dense, or a full-width card leaves a hole beside the one before it: the
       grid will not put a later short card into a gap a wide card could not
       fit. With five specs and one wide one that is two holes, which is what
       the layout looked like before this line.

       Dense reorders what you SEE against source order, which is a real cost
       and is affordable here for one reason: nothing in this list is a
       sequence. They are five independent facts about the same paper, and none
       of them is focusable, so nothing tabs through them in an order that could
       now disagree with the page. Do not carry this line to a list where the
       order means something. */
    grid-auto-flow: dense;
}

/* A <dl>'s <dt> and <dd> are siblings, so each pair is wrapped to make a card.
   The wrapper is the grid item; the pair inside it gets its own two-column
   grid, with the mark spanning both rows down the left. */
.clo-spec-item {
    display: grid;
    grid-template-columns: 52px minmax(0, 1fr);
    align-items: center;
    gap: 2px 16px;
    padding: clamp(14px, 1.8vw, 18px) clamp(16px, 2vw, 20px);
    border: 1px solid var(--border);
    border-radius: 16px;
    background: linear-gradient(150deg, rgba(255, 255, 255, .96), rgba(246, 226, 228, .5));
    box-shadow: 0 6px 18px rgba(35, 31, 46, .06);
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.clo-spec-item:hover {
    transform: translateY(-2px);
    border-color: var(--border-strong);
    box-shadow: 0 14px 30px rgba(35, 31, 46, .11);
}
/* The long ones. A sentence in half a row wraps to four lines; across the whole
   row it is one or two. */
.clo-spec-item.is-wide { grid-column: 1 / -1; }

/* The mark, on a tinted tile, spanning the label and the value beside it. The
   tile is back after the table experiment dropped it: with hairlines gone, the
   drawings had nothing to sit on and floated in the corner of each card. */
.clo-spec-ico {
    grid-row: span 2;
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    border-radius: 15px;
    background: linear-gradient(140deg, rgb(var(--brand-rgb) / .2), rgba(246, 226, 228, .5));
    border: 1px solid rgba(46, 41, 60, .13);
}

/* .9rem/.1em, matching .eo-spec-label on MyEO and .eso-fmt-label / .geo-fmt-label
   on the Earth and Geography sites. The three labels on this site sat at .76rem
   under .13–.16em of tracking, and uppercase under heavy tracking is the hardest
   combination to read small: caps lose word shape and the tracking pulls the
   letters further apart. The tracking was compensating for the size; with the
   size fixed it is no longer needed.

   clo-plate.css restates .17em for the ruled, non-cards spec variant. That one is
   scoped with :not(.clo-spec--cards) and deliberate, and is left as it is. */
.clo-spec-label {
    align-self: end;
    margin: 0;
    font-size: .9rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--accent);
}
/* The [data-theme] prefix is not decoration: warm-theme.css sets a colour on
   every <dd> at a weight this would otherwise lose to. */
[data-theme="warm"] .clo-spec-value {
    align-self: start;
    margin: 0;
    max-width: none;
    color: var(--text);
    font-weight: 600;
    font-size: 1.04rem;
    line-height: 1.4;
}

/* One column below 720px: two cards at that width give "Stable internet
   connection and scientific calculator" about four words a line. Every card is
   full width there, so `is-wide` has nothing left to say. */
@media (max-width: 720px) {
    .clo-spec { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
    .clo-spec-item { transition: none; }
    .clo-spec-item:hover { transform: none; }
}


/* ---- 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 its own drawing: a first reading for
   Primary, a worked correspondence 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. */
.clo-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) {
    .clo-cats {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: stretch;
        gap: clamp(18px, 2.2vw, 28px);
    }
}
.clo-cat {
    display: grid;
    /* drawing, 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);
    /* TOP-aligned, not centred. Centred was the visible fault in this pair:
       Secondary carries three rows to Primary's two, so centring each card's
       contents inside its own stretched box put the two <h3>s on different
       lines — "Secondary" sitting a rule-and-a-half above "Primary". Two cards
       that are meant to be read as a choice have to start on the same line, and
       nothing else in the pair was wrong. */
    align-items: start;
    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;
}
.clo-cat:hover {
    transform: translateY(-4px);
    box-shadow: 0 18px 40px rgba(35, 31, 46, .13);
    border-color: rgb(var(--brand-rgb) / .45);
}
.clo-cat-art {
    display: grid; place-items: center;
    filter: drop-shadow(0 10px 18px rgba(35, 31, 46, .22));
}
.clo-cat-art .clo-cat-art-svg { display: block; width: clamp(84px, 11vw, 112px); height: auto; }
.clo-cat-body { min-width: 0; }
.clo-cat h3 { margin: 0 0 12px; font-size: clamp(1.25rem, 2.2vw, 1.6rem); }
.clo-cat ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
/* Two columns by default: icon, then the school with its level under it. The
   level gets its own column only once the card is wide enough to hold both —
   see the min-width rule below. */
.clo-cat li {
    max-width: none;
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr);
    gap: 2px 9px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
}
.clo-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 */
.clo-cat li svg { justify-self: center; color: #2e293c; position: relative; top: 3px; grid-row: 1; }
.clo-cat-school { grid-column: 2; color: #231f2e; line-height: 1.5; min-width: 0; }
/* grid-column is EXPLICIT on both of these. Auto-placement put the level in
   the 22px icon column when it wrapped to a second row, which broke "Year 4-6"
   one character per line on a phone — the exact failure the single shared text
   cell had been guarding against. Naming the column is what makes the split
   safe; nothing here may rely on auto-placement again. */
.clo-cat-level { grid-column: 2; color: var(--text-soft); line-height: 1.5; min-width: 0; }
/* Ranged right, in their own column, from the width at which the two cards go
   side by side. Four year-ranges down one edge is the answer to the only
   question this section is asked, and in the shared text cell they used to
   share, they sat wherever the school name in front of them happened to end. */
@media (min-width: 900px) {
    .clo-cat li { grid-template-columns: 22px minmax(0, 1fr) auto; align-items: baseline; }
    .clo-cat li svg { top: 0; align-self: start; position: relative; }
    .clo-cat-level { grid-column: 3; text-align: right; white-space: nowrap; }
}
@media (max-width: 520px) {
    /* stacked: at this width the drawing and a wrapped school name fight for room */
    .clo-cat { grid-template-columns: 1fr; justify-items: center; text-align: center; }
    .clo-cat-body { width: 100%; }
    .clo-cat li { text-align: left; }
}
@media (prefers-reduced-motion: reduce) {
    .clo-cat { transition: none; }
    .clo-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. */
.clo-counters {
    display: flex; justify-content: center;
    gap: clamp(28px, 7vw, 90px); flex-wrap: wrap;
}
.clo-count { text-align: center; }
.clo-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%, rgb(var(--brand-rgb) / .22), rgba(255, 255, 255, .9) 66%);
    border: 1px dashed rgba(46, 41, 60, .42);
}
/* The travelling arc. At 1px in pale orange 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. */
.clo-ring::before {
    content: ""; position: absolute; inset: -9px; border-radius: 50%;
    border: 3px solid rgba(46, 41, 60, .16);
    border-top-color: var(--brand-deep);
    border-right-color: rgba(156, 31, 46, .55);
    filter: drop-shadow(0 0 4px rgba(156, 31, 46, .35));
    animation: clo-ring-spin 12s linear infinite;
}
@keyframes clo-ring-spin { to { rotate: 1turn; } }
@media (prefers-reduced-motion: reduce) { .clo-ring::before { animation: none; } }
.clo-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, var(--brand-deep), #2e293c);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
.clo-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 orange wash. */
.clo-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 */
.clo-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(46, 41, 60, .35), #04788a 45%, var(--brand));
    opacity: .5;
}
.clo-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. */
.clo-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(46, 41, 60, .2);
    /* the cream halo punches the rail out behind each disc */
    box-shadow: 0 0 0 7px var(--bg), 0 0 0 8px rgba(46, 41, 60, .12), 0 8px 18px rgba(35, 31, 46, .1);
    color: #2e293c;
    transition: transform .3s ease, box-shadow .3s ease;
}
.clo-station:hover .clo-station-node {
    transform: translateY(-4px);
    box-shadow: 0 0 0 7px var(--bg), 0 0 0 8px rgb(var(--brand-rgb) / .45), 0 14px 26px rgba(35, 31, 46, .16);
}
.clo-station h3 {
    margin: 0 0 8px; font-size: .98rem; line-height: 1.35;
    color: #231f2e; text-wrap: balance;
}
.clo-station-date {
    display: inline-block; margin: 0;
    padding: 5px 12px; border-radius: 999px;
    background: var(--water-soft);
    border: 1px solid rgba(46, 41, 60, .16);
    font-size: .86rem; font-weight: 700; color: #2e293c;
}

/* competition day: the one date a visitor must not miss. Marked with an orange ring
   and glow rather than an orange fill — a fill would sit under the illustration and
   flatten it. */
.clo-station.is-hl .clo-station-node {
    border: 2px solid var(--brand-2);
    box-shadow: 0 0 0 7px var(--bg), 0 0 0 9px rgba(156, 31, 46, .4), 0 10px 26px rgba(156, 31, 46, .3);
}
/* the date itself, not just the disc: a tinted pill sat too close to the pale ones
   beside it to register as the date that matters, so this one is a solid crimson
   chip with the CTA's own fill.

   WHITE on it, not the page ink. The fill used to be pale enough to carry dark
   text; on the crimson the same near-black measured 2.2:1 and the one date a
   visitor must not miss was the least readable thing on the page. */
.clo-station.is-hl .clo-station-date {
    background: linear-gradient(135deg, var(--brand), var(--brand-2));
    border-color: var(--brand-deep);
    color: #ffffff;              /* 7.5:1 and 7.9:1 across that fill */
    font-weight: 800;
    box-shadow: 0 6px 16px rgba(156, 31, 46, .34);
}
.clo-station.is-hl h3 { color: #2e293c; 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) {
    .clo-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. */
    .clo-rail::before { display: none; }
    .clo-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(18, 120, 127, .42);
    }
    .clo-station:first-child::before { top: 50%; }
    .clo-station:last-child::before { bottom: 50%; }
    .clo-station:only-child::before { display: none; }
    /* Title and date are ONE stacked block beside the disc, not two grid rows.
       ------------------------------------------------------------------------
       They used to be: `grid-template-columns: 74px 1fr` with the title placed in
       column 2 row 1 and the pill in column 2 row 2. Row 1 is as tall as the disc
       that shares it — 74px — so `align-items: center` floated the title in the
       middle of that 74px and dropped the pill onto a row that begins BELOW the
       whole disc. The two lines of one station ended up ~30px apart, which read
       as a date belonging to nothing rather than to the title above it.

       Spanning the disc across both rows does not fix it: a 74px item spanning
       two rows whose content only adds up to ~59px makes the grid share the
       surplus between them, which pushes the title and pill apart again.

       So the disc comes out of flow entirely and the station becomes a centred
       flex column holding just the two text lines. Their spacing is then the h3's
       own margin and nothing else, and `justify-content: center` centres the pair
       against the disc as a unit — which is what the row was always meant to be.

       Height comes from min-height alone, with no vertical padding — under
       border-box the two would argue about whether the 74px includes the padding,
       and the disc is centred on the station box, so one number is easier to
       reason about than three.

       110px, not 74px. 74 is the disc, and at 74 the stations stack so tightly
       that each disc's 7px cream halo meets the next one's and erases the rail
       running between them — the sequence stops looking like a sequence. 110
       leaves 18px clear above and below the disc, so with the 4px row gap there
       is ~26px of visible rail between one halo and the next. Still shorter than
       the old grid version, which ran 131px a station because the pill sat on a
       row of its own below the disc. */
    .clo-station {
        display: flex;
        flex-direction: column;
        justify-content: center;
        min-height: 110px;
        padding: 0 0 0 90px;           /* 74px disc + the 16px gutter it used to have */
        text-align: left;
    }
    /* Centred with a negative margin, NOT translateY(-50%): the hover rule owns
       `transform` on this element and would wipe the centring the moment a finger
       or cursor touched the station. -37px is half the 74px disc. */
    .clo-station-node {
        position: absolute;
        left: 0;
        top: 50%;
        margin: -37px 0 0;
    }
    .clo-station h3 { margin: 0 0 6px; }
    /* Flex blockifies the inline-block pill and stretch would run it the full
       column width — same reason the grid version needed justify-self. */
    .clo-station-date { align-self: flex-start; }
}
@media (prefers-reduced-motion: reduce) {
    .clo-station-node { transition: none; }
    .clo-station:hover .clo-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".
   ========================================================================== */
/* The band carries the glyph motif too, in the light-ink set. It is the one big
   opaque surface on the site, so the page-wide backdrop scatter (painted on
   body::before, far below this) cannot reach it — left alone it was the only
   section on any page with nothing drawn on it.

   Six layers, front to back: a seal in each bottom corner, the glyph-and-grid
   scatter tiled over the whole field, then the two blooms and the base that were
   already here. Restated as one `background` shorthand rather than layered on
   with a second rule: a stray `background-image` would win the image list while
   leaving this declaration's repeat/position/size in place, and the two would
   then disagree about how many layers exist.

   The three list properties below must stay the same length as the image list —
   six entries each, in the same order. Add a layer and all four lines change. */
.clo-workshop {
    position: relative;
    margin: clamp(18px, 3vw, 36px) 0;
    padding-block: clamp(34px, 5vw, 62px);
    background:
        var(--clo-seal-light),
        var(--clo-seal-light),
        var(--clo-ground-tex-light),
        radial-gradient(120% 90% at 12% -10%, rgb(var(--brand-rgb) / .20), transparent 58%),
        radial-gradient(110% 90% at 92% 4%, rgba(241, 196, 106, .14), transparent 55%),
        /* WARM, not cool. These three were a plum before the palette change and
           mapped to the near-black ink, which carries a slight cool cast — hue 256
           at 19% saturation. In a letterform that is invisible, but this band is
           the largest single field of colour on the site and a faint cast over an
           area that size reads plainly as aubergine, which is the old palette
           showing through on a red site. Same LIGHTNESS as before, so the white
           heading, the gold and the chip all keep the ratios they were tuned to;
           only the hue moves, to the brand's own crimson. */
        linear-gradient(165deg, #3b1115 0%, #4f171b 46%, #3f1216 100%);
    /* the two seals sit past the bottom edge so they read as pressed into the
       band's foot rather than as stickers floating on it; the negative insets are
       clipped by the border box, which is exactly what is wanted */
    background-position:
        left -26px bottom -30px,
        right -34px bottom -38px,
        left top, 0 0, 0 0, 0 0;
    background-size:
        188px 140px,
        224px 166px,
        440px 440px, auto, auto, auto;
    background-repeat:
        no-repeat, no-repeat, repeat, no-repeat, no-repeat, no-repeat;
    border-block: 1px solid rgb(var(--brand-rgb) / .35);
    color: #f3edec;
}
/* A ruled baseline along the foot, the same close the tinted bands get — in
   pale ink, and sitting inside the band's own bottom padding so it never meets
   the panel. */
.clo-workshop::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 22px;
    background: var(--clo-baseline-tile-light) bottom center / 220px 22px repeat-x;
    pointer-events: none;
}
/* the ruled seam the other bands use, in the light ink this one needs */
.clo-workshop::before {
    content: "";
    position: absolute; inset-inline: 0; top: 0; height: 6px;
    background-image: radial-gradient(circle at 3px 3px, rgb(var(--brand-rgb) / .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. */
.clo-workshop .clo-head .section-title,
[data-theme="warm"] .clo-workshop .clo-head .section-title { color: #fff; }
/* The two lines of the workshop title. Both are blocks, so the break between
   them is placed by the markup rather than by the browser — see the note in
   home.blade.php for why the name's last word drops to line two.

   Deliberately NOT text-wrap:nowrap. The break that matters is the one between
   these two blocks, and that is already guaranteed; forcing each line to hold
   would push "Language Explorer Preparatory" straight off the side of a 390px
   phone. Inside a line, wrapping is allowed and on a narrow screen it is
   required — text-wrap:balance is inherited from .section-title and evens those
   lines when it happens. */
.clo-ws-lead,
.clo-ws-tail { display: block; }
.clo-ws-tail { margin-top: 2px; }
/* 1.3 on the workshop title alone. The session line carries a "/", and Poppins
   Bold's slash rises .95em while a descender on the line above falls .28em: they
   need 1.23em between baselines, and the site's tight 1.1 leaves them 6px into
   each other. 1.3 clears by 3px — the value MyBO's title already uses. */
.clo-workshop .section-title { line-height: 1.3; }
/* Inline again, because the session now shares line two with the word in front
   of it. It was display:block back when it held that line by itself. */
/* Gold end to end. The middle stop was var(--brand) — the brand crimson — which is
   the band's own colour: measured 1.98:1 against it, so the middle third of
   whatever this coloured simply vanished. It never showed because clo-plate.css
   was overriding the whole gradient with a solid gold (see the note there); now
   that the override is a gradient again, this base rule has to be safe on its
   own. Light gold → gold → pale gold: 11.0, 9.1 and 12.2 to 1 on the band. */
.clo-workshop .section-title .grad {
    background: linear-gradient(92deg, #ffd98a 0%, #f1c46a 50%, #fff1c2 100%);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
/* The chip is darkened, not lightened, and that is the whole fix.
   #f2ddd9 on a rgba(255,255,255,.12) chip over this band measured 4.37:1, and
   3.53:1 over the band's lightest patch (the amber bloom in the top-left corner)
   — under the 4.5 AA floor for text this small, which the eyebrow is at .72rem.
   Lifting the text instead does not get there: even #f6ebea, almost white and
   well off the amber, only reaches 4.29:1 on that lightest patch, because the
   white chip is carrying the background up as fast as the text goes. Tinting the
   chip DOWN moves the two apart instead of together — .20 black gives 7.72:1
   average and 6.28:1 worst case with the amber text left exactly as designed. */
[data-theme="warm"] .clo-workshop .eyebrow {
    background: rgba(0, 0, 0, .20);
    border-color: rgb(var(--brand-rgb) / .5);
    color: #f2ddd9;
}
/* .clo-badge is kept legible on the band in case the eligibility chip is ever put
   back, but nothing renders it on this page today. */
.clo-workshop .clo-badge {
    background: linear-gradient(135deg, var(--brand), var(--brand-2));
    border-color: var(--brand-deep);
    color: #1c1922;
}
/* 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"] .clo-workshop .gx-glass {
    background: linear-gradient(180deg, rgba(255, 255, 255, .97) 0%, rgba(252, 248, 248, .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"] .clo-workshop .gx-glass p,
[data-theme="warm"] .clo-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. */
.clo-workshop .clo-tabs {
    justify-content: center; gap: 10px;
    border-bottom: 0;
    margin-bottom: clamp(18px, 2.4vw, 26px);
}
/* PALE fill, dark ink. The rule above says light for the ones you are not on and
   dark for the one you are on, and this is the light half. It was a pale rose
   wash; the palette sweep read it as brand colour and mapped it to a mid crimson
   while leaving the near-black label on top, which put "How To Register" and
   "Infopack" at 2.2:1 — the two lowest ratios anywhere on the site. Keeping the
   fill pale fixes the contrast and restores the light/dark distinction, which a
   saturated red had also flattened. */
.clo-workshop .clo-tab {
    border-radius: 999px;
    padding: 10px 20px;
    background: linear-gradient(150deg, #f9ecea, #f0d2d0);
    border: 1px solid #d9a9a6;
    color: #1c1922;               /* 12.3:1 on the darker end of that fill */
}
.clo-workshop .clo-tab:hover {
    background: linear-gradient(150deg, #f5ddda, #e9bfbc);
    color: #1a1720;               /* 10.6:1 */
}
/* 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. */
.clo-workshop .clo-tab[aria-selected="true"] {
    background: linear-gradient(150deg, #5e1119, #3f1216);
    border-color: #2e293c;
    color: #fff;
}
.clo-workshop .clo-tab:focus-visible { outline: 2px solid #f2ddd9; outline-offset: 2px; }

/* Blocks inside the panel — the workshop's own material runs as titled blocks,
   not as loose prose. */
.clo-wk-block {
    padding: clamp(16px, 2vw, 24px);
    border-radius: 16px;
    background: #fff;
    border: 1px solid rgba(46, 41, 60, .14);
    box-shadow: 0 6px 18px rgba(35, 31, 46, .05);
}
.clo-wk-block + .clo-wk-block,
.clo-wk-facts + .clo-wk-block,
.clo-wk-block + .clo-wk-facts { margin-top: 16px; }
.clo-wk-block h3 { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; font-size: 1.15rem; }
.clo-wk-block h4 { display: flex; align-items: center; gap: 9px; margin: 18px 0 8px; font-size: 1rem; }
.clo-wk-block h3 + p, .clo-wk-block h4 + ul { margin-top: 0; }
.clo-wk-block ul { margin: 0; padding-left: 1.15em; }
.clo-wk-block li { margin-bottom: .4em; }
.clo-wk-note {
    margin: 14px 0 0; padding: 10px 14px;
    border-left: 3px solid var(--brand); border-radius: 0 10px 10px 0;
    background: rgb(var(--brand-rgb) / .12);
    font-size: .92rem; font-style: italic;
}
.clo-wk-cta { margin: 18px 0 0; }
/* the Infopack tab is a centred sheet of posters, so its button centres with them */
.clo-wk-posters + .clo-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. */
.clo-wk-facts {
    display: grid; gap: 14px; margin-top: 16px;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.clo-wk-facts--two { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.clo-wk-fact {
    padding: 16px 18px;
    border-radius: 14px;
    background: linear-gradient(155deg, #d4636d, #a01d2b);
    border: 1px solid var(--brand-deep);
    color: #1c1922;
}
/* .gx-glass is an ancestor of these cards, so the panel's light-ink rules above
   out-specify a plain `.clo-wk-fact p`. Matched through the panel here, which is
   what keeps the ink dark on the amber fill. */
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact h4 { margin: 0 0 8px; color: #1c1922; font-size: .95rem; }
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact p { margin: 0; color: #1c1a26; font-weight: 600; }
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact li { color: #1c1a26; }
.clo-wk-fact dl { margin: 0; }
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact dt { margin-top: 8px; font-weight: 700; color: #1c1922; }
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact dt:first-child { margin-top: 0; }
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact dd { margin: 0 0 0 14px; color: #341b1e; }
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact b { color: #1c1922; }

/* Contact + social, two columns inside one block */
.clo-wk-contact { display: grid; gap: clamp(14px, 3vw, 40px); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.clo-wk-contact h4:first-child { margin-top: 0; }
[data-theme="warm"] .clo-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. */
.clo-wk-posters {
    display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fit, minmax(210px, 300px));
    justify-content: center;
}
.clo-wk-posters a {
    display: block; border-radius: 14px; overflow: hidden;
    border: 1px solid rgba(46, 41, 60, .18);
    box-shadow: 0 12px 26px rgba(35, 31, 46, .14);
    transition: transform .3s ease;
}
.clo-wk-posters a:hover { transform: translateY(-4px); }
.clo-wk-posters img { display: block; width: 100%; height: auto; }
@media (prefers-reduced-motion: reduce) {
    .clo-wk-posters a { transition: none; }
    .clo-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. */
.clo-tabs {
    display: flex; gap: 6px; flex-wrap: wrap;
    margin: 0 0 18px;
    border-bottom: 1px solid rgba(46, 41, 60, .18);
}
.clo-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;
}
.clo-tab:hover { color: var(--accent); background: rgb(var(--brand-rgb) / .10); }
.clo-tab[aria-selected="true"] { color: #2e293c; background: rgb(var(--brand-rgb) / .14); border-bottom-color: var(--brand); }
.clo-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. */
.clo-panel[hidden] { display: none !important; }

/* ---- Syllabus --------------------------------------------------------------
   Four statement cards, two up. The sibling sites run a Primary block beside a
   Secondary one because their syllabuses genuinely differ by age; this one does
   not — see the note on the section in home.blade.php — so what is laid out here
   is four claims about the paper rather than two lists of topics.

   auto-fit with a 300px floor rather than a fixed two columns: at three cards
   the row would otherwise leave a hole, and an editor adding a fifth point
   should not have to touch CSS. */
.clo-syl-points {
    display: grid;
    /* 240px, not 300: the home page has four points and the container is 1148px
       of usable width, so a 300px floor gives three columns and leaves the fourth
       card alone on a row of its own. At 240 all four sit on one line at desktop
       width, and the pages that carry three points still get three columns. */
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 16px;
}
.clo-syl-point {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: 16px;
    padding: clamp(18px, 2.2vw, 26px);
    border-radius: 18px;
}
/* The mark sits on a tinted disc, the same treatment .clo-feature-ico gives the
   objectives — the two sections are both "a mark and a sentence", so they read
   as one family rather than two inventions. */
.clo-syl-ico {
    display: grid; place-items: center;
    width: 46px; height: 46px; flex: none;
    border-radius: 14px;
    color: var(--accent);
    background: linear-gradient(180deg, rgb(var(--brand-rgb) / .16), rgba(241, 196, 106, .12));
    border: 1px solid var(--border);
}
.clo-syl-point p { margin: 0; max-width: none; }
.clo-syl-point h3 { margin: 0 0 8px; font-size: 1.05rem; }

/* A ticked list inside one of those cards. Not <ul>'s own bullets: the tick is a
   drawn icon that takes the accent, and a marker plus an icon is two bullets. */
.clo-ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.clo-ticks li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: 10px;
    max-width: none;
}
.clo-ticks li > svg { margin-top: .28em; color: var(--accent-2); flex: none; }

/* ---- Page banner -----------------------------------------------------------
   The single decorative image the Format and Study Guide pages open on, both of
   which the live site leads with. Rounded and edged so it reads as a plate on the
   paper rather than as a photograph bleeding to the container's edges, and capped
   in height so a wide screen does not turn a decorative header into the page. */
.clo-banner {
    display: block;
    width: 100%;
    max-height: 340px;
    object-fit: cover;
    border-radius: 18px;
    border: 1px solid var(--border);
    box-shadow: var(--shadow-sm);
}

/* ---- About: what it costs --------------------------------------------------
   Two column headings on the section's own ground, with a card under each
   holding one sentence — the live page's arrangement, and the right one.

   Two shapes came before it. Two .clo-syl-point cards drew both answers
   identically, so a reader had to read each in full to find out which was
   "free" and which was "you may have to pay" — the one distinction the section
   exists to make. Putting the heading inside the card fixed nothing: the
   headings are long (one names three olympiads) and inside a card the heading
   became the largest thing in it, leaving the sentence it introduces reading as
   a footnote to its own title.

   Out on the ground the headings behave as what they are — a pair of column
   headings — and each card holds one short answer.

   The answers are told apart by colour, not by a label: teal is this palette's
   settled ink (the ticks in the section above, the solved cells in the
   ornament), rose is the one that asks for attention. It lands on the phrase
   upstream itself emphasises, so nothing is invented to carry it.
   ========================================================================== */
.clo-costs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* Two explicit rows so both headings share the first and both cards start at
       the top of the second. A column wrapper each would let a three-line
       heading push its card below its neighbour's. */
    grid-template-rows: auto auto;
    /* stretch, not start: the two cards hold two and three lines of text, so
       `start` left the shorter one ending 26px above its neighbour. Framed cards
       side by side are read as a pair, and a pair that does not line up at the
       foot reads as untidy rather than as economical. The headings above still
       size to themselves — they are in their own row. */
    align-items: stretch;
    gap: clamp(14px, 1.8vw, 20px) clamp(16px, 2.4vw, 30px);
    margin-top: clamp(20px, 3vw, 30px);
}
.clo-cost-title {
    grid-row: 1;
    margin: 0;
    text-align: center;
    font-size: clamp(1.05rem, 1.6vw, 1.28rem);
    line-height: 1.32;
    color: #231f2e;
    text-wrap: balance;
}
.clo-cost-title.is-free { grid-column: 1; }
.clo-cost-title.is-payable { grid-column: 2; }

.clo-cost {
    grid-row: 2;
    /* No align-self here. It was `start`, which overrode the container's
       align-items and left the two-line card ending 26px above the three-line
       one beside it. The pair now takes the row height from .clo-costs. */
    padding: clamp(18px, 2.2vw, 26px) clamp(20px, 2.4vw, 30px);
    border: 1px solid var(--border);
    /* the answer, drawn: a band along the top edge in the colour of the answer */
    border-top-width: 4px;
    border-radius: 16px;
    background: var(--surface);
    box-shadow: 0 8px 22px rgba(35, 31, 46, .07);
}
.clo-cost.is-free { grid-column: 1; }
.clo-cost.is-payable { grid-column: 2; }
.clo-cost p {
    margin: 0;
    max-width: none;
    text-align: center;
    color: var(--text-soft);
    line-height: 1.62;
}
/* The emphasis upstream puts on the answering phrase, in the colour of that
   answer. This is the whole distinction: no badge, no label, no extra words. */
.clo-cost strong { font-weight: 800; }
.clo-cost.is-free { border-top-color: var(--accent-2); }
.clo-cost.is-free strong { color: var(--accent-2); }
.clo-cost.is-payable { border-top-color: var(--brand); }
/* the rose ink, not the fill rose — var(--brand) is 3.98:1 and this is body text */
.clo-cost.is-payable strong { color: var(--brand-ink); }

@media (max-width: 760px) {
    /* Stacked, each heading has to sit with its own card, so the two explicit
       rows go and the four children fall into source order — which is already
       heading, card, heading, card.

       THE .is-free/.is-payable QUALIFIERS ARE REQUIRED HERE, and leaving them
       off was a real bug on every phone: this block used to read
       `.clo-cost-title, .clo-cost { grid-column: 1 }` at (0,1,0), while the
       desktop placement above is `.clo-cost-title.is-payable { grid-column: 2 }`
       at (0,2,0). A media query adds NO specificity of its own, so the desktop
       rule went on winning inside it — the second heading and card stayed in a
       column 2 that `grid-template-columns: minmax(0, 1fr)` never declared, the
       declared column collapsed to 0px, and "MyCLO, APLO and the training camp"
       set itself one letter per line down the page.

       If a third cost card is ever added, its modifier has to be listed here
       too. That is the cost of placing by modifier class; the alternative —
       placing by :nth-child — breaks the moment the DOM order changes, and the
       note above explains why that order is deliberate. */
    .clo-costs { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; }
    .clo-cost-title.is-free,
    .clo-cost-title.is-payable,
    .clo-cost.is-free,
    .clo-cost.is-payable { grid-column: 1; grid-row: auto; }
}

/* ---- About: the gate, and the pair -----------------------------------------
   "Who It Is For, and Why" holds three things that are not the same kind of
   thing, and it used to draw them as three equal cards.

   "Who can participate?" is a RULE — one sentence, and the only fact on the page
   a reader has to check against themselves before the rest of it matters.
   Objectives and Advantages are lists, and they answer a question nobody asks
   until they know they qualify. Equal cards said all three were the same weight,
   and gave a one-line rule the same box as a four-item list, which left the rule
   floating in empty card and the list cramped.

   These classes are this section's own rather than the .clo-syl-point cards
   below — those still serve the Costs section on this page and the syllabus on
   /format-of-question, where three genuinely equal things ARE the content.
   ========================================================================== */

/* The gate. A rose edge down the left rather than a border all round: it is a
   called-out passage, not a card, and a full border would put it back in the
   family this section was taken out of. */
.clo-gate {
    display: grid;
    /* mark | sentence | figure. The sentence takes what is left, so the figure
       sits hard against the right edge whatever length the sentence runs to. */
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: clamp(16px, 2vw, 26px);
    /* FULL WIDTH, so the right edge lines up with the Objectives/Advantages pair
       below — and the figure is what earns that width.
       ------------------------------------------------------------------------
       Three versions of this panel are worth naming, because the first two each
       fixed the other one's fault:

         full width, no figure  — the right edge lined up exactly, but the 155-
                                  character sentence holds a 62ch measure and
                                  filled only half the box. A third of the panel
                                  was empty. While the fill faded out to nothing
                                  you could not see it; giving the box a visible
                                  edge made it obvious.
         width: fit-content     — no dead space, and the alignment went with it.
                                  The right edge landed in the middle of the
                                  Advantages column, on no grid line at all,
                                  which read as broken rather than as deliberate.

       The figure resolves both: the width is kept, so the panel still squares
       with the lists, and the space it buys is carrying the one number the
       sentence exists to state. */
    margin: clamp(20px, 3vw, 30px) 0 clamp(26px, 3.4vw, 40px);
    padding: clamp(18px, 2.4vw, 26px) clamp(20px, 2.6vw, 30px);
    border-left: 4px solid var(--brand);
    /* A hairline on the other three sides, and a tint that stays a tint across
       the panel. The fill used to end on rgba(253, 251, 250, .55), within a
       couple of points of the page's own lilac, so the block had no readable
       right edge at all. */
    border-top: 1px solid rgb(var(--brand-rgb) / .20);
    border-right: 1px solid rgb(var(--brand-rgb) / .20);
    border-bottom: 1px solid rgb(var(--brand-rgb) / .20);
    border-radius: 0 16px 16px 0;
    background: linear-gradient(100deg, rgb(var(--brand-rgb) / .13), rgb(var(--brand-rgb) / .045));
}
/* No percentage in the sentence means no figure, and a full-width panel with
   nothing on its right is the empty box described above — so in that case it
   hugs the sentence instead. */
.clo-gate--nofigure {
    grid-template-columns: auto minmax(0, 1fr);
    width: fit-content;
    max-width: 100%;
}
/* The figure. Display face, ranged right, with the qualifier under it small — the
   same division the Stages count chips make: the number for the eye, the words
   beside it for the meaning. */
.clo-gate-figure {
    flex: none;
    margin: 0;
    max-width: none;
    text-align: right;
    line-height: 1;
    padding-left: clamp(10px, 1.6vw, 20px);
    border-left: 1px solid rgb(var(--brand-rgb) / .22);
}
.clo-gate-figure b {
    display: block;
    font-family: var(--font-display);
    font-size: clamp(2rem, 3.6vw, 3.1rem);
    font-weight: 800;
    letter-spacing: -.02em;
    color: var(--accent);
}
.clo-gate-figure span {
    display: block;
    margin-top: 6px;
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--text-soft);
}
@media (max-width: 760px) {
    /* The figure drops under the sentence rather than squeezing it: below this
       the three columns leave the sentence about 22 characters a line. */
    .clo-gate { grid-template-columns: auto minmax(0, 1fr); }
    .clo-gate-figure {
        grid-column: 2;
        text-align: left;
        padding-left: 0;
        border-left: 0;
        margin-top: 14px;
        padding-top: 14px;
        border-top: 1px solid rgb(var(--brand-rgb) / .22);
    }
}
.clo-gate-mark {
    display: grid;
    place-items: center;
    width: 54px;
    height: 54px;
    border-radius: 16px;
    color: #fff;
    background: linear-gradient(140deg, var(--brand-2), var(--brand-3));
    box-shadow: 0 8px 20px rgba(122, 22, 34, .28);
}
/* Matches .clo-spec-label — see the note on it. */
.clo-gate-label {
    margin: 0 0 6px;
    font-size: .9rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--accent);
}
/* The largest body type in the section, because this is the sentence the whole
   section is gating on. */
.clo-gate-text {
    margin: 0;
    max-width: 62ch;
    font-size: clamp(1.02rem, 1.2vw, 1.12rem);
    line-height: 1.6;
    color: var(--text);
    font-weight: 600;
}

/* ---- the pair ------------------------------------------------------------
   Two halves of one answer, divided by a hairline rather than by two card
   edges. */
.clo-pair {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(24px, 3.4vw, 46px);
}
.clo-pair-col + .clo-pair-col {
    padding-left: clamp(24px, 3.4vw, 46px);
    border-left: 1px solid var(--border);
    /* the gap is already between them, so the divider needs the padding back
       rather than a second gap's worth of space */
    margin-left: calc(clamp(24px, 3.4vw, 46px) * -1);
}
.clo-pair-title {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0 0 14px;
    font-size: 1.02rem;
    color: #231f2e;
}
.clo-pair-title > svg { color: var(--accent); flex: none; }

@media (max-width: 760px) {
    /* Stacked, the divider becomes a rule above the second list — a left border
       on a full-width column is a stripe down the page. */
    .clo-pair { grid-template-columns: minmax(0, 1fr); gap: clamp(20px, 3vw, 26px); }
    .clo-pair-col + .clo-pair-col {
        padding: clamp(20px, 3vw, 26px) 0 0;
        margin-left: 0;
        border-left: 0;
        border-top: 1px solid var(--border);
    }
}

/* ---- About: the next international contest ---------------------------------
   The one line in the hero with a shelf life, and the reason the rest of the
   page exists: the contest this whole selection route ends at.

   A RULED BLOCK, not a chip. It was a bordered pill first, which was the right
   answer while the hero was the small centred .page-hero — a pill reads as a
   fixture, and centred under a centred title it had nowhere else to go. In a
   full-width hero it was wrong twice: a lozenge floating under a 3.6rem heading
   has no edge to line up with, and the pill's own border competed with the
   heading for the only strong line in the block.

   The rule does the work instead. It is the marginal rule of the ruled bands
   behind it turned upright, it aligns the label and the date to the same left
   edge the heading and the motto already sit on, and it is upstream's own
   arrangement for this block.

   Two lines, always: the label is a caption on the date, not a prefix to it.
   Side by side they run to 46 characters, which wrapped mid-city on a phone. */
.clo-next {
    margin: clamp(18px, 2.4vw, 26px) 0 0;
    padding: 4px 0 4px clamp(14px, 1.6vw, 20px);
    border-left: 3px solid var(--brand);
    max-width: none;
}
.clo-next-label {
    display: block;
    margin-bottom: 4px;
    font-size: .74rem;
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--text-soft);
}
.clo-next-value {
    display: block;
    font-family: var(--font-display);
    font-weight: 800;
    font-size: clamp(1.08rem, 1.7vw, 1.35rem);
    letter-spacing: .01em;
    line-height: 1.3;
    color: var(--accent);
}

@media (max-width: 900px) {
    /* The hero centres under 900px, and a left rule on a centred block is a
       stripe down one side of it. It becomes a rule UNDER the pair instead —
       the same mark, turned back the way the bands behind it run. */
    .clo-next {
        display: inline-block;
        margin-inline: auto;
        padding: 0 0 10px;
        border-left: 0;
        border-bottom: 3px solid var(--brand);
        text-align: center;
    }
}

/* ---- About: who can participate, as three rows -----------------------------
   This panel used to hold one sentence and a figure parsed out of it, because
   upstream answered "who can participate?" with the MyCLO cut-off alone. The
   live page now answers it three times — once per round — and the three
   answers are not variations on one rule: the first round is open to every
   school child in the country, the second is a top-10% gate, and the national
   team is bounded by the IOL's own regulations rather than by any result.

   So the figure goes. A single "10%" set large beside three rows would have
   been pointing at the middle one, which is the one a reader is LEAST likely to
   be measuring themselves against on a first visit.

   Everything else about the panel stays — the rose left edge, the mark, the
   fill — because it is still the passage on this page a reader has to check
   against themselves before the rest of it matters. .clo-gate is kept as the
   base class deliberately: the warm theme restyles .clo-gate and
   .clo-gate-mark, and a new class here would have dropped this panel out of
   that theme without anybody noticing until they switched to it. */
/* One column, not two. .clo-gate's own template is `mark | sentence | figure`,
   and both the panel-wide mark and the figure are gone from this variant: the
   mark moved into each row's own label, and the figure went with the single
   sentence it was parsed out of. */
.clo-gate--rows {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
}
.clo-who {
    margin: 0;
    display: grid;
    gap: clamp(14px, 1.8vw, 18px);
}
/* A hairline between rows rather than three boxes: they are three answers to
   one question, and boxing them would put this panel back in the family of
   equal cards the section was taken out of. */
.clo-who-row + .clo-who-row {
    padding-top: clamp(14px, 1.8vw, 18px);
    border-top: 1px solid rgb(var(--brand-rgb) / .18);
}
/* Matches .clo-gate-label, which is what this replaces — the round name is the
   label on its answer. */
/* The mark and the round's name read as one label, so they share a line and the
   sentence below is indented past the mark to sit under the WORDS rather than
   under the icon. Two custom properties rather than two magic numbers, because
   the indent has to stay equal to the mark plus its gap or the sentence drifts
   away from the label it belongs to. */
.clo-who-row {
    --who-mark: 38px;
    --who-gap: 13px;
}
.clo-who-round {
    display: flex;
    align-items: center;
    gap: var(--who-gap);
    margin: 0 0 7px;
    font-size: .86rem;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--accent);
}
.clo-who-text {
    margin: 0 0 0 calc(var(--who-mark) + var(--who-gap));
    max-width: 62ch;
    font-size: clamp(1rem, 1.1vw, 1.06rem);
    line-height: 1.6;
    color: var(--text);
    font-weight: 600;
}

/* The per-row mark. .clo-gate-mark is kept as the base class rather than a new
   name of its own: the warm theme restyles .clo-gate-mark — squaring it and
   giving it the plate's cast shadow — and a fresh class would have dropped these
   three out of that theme without anything failing. Only the size changes. */
.clo-gate-mark--sm {
    width: var(--who-mark);
    height: var(--who-mark);
    border-radius: 12px;
    box-shadow: 0 6px 14px rgba(122, 22, 34, .24);
}

@media (max-width: 560px) {
    /* The indent costs 51px of a 320px row — about six characters a line on the
       longest of the three sentences. The label above it is already flush left
       and the rule between rows carries the grouping, so the sentence goes back
       to the panel's own edge. */
    .clo-who-text { margin-left: 0; }
}

/* ---- About: the IOL 2027 cycle, as a table ---------------------------------
   Six rows, three columns, and four of the six dates are "To be announced" —
   which is exactly why this is a table and not the date rail the home page
   uses. A rail draws a sequence advancing toward a day; this sequence has two
   fixed points and four unknowns, and a rail would have had to draw the
   unknowns as though they were scheduled.

   Set on a white plate on the band's tint, so it reads as a published schedule
   rather than as part of the page's prose.

   overflow-x on the wrapper, not the table: three columns of real text do not
   fit 360px at a readable size, and a horizontal scroll on one element is a
   better answer than shrinking the type or truncating the Format column. */
.clo-table-wrap {
    margin-top: clamp(20px, 3vw, 28px);
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: 16px;
    background-color: var(--surface);
    box-shadow: 0 8px 22px rgba(35, 31, 46, .07);
    scrollbar-width: thin;

    /* A SHADOW ON THE EDGE THAT STILL HAS TABLE BEHIND IT.
       -----------------------------------------------------------------------
       On a phone the Format column is off-screen, and an overflow box gives no
       sign of that: the first capture of this page showed "Online · 1 hour 30
       minut" cut at the plate's edge and nothing to say the rest was reachable.

       Four layers, the standard scroll-shadow trick and no script. The two
       `local` layers are painted in the plate's own colour and scroll WITH the
       content, so they sit over the shadow and hide it; the two `scroll` layers
       are fixed to the box. At the left end the local cover is exactly over the
       left shadow, so only the right one shows — and it disappears as the
       content reaches each end. A table that fits shows neither. */
    background-image:
        linear-gradient(to right, var(--surface), rgba(255, 255, 255, 0)),
        linear-gradient(to left, var(--surface), rgba(255, 255, 255, 0)),
        linear-gradient(to right, rgba(46, 41, 60, .17), rgba(46, 41, 60, 0)),
        linear-gradient(to left, rgba(46, 41, 60, .17), rgba(46, 41, 60, 0));
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 30px 100%, 30px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
}
.clo-table {
    width: 100%;
    min-width: 640px;
    border-collapse: collapse;
    font-size: .96rem;
}
.clo-table th,
.clo-table td {
    padding: 13px clamp(14px, 1.6vw, 20px);
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border);
}
.clo-table thead th {
    font-family: var(--font-display);
    font-size: .74rem;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--accent);
    background: rgb(var(--brand-rgb) / .06);
    border-bottom-color: rgb(var(--brand-rgb) / .2);
    white-space: nowrap;
}
/* The stage is the row's name, so it is set as its heading rather than as the
   first of three equal cells. */
.clo-table tbody th {
    font-weight: 700;
    color: var(--text);
}
.clo-table td { color: var(--text-soft); }
/* The date column is what the table is read for. Tabular figures so the six
   dates line up down the column rather than drifting with their digit widths. */
.clo-table .clo-table-when {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--text);
    white-space: nowrap;
}
/* "To be announced" is not a date and should not be set like one — it is the
   absence of one, and four of the six rows say it. Lighter, and not bold, so
   the two rows that DO carry a date are what the eye lands on. */
.clo-table .clo-table-when.is-tba {
    font-weight: 500;
    font-style: italic;
    color: var(--text-soft);
}
.clo-table tbody tr:last-child :is(th, td) { border-bottom: 0; }

/* ---- THE LAST ROW IS THE CONTEST ITSELF ----------------------------------
   IOL 2027 in Bangkok: the one row the other five lead to, and the only one on
   the page carrying a date somebody would put in a diary.

   It had a 5%-alpha brand tint, which was a whisper — at that strength it read
   as banding in the table rather than as emphasis, and on the cooler ground it
   all but vanished. It is gold now, and strongly enough to be seen.

   GOLD, NOT THE PAGE'S OWN COLOUR, and for the reason the route's last key is
   gold: the brand hue is what the whole table is already drawn in, so
   brand-on-brand would mark the row without distinguishing it. Gold is this
   page's second accent and it means one thing on it — the IOL, the thing the
   route ends at. The stage 05 chip above and this row are the same fact stated
   twice, so they are stated in the same ink.

   THE LEFT EDGE IS AN INSET SHADOW, not a border. The table is
   border-collapse: collapse, where a border on one cell negotiates with its
   neighbours and with the wrapper's rounded corner; an inset shadow is drawn
   inside the cell's own box and does neither. */
.clo-table tbody tr:last-child {
    background: linear-gradient(90deg, rgb(241 196 106 / .42) 0%, rgb(241 196 106 / .18) 100%);
}
.clo-table tbody tr:last-child th {
    box-shadow: inset 4px 0 0 var(--gold);
}
/* The plum, not the brand ink: on a gold wash the brand hue is the one thing
   that does not separate from it. Plum is 11:1 here. */
.clo-table tbody tr:last-child :is(th, .clo-table-when) {
    color: #2e293c;
    font-weight: 800;
}
.clo-table tbody tr:last-child td:last-child { color: #4a4234; }

/* ---- the same six rows, stacked ----------------------------------------
   Below 700px the three columns do not fit, and the scroll box they were in
   was the wrong answer: the first phone capture of this page cut "Online · 1
   hour 30 minut" at the plate's edge, and a reader with no reason to think
   there was a third column never saw the Format of a single stage. A scroll
   shadow made it discoverable; discoverable is not the same as read.

   So the table becomes six cards, one per stage, and the header row is
   replaced by a label in front of each value. The labels are printed from
   data-label through ::before — decoration, not content — and <thead> is
   hidden with clip-path rather than display:none, so the real
   <th scope="col"> association survives for a screen reader, which is
   navigating the table rather than looking at it. */
@media (max-width: 700px) {
    /* The wrapper stops being a plate: each row is its own now, and a plate
       around six plates is an edge that means nothing. */
    .clo-table-wrap {
        overflow-x: visible;
        border: 0;
        background: none;
        box-shadow: none;
    }
    .clo-table { min-width: 0; }
    .clo-table, .clo-table tbody, .clo-table tr, .clo-table th, .clo-table td { display: block; }
    .clo-table thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
    }
    .clo-table tbody tr {
        padding: 15px 17px;
        border: 1px solid var(--border);
        border-radius: var(--plate-r, 14px);
        background: var(--surface);
    }
    .clo-table tbody tr + tr { margin-top: 12px; }
    .clo-table tbody :is(th, td) { padding: 0; border: 0; }
    .clo-table tbody th { font-size: 1.02rem; margin-bottom: 8px; }
    .clo-table tbody td + td { margin-top: 5px; }
    /* Wrapping, not nowrap: at this width a date and its label share a line
       with the value and both have to be able to break. */
    .clo-table .clo-table-when { white-space: normal; }
    .clo-table tbody td::before {
        content: attr(data-label) ' · ';
        font-weight: 800;
        font-style: normal;
        letter-spacing: .06em;
        text-transform: uppercase;
        font-size: .7rem;
        color: var(--text-soft);
    }
    /* The contest itself keeps its gold on a phone too — the row is now a card,
       so the wash and the left edge move onto it. The edge goes back to a real
       border here: outside a collapsed table there is nothing for it to
       negotiate with, and the <th> it was drawn on is no longer a first column. */
    .clo-table tbody tr:last-child {
        border-color: var(--gold);
        border-left-width: 4px;
        background: linear-gradient(180deg, rgb(241 196 106 / .34) 0%, rgb(241 196 106 / .14) 100%);
    }
    .clo-table tbody tr:last-child th { box-shadow: none; }
}

/* A footnote under a block, not a paragraph of the page: the key-dates table,
   the selection criteria and the costs pair each close on one.

   NOT .clo-note, which is taken. That class is the INSTRUCTION strip on the
   results and study-guide pages — a bordered, tinted panel with a rose left edge
   for the one line on a page that tells the reader what to do. These three are
   the opposite: quiet asides under a block that has already said its piece, and
   borrowing that class drew a call-out box around "This page is updated as
   further details are confirmed." */
.clo-footnote {
    margin: clamp(12px, 1.6vw, 16px) 0 0;
    max-width: 68ch;
    font-size: .9rem;
    line-height: 1.6;
    color: var(--text-soft);
}
/* The centred variant, for a footnote closing a pair of cards rather than a
   single block. Both the measure and the lines are centred here — unlike the
   selection-criteria card above, where the lines stay ranged left because four
   ticks need one column to run down. Three lines of closing prose under a
   symmetrical pair have no such column, and centring them is what stops the note
   hanging off the left-hand card. */
.clo-footnote--center {
    margin-inline: auto;
    text-align: center;
}

/* Underlined, not just coloured. A footnote is set in the muted ink, and at that
   size a link marked by colour alone was reading as emphasis — which is exactly
   what the one link in here is not: it is the way to the fees. */
.clo-footnote a {
    color: var(--accent);
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* ---- About: Malaysia at the IOL, as three accordion rows -------------------
   Three write-ups fully expanded run about 3,500px — a third of this page — and
   a reader who came for the current cycle had to scroll two years of history to
   reach the Contact block. Closed, the past years are a line each, and the whole
   section fits one screen with its newest entry already open. Upstream does the
   same, which is where the shape comes from.

   Nothing is hidden from anything but the eye: <details> keeps every sentence in
   the document, so the content audit reads it and a search engine indexes it.

   Not .faq-item, which is this site's other accordion. That one belongs to the
   FAQ page — dark glass, a numbered chip per row, sixteen of them in two columns
   — and three plates on the warm ground is a different object. */
.iol-years {
    display: grid;
    gap: clamp(12px, 1.6vw, 16px);
    margin-top: clamp(20px, 3vw, 28px);
}
.iol-year {
    border: 1px solid var(--border);
    border-radius: 16px;
    background: var(--surface);
    box-shadow: 0 8px 22px rgba(35, 31, 46, .07);
    /* A closed row is a plate with a heading on it; an open one is a panel. The
       background shifts a shade so the two states are told apart by more than
       the mark on the right. */
    transition: box-shadow .25s, border-color .25s;
}
.iol-year:hover { border-color: rgb(var(--brand-rgb) / .28); }
.iol-year[open] {
    border-color: rgb(var(--brand-rgb) / .22);
    box-shadow: 0 16px 38px rgba(35, 31, 46, .1);
}
.iol-year > summary {
    display: flex;
    align-items: center;
    gap: clamp(12px, 1.6vw, 18px);
    padding: clamp(15px, 1.9vw, 20px) clamp(18px, 2.2vw, 26px);
    cursor: pointer;
    /* Both are needed: Firefox draws the triangle through list-style, WebKit
       through its own pseudo-element. */
    list-style: none;
    border-radius: inherit;
}
.iol-year > summary::-webkit-details-marker { display: none; }
/* The row is a click target the width of the panel, so it needs a focus ring of
   its own — the default outline on a flex summary lands on the text alone. */
.iol-year > summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -3px;
}

/* The heading inside the summary — <x-clo-year-head compact>. It is an <h2>, so
   it has to lose the block margins and the display size it carries elsewhere or
   the row grows to heading height and the mark stops sitting centred. */
.iol-year-line {
    margin: 0;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 10px;
    font-family: var(--font-display);
    font-size: clamp(1.02rem, 1.5vw, 1.2rem);
    font-weight: 700;
    line-height: 1.3;
    color: #231f2e;
}
/* The year keeps the gradient it has in the full heading, so a closed row and an
   open one name the same thing the same way. */
.iol-year-name .grad {
    background: var(--grad-brand);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
/* The separator between the year and its host city. It was set in the border
   ink to sit back from both — and at 28% alpha on white that is 1.75:1, which
   docs/contrast-runtime.php failed it for, rightly: it is a visible glyph on the
   page, not a rule drawn in CSS, so it is held to the same 4.5:1 as the words
   either side of it. The same ink as the place it separates, lightened only by
   staying un-bold. */
.iol-year-dash { color: var(--text-soft); font-weight: 400; }
.iol-year-place { font-weight: 600; color: var(--text-soft); }
.iol-year-line .clo-place-flag {
    width: 26px;
    height: auto;
    border-radius: 3px;
    box-shadow: 0 1px 3px rgba(35, 31, 46, .28);
}

/* The +/− mark. Two bars, and the upright one is scaled away when the row opens,
   so the plus turns into a minus rather than swapping one glyph for another —
   a font-dependent character would be the one piece of this row that does not
   match the drawn icons everywhere else on the page. */
.iol-year-mark {
    position: relative;
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    margin-left: auto;
}
.iol-year-mark::before,
.iol-year-mark::after {
    content: '';
    position: absolute;
    inset: 50% 0 auto;
    height: 2.5px;
    border-radius: 2px;
    background: var(--accent);
    transform: translateY(-50%);
    transition: transform .25s ease, opacity .2s ease;
}
.iol-year-mark::after { transform: translateY(-50%) rotate(90deg); }
.iol-year[open] .iol-year-mark::after { transform: translateY(-50%) rotate(90deg) scaleX(0); }

.iol-year-body {
    padding: 0 clamp(18px, 2.2vw, 26px) clamp(20px, 2.4vw, 28px);
}
/* A hairline between the row and what it opens onto, set inside the padding so
   it spans the panel rather than the text. */
.iol-year-body::before {
    content: '';
    display: block;
    height: 1px;
    margin-bottom: clamp(16px, 2vw, 22px);
    background: var(--border);
}
.iol-year-body > .prose > :first-child { margin-top: 0; }

@media (prefers-reduced-motion: reduce) {
    .iol-year,
    .iol-year-mark::before,
    .iol-year-mark::after { transition: none; }
}

@media (max-width: 560px) {
    /* The place drops under the year rather than squeezing it: "IOL 2025 —
       Taipei, Taiwan" is 24 characters and the mark takes 26px of a 320px row.

       THE BREAK IS FORCED ON THE YEAR, not on the place. Giving the place
       flex-basis:100% was the obvious way round and it cost a third line: the
       place then filled its row, so the flag beside it wrapped again and a
       closed row ran year / place / flag down the panel. With the year taking
       the full first line, the place and its flag flow together on the second,
       which is the pair they are. */
    .iol-year-line { gap: 2px 8px; }
    .iol-year-dash { display: none; }
    .iol-year-name { flex-basis: 100%; }
    .iol-year-place { font-size: .9rem; }
}

/* ---- About: the contact sign-off -------------------------------------------
   Three short paragraphs on open band ground under a centred heading, with no
   panel to give them an edge. At the site's 66ch prose measure and ranged left
   they ended halfway across a full-width section with nothing on the right,
   which read as a column that had lost its neighbour. Centred under their own
   heading they read as what they are: the sign-off at the foot of a long page. */
.clo-contact {
    max-width: 62ch;
    margin-inline: auto;
    text-align: center;
}
.clo-contact p { max-width: none; }

/* ---- About: how the team is selected ---------------------------------------
   The four thresholds, above the two cost cards they share a section with.
   Ticks rather than a numbered list: these are conditions, all four of which
   hold at once, where the five stages above them are a sequence. Numbering them
   would have made the page state the same route twice in two different shapes.

   THE GROUP IS CENTRED, THE LINES ARE NOT. This note used to say the block was
   "left where it is rather than centred, so the list sits under the section
   heading as prose does" — and on the page that was simply wrong to look at: the
   section heading is centred like every other on the site, and the sub-heading,
   the four criteria and the footnote were each ranged left across the section's
   full width. They ended hard against the left edge with the right half of the
   band empty and a centred heading floating over nothing.

   So the three of them share one wrapper, capped at the reading measure, and the
   CAP is centred. The lines inside stay left-aligned: four criteria set
   centre-aligned would give the list four different left edges and no column for
   the eye to run down.

   The cap lives on the wrapper rather than on each child, which is what puts the
   sub-heading, the ticks and the footnote on one shared left edge instead of
   three — they had three different max-widths before.

   The cost cards below are deliberately NOT in this wrapper: they are a pair
   spanning the section, and pulling them into a 72ch measure would strand them
   in the middle of it. */
/* ON A CARD, like everything else in this section.

   The four criteria sat as bare text on the band while the two cost answers
   below them sat on white plates — so the section had one thing floating on the
   tint and two things on paper, which read as the criteria having been forgotten
   rather than as a deliberate difference.

   The measure is stated as 72ch + the padding rather than 72ch flat: box-sizing
   is border-box everywhere on this site, so a bare 72ch cap would have taken the
   padding OUT of the reading measure and set the criteria narrower than the
   prose they restate. */
.clo-criteria-block {
    max-width: calc(72ch + 2 * clamp(22px, 2.8vw, 34px));
    margin-inline: auto;
    padding: clamp(20px, 2.6vw, 30px) clamp(22px, 2.8vw, 34px);
    border: 1px solid var(--border);
    border-radius: 16px;
    background: var(--surface);
    box-shadow: 0 8px 22px rgba(35, 31, 46, .07);
}
/* The sub-heading is the card's first line, so it loses the space it was given
   for sitting on open ground. */
.clo-criteria-block > :first-child { margin-top: 0; }
.clo-criteria {
    max-width: none;
    margin-top: clamp(16px, 2.2vw, 22px);
}
.clo-criteria li { font-size: 1.01rem; line-height: 1.6; color: var(--text-soft); }
/* The threshold is the point of each line — the phrase upstream bolds — and it
   takes the rose, the same ink the payable cost card uses for the phrase that
   answers it. */
.clo-criteria strong { font-weight: 800; color: var(--brand-ink); }
.clo-criteria-title {
    margin: 0;
    font-size: clamp(1.05rem, 1.6vw, 1.22rem);
    color: #231f2e;
}

/* ---- Syllabus: THE SPECIMEN ------------------------------------------------
   Four statements about the paper, beside a drawing of one.

   The four lines are not topics — they are claims, and three of the four are
   reassurances: everything you need is in the question, you do not have to be
   multilingual, no working is required in the first round. A claim like that is
   believed next to evidence, which is what the specimen is for.

   It also breaks a rhythm that had run too long. By the time a reader reaches
   this section they have passed the quick-facts strip, the objectives, the date
   rail, five plates and five spec cards — every one of them a grid of boxes.
   A fourth grid here would have been the point at which the page stops having
   sections and starts having a texture.

   The drawing comes SECOND in the source and first on screen. The statements are
   the content; a phone and a screen reader both get them before the picture.
   ========================================================================== */

/* ---- The spec as a GRID OF CARDS -------------------------------------------
   /format-of-question only. The home page keeps the sheet, and that is a
   deliberate split rather than an oversight: the quick-facts bar under the home
   hero already carries four of those five values as big figures, so drawing them
   again as large tiles made that page say the same thing twice in the same
   voice. /format-of-question has no such bar — the specs ARE the page — so
   there the drawing can lead.

   Three across, each spec centred under its own illustration: the mark first
   because it is what tells the six apart at a glance, then the label, then the
   value. No card chrome at all — no border, no fill, no shadow. Six framed tiles
   on an open page read as six buttons; unframed, the illustrations carry the
   grouping and the page stays quiet.

   The same <dl> underneath, so a screen reader still hears "Answering time,
   3 hours" as one pair. Only the presentation changes. */
.clo-spec--cards {
    max-width: 1080px;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(26px, 3.6vw, 52px) clamp(20px, 2.6vw, 34px);
    /* Every card the same height. The values run from two words to a sentence,
       so the natural rows came out at noticeably different heights and the two
       rows read as two separate things rather than as one grid of six. This
       matters more now the plate layer frames each card: an uneven row of boxes
       looks like a mistake where uneven unframed columns just looked airy.
       align-content:start below keeps the six drawings on one line regardless.

       Letting the cards size to their own content was tried (align-items:start)
       and reverted: it does empty the space under "Online", but six framed boxes
       ending at three different heights read as untidier than the space did.
       Equal heights are the deliberate choice, and the space inside a short card
       is the price of them.

       Two ways out were checked and neither works at this width. Centring the
       content inside the card drifts the drawings off a common line — the very
       thing align-content:start is here to prevent. Widening the long card (the
       is-wide treatment this file already carries for the non-cards layout)
       does not widen its text, because the card content is centred and capped,
       and the reflow drops that card to the end of the grid leaving a hole where
       it was. Shortening the Requirements sentence at /admin is the only thing
       that would actually close the gap. */
    grid-auto-rows: 1fr;
}
.clo-spec--cards .clo-spec-item {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    text-align: center;
    /* Pack to the TOP of the row. The items stretch to the tallest in their row
       (Necessity runs to four lines), and without this the three implicit rows
       inside each item stretch with it — so the drawing and the label of a short
       spec drifted to the middle of the box while a tall one stayed at the top,
       and the six read as scattered rather than as a grid. */
    align-content: start;
    gap: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}
.clo-spec--cards .clo-spec-item:hover { transform: none; box-shadow: none; }
/* The plate under the drawing goes too: at this size the illustration is the
   thing, and a tinted chip behind it only crops it into a badge. */
.clo-spec--cards .clo-spec-ico {
    grid-row: auto;
    width: auto;
    height: auto;
    margin-bottom: clamp(12px, 1.6vw, 18px);
    border: 0;
    border-radius: 0;
    background: none;
}
.clo-spec--cards .clo-spec-label {
    align-self: auto;
    margin-bottom: 8px;
    font-size: clamp(1.05rem, 1.5vw, 1.3rem);
    font-weight: 800;
    /* Tracking comes off as the type goes up: .13em is right on an 11px label
       and reads as a gap at 20px. */
    letter-spacing: .01em;
    text-transform: uppercase;
    color: #231f2e;
}
.clo-spec--cards .clo-spec-value {
    align-self: auto;
    max-width: 34ch;
    font-size: .98rem;
    line-height: 1.55;
    color: var(--text-soft);
}
@media (max-width: 900px) {
    .clo-spec--cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
    /* One across rather than two: at this width a two-column value wraps to five
       lines and the six read as a wall. */
    .clo-spec--cards {
        grid-template-columns: minmax(0, 1fr);
        gap: clamp(22px, 6vw, 30px);
        /* and the equal-height rule comes OFF. With one card per row it stops
           levelling a row and starts making all six as tall as the longest, so
           five cards would carry a screenful of empty space to keep Necessity
           company. Levelling only means anything when a row has more than one. */
        grid-auto-rows: auto;
    }
}


/* ---- Study guide: THE THREE DOORS ------------------------------------------
   Three tiles, each a real link to a document hosted off-site. Drawing, title,
   action — the same reading order as the format grid on /format-of-question,
   so the two pages that hand a reader a set of things feel like one site.

   These were rows first, on the reasoning that titles told apart by their words
   want the full width. That is still true of the words — the longest title wraps
   to two lines in a tile — and the tiles win anyway: three equal doors with a
   readable drawing on each is the shape a reader expects when the job is "pick
   one and download it". The note in study-guide.blade.php carries the trade.

   Card chrome HERE, unlike the format grid, and the difference is the point: a
   format spec is a fact to read, and these are things to click. The border and
   the lift say so, and the whole tile is the hit area. */
.guide-tiles {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(14px, 1.8vw, 22px);
    margin-top: clamp(6px, 1vw, 12px);
}
.guide-tile {
    display: grid;
    justify-items: center;
    /* Packed to the top so the drawings sit on one line across the row even when
       one title wraps and its neighbours do not — the same fault the format grid
       had before align-content was set on it. */
    align-content: start;
    text-align: center;
    gap: 4px;
    padding: clamp(22px, 2.8vw, 32px) clamp(16px, 2vw, 22px);
    border: 1px solid var(--border);
    border-radius: 18px;
    background: var(--surface);
    box-shadow: 0 6px 18px rgba(35, 31, 46, .06);
    text-decoration: none;
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.guide-tile:hover,
.guide-tile:focus-visible {
    transform: translateY(-3px);
    border-color: rgb(var(--brand-rgb) / .45);
    box-shadow: 0 18px 38px rgba(35, 31, 46, .13);
}
.guide-tile-art {
    display: grid;
    place-items: center;
    margin-bottom: clamp(10px, 1.4vw, 16px);
}
.guide-tile-title {
    font-family: var(--font-display);
    font-size: clamp(1.05rem, 1.5vw, 1.28rem);
    font-weight: 800;
    line-height: 1.25;
    color: #231f2e;
}
.guide-tile-note {
    font-size: .93rem;
    color: var(--text-soft);
}
@media (max-width: 820px) {
    .guide-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
    .guide-tiles { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
    .guide-tile { transition: none; }
    .guide-tile:hover, .guide-tile:focus-visible { transform: none; }
}

.clo-specimen {
    display: grid;
    /* The art column is fixed rather than fractional. It is a drawing at a fixed
       aspect ratio, and giving it a fraction of the row means it grows on a wide
       monitor into the largest thing on the page — which it is not. */
    grid-template-columns: minmax(0, 1fr) 340px;
    align-items: center;
    gap: clamp(28px, 4vw, 56px);
    margin-top: clamp(20px, 3vw, 30px);
}
/* Second in the source, first on screen. */
.clo-specimen-art { grid-column: 2; grid-row: 1; }
.clo-rules { grid-column: 1; grid-row: 1; }

@media (min-width: 981px) {
    /* The drawing to the LEFT of the copy on a wide screen: it is the thing the
       four statements are about, and English reads the subject before the
       comment on it. */
    .clo-specimen-art { grid-column: 1; }
    .clo-rules { grid-column: 2; }
    .clo-specimen { grid-template-columns: 340px minmax(0, 1fr); }
}

.clo-specimen-svg {
    display: block;
    width: 100%;
    height: auto;
    /* the leaf sits at a slight angle, the way a sheet lies on a desk — the one
       thing that stops a rectangle of ruled lines reading as a screenshot */
    transform: rotate(-1.6deg);
    filter: drop-shadow(0 18px 34px rgba(35, 31, 46, .16));
}

/* ---- the four statements -------------------------------------------------
   No boxes. The section's whole argument is that this is not a syllabus of
   topics, and four bordered cards would say the opposite before a word of it was
   read. A mark, a sentence, and a hairline between. */
.clo-rules { list-style: none; margin: 0; padding: 0; display: grid; }
/* These were rows divided by a rule, which is why the padding was vertical only
   and why the first and last of them dropped the padding on the outside edge —
   there was no box there to sit inside of. clo-plate.css then gave .clo-rule a
   border, a white fill and a lift, making each one a card, and the old padding
   came with it: the text ran to the card's left and right edges, and the first
   and last cards had their text against the top and bottom edge as well.

   So: padding on all four sides, and the two edge exceptions are gone. They
   belonged to the divider layout and there is no divider layout any more —
   data-theme="warm" is unconditional on this site, so the plate look is the
   only look. */
.clo-rule {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr);
    align-items: start;
    gap: 16px;
    padding: clamp(16px, 2vw, 20px) clamp(18px, 2.2vw, 22px);
    border-top: 1px solid var(--border);
}
.clo-rule:first-child { border-top: 0; }

.clo-rule-ico {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: 13px;
    color: var(--accent);
    background: linear-gradient(140deg, rgb(var(--brand-rgb) / .2), rgba(253, 251, 250, .7));
    border: 1px solid rgba(46, 41, 60, .13);
}
.clo-rule p {
    margin: 0;
    max-width: none;
    align-self: center;
    color: var(--text-soft);
    font-size: 1.02rem;
    line-height: 1.6;
}

/* Below 980px the drawing goes over the copy and comes in off its fixed column,
   capped so it does not become the section. */
@media (max-width: 980px) {
    .clo-specimen { grid-template-columns: minmax(0, 1fr); }
    .clo-specimen-art { grid-column: 1; grid-row: 1; justify-self: center; width: min(300px, 78vw); }
    .clo-rules { grid-column: 1; grid-row: 2; }
}

/* ---- How to register, and the sample paper ---------------------------------
   Both sit inside the pricing panel, under the fee cards: the live page puts
   them there, and it is the right place — the question a teacher has after the
   price is "how", not "what next".

   Three columns per row so the button lines up down the block whatever length
   the sentence beside it runs to. Below 720px the button drops under its own
   sentence rather than squeezing the copy into a ribbon. */
.clo-howto { display: grid; gap: 12px; margin-top: clamp(20px, 3vw, 30px); }
.clo-howto-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 16px;
    padding: 14px 18px;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface);
}
.clo-howto-row p { margin: 0; max-width: none; }
.clo-howto-row .btn { flex: none; white-space: nowrap; }
@media (max-width: 720px) {
    .clo-howto-row { grid-template-columns: auto minmax(0, 1fr); }
    .clo-howto-row .btn { grid-column: 1 / -1; justify-self: start; }
}

/* The sample paper. Deliberately a ROW like the other three and not a shape of
   its own — that is the whole point of the block being one list — but it is the
   one thing here a visitor can take away and work through today, so it keeps a
   tint and a heavier border to say so.

   It went through two wrong versions before this. First a centred island under
   the block: heading, copy and button all on the middle axis, under three
   left-aligned rows, which made a reader reset their eye for one item. Then a
   left-aligned strip of its own, which fixed the axis but still left it a fourth
   kind of thing in a block that already had three.

   Emphasis by TINT alone, with the button on ghost like its neighbours. The
   earlier version put a second primary here, which set the page's actual goal
   ("Register Now") and an onward route in the same voice. The tint is enough:
   nothing else in the list is coloured, so it is still the row the eye lands on
   first. */
.clo-howto-row--feature {
    border-color: var(--border-strong);
    background: linear-gradient(135deg, rgb(var(--brand-rgb) / .13), rgba(241, 196, 106, .09));
}
/* The lead-in that replaced this row's heading. It is the only bold in the list,
   which is what names the row without giving it a second line of type. */
.clo-howto-row--feature p b { color: #231f2e; }

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

/* ---- 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. */
.clo-facts {
    position: relative;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    overflow: hidden;
}
.clo-facts::after {
    content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
    background: linear-gradient(135deg, var(--brand) 0%, #04788a 55%, #2e293c 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. */
.clo-fact { position: relative; text-align: center; padding: clamp(18px, 2.4vw, 26px) clamp(16px, 1.8vw, 28px); }
.clo-fact + .clo-fact::before {
    content: ""; position: absolute; left: 0; top: 22%; height: 56%; width: 1px;
    background: rgba(46, 41, 60, .16);
}
.clo-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: #2e293c;
}
/* Its own class rather than `.clo-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. */
.clo-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) {
    .clo-facts { grid-template-columns: repeat(2, 1fr); }
    /* with two per row, cells 1 and 3 start a row and must not carry a divider */
    .clo-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 `.clo-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. */
.clo-card-center { text-align: center; }
.clo-card-center p,
.clo-card-center .prose,
.clo-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. */
.clo-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. */
.clo-links { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.clo-links > :last-child:nth-child(odd) { grid-column: 1 / -1; justify-self: center; max-width: 520px; width: 100%; }
.clo-link { text-align: center; }
.clo-link h3 { margin: 0 0 14px; font-size: var(--fs-h4); color: var(--accent); }
@media (max-width: 700px) {
    .clo-links { grid-template-columns: minmax(0, 1fr); }
    .clo-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, var(--brand-deep), #2e293c);
    -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.clo-addr` on purpose: `.tile p` above is (0,1,1), so a bare
   `.clo-addr` at (0,1,0) would lose to it and keep inheriting `pretty`. */
.tile p.clo-addr { text-wrap: balance; }

/* ---- An ordered run of named things, set as a legend -----------------------
   Used by the five stages on /about-us.

   Three cards across with the last two centred left an orphan pair and told the
   reader nothing about order. A plotted route fixed the order but cost a screen
   and a half of height: alternating cards means every row carries an empty side,
   so half the section was blank by construction.

   A legend is what a chart uses when it has to explain five things in the least
   room possible — a key, one line per entry, symbols in a column down the left.
   It keeps the numbering, so the order still reads, and it has no facing side to
   leave empty. Two columns on a wide screen, one on a phone. About 340px tall
   against the route's 1250px, for the same five objectives and the same words.

   The rule between entries is a hairline, not a border: a legend's entries are
   separated, not boxed, and five boxes inside a panel that is itself a box was
   the visual noise the cards had in the first place. */

.clo-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    /* redesign.css caps `.gx-glass li` at the reading measure, which is right for
       a bullet of prose and wrong for a list item that is the layout. It is
       written in :where(), so it carries no specificity and nothing was
       overriding it. */
    max-width: none;
}
.clo-legend > li { max-width: none; break-inside: avoid; }
.clo-legend-entry {
    display: grid;
    /* must match .clo-legend-key's width, or the name beside it stops lining up */
    grid-template-columns: 40px minmax(0, 1fr);
    align-items: start;
    gap: 13px;
    padding: 14px 0;
    border-top: 1px solid rgba(46, 41, 60, .13);
}
/* A step up from 34px/.82rem. At that size the numbers were a caption on the
   stage rather than the thing carrying its order — and order is the whole point
   of this section, which is why it is an <ol> and not a list of cards. The disc
   grows with the figure so the digits keep their ring of space. */
.clo-legend-key {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: #fefdfc;
    border: 2px solid #2e293c;
    color: #2e293c;
    font-weight: 800;
    font-size: .95rem;
    /* tabular figures so 01 and 05 are the same width and the discs do not jitter */
    font-variant-numeric: tabular-nums;
}
.clo-legend-name {
    margin: 0 0 .15em;
    font-size: 1rem;
    line-height: 1.3;
    color: #231f2e;
}
.clo-legend-note {
    margin: 0;
    font-size: .92rem;
    line-height: 1.5;
    color: var(--text-soft);
}

/* THE LAST STAGE IS PICKED OUT, and this note used to say the opposite. Both
   decisions were right for the section as it stood at the time, so here is the
   change rather than a silent overwrite.

   The old reasoning: the last entry had been filled orange while this was a
   plotted route with a line running through it, where it read as the destination
   the line ended at; once the line went it read only as a rank, the source
   claimed none, and the emphasis was keyed to :last-child — position, not
   meaning — so a sixth entry would have moved it to whatever happened to end up
   last.

   What changed is the section. This was a flat list of five stages; upstream's
   IOL 2027 rewrite made it "The Selection Process", and a process is not a list
   of peers — it is a sequence with an end, and that end is the thing every stage
   before it exists to reach. The objection about :last-child falls with it: in a
   selection process the last stage IS the destination by definition, so position
   and meaning are the same fact here. Add a sixth stage and the emphasis
   correctly moves to it, because the route now ends there.

   The fill only. The stage's name and note stay as they are — picking out the
   whole row would make the other four read as preamble, and a reader still has
   to walk all five. */
.clo-legend > li:last-child .clo-legend-key {
    background: var(--grad-brand);
    border-color: transparent;
    color: #fff;
    box-shadow: 0 6px 16px rgba(122, 22, 34, .3);
}

@media (min-width: 820px) {
    /* Two COLUMNS, not a two-column grid. A grid makes both columns share a row
       height, so the tallest entry of each pair sets the height of the entry
       beside it and leaves a hole under the shorter one — with entries running
       from two lines to five, that hole was most of the saving. Column flow lets
       each entry take only the height it needs and fills down then across, so
       01–03 sit left and 04–05 right with nothing padded to match.

       Every entry keeps its top rule here, including the first of each column:
       with column flow there is no way to know which entry starts column two,
       and a rule across the top of both columns reads as the legend's own head
       rule rather than as one column being ruled and the other not. */
    .clo-legend {
        columns: 2;
        column-gap: clamp(24px, 3vw, 44px);
    }
}

/* ---- The twelve syllabus topics, set as an index ---------------------------
   See the note in about-us.blade.php for why this is not a grid of cards. The
   short version: four cards across gave each topic 215px, which turned a
   forty-word description into twelve lines and broke the topic names mid-word,
   and every grid row was as tall as its tallest card.

   Column flow rather than a two-column grid, for the same reason the Objectives
   legend uses it: a grid would make each pair of entries share a height, and
   these run from three lines to six. */

.clo-index {
    list-style: none;
    margin: 0;
    padding: 0;
    /* redesign.css caps `.gx-glass li` at the reading measure — right for a
       bullet of prose, wrong for a list item that is the layout, and written in
       :where() so it carries no specificity to notice. */
    max-width: none;
}
.clo-index > li { max-width: none; break-inside: avoid; }
.clo-index-entry {
    padding: 13px 0;
    border-top: 1px solid rgba(46, 41, 60, .13);
}
.clo-index-topic {
    margin: 0 0 .2em;
    font-size: .98rem;
    line-height: 1.3;
    color: #231f2e;
    /* redesign.css sets `overflow-wrap: anywhere` on every heading, which breaks
       INSIDE a word as soon as the word does not fit and also shrinks the
       heading's min-content — which is how "resource management" came to be set
       as "resource manageme / nt" in the old narrow cards. `break-word` breaks
       only when a word genuinely cannot fit and leaves min-content alone, the
       same correction clo-theme.css already makes for table cells. */
    overflow-wrap: break-word;
}
/* The number sits in the margin rather than in the text, so the topic names line
   up with each other instead of starting at four different x positions behind
   "1", "10", "11", "12". Padding on the heading makes room for it. */
.clo-index-topic { padding-left: 32px; }
.clo-index-num {
    display: inline-block;
    width: 32px;
    margin-left: -32px;
    color: #04788a;
    font-weight: 700;
    /* tabular figures so 01 and 12 occupy the same width */
    font-variant-numeric: tabular-nums;
}
.clo-index-note {
    margin: 0;
    padding-left: 32px;
    font-size: .9rem;
    line-height: 1.5;
    color: var(--text-soft);
}

@media (min-width: 820px) {
    .clo-index {
        columns: 2;
        column-gap: clamp(28px, 3.4vw, 48px);
    }
}

/* 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 (study guide, 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: var(--brand-deep); }
.link-row span { font-weight: 600; font-size: .95rem; }

/* ---- Past-year papers, one column per year --------------------------------
   The years come from the database, so auto-fit rather than a stated column
   count: two years give two columns, three give three, and a single year fills
   the panel rather than sitting in half of it with a hole alongside.

   320px is the floor because a paper title plus its icon needs about that
   much before the row starts wrapping; below it the columns stack.

   align-items: start so a year with three papers does not stretch the heading
   of the year beside it that has one. */
.clo-years {
    display: grid;
    gap: clamp(20px, 3vw, 36px);
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    align-items: start;
}
/* The year is a column head now, not a section heading with the panel to
   itself, so it drops from the h2 display size to something proportionate to
   the one or two rows beneath it. */
.clo-year-title {
    margin: 0 0 12px;
    font-size: clamp(1.35rem, 2.2vw, 1.7rem);
}
/* The paper itself, previewed in the browser's own PDF viewer.
   ---------------------------------------------------------------------------
   A4 by aspect-ratio rather than a fixed height, so the preview keeps the
   proportions of the thing it is previewing at any column width.

   Hidden below 820px, where the columns have stacked: a phone would be showing
   a page of A4 at ~340px wide — unreadable — and iOS Safari renders only the
   first page of a PDF in an iframe, often nothing at all. The download link
   underneath is the whole interaction there, which is what it was before. */
.paper { display: grid; gap: 10px; }
.paper + .paper { margin-top: 18px; }
.paper-preview {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1.414;
    border: 1px solid var(--border-strong);
    border-radius: 12px;
    background: #fff;
    box-shadow: var(--shadow-sm);
}
/* Shown by <object> only when the browser has no PDF viewer, so it is never on
   screen beside a working preview — it replaces it. */
.paper-fallback {
    margin: 0;
    display: grid;
    place-content: center;
    height: 100%;
    padding: 24px;
    text-align: center;
    font-size: .92rem;
    color: var(--text-soft);
}
@media (max-width: 819px) {
    .paper-preview { display: none; }
}

/* ---- Instruction note ------------------------------------------------------
   For the one line on a page that tells the reader what to DO, as opposed to
   what happened. On the results page that is "press CTRL + F and find your
   school name", which used to be the last paragraph of the announcement, in
   italics — where it read as prose trailing off rather than as the instruction
   for the buttons directly beneath it.

   Water blue rather than orange: orange is this brand's call-to-action, and the
   buttons under this note are the actual action. */
.clo-note {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin: 22px 0 0;
    padding: 13px 16px;
    border: 1px solid rgb(var(--brand-rgb) / .22);
    border-left: 4px solid var(--brand);
    border-radius: 12px;
    background: rgba(241, 196, 106, .13);
    color: #2e293c;
    font-size: .95rem;
    line-height: 1.55;
    /* the shared .gx-glass measure cap would hold this to 66ch inside a panel it
       is meant to span */
    max-width: none;
}
.clo-note .gx-ico { flex: 0 0 auto; color: var(--brand); margin-top: 2px; }

/* Centred wrap for a link list whose count does not divide by its column count.
   The default .link-list is an auto-fit grid, which is right when the rows come
   out full — four links in a three-wide row do not, and grid cannot centre a
   part-full last row. Flex can. Each link keeps a third of the row so the
   orphan matches the width of the three above it rather than stretching. */
.link-list--centred {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
}
.link-list--centred > .link-row { flex: 0 1 calc((100% - 2 * 10px) / 3); }
@media (max-width: 820px) {
    .link-list--centred > .link-row { flex-basis: calc((100% - 10px) / 2); }
}
@media (max-width: 560px) {
    .link-list--centred > .link-row { flex-basis: 100%; }
}

/* ---- The year title blocks ------------------------------------------------
   Two sections on /about-us that differ only by a year need a heading you can
   identify at a glance while scrolling, not three lines of plum and a line of
   grey.

   Two things do the work, and neither adds a word of content:
     - the heading itself, larger and set tighter than a normal section title,
       so it reads as a lockup rather than as a sentence that wrapped;
     - the year in the heading in the brand gradient, picked out of the plum,
       with the host city under it as a pin on a chip because it is a place.

   The sibling sites have a third: the year again, set enormous and nearly
   transparent behind the block. It is not here — see the note further down. */
/* max-width: none, because .clo-head caps every section head at 720px. That is
   right for a two-word title over a centred lead, and it is what broke these
   headings across an extra line — they need about 850px at this size and the
   panel has 944, so the only thing standing between them and one fewer line was
   the shared cap. */
/* .clo-head.clo-year-head, not .clo-year-head: both are one class, so they tie
   on specificity and .clo-head — which is 1000 lines further down this file —
   wins on order. Written as one class it lost silently and the heading kept
   wrapping with the override sitting right there looking correct. */
.clo-head.clo-year-head {
    position: relative;
    max-width: none;
    padding-top: clamp(8px, 1.6vw, 22px);
    /* THE GAP TO THE CITY CHIP IS SET HERE, and it has to be: .clo-head is a
       column flex container, so the space between the heading and the chip is
       its `gap`, not a margin on either of them.

       .clo-head .section-title sets `margin: 0` about 800 lines below this at
       the same specificity as .clo-year-head .section-title — so a margin-bottom
       written there loses on source order and does nothing at all. It was tuned
       three times before anyone measured the rendered pixels and found the gap
       had not moved once.

       Two classes here, so this is (0,2,0) and beats .clo-head`s (0,1,0)
       whatever the order. 22px against the shared 6px: this heading runs at
       3.15rem with descenders that drop a long way, and the chip below it has a
       border they were landing on. */
    gap: 22px;
}

/* No divider under these two.

   Every other section head on the site closes with the bar, and it earns its
   place there because the heading is the last thing in the block. These two are
   not: the host-city chip sits under the heading, and it is already a self-
   contained object on the same centre line. A bar beneath it made three marks
   stacked one above another — title, chip, bar — which reads as a heading that
   could not decide where it ended.

   The chip closes the block on its own. Written as `content: none` rather than
   by taking .clo-head off the element, because the rest of that class — the
   centring, the spacing, the eyebrow rules — is exactly what these blocks want.

   BOTH classes in the selector, for the reason spelled out at the top of this
   block: .clo-year-head alone is (0,1,0) and ties with .clo-head, which defines
   ::after about 1,500 lines further down and therefore wins on order. Written
   as one class this rule looked correct and did nothing. */
.clo-head.clo-year-head::after { content: none; }

/* NO WATERMARK, and this note is here so nobody adds one back.

   The sibling Geography site draws the year enormous and nearly transparent
   behind this block, via a data-year attribute. It works there because its
   headings are short — "Malaysian Team at iGeo 2025" sets on two lines and
   leaves the numeral clean space to sit in.

   MyCLO's are not. "Malaysian Team at the International Linguistics Olympiad
   (IOL) 2025" runs to three lines and fills the block, so the numeral had
   nowhere to go but directly behind the words — and at any opacity dark enough
   to see, it read as a rendering fault rather than as texture.

   Dropping it costs nothing the section needs. The year is already picked out
   of the heading in the brand gradient, which is the same emphasis stated once
   instead of twice. */
/* Bigger and tighter than the shared section title: this one is a lockup, and a
   heading set at normal leading over two lines leaves a gap the watermark shows
   straight through. */
.clo-year-head .section-title {
    font-size: clamp(1.9rem, 4.4vw, 3.15rem);
    /* 1.15, not the 1.04 this was set at.

       1.04 is tight enough that the descenders of one line land in the space the
       next line's capitals rise through — the y of "Malaysian" against the L of
       "Linguistics" beneath it. It reads as two lines fighting rather than as a
       lockup, and no amount of margin under the block fixes it, because the
       collision is INSIDE the heading.

       Still tight by body-copy standards: at this size a heading wants less
       leading than a paragraph, and 1.15 keeps the two lines reading as one
       object while giving the descenders somewhere to go. */
    line-height: 1.15;
    letter-spacing: -.015em;
    /* No margin-bottom here. It would be dead: .clo-head .section-title sets
       `margin: 0` further down this file at the same specificity, and the space
       under this heading is .clo-head`s flex `gap` — set on
       .clo-head.clo-year-head above. */
}
/* The site gradient runs orange into navy, which reads as a deliberate ramp
   across a word like "Objectives" — but a year is four characters, so the navy
   end landed on the last digit and "2025" came out with a grey 5 that looked
   like a rendering fault. Warm the whole run instead: over four characters the
   ramp should read as one colour deepening, not as two colours meeting.
   (0,3,0) so it outranks redesign.css's own `.section-title .grad`. */
.clo-year-head .section-title .grad {
    background: linear-gradient(96deg, #e45969 0%, var(--brand) 45%, var(--brand-deep) 100%);
    background-clip: text;
    -webkit-background-clip: text;
}
.clo-place {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    padding: 5px 14px 5px 11px;
    border-radius: 999px;
    border: 1px solid rgba(46, 41, 60, .18);
    background: #fefdfc;
    box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
    color: #2e293c;
    font-size: .92rem;
    font-weight: 600;
    letter-spacing: .01em;
}
.clo-place .gx-ico { color: var(--brand); flex: 0 0 auto; }
/* The host country's flag, closing the chip. width:auto because these are not
   all one ratio — Ireland is 2:1, Thailand and Indonesia 3:2 — so fixing the
   width would stretch two of the three. The hairline is what keeps a flag with
   a white band (all three of them) from bleeding into the chip behind it. */
.clo-place-flag {
    flex: 0 0 auto;
    height: 1.05em;
    width: auto;
    margin-left: 3px;
    border-radius: 2px;
    box-shadow: 0 0 0 1px rgba(46, 41, 60, .25);
}


/* 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 navy — 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(28, 26, 39, .22);
}
.video-stage iframe {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    background: #12181a;
}

.video-side {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: linear-gradient(170deg, #231f2e 0%, #240a0e 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: rgb(var(--brand-rgb) / .16);
    border: 1px solid rgb(var(--brand-rgb) / .42);
    color: #f2ddd9;
    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: rgb(var(--brand-rgb) / .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: rgb(var(--brand-rgb) / .17);
    border-left-color: var(--brand);
}
.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 — THE RULED PAGE
   ----------------------------------------------------------------------------
   Five ruled bands stacked behind a two-column split (copy left, brand mark
   right). The bands are gradients rather than an image, so they scale to any
   viewport and cost nothing to load.

   They are the ruling of a page before anything is written on it: the faint
   guide bands a scribe lays down, palest at the head where the text will sit and
   deepening toward the foot where the leaf falls into shadow. The ruled baseline
   at the very bottom (.clo-hero::after, in the glyph layer below) is the one line
   that HAS been written on — so the hero reads as a page in the middle of being
   filled in, which is the state every problem in this competition is in.

   It replaces the sibling Geography site's hypsometric relief, which drew the
   same five slabs as elevation tints: the right motif for that competition, and
   the wrong one here.
   ========================================================================== */

.clo-hero {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    /* Desktop only. Both clamps keep their mobile minimums, so a phone renders
       exactly as it did; what changed is the caps, which were set before the stats
       bar moved under the hero. At 1440x900 — the commonest laptop — 68/80px here
       plus .section--tight's 40px pushed that bar half off the bottom of the
       screen, so the first thing a visitor saw ended on a sliced card with nothing
       to signal there was more page. 44/40 here and 28 there brings the whole bar
       above the fold and lets the next section's eyebrow show under it. About
       72px, all of it padding — copy, badge and type scale are untouched. */
    padding: clamp(28px, 5vw, 44px) 0 clamp(34px, 6vw, 40px);
    margin-bottom: clamp(10px, 2vw, 24px);
}

/* The ruled bands. Each is a horizontal slab with a slight tilt — the tilt is
   what makes them read as a sheet lying on a desk rather than as CSS stripes.

   The ramp deepens DOWNWARD, which is a legibility choice rather than a
   decorative one: the headline and the Register button sit in the top third, and
   the ramp's dark end under 3rem type would have cost either the contrast or the
   tint. Running it downward puts the palest bands where the text is and lets the
   deepest land in the empty space at the foot, under the written line.

   EVERY BAND FADES IN AS WELL AS OUT, and that is the one thing here that must
   not be simplified back. Each gradient used to start at its full alpha, so the
   top of every slab was a hard step from nothing to .55 — and a slab that is
   tilted -1.6deg and rounded turns that step into a long curved diagonal drawn
   across the hero. At most widths it lands in empty space and reads as the edge
   of a sheet. At around 1050px it landed straight through the headline, cutting
   "Language Explorer / Competition" in half.

   Fading in at 0% and out at 100%, with the band's own value at 34%, leaves the
   ramp exactly as designed and leaves no edge anywhere for the tilt to draw.

   Every band is a wash — .06 to .22 alpha — so the whole ramp reads as tinted
   paper rather than as five stripes. */
.clo-hero-relief { position: absolute; inset: 0; z-index: -1; }
.clo-hero-relief span {
    position: absolute;
    left: -8%; right: -8%;
    transform: rotate(-1.6deg);
    border-radius: 40%;
}
/* the head margin — the strip above the first ruled line, left empty on a page */
.clo-hero-relief span:nth-child(1) { top: 0;   height: 20%; background: linear-gradient(180deg, rgba(241, 196, 106,0) 0%, rgba(241, 196, 106,.20) 34%, rgba(241, 196, 106,0) 100%); }
/* first ruling */
.clo-hero-relief span:nth-child(2) { top: 16%; height: 26%; background: linear-gradient(180deg, rgba(246, 226, 228,0) 0%, rgba(246, 226, 228,.55) 34%, rgba(246, 226, 228,0) 100%); }
/* second ruling */
.clo-hero-relief span:nth-child(3) { top: 40%; height: 24%; background: linear-gradient(180deg, rgba(242, 221, 217,0) 0%, rgba(242, 221, 217,.42) 34%, rgba(242, 221, 217,.06) 100%); }
/* third ruling */
.clo-hero-relief span:nth-child(4) { top: 62%; height: 22%; background: linear-gradient(180deg, rgba(124, 156, 176,0) 0%, rgba(124, 156, 176,.24) 34%, rgba(124, 156, 176,.04) 100%); }
/* the foot of the leaf, where the sheet falls into shadow */
.clo-hero-relief span:nth-child(5) { top: 80%; height: 26%; background: linear-gradient(180deg, rgba(46, 41, 60,0) 0%, rgba(46, 41, 60,.20) 34%, rgba(46, 41, 60,.02) 100%); }

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

/* The hero lockup is TWO lines, and the type is sized to keep it that way.
   ---------------------------------------------------------------------------
   The <h1> carries a hard <br> before "Competition", so the intended shape is
   "Language Explorer" over "Competition". It was set at clamp(2.1rem, 5.2vw,
   3.9rem) and at every width from 1000px up "Language Explorer" was wider than
   the column it sits in, so it wrapped again and the lockup came out as three
   lines with "Explorer" stranded in the middle of it.

   Measured rather than guessed at. In this face "Language Explorer" is 9.58em
   wide, and the copy column — 1.05fr of a two-column grid inside a 1180px
   container — settles at 565px once the container stops growing. 565 / 9.58 =
   59px, so the ceiling is 3.6rem (57.6px) with a little to spare. The fluid
   middle needs the same treatment: the column is 471px at a 1000px viewport,
   which allows 49px, and 4.85vw gives 48.5.

   Below ~900px the grid collapses to one column, the <br> is hidden, and the
   title has the full width to wrap in however it likes — none of this applies
   there.

   So: if this face is ever changed, re-measure. A wider one breaks the lockup
   again and it will look like a wrapping bug rather than a font change. */
/* 1.16, not the 1.06 this was set at.
   ---------------------------------------------------------------------------
   The hero title is the one place on the site where two lines of 57px display
   type stack, and 1.06 left no room between them: Poppins drops the "g" of
   "Language" about .21em below its baseline and puts the dot of the "i" in
   "Competition" high on the line under it, so the two met and the pair read as
   one smudged block.

   It is a leading problem, not a kerning one — nothing is wrong with either
   glyph, they simply have no room. 1.16 opens the gap and is still tight enough
   that the two lines read as one lockup rather than as two headings. Do not take
   it back under about 1.12: that is where the descenders start touching again at
   the top of the clamp. */
.clo-hero-copy h1 { font-size: clamp(2.1rem, 4.85vw, 3.6rem); margin: 10px 0 14px; line-height: 1.16; }

/* ABOUT'S HEADING WRAPS, where the home page's is broken by hand.
   ---------------------------------------------------------------------------
   redesign.css puts text-wrap: balance on `.page-hero h1` — every inner page's
   heading has it. About's had it too until the page moved off .page-hero and
   onto the hero, so this restores a rule the heading used to carry rather than
   adding a new one. Scoped to About, because the home page's heading must NOT
   have it: that one holds a hard <br> to set "Language Explorer" over
   "Competition", and balance would fight a break that is there on purpose.

   IT DOES NOT CHANGE THIS PARTICULAR HEADING, and that is worth writing down
   so the next person does not try it again expecting it to.

   "Malaysian Computational Linguistics Olympiad" is four long words. At the
   copy column's 565px and the h1's 57.6px ceiling, "Computational" alone runs
   about 410px and any two of them together overflow — so there is exactly one
   way to set it at every width the site has, one word per line, and balance
   reaches the same answer as the browser did without it.

   So docs/runtime-audit.php reports a widow here — "Olympiad" alone on the last
   line — at all four of 1024px, 1440px, 375px and 320px, and that warning is
   EXPECTED. Its own
   note says to look at the page before changing anything it flags, and the page
   is right: four lines of one word each, with the last in the brand rose, reads
   as a stacked lockup and not as a heading that ran out. It is also how the
   live site sets the same name.

   The site's usual remedy — a non-breaking space tying the last two words —
   would make this worse, not better: "Linguistics Olympiad" as one unbreakable
   token is about 630px and would overflow the column it sits in. */
.clo-hero--about .clo-hero-copy h1 { text-wrap: balance; }
/* THE MOTTO, then the description. Two paragraphs in a row set identically read
   as one paragraph broken in half, so the motto keeps its own voice: it is the
   competition's own line, already carrying its quotation marks, and it runs in
   the brand crimson at 7.1:1 on the hero ground. The description under it is the
   ordinary lead — body grey, the colour the rest of the page reads in. */
.clo-hero-copy .clo-hero-motto {
    margin: 0 0 10px;
    max-width: 46ch;
    font-family: var(--font-display);
    font-size: clamp(1.02rem, 1.45vw, 1.16rem);
    font-weight: 600;
    line-height: 1.45;
    color: var(--brand);
}
.clo-hero-copy .lead { margin: 0 0 22px; max-width: 52ch; }
.clo-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. */
/* PAPER: grain and a vignette, over the whole page.
   ---------------------------------------------------------------------------
   The field had marks on it but no surface under them. Every pixel of the
   background was one of about four flat lilacs, and flat colour is the thing
   that reads as "a web page with some decoration on it" rather than as a sheet
   of anything.

   Two layers, both on one pseudo-element so there is only one extra box:

   1. GRAIN — fractal noise from an inline SVG filter, at .05. It is doing the
      job a paper stock does in print: breaking the flatness at a scale too fine
      to look at directly. Turn it off and the page goes back to looking
      digital; turn it up past about .08 and text starts to feel like it is
      printed on sandpaper.

      baseFrequency .9 is deliberately high — coarse noise reads as dirt, fine
      noise reads as tooth. numOctaves 3 keeps it from tiling visibly.

   2. VIGNETTE — a very soft radial darkening at the corners. It curves the
      sheet. Centred at 50% 35% rather than dead centre, because the eye enters
      near the top and the light should look like it is falling there.

   position: fixed and z-index -1, on the same plane as #space-bg but after it,
   so the grain lies OVER the drawn marks — grain under the ink would be a
   texture behind glass. pointer-events: none, and aria-hidden by virtue of
   being a pseudo-element, so nothing here reaches the accessibility tree.

   Both are pure CSS: no image request, nothing to preload, no layout cost. */
/* The motif tile — four emblems on a strict grid.
   ---------------------------------------------------------------------------
   WHAT MAKES THIS DIFFERENT FROM THE SCATTER IT REPLACED, since it is the same
   instinct and the same palette: this one is REGULAR. The old backdrop tiled
   figures at random positions and sizes, with .clo-glyphs planting more in the
   margins and a 25 KB canvas drifting a further set over the top. Randomness at
   that density does not read as decoration, it reads as mess — which is what it
   was called.

   Four emblems, one per quadrant of a 260px tile, same size, same weight, same
   ink, repeating on a fixed grid. A regular repeat is read as wallpaper and
   then ignored, which is the whole job of a background. Nothing moves, nothing
   is random, and every instance is identical to every other.

   THEY ARE EMBLEMS, NOT DIAGRAMS. A glyph, a phonetic bracket, a morphology
   tree, an aligned pair. You register "language" and move on. The set the site
   had before drew WORKING — substitution grids, bracketed transcriptions, a
   ringed run with a thread to its gloss. Those invite reading, and a thing that
   invites reading has no business behind a paragraph.

   The subject rule from the old partials/clo-glyphs.blade.php is kept, because
   it is what stops this set drifting: the ornament comes from WRITING and from
   the ANALYSIS of writing. Not from language as speech — no mouths, no speech
   bubbles, no waveforms — and not from computing: no terminals, no braces, no
   binary. A computational-linguistics problem is a page of an unknown script
   and the logic you bring to it. */
:root[data-theme="warm"] {
    --clo-motif: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='260' height='260' viewBox='0 0 260 260'><g fill='none' stroke='%237a1622' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' opacity='0.5'><path d='M58 50v30M58 57h13M58 69h9M75 50v11'/><path d='M184 52h-6v26h6M206 52h6v26h-6M199 58l-8 14'/><path d='M65 178v10M52 202 65 188 78 202M52 202v5M78 202v5'/><path d='M181 186h28M181 204h28M195 189v12M192 192l3-3 3 3M192 201l3 3 3-3'/></g></svg>");
}

/* THE GRAIN, on its own element and deliberately still.
   ---------------------------------------------------------------------------
   The emblems and the grain were one element until the emblems started moving.
   They are separated now so the paper can stay put while the marks drift under
   it — which is the right way round: paper does not travel, and a grain that
   moved with the motif would read as the whole screen sliding.

   The stack still puts grain OVER emblems, which is what makes them read as
   printed on the sheet rather than floating above it: the motif is on
   body::before at z-index -3, this is body::after at -1.

   IT IS PAINTED, NOT DOWNLOADED: two data URIs, no request, no image file, no
   script. What it replaced was 25 KB of JavaScript running a requestAnimationFrame
   loop on every page for the life of the visit.

   baseFrequency .9 with 4 octaves gives a fine, even grain with no visible
   repeat at 180px. Coarser and the tile starts to show; finer and it disappears
   into the pixel grid on a high-DPI screen. multiply rather than the soft-light
   this used before: soft-light lifted the wash as much as it darkened it and
   left the grain looking like haze rather than tooth. */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
        url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='180' height='180' filter='url(%23g)' opacity='0.42'/></svg>");
    background-size: 180px 180px;
    background-repeat: repeat;
    opacity: .5;
    mix-blend-mode: multiply;
}

@media print {
    body::after { display: none; }
}
/* The dark bands (the workshop section) have their own ground, and soft-light
   over dark plum lifts it to a muddy grey. The vignette alone there. */
[data-theme="warm"] .clo-workshop { isolation: isolate; }
/* The hero ribbon's styles lived here — .clo-ribbon, its dot and the pulse
   keyframes, plus a --closed variant. All removed with the ribbon itself; see
   the note in redesign/home.blade.php where it used to be rendered. */

/* The workshop heading needs more than the shared 720px.
   ---------------------------------------------------------------------------
   "Language Explorer Preparatory Workshop 2026/2027" is the longest title on
   the site. At the desktop size (49.6px) the phrase "Language Explorer
   Preparatory" alone measures 795px — wider than .clo-head's 720px cap — so
   text-wrap: balance did the only thing left to it and split the title three
   ways, leaving "Preparatory" on a line by itself in the middle. Two lines at
   1024px and 768px, three at 1200px and above: the wrap got WORSE as the screen
   got bigger, which is the signature of a cap rather than a shortage of room.

   880px, against 795 needed, so it holds if the wording gains a word. There is
   room for it: this head sits in a 1180px container.

   The 795 figure is the measurement that matters again. The title is now broken
   by hand into "Language Explorer Preparatory" and "Workshop 2026/2027" (see
   home.blade.php), so line one is exactly that phrase and 880px fits it at the
   full --fs-h2 — which is why the temporary step down to 2.6rem that a
   single-line name needed has been taken back out. This heading is the same size
   as the other nine again.

   Safe to widen here specifically because this head carries an eyebrow and a
   title and nothing else — no lead paragraph, so nothing is being stretched to
   an unreadable measure. Do not lift this to .clo-head generally; every other
   section on the page has a lead under its title, and 720 is what keeps those
   at a reading width. */
.clo-workshop .clo-head {
    max-width: 880px;
}
/* Key visual — the competition mark, drifting on a long, slow bob. */
.clo-hero-art { position: relative; min-height: clamp(240px, 34vw, 430px); }

/* ---- the plated variant, for /about-us ------------------------------------
   The home page's hero art is a drawn scene: soft edges, its own light, and it
   belongs directly on the ruled bands. About's is the MyCLO logo — a hard cyan
   rectangle with a speech tail, drawn for white stock — and on the bands it read
   as a picture pasted onto the page rather than as a mark on it.

   Same plate .al-plate gives the two marks in the panel below, and for the same
   reason, so the page has one answer to "artwork drawn for white stock" instead
   of two. It centres rather than fills: the logo is 4:3 and the column is taller
   than that, and stretching it to the column height would blow the wordmark up
   past the <h1> beside it. */
.clo-hero-art--plate {
    display: grid;
    place-items: center;
    min-height: 0;
    padding: clamp(18px, 2.4vw, 30px);
    border-radius: 20px;
    background: linear-gradient(180deg, #fff 0%, #faf1ef 100%);
    border: 1px solid rgba(46, 41, 60, .12);
    box-shadow: 0 18px 40px rgba(35, 31, 46, .16);
}
/* The plate carries the shadow now, so the mark inside it drops its own — two
   shadows on one object is what makes a plated logo look like a sticker. The bob
   stays: it is the plate's contents that move, which reads as the mark sitting
   loose on the card. */
.clo-hero-art--plate .clo-hero-mark { filter: none; }
.clo-hero-mark {
    display: block; width: 100%; height: auto;
    filter: drop-shadow(0 26px 50px rgba(35, 31, 46,.24));
    animation: clo-bob 9s ease-in-out infinite;
}

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

@media (max-width: 900px) {
    .clo-hero-grid { grid-template-columns: minmax(0, 1fr); text-align: center; }
    .clo-hero-copy .lead,
    .clo-hero-copy .clo-hero-motto { margin-inline: auto; }
    .clo-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. */
    .clo-hero-copy h1 br { display: none; }
    /* art first would push the copy below the fold on a phone */
    .clo-hero-art { order: 2; max-width: 460px; margin: 0 auto; }
}

/* Motion is decorative here — none of it conveys information. */
@media (prefers-reduced-motion: reduce) {
    .clo-hero-mark { 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; }

/* Flag artwork in prose.
   Windows ships no font that composes a regional-indicator pair (U+1F1F2 U+1F1FE
   for Malaysia) into a flag, so the emoji renders there as the bare letters "MY"
   — which is what the copy was doing at the end of "Good luck, Team Malaysia!".
   The flag is public/images/emoji/flag-my.svg now, drawn rather than typed.

   Matched on the path, not only on .flag-my: an editor pasting a flag into the
   admin will not know to add a class, and the path is what the swap writes. The
   class is on it too, so the engine's own .eyebrow .flag-my rule still applies if
   one ever lands in an eyebrow.

   The width override is the point of the rule: redesign.css sizes inline emoji
   images 1em SQUARE, and a flag is 2:1, so without this it arrives squashed. */
.prose img[src*="/emoji/flag-"], .prose img.flag-my {
    width: auto; height: .95em;
    vertical-align: -.1em;
    border-radius: 2px;
    box-shadow: 0 0 0 1px rgba(46, 41, 60, .22);
}


/* ---- The medallists' list -------------------------------------------------
   A bronze medal for a marker instead of a bullet, on the one list that names
   people who won one. Drawn in the same construction as x-clo-award-icon's
   medal — ribbon pair, disc, star — so it belongs to the set the site already
   draws rather than arriving as a stock emoji.

   A background image on the <li>, not ::marker: ::marker takes almost no
   properties in a way browsers agree on, and background-size is the one thing
   this needs. list-style: none removes the bullet it replaces.

   The class rides the <ul> in the content block, so the same list can be
   pasted into any richtext field and mark itself. */
.prose ul.medal-list {
    list-style: none;
    padding-left: 4px;
}
.prose ul.medal-list li {
    position: relative;
    padding-left: 34px;
    min-height: 24px;
    background: url("/images/emoji/medal-bronze.svg") left .12em / 22px 22px no-repeat;
}

/* Captioned photo grid (About page iGeo team write-ups).

   Flex, not grid, and the reason is the last row. These sets are whatever length
   the content team uploaded — the 2023 camp has six photos in four columns — so
   a trailing row is usually short. Grid places those leftovers into columns 1
   and 2, which pins them to the left edge under a full row and reads as a
   layout that ran out rather than a set that ended. `justify-content: center`
   on a flex line centres them, and there is no grid equivalent: the tracks are
   fixed by the container, so items cannot be centred within a row.

   flex-grow is 0, which is the other half of it. Left to grow, the two orphans
   would stretch to half the panel each and the last row would be photos twice
   the size of the ones above. Every figure keeps the width it would have had in
   a full row, whichever row it lands on.

   --pg-cols is the whole responsive story: one basis formula, and a breakpoint
   only says how many columns fit. The counts below are where the old
   `auto-fit, minmax(230px, 1fr)` changed its own mind, so nothing reflows at a
   different width than it used to.

   The -1px is a rounding guard. Four bases plus three gaps come to exactly 100%,
   and a sub-pixel overshoot there would wrap the fourth photo onto its own line;
   a pixel shaved off each basis makes that impossible, and `justify-content`
   absorbs the slack. */
.photo-grid {
    --pg-gap: 16px;
    /* --pg-fit is how many columns the VIEWPORT can take; --pg-max is how many
       the gallery has photographs for, set inline by x-clo-photo-grid. The grid
       uses whichever is smaller, so a two-photograph gallery lays out as two
       half-width tiles instead of two quarter-width ones stranded in the middle
       of a four-column row — and still collapses to one column on a phone,
       because --pg-fit wins there. */
    --pg-fit: 4;
    --pg-cols: min(var(--pg-fit), var(--pg-max, 99));
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--pg-gap);
    margin-top: 26px;
}
/* `> *`, not `figure`.
   ---------------------------------------------------------------------------
   This said `.photo-grid figure` and matched NOTHING on this site: the tiles are
   <a class="ph-link">, because every one of them opens the lightbox. So the
   column arithmetic below never applied, the flex items fell back to sizing on
   their images' intrinsic widths, and the wall has been laying out as ragged
   centred rows of three this whole time rather than as the four-column grid the
   variable names describe. It looks deliberate enough that nobody questioned it
   until the same grid was reused under the write-ups with eight photographs and
   two, where three uneven rows and a stranded pair are unmistakable.

   The selector is left loose on purpose — a tile that is not a link (a figure
   with a visible caption, say) should land in the same column. */
.photo-grid > * {
    margin: 0;
    min-width: 0;
    flex: 0 1 calc((100% - (var(--pg-cols) - 1) * var(--pg-gap) - 1px) / var(--pg-cols));
}
@media (max-width: 1090px) { .photo-grid { --pg-fit: 3; } }
@media (max-width:  830px) { .photo-grid { --pg-fit: 2; } }
@media (max-width:  550px) { .photo-grid { --pg-fit: 1; } }
.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 > *:hover img { transform: translateY(-3px); box-shadow: var(--shadow); }
/* One or two photographs are not a wall, and cropping them to the wall's 190px
   band makes them 2.4:1 strips — which is what the pair under the 2023 write-up
   looked like, two letterboxes where the live site has two photographs. The
   fixed height exists so that rows of thumbnails line up with each other; with
   one row of two there is nothing to line up with. */
.photo-grid--few img { height: auto; }

/* A single photo shown whole, rather than as one cell of a grid.
   ---------------------------------------------------------------------------
   The grid above fixes every image at 190px and crops to fill, which is what
   makes a row of thumbnails line up. For a lone photo it is the wrong rule
   twice over: the 4:3 iGeo Task Force group shot came out as a 5.5:1 strip with
   the heads cropped off the nine people its caption names.

   height:auto with the intrinsic width/height that x-clo-img writes onto the
   tag means the browser reserves the right box before the file arrives, so
   nothing shifts as it loads. No object-fit: with no fixed height there is
   nothing to fit into. */
.photo-grid--full { --pg-max: 1; }
.photo-grid--full img {
    height: auto;
    max-height: none;
    object-fit: initial;
}
/* Whole, but not enormous. Uncropped at the full 1030px panel this 4:3 photo is
   770px tall and takes most of a screen on its own. Capped at 660 it comes out
   about 495 — large enough to pick out nine faces, small enough that the
   write-up above and the years below stay in view with it. The row is a centred
   flex, so the cap centres the figure without anything else. */
.photo-grid--full > * { max-width: 660px; }

/* Captions centre under their photo, which is what the rest of the figure now
   does — the row itself is centred, so a caption pinned to the photo's left edge
   was the one element still aligning to something else.

   text-wrap: balance is not optional here. Four of these captions wrap to two
   lines, and centred text that breaks into a long line over a two-word stub
   reads as a wrapping accident rather than a decision; balance splits the lines
   evenly so the block sits as a tidy centred wedge. It is safe to lean on for
   this: the spec caps balancing at a handful of lines, and no caption in the set
   is longer than two. Where the browser does not support it the caption simply
   wraps normally, still centred. */
.photo-grid figcaption {
    margin-top: 8px;
    font-size: .86rem;
    color: var(--text-soft);
    text-align: center;
    text-wrap: balance;
}

/* ---- 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 var(--brand-deep); outline-offset: 3px; }

.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, var(--brand), var(--brand-2));
    border-color: var(--brand-deep);
    color: #1c1922;
}
.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. Manuscript plum rather than the rose used for
   invitations, because it narrows who something is for rather than inviting. */
.clo-badge {
    display: inline-block;
    margin-left: 8px;
    padding: 5px 12px;
    border-radius: 99px;
    background: #2e293c;
    color: #f4e5e4;
    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(13, 21, 29, .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);
}
/* MyGeo's ramp — orange through gold to earth brown — 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, var(--brand) 0%, #04788a 55%, #2e293c 100%);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .5),
                0 30px 80px rgba(20, 18, 28, .45),
                0 0 60px rgb(var(--brand-rgb) / .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: rgb(var(--brand-rgb) / .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: var(--brand);
    color: #1c1922;
    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: var(--brand); border-color: rgba(255, 255, 255, .85); }
[data-theme="warm"] .popup-close:hover { background: #d4636d; 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 a page-setting line: sections alternate
   between an open "surface" treatment and a tinted "seam" band, separated by a
   ruled baseline. The page then reads as a run of leaves rather than one long
   sheet, 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. */
.clo-band {
    position: relative;
    background: linear-gradient(180deg, rgba(246, 226, 228, .28) 0%, rgba(252, 248, 248, .55) 100%);
    border-block: 1px solid rgba(46, 41, 60, .10);
}
/* The highlighted band — Awards on the home page, Practice Books on Reading
   Materials. It was a .13/.09 amber wash, which on cream is close enough to
   nothing that the section read as unmarked; this gives it a real amber field,
   a rose hairline top and bottom, and a seal in each lower
   corner, so it is visibly a called-out section rather than a faint tint.

   Deliberately kept BELOW the workshop band, and it must stay that way. The
   workshop is the page's one strong break — solid deep plum, light text — and
   the reason the rest of the page is quiet is so that break can register. This
   band is a warm wash under dark text: clearly emphasised, clearly second. Push
   it much further and the page has two things shouting and no hierarchy.

   Same rule as the workshop stack: the three list properties are one entry per
   image, in order. Add a layer and all four lines change together. */
.clo-band--deep {
    background:
        var(--clo-seal-dark),
        var(--clo-seal-dark),
        radial-gradient(120% 90% at 10% -12%, rgb(var(--brand-rgb) / .30), transparent 58%),
        radial-gradient(110% 90% at 92% 6%, rgba(241, 196, 106, .20), transparent 55%),
        linear-gradient(180deg, rgb(var(--brand-rgb) / .22) 0%, rgba(18, 120, 127, .15) 100%);
    background-position:
        left -22px bottom -26px,
        right -30px bottom -34px,
        0 0, 0 0, 0 0;
    background-size:
        168px 125px,
        200px 148px,
        auto, auto, auto;
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
    /* longhand, so it overrides only the colour of .clo-band's border-block
       shorthand and leaves its 1px/solid alone */
    border-block-color: rgba(156, 31, 46, .40);
}

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

/* Centred on the page field, above the panel — the sibling sites' arrangement. */
.clo-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;
}
.clo-head .section-title { margin: 0; }

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

/* ---- Stages: THE ROUTE ------------------------------------------------------
   Five stops on one line, not five cards in a stack.

   Three arrangements came before this one and all three are worth naming,
   because all three are the obvious thing to try again.

   A 2x2 GRID OF CARDS (the sibling sites' shape) cannot show the one thing this
   section is about. The content is a narrowing — everyone, then the top 10%,
   then fifteen, then four — and five equal boxes say the opposite, with the
   figures surviving only as a chip in the corner of each.

   A THREAD WITH MEASURED BARS showed the narrowing and cost too much for it. The
   bars had to be invented (a true proportion puts stages 3 to 5 at a pixel
   each), and once invented a horizontal bar under a heading reads as a progress
   bar — something the visitor is part-way through — which is the wrong idea
   entirely.

   FIVE TINTED PLATES, each carrying a big number and the stage's mark, is what
   this replaced and it very nearly worked. What it never did was CONNECT: the
   tint deepened down the stack, but nothing said "then". Five plates read as a
   list of five things, and the section is called The Five Stages because they
   are one route with five stops on it.

   So the tint moves onto a SPINE running down through all five, the marks come
   off their plates and become NODES on that spine, and each node ties into its
   card with a short tick. The route is now drawn rather than implied. The depth
   still claims nothing numerical — it is a line getting darker, not a measure —
   and the chip on each card still carries the actual figure. Same division of
   labour as the Awards podium: the drawing gives the shape, the number gives the
   truth.

   The stage NUMBER left the rail when the rail became a route, and now sits at
   the head of its card. A spine running down through a numbered pill reads as a
   line interrupted; the mark is the thing that wants to be on the line.
   ========================================================================== */
.clo-steps {
    /* One place the gap is written, because the spine segments reach into it to
       close the run between one node and the next — see .clo-step-rail::before.
       If these two ever disagree the route breaks into five dashes. */
    --steps-gap: clamp(14px, 1.6vw, 20px);
    --rail-w: 112px;
    list-style: none;
    margin: clamp(20px, 3vw, 30px) 0 0;
    padding: 0;
    display: grid;
    gap: var(--steps-gap);
}

.clo-step {
    display: grid;
    grid-template-columns: var(--rail-w) minmax(0, 1fr);
    align-items: stretch;
}

/* ---- the rail -------------------------------------------------------------
   Transparent: the card's border, background and shadow all sit on
   .clo-step-main now, so the spine has open ground to run through. That is the
   one structural change from the plate version — while the card background was
   on the <li>, anything drawn in the left column was drawn ON the card, and a
   route cannot pass between two cards it is painted inside. */
.clo-step-rail {
    position: relative;
    display: grid;
    place-items: center;
}

/* The spine, one segment per stage.
   ---------------------------------------------------------------------------
   Drawn per row rather than as a single line down the <ol>, because a single
   line would have to start and end at the FIRST and LAST node's centres, and
   those are wherever five variable-height rows happen to put them — not a
   position CSS can name.

   Per row it is exact: each segment runs the full height of its row and on into
   the gap below, so consecutive segments meet. The first starts at its own
   node's centre and the last stops at its own, which is what keeps the route
   from poking out of either end of the run.

   --tone deepens it down the five, and each segment is a gradient from its own
   tone to the next one's, so the joins do not show as steps. */
.clo-step-rail::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 0;
    bottom: calc(-1 * var(--steps-gap));
    width: 3px;
    transform: translateX(-50%);
    border-radius: 999px;
    background: linear-gradient(to bottom,
        rgba(122, 22, 34, calc(.13 + var(--tone) * .1)),
        rgba(122, 22, 34, calc(.13 + (var(--tone) + 1) * .1)));
}
.clo-step:first-child .clo-step-rail::before { top: 50%; }
.clo-step:last-child .clo-step-rail::before { bottom: 50%; }

/* The tick tying the node into its card. Short on purpose: long enough to read
   as a connection, not so long that it becomes a second horizontal rule. */
.clo-step-rail::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 0;
    width: 12px;
    height: 2px;
    transform: translateY(-50%);
    background: rgba(122, 22, 34, calc(.13 + var(--tone) * .1));
}

/* ---- the node ------------------------------------------------------------
   The mark of the body running the stage, sitting on the spine.

   The white plate under it is not decoration. All five marks are artwork drawn
   for white stock — the APLO letterform, the IOL speech bubble, the Explorer
   badge — and the page behind them is pale lilac with a glyph pattern running
   through it. Dropped straight onto that, the pale blues in APLO's mark and
   IOL's disappear. Same reasoning as .al-plate in the About lockup.

   A FIXED BOX with object-fit:contain, because these five files are not one
   family: a 150x150 square badge, three landscape bubbles at 4:3, and a square
   APLO mark. The box is what makes them a set; contain is what stops it
   distorting them.

   THE BOX IS NOT ENOUGH ON ITS OWN. contain fits each file's CANVAS, and the
   canvases hold very different amounts of artwork — the Explorer badge's ink
   fills 96% of its frame height, APLO's fills 67%. Fitted by canvas alone the
   APLO mark comes out around two thirds the size of the badge above it. The
   per-file --mark-scale set in home.blade.php corrects for that, and the note
   there carries the measurements.

   The 88x66 box keeps the image box at 72x50 — the same 1.44 ratio the smaller
   plate version used, which is why those measured scales still hold after the
   node grew. Change that ratio and they must be measured again. */
.clo-step-mark {
    --mark-w: 88px;
    --mark-h: 66px;
    --mark-pad: 8px;
    position: relative;              /* over the spine, not under it */
    z-index: 1;
    display: grid;
    place-items: center;
    width: var(--mark-w);
    height: var(--mark-h);
    padding: var(--mark-pad);
    border-radius: 14px;
    background: #fff;
    /* The ring is the node's own tone; the soft shadow lifts it off the page so
       the spine reads as passing behind it rather than stopping at it. */
    box-shadow:
        0 0 0 3px rgba(122, 22, 34, calc(.10 + var(--tone) * .1)),
        0 6px 16px rgba(35, 31, 46, .12);
}
/* The image box is stated in PIXELS, computed from the node. It was width:100%
   / height:100% first and that silently did not work: the node is a grid whose
   ROW is content-sized, so a percentage height had nothing definite to resolve
   against and fell back to auto. Each image then took its natural aspect from
   the width alone — which put APLO's square canvas at 64x64 inside a 56px chip
   and painted its opaque white corners straight over the plate behind it. The
   marks were not misaligned; they were being sized by a rule that was not
   taking effect.

   calc() off the same variables keeps this honest: change the node and the
   image box follows, and there is no percentage left to resolve. */
.clo-step-mark img {
    display: block;
    width: calc(var(--mark-w) - 2 * var(--mark-pad));
    height: calc(var(--mark-h) - 2 * var(--mark-pad));
    object-fit: contain;
    /* Scaled about the centre, which is safe here because every one of the five
       marks is centred in its own canvas to within a couple of pixels — checked,
       not assumed. Default 1 so a mark added without a measurement still lands
       somewhere sensible rather than collapsing. */
    transform: scale(var(--mark-scale, 1));
}

/* The arrival. Solid rather than a sixth shade of the ramp — the journey stops
   deepening here and lands, and the page has one place that says so. */
.clo-step.is-goal .clo-step-mark {
    box-shadow:
        0 0 0 3px #7a1622,
        0 8px 20px rgba(122, 22, 34, .28);
}
.clo-step.is-goal .clo-step-rail::after { background: #7a1622; }
.clo-step.is-goal .clo-step-main { border-color: rgba(122, 22, 34, .4); }

/* ---- the card ------------------------------------------------------------
   Everything that used to sit on the <li> — border, background, radius, shadow
   — lives here now, so the rail beside it stays open ground for the route. */
.clo-step-main {
    padding: clamp(13px, 1.7vw, 18px) clamp(16px, 2vw, 22px);
    min-width: 0;
    border: 1px solid var(--border);
    border-radius: 16px;
    background: var(--surface);
    box-shadow: 0 6px 18px rgba(35, 31, 46, .06);
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.clo-step:hover .clo-step-main {
    transform: translateY(-2px);
    border-color: var(--border-strong);
    box-shadow: 0 14px 30px rgba(35, 31, 46, .12);
}

.clo-step-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px 12px;
    margin-bottom: 6px;
}
/* The order marker, at the head of its card. It sits on the same baseline as the
   stage name and leads it, at roughly the name's own size — set smaller than
   this it stopped reading as the stage NUMBER and started reading as a label
   prefix, which is not what it is: the five of them are a count, and the eye
   should be able to run down 01..05 without hunting for them. It is still well
   under the plate version's 1.9rem, because the mark on the rail beside it is
   what identifies the stage now; this only has to carry the order. */
.clo-step-n {
    flex: none;
    font-family: var(--font-display);
    font-size: clamp(1.3rem, 1.7vw, 1.55rem);
    font-weight: 800;
    letter-spacing: .01em;
    line-height: 1.1;
    color: var(--accent);
    opacity: 1;
}
/* min-width:0 so a long name wraps inside the row instead of pushing it wider —
   "Malaysian Computational Linguistics Olympiad (MyCLO)" is 52 characters and
   does exactly that without it. */
.clo-step-name {
    min-width: 0;
    margin: 0;
    font-size: clamp(1rem, 1.35vw, 1.14rem);
    line-height: 1.3;
    color: #231f2e;
}
/* Ranged right, so the five figures stack into a column that reads straight
   down independently of the route. */
.clo-step-count {
    flex: none;
    margin-left: auto;
    padding: 3px 11px;
    border-radius: 999px;
    font-size: .76rem;
    font-weight: 800;
    letter-spacing: .04em;
    white-space: nowrap;
    color: var(--accent);
    background: rgba(246, 226, 228, .9);
    border: 1px solid rgba(46, 41, 60, .14);
}
.clo-step.is-goal .clo-step-count {
    color: #fff;
    background: #7a1622;
    border-color: transparent;
}

.clo-step-body ul { margin: 0; padding-left: 17px; }
.clo-step-body li {
    color: var(--text-soft);
    margin-bottom: .16em;
    max-width: 78ch;
    font-size: .95rem;
    line-height: 1.5;
}
.clo-step-body li:last-child { margin-bottom: 0; }

@media (max-width: 560px) {
    /* The route SURVIVES on a phone rather than collapsing into a bar across the
       top of each card, which is what the plate version did. A vertical line down
       the left is the one timeline shape that works at any width — it only has to
       be narrow enough to leave the card a readable measure, so the rail and the
       node come down together and the spine stays exactly where it was. */
    .clo-steps { --rail-w: 72px; }
    .clo-step-mark { --mark-w: 56px; --mark-h: 44px; --mark-pad: 6px; border-radius: 11px; }
    .clo-step-rail::after { width: 8px; }
    /* margin-left:auto on a wrapped flex row would strand the chip alone on the
       right of its own line, so it ranges left with the name instead. */
    .clo-step-count { margin-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .clo-step-main { transition: none; }
    .clo-step:hover .clo-step-main { transform: none; }
}

/* ---- Awards: the podium ---------------------------------------------------
   Three cards, one per band, each showing the award at the size the award is
   worth. The prize band is raised out of the row, ringed in its own orange and
   flagged; the two behind it step down in icon size, type size and lift.

   This was three bars whose WIDTH carried the 10/40/50 split and nothing else.
   Width alone put the largest, loudest mark in the section on "remaining 50% —
   certificate of participation", the least of the three awards, and left the
   prize tier the smallest thing on screen. So the split moved to a meter along
   the foot of each card, where it stays honest, and the card itself carries the
   worth. Freeing the label from having to fit inside its own bar is also what
   let the 10% band finally be drawn at a true 10% rather than a floored 22%.

   Keyed by the KIND of award, the same way the icons are, never by row
   position: reorder the bands in admin and each keeps its own metal, its own
   size and its flag.

   The middle card is deliberately COOL. Every other surface here is warm — the
   band wash, the panel, the prize card — so a third warm card was the reason
   the three read as one gradient rather than as three tiers. The grey-blue is
   the palette's water side, separating on temperature where sand on sand could
   not separate on lightness alone.

   Navy #231f2e carries every label; orange stays fill-only, the brand's rule. */

.clo-awards {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    /* start, not stretch: the cards are deliberately different heights, and
       stretching them to match would undo the step down the podium. */
    align-items: start;
    gap: 18px;
}
.clo-award {
    position: relative;
    display: grid;
    justify-items: center;
    text-align: center;
    gap: 10px;
    padding: 30px 22px 26px;
    border-radius: 20px;
    border: 1px solid rgba(46, 41, 60, .16);
    background: linear-gradient(180deg, #fefdfc, #f7f1f0);
    box-shadow: 0 16px 32px -24px rgba(46, 41, 60, .55);
}
.clo-award-ico { display: grid; place-items: center; }
.clo-award-band {
    margin: 0;
    color: #231f2e;
    font-weight: 800;
    line-height: 1.1;
    font-size: clamp(1.45rem, 3vw, 1.85rem);
}
/* 30ch: long enough for "will receive a Gold, Silver or Bronze Award
   Certificate" to break into three tidy lines rather than one thin column. */
.clo-award-name { margin: 0; max-width: 30ch; color: var(--text-soft); }

/* The split, drawn once, across the top of the block.
   ---------------------------------------------------------------------------
   Each segment is flex-sized by its own share, so the bar IS the proportion
   rather than a picture of one: `flex: var(--w)` with --w set to the parsed
   percentage means a 10/40/50 split is exactly 10/40/50 of the space, and a
   band edited in admin moves its segment without anything here changing.

   Width alone is what this section started with and it failed, because the
   widest mark landed on the least award. It works here and not there because
   the bar is no longer the AWARD — it is a separate object above the cards, a
   legend, and the cards carry the worth. Nobody reads the widest segment of a
   legend as the most important thing in it.

   The three fills are the three cards' own colours, so the key between bar and
   card needs no dots, labels or lines drawn between them: the segment above is
   simply the same colour as the card below it.

   min-width:0 so the 10% segment can be a true 10% — without it the flex base
   of its own label would hold it wider and quietly lie about the split. The
   label is hidden rather than allowed to do that if it will not fit. */
.clo-award-split {
    display: flex;
    gap: 5px;
    margin: 0 0 clamp(20px, 2.6vw, 30px);
}
.clo-award-seg {
    flex: var(--w) 1 0;
    min-width: 0;
    display: grid;
    place-items: center;
    overflow: hidden;
    height: 34px;
    border-radius: 9px;
    font-size: .76rem;
    font-weight: 800;
    letter-spacing: .04em;
    white-space: nowrap;
}
/* Deeper than the cards they key to, and that is deliberate. Set to the cards'
   own fills the bar did not work: honourable (#fdfaf9→#f2e1e0) and participation
   (#fdfaf9→#f0e4e3) are within a few points of each other, which is fine at card
   size where a heading and an icon tell them apart, and useless in a 34px strip
   where the colour is the ONLY thing carrying the key.

   So each segment takes its card's ACCENT — the hue already on that card's
   border, rose then plum then teal — at a strength that separates the three at a
   glance. Same three hues, same order, enough apart to be read as a key. */
.clo-award-seg--gold {
    background: linear-gradient(180deg, #eed4d2, #f0a3ac);
    box-shadow: inset 0 0 0 1px rgb(var(--brand-rgb) / .55);
    color: #791420;
}
.clo-award-seg--honorable {
    background: linear-gradient(180deg, #f1dfdd, #e4c5c2);
    box-shadow: inset 0 0 0 1px rgba(46, 41, 60, .28);
    color: #2e293c;
}
.clo-award-seg--participation {
    background: linear-gradient(180deg, #d8e9ea, #bcd8da);
    box-shadow: inset 0 0 0 1px rgba(4, 120, 138, .38);
    color: #0b555c;
}

/* The flag rides the top edge, so it must clear the card: the row below reserves
   the space it hangs into. */
.clo-award-flag {
    position: absolute; top: -14px; left: 50%; transform: translateX(-50%);
    padding: 6px 16px; border-radius: 99px; white-space: nowrap;
    background: linear-gradient(135deg, #e8717f, var(--brand));
    color: #231f2e;
    font-size: .72rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
    box-shadow: 0 10px 20px -10px rgba(134, 24, 36, .95);
}

/* Gold / Silver / Bronze — the prize band. Deepest fill, largest medal, largest
   type, a ring of its own colour, and the only card that leaves the row. */
.clo-award--gold {
    border-color: rgb(var(--brand-rgb) / .8);
    /* Deep enough to hold its own against the panel. A paler wash read as cream
       on cream: the card the section is built around cannot be the one you have
       to look for. */
    background: linear-gradient(180deg, #f5e7e6, #f3b6bd);
    box-shadow: 0 0 0 3px rgb(var(--brand-rgb) / .26),
                0 28px 48px -24px rgba(134, 24, 36, .95);
}
.clo-award--gold .clo-award-band { font-size: clamp(1.6rem, 3.4vw, 2.1rem); }

/* Honourable mention — pewter. */
.clo-award--honorable {
    border-color: rgba(46, 41, 60, .26);
    background: linear-gradient(180deg, #fdfaf9, #f2e1e0);
}

/* Participation — everyone receives it, so it sits quietest and nearest the
   panel it is printed on. */
.clo-award--participation {
    border-color: rgba(4, 120, 138, .3);
    background: linear-gradient(180deg, #fdfaf9, #f0e4e3);
}

@media (min-width: 880px) {
    /* Three across. Below this the cards would be too narrow for "will receive a
       Gold, Silver or Bronze Award Certificate" to break tidily, so they stack. */
    .clo-awards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; padding-top: 14px; }
    /* The podium step. Only here: stacked, a raised card is just a gap. */
    .clo-award--gold { transform: translateY(-14px); }
}
@media (max-width: 879px) {
    /* Stacked, the flag hangs into the gap above the first card rather than into
       the panel's own padding, so the row needs its own clearance. */
    .clo-awards { padding-top: 14px; }
}


/* ---- 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. */

.clo-closing {
    text-align: center;
    background: linear-gradient(135deg, var(--brand-100) 0%, var(--water-soft) 100%);
    border-color: rgba(156, 31, 46, .25);
}
.clo-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. */
.clo-closing p { max-width: none; margin: clamp(20px, 3vw, 28px) auto 0; }
.clo-closing .btn { padding: 16px 34px; font-size: 1.05rem; }

/* ============================================================================
   THE GLYPH LAYER — tablets, brackets, transcription rules and tally marks
   ----------------------------------------------------------------------------
   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 MyCLO palette landed on the colours — plum, rose, lilac
   paper — but every ornament on the site was still a circle, a ring or a
   gradient band inherited from the siblings. Nothing was shaped like the
   subject. These are the shapes, and they are taken from WRITING and from the
   ANALYSIS of writing: a tablet of unread script, a phonetic bracket, a
   substitution grid, a transcription rule, a tally of lines.

   That distinction is the whole design rule here, and it is worth keeping.
   MyCLO's subject is not language as speech and it is not computing: it is what
   you do with a page of symbols you cannot yet read. So there are no mouths,
   waveforms or speech bubbles in this set, and no terminals, braces or binary
   either. Anything added later should come off a page, or off the working
   somebody did beside it.

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

       page backdrop   a whisper-faint scatter of glyphs and grids, tiled
       .clo-glyphs     tablets, brackets and substitution grids in the margins
       .page-hero      a bracketed transcription either side of every inner hero
       .clo-hero       a ruled baseline along the foot of the home page's page
       .clo-head       a transcription rule under every section title
       .clo-band       the same ruled baseline along the foot of every band
       .site-footer    tally ticks along the top edge, like a manuscript's count

   The artwork is held in custom properties so a tile used in two places (the
   ruled baseline 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 {
        /* No --clo-rule token any more: the section divider is a plain CSS shape,
       drawn at .clo-head::after further down this file. See the note there for
       the two ornaments it replaced and why both were wrong. */

    /* The ruled baseline: the lines a page is written on, with a run of script
       set on them. Tiled horizontally along the foot of a band. The seam is at
       x=0/220, where nothing sits.

       Two ruled lines and a run of marks, in the three heights a real script
       uses — ascenders, x-height bodies, descenders. Drawn as marks rather than
       as an even comb because a comb reads as a fence: it is the UNEVENNESS of
       writing that makes a row of strokes read as language, and at 22px tall
       that difference is the only thing the eye has to go on. */
    --clo-baseline-tile: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 22'><g fill='none' stroke='%235f5470' stroke-linecap='round'><path d='M0 18h220' stroke-width='1.4' opacity='.3'/><path d='M0 6h220' stroke-width='1' opacity='.14'/><g stroke-width='1.7' opacity='.34'><path d='M8 18V8M8 12h5M20 18V5M20 18h6M34 18v-9a4 4 0 0 1 8 0v9M52 18V7M52 11h5'/></g><g stroke-width='1.6' opacity='.26'><path d='M72 18V6M72 18h6M86 18v-8M82 10h8M100 18V9a4 4 0 0 1 8 0M100 22v-4M118 18V7'/></g><g stroke-width='1.7' opacity='.3'><path d='M138 18V5M134 5h9M152 18v-9a4 4 0 0 1 8 0v9M170 18V8M170 12h5M184 18V6M184 18h6M198 18v-8'/></g></g></svg>");

    /* The hero bracket: a phonetic bracket with a transcribed run inside it and
       a stress mark above. Drawn opening to the right; the left-hand copy is
       mirrored in CSS rather than redrawn. The dot at the outer corner is the
       one filled mark, so the pair has a point the eye can land on. */
    --clo-bracket-art: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 80'><g fill='none' stroke='%236b2e90' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round' opacity='.7'><path d='M30 8H14v64h16'/><path d='M86 8h-8'/></g><g fill='none' stroke='%233d1b57' stroke-width='2' stroke-linecap='round' opacity='.55'><path d='M34 56V32M30 32h9M50 56V38a5 5 0 0 1 10 0v18M72 56V34M72 44h7'/></g><g fill='none' stroke='%233d1b57' stroke-width='1.7' stroke-linecap='round' opacity='.35' stroke-dasharray='5 5'><path d='M30 68h56'/></g><path d='M34 24l5-8' fill='none' stroke='%236b2e90' stroke-width='2' stroke-linecap='round' opacity='.5'/><circle cx='86' cy='8' r='2.6' fill='%23e0447a' opacity='.85'/></svg>");

    /* Tally ticks along the footer's top edge — the line count ruled down the
       margin of a manuscript, where every fifth line is marked long. The dark
       panel then reads as the leaf the page is written on.

       Plum, NOT the footer's own pale ink. The ticks are rooted on the footer's
       top border but rise out of it, so every pixel of them is drawn on the
       lilac section above — in --footer-muted (#e0aeb0, chosen for dark ground)
       they came out as faint scratches on nothing. */
    --clo-tick-tile: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 18'><g fill='none' stroke='%233d1b57' stroke-width='1.8' stroke-linecap='round' opacity='.34'><path d='M8 18v-5M24 18v-5M40 18v-5M56 18v-5M72 18v-5M88 18v-5M104 18v-5M120 18v-5M136 18v-5M152 18v-5'/><path d='M8 18V6M88 18V6' stroke-width='2.1'/></g></svg>");

    /* The backdrop scatter: two glyph clusters, a small substitution grid and
       three alignment crosses on a 440px tile. Deliberately sparse and almost
       transparent — at ~.055 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.

       440, not the 320 the sibling site drew its scatter at. The shapes are the
       right size at 320 but the repeat put ~50 of them on a 1600x1100 screen,
       and clo-bg.js is already drifting glyphs 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 marks start competing with the ones in the margins. */

    /* ---- Light-ink set, for the deep workshop band --------------------------
       Same three figures, redrawn in lilac paper instead of plum. A recolour is
       genuinely needed rather than a filter or an opacity tweak: the dark set is
       #2e293c on lilac, and #2e293c on a #2e293c band is nothing at all. The
       band is the one big opaque surface on the site, so the page-wide backdrop
       scatter cannot show through it either — without these it is the only
       section with no motif on it, which is what prompted them.

       Alphas run higher than the light-ground originals (.05 -> .09ish). Pale
       ink on dark ground reads weaker at the same opacity, and the band is a
       large unbroken field where too little just looks like a rendering
       artefact. */
    --clo-ground-tex-light: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 440 440'><g fill='none' stroke='%23efe4f8' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' opacity='.09'><path d='M52 120V88M52 100h8M70 120V84M70 120h9M92 120v-14a6 6 0 0 1 12 0v14'/><path d='M300 388V356M300 366h8M320 388v-18a6 6 0 0 1 12 0v18M320 396v-8'/></g><g fill='none' stroke='%23e0447a' stroke-width='2.2' stroke-linecap='round' opacity='.10'><path d='M328 118h80v52h-80z'/><path d='M328 144h80M354 118v52M382 118v52'/></g><g fill='none' stroke='%23c3a0e0' stroke-width='2' stroke-linecap='round' opacity='.09'><path d='M158 290h14M165 283v14M258 66h14M265 59v14M98 344h14M105 337v14'/></g></svg>");

    /* A seal rising out of the band's bottom edge — a ring of script around a
       centred mark, the stamp pressed at the foot of a finished page. Cropped by
       the band's own box. Placed as a background layer rather than a
       pseudo-element because the band's ::before is the top seam and its ::after
       is the ruled baseline, and backgrounds clip to the border box for free so
       nothing can spill past the section. */
    --clo-seal-light: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 124 124'><g fill='none' stroke='%23efe4f8' stroke-width='2.2' opacity='.12'><circle cx='62' cy='62' r='42'/><circle cx='62' cy='62' r='33'/></g><g fill='none' stroke='%23efe4f8' stroke-width='2.4' stroke-linecap='round' opacity='.14'><path d='M62 30v-8M62 94v8M30 62h-8M94 62h8M40 40l-6-6M84 84l6 6M84 40l6-6M40 84l-6 6'/></g><g fill='none' stroke='%23efe4f8' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' opacity='.16'><path d='M52 74V50M52 60h7M68 74V48a5 5 0 0 1 10 0'/></g></svg>");

    /* The same seal in plum, for the rose .clo-band--deep on light ground. Kept
       as its own token rather than filtering the light one: these two are the
       same drawing in the site's two inks, and a filter chain on a background
       image cannot be scoped to one layer of a stack anyway. */
    --clo-seal-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 124 124'><g fill='none' stroke='%236b2e90' stroke-width='2.2' opacity='.12'><circle cx='62' cy='62' r='42'/><circle cx='62' cy='62' r='33'/></g><g fill='none' stroke='%233d1b57' stroke-width='2.4' stroke-linecap='round' opacity='.10'><path d='M62 30v-8M62 94v8M30 62h-8M94 62h8M40 40l-6-6M84 84l6 6M84 40l6-6M40 84l-6 6'/></g><g fill='none' stroke='%233d1b57' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' opacity='.13'><path d='M52 74V50M52 60h7M68 74V48a5 5 0 0 1 10 0'/></g></svg>");

    /* The ruled baseline for the band's foot, in pale ink rather than the muted
       grey the light-ground bands use. Same marks, same 220x22 tile. */
    --clo-baseline-tile-light: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 22'><g fill='none' stroke='%23efe4f8' stroke-linecap='round'><path d='M0 18h220' stroke-width='1.4' opacity='.26'/><path d='M0 6h220' stroke-width='1' opacity='.12'/><g stroke-width='1.7' opacity='.3'><path d='M8 18V8M8 12h5M20 18V5M20 18h6M34 18v-9a4 4 0 0 1 8 0v9M52 18V7M52 11h5'/></g><g stroke-width='1.6' opacity='.22'><path d='M72 18V6M72 18h6M86 18v-8M82 10h8M100 18V9a4 4 0 0 1 8 0M100 22v-4M118 18V7'/></g><g stroke-width='1.7' opacity='.26'><path d='M138 18V5M134 5h9M152 18v-9a4 4 0 0 1 8 0v9M170 18V8M170 12h5M184 18V6M184 18h6M198 18v-8'/></g></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 lilac 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. */
/* (The second ground sheet that used to sit here — the tiled glyph scatter over
   four more blooms — is gone. It is what THE GROUND at the top of this file
   replaced; body::before is neutralised there and nothing repaints it.) */

/* NOTE — no per-page ground override lives here, and that is a decision rather
   than an omission.

   The sibling Geography site washes its three international pages in a second
   colour, because there the international olympiad is three pages of its own and
   the ground can carry the split. MyCLO keeps its two international write-ups —
   IOL 2025 and IOL 2023 — as sections of /about-us, which is where the live site
   keeps them. A ground that changed halfway down a page would read as a
   rendering fault, not as a category, so the split is carried inside the page by
   .clo-band instead. Nine pages, one paper.
   ========================================================================== */

/* ---- The gutter layer — REMOVED --------------------------------------------
   Tablets, phonetic brackets and a substitution grid, planted fixed in the two
   empty margins above 1360px and slowly rotating. It is gone, along with the
   canvas that drifted more marks behind it, because both were ornament attached
   to the PAGE rather than to a surface — which is the rule the whole set now
   follows. What replaced them is THE GROUND further up this file: one wash, one
   regular motif tile, grain over both.

   The .f-tablet-*, .f-grid-* and .f-brackets-* positioning rules went with it,
   as did @keyframes clo-drift and the 1360px breakpoint that gated the layer.
   The motif itself did not: it still appears on every surface that owns one —
   the hero baseline, the rule under each section head, the seam on every tinted
   band, the seals on the workshop band, the tally ticks in the footer. */

/* ---- Hero brackets ---------------------------------------------------------
   Every inner page opens with `section.container.page-hero`, so one pair of
   pseudo-elements here reaches About, Format of Question, Study Guide, Contact,
   FAQs, Past-Year Questions, both results pages and the 404.

   A bracket either side is the one ornament that gains from being a PAIR rather
   than a repeat: [ and ] enclose the title between them, which is exactly what a
   transcription bracket does to the run it is written around.

   They are pinned to the container's outer edges. The hero's text is centred and
   measure-capped well inside that, so the brackets 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(--clo-bracket-art) center / contain no-repeat;
    opacity: .5;
    pointer-events: none;
}
.page-hero::before { left: 2px; scale: -1 1; }   /* mirrored, so the pair encloses the title */
.page-hero::after  { right: 2px; }
/* Under ~1000px the hero's copy starts to use the full container and the
   brackets would crowd it. They go, and the section divider carries the
   motif. */
@media (max-width: 1000px) {
    .page-hero::before, .page-hero::after { display: none; }
}

/* ---- Hero foot -------------------------------------------------------------
   The home page has no .page-hero — it opens on .clo-hero, whose ground is a
   stack of ruled bands. Brackets in its corners would fight the brand mark, so
   it gets the ruled baseline instead: a line of script set along the foot of the
   page, which is what the bands above it are ruled FOR. .clo-hero already clips
   its overflow and isolates its stacking context, so this stays inside both. */
.clo-hero::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 30px;
    background: var(--clo-baseline-tile) bottom center / 300px 30px repeat-x;
    opacity: .55;
    pointer-events: none;
}

/* ---- Section divider -------------------------------------------------------
   .clo-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. */
/* The divider itself: a short rounded bar, rose into lilac.

   Two ornaments came before it and both were wrong in the same way — they were
   trying to say something.

   The first was a hairline with ticks at each end, which is the Geography site's
   neatline with different end-caps: same line, same three marks, same silhouette
   at a glance.

   The second replaced it with three marks from this site's invented script, set
   as a printer's asterism. It was genuinely MyCLO's and it did not read as a
   divider — at 20px on a page of real text, three unfamiliar glyphs read as
   symbols somebody had left there, not as a break.

   A divider's whole job is to be recognised instantly and then ignored. It
   repeats a dozen times on the home page; it is furniture. So it is a bar: no
   data URI, no marks, nothing to decode. The gradient is the only brand in it,
   and that is enough. */
.clo-head::after {
    content: "";
    width: 64px;
    height: 4px;
    margin-top: 14px;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--brand), var(--accent-3));
}
/* The preparatory-workshop section runs on a dark panel, where the rose end of
   the gradient sits too close to the ground behind it. Flattened to white and
   dropped back. */
.clo-workshop .clo-head::after {
    filter: brightness(0) invert(1);
    opacity: .45;
}

/* ---- Band baseline ---------------------------------------------------------
   A tinted band already carries a seam at its top edge (.clo-band::before
   above). This closes the other edge with the ruled baseline the band's copy is
   written on. It lives in the band's bottom padding — clamp(32px, 5vw, 64px) —
   so there is always room for 22px of rule under the last element.

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

/* ---- Footer edge -----------------------------------------------------------
   Tally ticks along the top of the footer — the line count ruled down a
   manuscript's margin. Offset by its own height so the ticks rise 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(--clo-tick-tile) bottom center / 160px 18px repeat-x;
    pointer-events: none;
}

/* ---- Study Guide: the download cards ---------------------------------------
   A drawn mark, the title and its note, and a chevron. The live MyCLO page
   presents its three preparation documents as icon-over-title tiles; this keeps
   the icon but turns the tile on its side, because the three titles — "Types of
   OL Problems", "Sample Problems", "Sample Solutions" — are told apart by their
   words, not by their marks, and a row gives the words the width to be read at a
   glance.

   Grid rather than flex on the row, because the three parts have different
   jobs: the mark is a fixed 72px column, the meta takes whatever is left, and
   the chevron is pinned to the end. A flex row with margin-auto on the chevron
   does the same thing until the title wraps, at which point the chevron drifts. */
.book-list { display: grid; gap: 12px; }
.book-row {
    display: grid;
    grid-template-columns: 72px minmax(0, 1fr) auto;
    align-items: center;
    gap: 16px;
    padding: 12px 16px;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface);
    color: inherit;
    text-decoration: none;
    transition: border-color .2s, box-shadow .2s, transform .2s;
}
.book-row:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
    transform: translateY(-2px);
}
.book-cover {
    display: flex; align-items: center; justify-content: center;
    width: 72px; height: 88px;
    border-radius: 8px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    overflow: hidden;
}
.book-cover img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.book-cover--icon { color: var(--accent); }
.book-meta { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.book-meta b { font-size: 1.02rem; line-height: 1.35; }
.book-sub { font-size: .85rem; color: var(--text-mute); }
.book-go { color: var(--accent); flex: none; transition: transform .2s; }
.book-row:hover .book-go { transform: translateX(3px); }

@media (max-width: 520px) {
    /* the chevron is decoration and the whole row is the target, so it goes
       first when width is short — the cover and the title are the content */
    .book-row { grid-template-columns: 56px minmax(0, 1fr); gap: 12px; padding: 10px 12px; }
    .book-cover { width: 56px; height: 70px; }
    .book-go { display: none; }
}


/* The sibling Geography site carries a second copy of the year block sized for
   a page HERO, because there each olympiad year is a page of its own. MyCLO
   keeps its two international write-ups as sections of /about-us — which is
   where the live site keeps them — so the year block only ever appears inside a
   card, and the hero variant has been dropped rather than left as dead rules
   nothing on nine pages can select.
   ========================================================================== */

/* ---- The closed-registration chip -----------------------------------------
   Matched to the sibling sites: a lock, Title Case, and a SOLID border.

   redesign.css gives this a dashed border, which on a pill the size of a button
   reads as a dropzone — the one thing it is not. It is a statement, not an
   invitation, and a solid hairline says that without shouting.

   Centred when it stands alone in its block. The workshop's is the one that
   spends most of the year closed, and left-aligned under a centred "How to
   register" list it read as a button someone had forgotten to finish. The
   :has() rule catches every other lone chip — the hero and header keep their
   flex rows, where text-align does nothing, so nothing there moves. */
.btn-closed {
    border-style: solid;
    border-color: var(--border-strong);
    background: var(--surface-2);
    color: var(--text-soft);
    letter-spacing: .01em;
}
.btn-closed .gx-icon { color: currentColor; opacity: .85; }

.clo-wk-cta { text-align: center; }
p:has(> .btn-closed:only-child) { text-align: center; }

/* NO inverted variant inside .clo-workshop. The band behind that section is dark,
   but the chip does not sit on the band — it sits inside .clo-wk-block, which is
   background: #fff. An earlier version inverted it here on the assumption that
   "dark section" meant "dark ground", and painted white text on a white card:
   the chip rendered as pale ghost text with no pill and no border at all.

   If a chip ever does need to sit on the band itself, scope it to that element
   rather than to the section. */

/* The venue's sub-note — the Zoom-link aside. Small and italic so the Venue
   column stays level with Date and Time beside it: as a second full-size line it
   made that card twice the height of its neighbours and the row of three read as
   one long fact and two short ones.

   Selector matched to the rule it has to beat. `[data-theme="warm"]
   .clo-workshop .gx-glass .clo-wk-fact p` above is (0,4,1) and sets margin: 0
   and font-weight: 600 on every <p> in these cards, so a bare .clo-wk-fact-note
   at (0,1,0) lost both — the note sat flush against the venue line at full
   weight, which is most of what made it read as a second line rather than an
   aside. The weight is dropped back here too, for the same reason. */
[data-theme="warm"] .clo-workshop .gx-glass .clo-wk-fact .clo-wk-fact-note {
    margin: 5px 0 0;
    font-size: .84rem;
    font-style: italic;
    font-weight: 500;
    line-height: 1.45;
    opacity: .85;
}

/* Olympiad Moments — the sub-headings inside a year, and the cross-link out to
   the full write-up on About. A year runs lead → camp → result, and without a
   marked break the three read as one long column of prose. */

/* ---- Camp photographs, as a strip -----------------------------------------
   The live …-igeo-myteam pages run these in a Swiper carousel showing three at
   a time. Ours was the full grid: 21 photographs stacked six rows deep between
   the training-camp card and the result, which pushed the result off the screen
   and made the year read as a photo album with some text attached.

   A scroll-snapping strip, not a carousel with JS. It swipes on touch, scrolls
   with the wheel, and takes arrow keys because it is focusable — which is
   everything the Swiper was doing, minus the library. The site had exactly this
   pattern before as .faces-strip; this is that idea back where it earns its
   keep, on the one gallery long enough to need it.

   proximity, not mandatory: mandatory re-snaps during a flick, so a fast swipe
   is dragged back to the nearest photo instead of coasting. */
.moment-strip {
    display: flex;
    gap: 14px;
    overflow-x: auto;
    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;
    margin: clamp(18px, 2.4vw, 26px) 0 0;
    padding-bottom: 12px;
    scrollbar-width: thin;
}
.moment-strip figure { margin: 0; flex: 0 0 auto; scroll-snap-align: start; }
.moment-strip img {
    display: block;
    width: clamp(210px, 24vw, 300px);
    /* One ratio at every width: clamping width and height independently made the
       crop change between breakpoints on the same photograph. */
    aspect-ratio: 4 / 3;
    height: auto;
    object-fit: cover;
    border-radius: 12px;
    border: 1px solid var(--border);
}
.moment-strip figcaption {
    margin-top: 8px;
    max-width: clamp(210px, 24vw, 300px);
    font-size: .85rem;
    color: var(--text-soft);
}
.moment-strip:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 4px;
    border-radius: 12px;
}

/* A part heading, sitting on the page between two cards rather than inside one —
   the shape the live …-igeo-myteam pages use, and the reason a year now reads as
   three parts instead of one wall of prose. Centred, because it labels the card
   under it and the year heading above is centred too.

   Larger than it was: at 1.08rem inside a panel it was a run-in label, and out
   here it has to hold its own between two cards. */
.moment-sub {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin: clamp(30px, 4vw, 46px) auto clamp(14px, 1.6vw, 18px);
    font-size: clamp(1.15rem, 1.9vw, 1.5rem);
    line-height: 1.2;
    text-align: center;
    color: #231f2e;
}
.moment-sub .gx-icon { color: var(--accent); flex: none; }
/* Cards no longer butt against each other: each part is its own block, so the
   gap between them is what separates the passages. */
.moment-card + .moment-sub { margin-top: clamp(30px, 4vw, 46px); }
.moment-more { margin: 14px 0 0; font-size: .94rem; }
.moment-more a { font-weight: 600; }

/* Ragged right on a phone.
   ---------------------------------------------------------------------------
   .clo-justify sets justified text at every width, and the note beside that
   rule names ragged-right as the fallback "if the spacing is ever too loose".
   On a 390px screen it is: the panel measure drops to ~340px, roughly 40
   characters, and with `hyphens: none` the only place the browser can take up
   slack is the word gaps. A one-line paragraph is the worst of it — a closing
   line of eight words stretched the full width, with gaps wide enough to read as
   columns.

   This does NOT contradict the decision documented on .clo-intro-lead, which
   deliberately justifies at all widths. That block is the reverse case: it
   shares the Introduction panel with the objectives list above 900px and takes
   the full container below it, so its measure is WIDEST on a phone. Here the
   measure is narrowest on a phone, so the same reasoning lands the other way.

   640px, not 900: at 640 the measure is still ~590px and justifies cleanly.
   The break is where the spacing goes, not where the layout does. */
@media (max-width: 640px) {
    .clo-justify p,
    .clo-justify li {
        text-align: left;
    }
}

/* ---- The playlist on a narrow phone -------------------------------------
   The row is a three-column grid: a 92px still, the title, and the run time.
   At 320-430px that left the TITLE about 55px wide — one word per line for a
   45-character tutorial name, five rows of it down the panel.

   Below 480px the still drops to 64px and the run time moves under the title,
   so the title gets the full remaining width instead of sharing the row with
   two fixed columns. Nothing is hidden: the still is decorative (alt="") and
   the run time is still read, just on its own line.

   align-items: start here rather than centre, because the right column now
   holds two stacked lines and centring them against a 36px still left the
   title sitting below the top of the row. */
@media (max-width: 480px) {
    .video-item {
        grid-template-columns: 64px minmax(0, 1fr);
        align-items: start;
        gap: 9px;
    }
    .video-thumb { width: 64px; height: 36px; grid-row: span 2; }
    /* break-word, not the `anywhere` this span INHERITS from its <li>.
       overflow-wrap is an inherited property, and redesign.css sets `anywhere`
       on li — so the title was breaking at any character and set
       "Teacher-in-Char / ge" even though the content spells it with
       non-breaking hyphens precisely to hold it together. break-word breaks a
       word only when it genuinely cannot fit, which at this column width it
       can. */
    .video-name { grid-column: 2; overflow-wrap: break-word; }
    .video-time { grid-column: 2; justify-self: start; }
}

/* ---- …and the still goes entirely on the narrowest phones ----------------
   Measured, not guessed: at 320px the 64px still leaves the title a 124px
   column, and "Teacher-in-Charge" needs 143px in this face. It cannot fit, so
   it breaks mid-word as "Teacher-in-Char / ge" — and the content spells that
   name with NON-BREAKING hyphens (U+2011) precisely to stop it being split.
   No wrap setting can honour that in a column narrower than the word.

   So below 400px the still is dropped and the title takes the whole row,
   which gives it about 197px and lets the non-breaking hyphens do their job.
   The still is the only thing lost and it is decorative — alt="" — while the
   title is the thing a reader picks the video by. */
@media (max-width: 400px) {
    .video-item { grid-template-columns: minmax(0, 1fr); }
    .video-thumb { display: none; }
    .video-name, .video-time { grid-column: 1; }
}

/* Ragged right on a wide screen — the other end of the same fallback.
   ---------------------------------------------------------------------------
   The justify rule names TWO supported states, "justify + hyphens:none" and
   "ragged-right … the fallback if the spacing is ever too loose", and the note
   on the uncapped measure spells out when the second is needed:

       "at 1440px a line runs to roughly 100 characters, which is past the ~80
        where the eye starts losing its place on the return sweep, and justified
        text has no ragged edge left to navigate by. If that ever reads badly,
        the fix is the fallback already named above — ragged right — NOT a
        re-cap of the measure."

   That condition is met. Measured on /about-us: the justified column is 542px
   (~59ch) at 640px wide, 737px (~80ch) at 860, and 1042px (~113ch) from 1200
   up, where the container stops growing. 113 characters with no hyphenation and
   no ragged edge is the case the note describes.

   So this is the SAME fallback the narrow end already uses — there is a
   @media (max-width: 640px) rule doing exactly this, for the opposite reason
   (at ~40ch the word gaps open too wide). Justification now holds the band
   between them, 641-999px, which is ~59-93ch: the range where it sets cleanly.
   The note's own line covers why the two ends differ — "the break is where the
   spacing goes, not where the layout does."

   1000px, measured rather than picked: at 960 the measure is ~90ch and the
   audit passes it; at 1024 it is ~96ch and the audit flags it. The breakpoint
   sits where the measurement crosses, not on a device boundary.

   THE MEASURE IS STILL NOT CAPPED, deliberately, and re-capping it is the one
   thing the note rules out: it was tried, and it centred the copy inside a
   panel whose photo grids and year headings span the full width, so the text
   read as a narrow column that had missed its box.

   Reverting is deleting this block. */
@media (min-width: 1000px) {
    .clo-justify p,
    .clo-justify li {
        text-align: left;
    }
}
