/* =============================================================
   Biology Explorer Competition — Modern Biology Theme (redesign)
   Design system: tokens, base, layout, components
   ============================================================= */

/* Visually hidden but available to screen readers (headings, labels). */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Skip-to-content link — off-screen until focused, then pinned top-left. */
.skip-link {
    position: fixed; top: 8px; left: 8px; z-index: 1000; transform: translateY(-160%);
    background: var(--grad-brand, #0e9d8f); color: #fff; padding: 10px 18px; border-radius: 10px;
    font-weight: 600; text-decoration: none; box-shadow: 0 10px 30px rgba(0,0,0,.4);
    transition: transform .2s;
}
.skip-link:focus { transform: translateY(0); outline: 2px solid #fff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .skip-link { transition: none; } }

/* Scroll-to-top button (bottom-left, appears after scrolling down) */
.to-top { position: fixed; left: 24px; bottom: 24px; z-index: 150;
    width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center; cursor: pointer;
    color: #fff; border: 1px solid rgba(120, 230, 175, .5);
    background: linear-gradient(140deg, #0d8578 0%, #0f7e98 100%);
    box-shadow: 0 12px 30px rgba(10,90,90, .55), 0 0 22px rgba(60,210,196, .4);
    opacity: 0; visibility: hidden; transform: translateY(14px); transition: opacity .28s, transform .28s, visibility .28s, box-shadow .2s; }
.to-top.show { opacity: 1; visibility: visible; transform: translateY(0); }
.to-top:hover { transform: translateY(-3px); box-shadow: 0 18px 44px rgba(12,100,100, .65), 0 0 34px rgba(60,210,196, .6); }
@media (max-width: 600px) { .to-top { left: 16px; bottom: 16px; width: 48px; height: 48px; } }

/* Floating WhatsApp button — bottom-right, opposite the scroll-to-top on the left */
.wa-float { position: fixed; right: 24px; bottom: 24px; z-index: 150;
    width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center;
    color: #fff; background: #25D366; box-shadow: 0 10px 28px rgba(37, 211, 102, .45);
    transition: transform .2s, box-shadow .2s; }
.wa-float:hover { transform: translateY(-3px) scale(1.05); box-shadow: 0 14px 34px rgba(37, 211, 102, .6); }
.wa-float:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
@media (max-width: 600px) { .wa-float { right: 16px; bottom: 16px; width: 52px; height: 52px; } }
@media (prefers-reduced-motion: reduce) { .wa-float { transition: none; } }

/* ---------- Self-hosted Poppins (no external font dependency) ----------
   Two @font-face rules per weight, split by unicode-range: `-ext` is the
   Latin-Extended subset, the unsuffixed file is Latin. They must NOT be two
   urls in one src — a comma-separated src is a FALLBACK chain, so only the
   first file is ever fetched and Latin-Extended glyphs silently drop to a
   system font. With unicode-range the browser fetches whichever subset the
   page actually needs (usually just Latin here, so -ext costs nothing). */
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 400; font-display: swap;
    src: url('/fonts/poppins/poppins-400-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 400; font-display: swap;
    src: url('/fonts/poppins/poppins-400.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 500; font-display: swap;
    src: url('/fonts/poppins/poppins-500-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 500; font-display: swap;
    src: url('/fonts/poppins/poppins-500.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 600; font-display: swap;
    src: url('/fonts/poppins/poppins-600-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 600; font-display: swap;
    src: url('/fonts/poppins/poppins-600.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 700; font-display: swap;
    src: url('/fonts/poppins/poppins-700-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 700; font-display: swap;
    src: url('/fonts/poppins/poppins-700.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 800; font-display: swap;
    src: url('/fonts/poppins/poppins-800-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 800; font-display: swap;
    src: url('/fonts/poppins/poppins-800.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

:root {
    /* Deep-space background scale (kept dark). Brand palette is TURQUOISE + GOLD,
       intentionally different from the sibling MyAO app's blue/violet. Turquoise sits
       close to the BEC logo's cyan (#60C0E0), so it still reads on-brand. */
    --bg-950: #04050e;
    --bg-900: #070a1c;
    --bg-850: #0b1024;
    --bg-800: #10162e;
    --bg-700: #1b2450;
    --surface: rgba(255, 255, 255, .045);
    --surface-2: rgba(255, 255, 255, .07);
    --border: rgba(110,215,200, .16);
    --border-strong: rgba(110,215,200, .32);

    /* Brand + accents — TURQUOISE primary + GOLD accent (not MyAO's blue/violet). */
    --brand: #25cbbb;
    --brand-600: #0e9d8f;
    --brand-glow: rgba(37,203,187, .55);
    --violet: #1f9fb8;
    /* alias kept so existing markup using --violet-accent keeps working; it is now an
       aqua-teal partner to the turquoise brand (no longer violet). */
    --violet-accent: #1f9fb8;
    --teal: #3fd6c0;
    --gold: #ffcf6a;
    --pink: #ffcf6a;

    /* Text */
    --text: #eef1ff;
    --text-soft: #c3cbf0;
    --muted: #8f9bd0;

    /* Gradients */
    /* Deep enough that white text on it clears WCAG AA.
       Used by every button and numbered badge, so contrast here pays off site-wide. */
    --grad-brand: linear-gradient(135deg, #0d8578 0%, #0f7e98 100%);
    --grad-gold: linear-gradient(135deg, #ffd679 0%, #ff9d5c 100%);
    --grad-text: linear-gradient(120deg, #b8ede6 0%, #ffffff 40%, #d6f3ee 100%);
    /* vibrant, playful section-title gradient (kids' event) — brighter than --grad-text */
    --grad-title: linear-gradient(100deg, #3fe3d4 0%, #2ec6d0 46%, #7fe0d0 100%);

    /* Shape + depth */
    --r-sm: 12px;
    --r-md: 18px;
    --r-lg: 28px;
    --r-pill: 999px;
    --shadow-sm: 0 6px 20px rgba(0, 0, 0, .35);
    --shadow-md: 0 18px 50px rgba(2, 6, 23, .55);
    --shadow-glow: 0 0 0 1px var(--border), 0 20px 60px rgba(30,170,160, .18);

    --container: 1180px;
    --nav-h: 76px;

    /* Footer surface — its own token set so the footer can be a distinct panel
       (deep ink in warm, near-black in dark) without every .footer rule needing a
       per-theme override. */
    --footer-bg: linear-gradient(170deg, rgba(6,9,24,.92) 0%, rgba(4,6,18,.96) 100%);
    --footer-text: #eef1ff;
    --footer-muted: #9aa6d8;
    --footer-border: rgba(110,215,200, .16);
    --footer-accent: var(--brand);   /* link hover + contact icons ON the footer panel */

    --font-display: 'Poppins', system-ui, sans-serif;
    --font-body: 'Poppins', system-ui, -apple-system, sans-serif;

    /* Type scale — fluid clamps, one source of truth. Values chosen to match (and
       unify) the sizes that were previously hardcoded per selector. */
    --fs-eyebrow: .78rem;
    --fs-body:    1rem;
    --fs-lead:    clamp(1rem, 1.6vw, 1.18rem);
    --fs-h4:      clamp(1.05rem, 1.6vw, 1.3rem);
    --fs-h3:      clamp(1.25rem, 2.1vw, 1.4rem);
    --fs-h2:      clamp(1.9rem, 4.2vw, 3.1rem);
    --fs-h1:      clamp(2.2rem, 5.5vw, 3.6rem);
    --fs-display: clamp(2.6rem, 6.5vw, 5rem);   /* home hero lockup */

    /* Font weight */
    --fw-regular:  400;
    --fw-medium:   500;
    --fw-semibold: 600;   /* default heading weight */
    --fw-bold:     700;   /* display + section titles */
    --fw-extrabold:800;   /* counters / stat numbers */

    /* Line-height */
    --lh-tight:   1.1;    /* display headings */
    --lh-snug:    1.3;    /* sub-headings */
    --lh-body:    1.65;
    --lh-relaxed: 1.75;

    /* Letter-spacing */
    --ls-display: -.02em; /* very large display reads tightest */
    --ls-tight:   -.01em; /* headings */
    --ls-label:   .18em;  /* eyebrows / small caps */

    /* Reading measure — max line length for comfortable reading */
    --measure:        66ch;
    --measure-narrow: 52ch;

    /* Spacing scale (4px base) — gaps, padding, margins */
    --sp-1: .25rem;  --sp-2: .5rem;   --sp-3: .75rem;  --sp-4: 1rem;
    --sp-5: 1.5rem;  --sp-6: 2rem;    --sp-7: 3rem;    --sp-8: 4rem;
}

/* ---------- Base ---------- */
* { box-sizing: border-box; }
/* overflow-x: clip, not hidden — hidden turns html/body into a scroll container and
   breaks position:sticky on the header. clip stops sideways scroll without that. */
/* scroll-padding-top keeps #anchor targets clear of the sticky header — without it
   a fragment link (e.g. /ibo#ibo2025) lands with its heading hidden behind the nav. */
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; overflow-x: clip;
    scroll-padding-top: calc(var(--nav-h) + 20px); }
html, body { max-width: 100%; }
h1, h2, h3, h4 { overflow-wrap: anywhere; }
/* Body copy needs the same escape hatch headings already have. Long slash-joined
   tokens ("diploma/degree/equivalent", "STPM/ STAM/ A-Levels/") have no break
   opportunity — browsers do not break after "/" — so inside a narrow card with
   overflow:hidden they were cut off mid-word at 375px.
   `anywhere`, NOT `break-word`: several of these list items are flex containers,
   so their text sits in a flex item whose automatic minimum size is min-content
   (the longest word). `break-word` breaks lines but explicitly does NOT reduce
   min-content, so the flex item still refused to shrink and the text still
   overflowed. `anywhere` reduces min-content too, which is what actually lets
   the item shrink. Same reason the heading rule above uses it. */
p, li, dd, dt, figcaption, blockquote, td, th { overflow-wrap: anywhere; }
body {
    margin: 0;
    font-family: var(--font-body);
    color: var(--text);
    background: var(--bg-950);
    line-height: 1.65;
    overflow-x: clip;
    -webkit-font-smoothing: antialiased;
}
/* height:auto so width/height attributes (added for CLS) scale by aspect ratio
   instead of forcing the literal pixel height. Elements that need a fixed size
   set it explicitly (.brand img, etc.) and override this. */
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea, optgroup { font-family: inherit; }
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: var(--fw-semibold); line-height: var(--lh-tight); margin: 0 0 .4em; letter-spacing: var(--ls-tight); }
/* the two top display tiers carry more weight so section titles read distinctly
   above card/sub headings (which stay semibold) */
.section-title, .gx-head h2, .page-hero h1 { font-weight: var(--fw-bold); }
/* any heading sitting directly under an eyebrow pill is a title → bold, so in-card
   titles (glass/prose cards) match the section titles rather than reading semibold */
.eyebrow + h1, .eyebrow + h2, .eyebrow + h3, .eyebrow + h4 { font-weight: var(--fw-bold); }
p { margin: 0 0 1rem; color: var(--text-soft); }
::selection { background: rgba(60,210,196, .35); }

/* Three.js animated biology canvas (drifting specimens) — fixed behind all content,
   above the body::before gradient (z-index -3) so that gradient still shows through */
#space-bg { position: fixed; inset: 0; z-index: -1; pointer-events: none; width: 100%; height: 100%; overflow: hidden; }
#space-bg canvas { display: block; width: 100% !important; height: 100% !important; }

/* Biology backdrop applied to <body> behind everything */
body::before {
    content: "";
    position: fixed; inset: 0; z-index: -3;
    /* Deep-space base. The PixiJS canvas (pixi-bg.js) paints its nebula/stars/cells
       over this on its transparent canvas; if WebGL/JS is unavailable, this subtle
       blue-violet space gradient is the fallback backdrop. */
    background:
        radial-gradient(1200px 800px at 72% -10%, rgba(16,120,130, .30), transparent 60%),
        radial-gradient(1000px 720px at 8% 112%, rgba(16,130,120, .24), transparent 60%),
        linear-gradient(180deg, var(--bg-950), var(--bg-900) 40%, var(--bg-950));
}

/* ---------- Layout ---------- */
.container { width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 24px; }
.section { padding: clamp(32px, 5vw, 64px) 0; position: relative; }
.section--tight { padding: clamp(22px, 3.5vw, 40px) 0; }
.center { text-align: center; }
.stack > * + * { margin-top: 1rem; }

.eyebrow {
    display: inline-flex; align-items: center; gap: 8px;
    font-family: var(--font-body); font-weight: 600; font-size: var(--fs-eyebrow);
    /* lighter than --brand: small uppercase text needs real contrast on the glass */
    letter-spacing: var(--ls-label); text-transform: uppercase; color: #7fdcd0;
    background: rgba(37,203,187, .1); border: 1px solid var(--border-strong);
    padding: 7px 15px; border-radius: var(--r-pill);
}
/* Inline flag artwork, for where an emoji flag would degrade to bare letters on
   Windows. Sized like an emoji; the parent's flex gap handles the spacing. */
.eyebrow .flag-my { flex: 0 0 auto; height: 1.15em; width: auto; display: block;
    border-radius: 2px; box-shadow: 0 0 0 1px rgba(255, 255, 255, .28); }
/* Inline SVG icons (<x-icon>) — inherit currentColor and scale with their text.
   In eyebrows they sit at ~1.25em and pick up the eyebrow's light-blue tone. */
.gx-ico { flex: 0 0 auto; display: inline-block; vertical-align: -0.18em; }
.eyebrow .gx-ico { width: 1.25em; height: 1.25em; opacity: .95; }
.section-title { font-size: var(--fs-h2); }
/* balance multi-line headings so a lone last word never strands on its own line
   (e.g. "…(IRAO) 2022"). Ignored gracefully by browsers without text-wrap support. */
.section-title, .page-hero h1, .hero-title, .gx-glass.prose h2, .gx-glass.prose h3 { text-wrap: balance; }
.grad-text {
    background: var(--grad-text); -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* section titles carry the brighter, playful gradient site-wide */
.section-title .grad {
    background: var(--grad-title); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.lead { font-size: var(--fs-lead); color: var(--text-soft); max-width: var(--measure); line-height: var(--lh-body); }
.center .lead { margin-inline: auto; }

/* Reading measure ------------------------------------------------------------
   Only .lead was actually capped, so body copy inside full-width cards was
   running 93–118ch (comfortable is 60–75ch; --measure is 66ch). This extends the
   cap to reading copy inside panels.
   Wrapped in :where() ON PURPOSE — that gives the rule ZERO specificity, so any
   page that has deliberately opted out (e.g. `.ibo-article p { max-width: none }`,
   which is a considered trade-off against empty space on that page's wide cards)
   keeps winning without needing !important or a re-order. This is a default,
   not an override. */
:where(.gx-glass p:not(.lead), .gx-glass li) { max-width: var(--measure); }
/* centred empty-state copy has to keep its centring once it stops being full-width */
.books-empty p, .pyq-empty p { margin-inline: auto; }
/* No text-shadow on headings/leads — per the user's preference, text stays flat with no
   dark halo behind it. Legibility relies on the light-on-dark colour contrast instead. */

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex; align-items: center; gap: 10px;
    font-family: var(--font-display); font-weight: 600; font-size: 1rem;
    padding: 14px 26px; border-radius: var(--r-pill); border: 0; cursor: pointer;
    transition: transform .25s cubic-bezier(.2,.8,.2,1), box-shadow .25s, filter .25s;
    line-height: 1; white-space: nowrap;
}
.btn-primary { background: var(--grad-brand); color: #fff; box-shadow: 0 10px 30px var(--brand-glow); }
.btn-primary:hover { transform: translateY(-3px); box-shadow: 0 16px 40px var(--brand-glow); filter: brightness(1.08); }
.btn-gold { background: var(--grad-gold); color: #3a2400; box-shadow: 0 10px 30px rgba(255, 170, 80, .45); }
.btn-gold:hover { transform: translateY(-3px); filter: brightness(1.06); }
.btn-ghost { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); backdrop-filter: blur(8px); }
.btn-ghost:hover { transform: translateY(-3px); background: var(--surface-2); border-color: var(--brand); }
.btn-sm { padding: 10px 18px; font-size: .9rem; }
/* Register CTAs after the registration window closes — see components/register-cta.blade.php.
   Occupies the same slot as the button it replaces so nothing shifts, but reads as
   unavailable rather than clickable. Not a link, so there is no hover/active state. */
.btn-closed { background: var(--surface-2); color: var(--muted); border: 1px dashed var(--border-strong);
    box-shadow: none; cursor: not-allowed; font-weight: 600; }
/* Nav CTA — punchier register button (Kancil cue): uppercase, bold, arrow that
   nudges on hover. Keeps the brand gradient. */
.btn-cta { position: relative; z-index: 0; text-transform: uppercase; font-weight: 700; letter-spacing: .04em; gap: 8px; }
/* soft breathing glow behind the button to draw the eye to the primary CTA */
.btn-cta::before {
    content: ""; position: absolute; inset: -2px; z-index: -1; border-radius: inherit;
    background: var(--grad-brand); filter: blur(13px); opacity: .5;
    animation: ctaGlow 2.4s ease-in-out infinite;
}
@keyframes ctaGlow {
    0%, 100% { opacity: .3; transform: scale(.97); }
    50%      { opacity: .65; transform: scale(1.05); }
}
.btn-cta .cta-arrow { transition: transform .2s; }
.btn-cta:hover .cta-arrow { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
    .btn-cta .cta-arrow { transition: none; }
    .btn-cta::before { animation: none; }
}

/* "LED" neon border for the Register CTAs — a conic gradient spinning behind the
   button, with an inner plate on top of it so only a 2px rim shows. The button is
   overflow:hidden so the spinning square is clipped to the pill.
   Paint order inside the button: background → ::before (-2) → ::after (-1) → label. */
.btn-neon { position: relative; z-index: 0; overflow: hidden; }
.btn-neon::before {
    content: ""; position: absolute; z-index: -2; inset: auto; left: 50%; top: 50%;
    width: 240%; aspect-ratio: 1; translate: -50% -50%;
    /* resets, in case this lands on .btn-cta whose ::before is a blurred glow */
    filter: none; opacity: 1; border-radius: 0;
    background: conic-gradient(from 0turn, #25cbbb, #7fdcd0 14%, #ffcf6a 30%,
        #e0714b 46%, #25cbbb 64%, #7fdcd0 82%, #25cbbb 100%);
    animation: neonSpin 3.4s linear infinite;
}
.btn-neon::after {
    content: ""; position: absolute; z-index: -1; inset: 2px; border-radius: inherit;
    background: var(--grad-brand);
}
@keyframes neonSpin { to { rotate: 1turn; } }
@media (prefers-reduced-motion: reduce) { .btn-neon::before { animation: none; } }

/* (Removed the old light ".panel" and ".card"/".card__icon" blocks — dead after the
   galaxy-glass redesign; nothing in any view used them. Along with .facts / .cta-band
   selectors below, verified 0 usages.) */
.grid { display: grid; gap: 18px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* grid/flex children must be allowed to shrink below content width (prevents overflow) */
.grid > *, .hero-copy, .hero-visual, .split > *, .contact-grid > *, .footer-grid > * { min-width: 0; }

/* ---------- Header ---------- */
.site-header {
    position: sticky; top: 0; z-index: 100; height: var(--nav-h);
    display: flex; align-items: center;
    background: rgba(7, 10, 28, .55);
    backdrop-filter: blur(18px) saturate(140%);
    border-bottom: 1px solid var(--border);
    transition: background .3s, box-shadow .3s;
}
/* thin gradient accent line along the very top edge (Kancil cue) */
.site-header::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
    background: var(--grad-brand); z-index: 1;
}
.site-header.scrolled { background: rgba(6, 8, 22, .9); box-shadow: 0 10px 30px rgba(0,0,0,.4); }
.nav { display: flex; align-items: center; justify-content: space-between; gap: 20px; width: 100%; }
/* Brand lockup — proportions follow the Kancil Science spec (40px mark / 16px name /
   10px tagline, stepping to 56 / 20 / 11 at 640px). Only the sizing was borrowed:
   the colours stay on MyBO's turquoise tokens, so both themes still work.
   The ring replaces the old green drop-shadow, which was a stale colour from
   before the palette moved to turquoise. */
.brand { display: flex; align-items: center; gap: 8px; font-family: var(--font-display); font-weight: 600;
    min-width: 0; flex-shrink: 0; }
.brand img { width: 40px; height: 40px; flex-shrink: 0; border-radius: 50%; object-fit: cover;
    filter: none; box-shadow: 0 0 0 2px rgba(63, 214, 192, .4); transition: box-shadow .15s; }
.brand:hover img { box-shadow: 0 0 0 2px var(--teal); }
@supports (box-shadow: 0 0 0 2px color-mix(in srgb, red 40%, transparent)) {
    .brand img { box-shadow: 0 0 0 2px color-mix(in srgb, var(--teal) 40%, transparent); }
}
/* Two-line brand lockup (legacy ".brand span" sizing removed — it out-specified
   these and forced COMPETITION back to the title size, which stacked the two lines
   tight enough to look overlapped). */
.brand-name { display: flex; flex-direction: column; gap: 2px; line-height: 1.25; min-width: 0; }
/* truncate rather than wrap — the lockup must stay two lines at every width */
.brand-title { font-size: 16px; font-weight: 800; letter-spacing: -.025em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.brand-sub { font-size: 10px; font-weight: 600; letter-spacing: .2em; text-transform: uppercase;
    color: var(--brand-600); }
@media (min-width: 640px) {
    .brand { gap: 12px; }
    .brand img { width: 56px; height: 56px; }
    .brand-title { font-size: 20px; }
    .brand-sub { font-size: 11px; }
}
.nav-links { display: flex; align-items: center; gap: 4px; list-style: none; margin: 0; padding: 0; }
.nav-links > li { position: relative; }
.nav-links a {
    display: inline-flex; align-items: center; gap: 6px; padding: 10px 15px; border-radius: var(--r-pill);
    font-weight: 500; font-size: .95rem; color: var(--text-soft); transition: color .2s, background .2s;
}
.nav-links a { position: relative; }
.nav-links a:hover { color: #fff; background: var(--surface-2); }
/* active page: teal text + a teal underline bar (Kancil-style indicator), no pill.
   ::before because dropdown items already use ::after for the caret. */
.nav-links a.active { color: var(--teal); background: transparent; }
.nav-links a.active::before {
    content: ""; position: absolute; left: 15px; right: 15px; bottom: 3px; height: 2.5px;
    border-radius: 2px; background: var(--teal); box-shadow: 0 0 10px rgba(53, 214, 194, .6);
}
.has-dropdown > a::after { content: "▾"; font-size: .7em; opacity: .8; }
.dropdown {
    position: absolute; top: calc(100% + 10px); left: 0; min-width: 210px; list-style: none; margin: 0; padding: 8px;
    background: rgba(12, 16, 40, .96); border: 1px solid var(--border-strong); border-radius: var(--r-md);
    box-shadow: var(--shadow-md); backdrop-filter: blur(16px);
    opacity: 0; visibility: hidden; transform: translateY(8px); transition: all .22s; z-index: 120;
}
/* :focus-within as well as :hover — without it the dropdown only ever opens for a
   mouse, so a keyboard user can't reach Syllabus / Reading Materials / Past Year
   Questions, which live only in this menu. */
.has-dropdown:hover .dropdown,
.has-dropdown:focus-within .dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
/* nowrap so the panel sizes to its longest label. Without it the panel sticks at
   min-width (it's absolutely positioned inside a narrow li, so it never grows to
   fit) and a longer item like "MyBO Results 2025/2026" wraps to two lines while
   its neighbours sit on one. */
.dropdown a { display: block; padding: 10px 14px; border-radius: 10px; white-space: nowrap; }
.nav-cta { display: flex; align-items: center; gap: 10px; }
.nav-toggle { display: none; background: var(--surface); border: 1px solid var(--border-strong); color: #fff; width: 46px; height: 46px; border-radius: 12px; cursor: pointer; font-size: 1.3rem; }

/* ---------- Touch targets ----------
   Scoped to `pointer: coarse` so mouse/trackpad users keep the current, tighter
   spacing — only touch devices pay the extra height. 44px is the target used
   here (Apple HIG / WCAG 2.5.5 AAA); WCAG 2.2 AA only requires 24px, so these
   were not all failures, but text links at 20–24px are genuinely fiddly to tap.
   min-height (not padding) so nothing re-flows on desktop if the query ever
   matches unexpectedly. */
@media (pointer: coarse) {
    /* footer link columns + contact rows: give the whole row a tappable height.
       .f-contact keeps its own align-items (flex-start, so the icon stays pinned
       to the first line of a wrapped address) — only the height floor is added. */
    .footer li a, .c-mail { display: flex; align-items: center; min-height: 44px; }
    .f-contact { min-height: 44px; }
    /* .footer-bottom links (Privacy Policy) sit outside the <li> columns */
    .footer-bottom a { display: inline-flex; align-items: center; min-height: 44px; }
    /* small buttons + disclosure rows */
    .btn-sm { min-height: 44px; }
    .faq-btn { min-height: 44px; }
    summary { min-height: 44px; }
}

/* ---------- Footer ----------
   Deliberately a distinct dark panel, not a continuation of the page: it closes the
   page instead of trailing off into more background. Colours come from the
   --footer-* tokens so warm/dark only re-point the token set. */
.site-footer {
    border-top: 1px solid var(--footer-border);
    background: var(--footer-bg);
    color: var(--footer-muted);
    padding: 64px 0 28px; margin-top: 40px;
}
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 40px; }
/* brand column is the tallest — cap its measure and tighten leading so it doesn't
   set the footer's height on its own */
.footer-brand img { width: 72px; margin-bottom: 14px; }
/* text-wrap: pretty — the description was dropping "students." onto a line alone */
/* 320px matches MyAO's brand paragraph — the old 42ch ran the description out to a
   long line that sat oddly against the short address lines under it */
.footer-brand p { max-width: 320px; font-size: 1rem; line-height: 1.6; margin: 0 0 12px;
    color: var(--footer-muted); text-wrap: pretty; }
/* max-width:none — the address carries its own line break, so the 42ch measure
   would only add a third line the copy wasn't written for */
.footer-brand p.f-addr { font-size: .95rem; opacity: .9; max-width: none; }
/* footer column headings are h2 for a correct heading outline; styled compact to keep
   their original footer look (was .footer h4). */
.footer h2 { font-size: 1rem; letter-spacing: .1em; text-transform: uppercase; color: var(--footer-text); margin-bottom: 16px; }
.footer ul { list-style: none; padding: 0; margin: 0; }
.footer li { margin-bottom: 10px; }
.footer a { color: var(--footer-muted); font-size: 1rem; transition: color .2s; }
.footer a:hover { color: var(--footer-accent); }
/* Footer contact rows — icon pinned beside its text (never orphaned on its own line)
   and optically centred on the first line: (1.5em line box − 16px icon) / 2 = 4px. */
.f-contact { display: flex; align-items: flex-start; gap: 10px; line-height: 1.5; }
.f-contact svg { flex: 0 0 auto; margin-top: 4px; color: var(--footer-accent); }
.f-contact span { overflow-wrap: break-word; }
/* mybo@olympiad.my is short enough to sit at the same size as the hotlines, so the
   whole column shares one type size and one icon offset (no per-row nudging) */
.f-email { font-size: 1rem; }
/* the two blocks in the contact column ("Get in touch" / "Follow us") sit closer
   together than separate columns would */
.footer .f-contacts { margin-bottom: 18px; }

/* Brand-filled circular social buttons — white glyph on the brand colour, FLAT.
   The old version gave each one a big coloured box-shadow; five of those side by side read as
   coloured haze rather than five crisp buttons, so the colour now lives only in the
   circle itself and depth comes from one neutral shadow on hover. */
.socials { display: flex; gap: 10px; flex-wrap: wrap; }
.socials a {
    width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
    color: #fff; border: 0; transition: transform .2s, box-shadow .2s, opacity .2s;
}
/* one optical size for all five. fill (not color) on purpose: inside a .gx-glass card,
   `[data-theme="warm"] .gx-glass a:not(.btn)` outranks `.socials a` and was tinting the
   glyphs deep teal on their brand-coloured circles. A CSS fill beats both that inherited
   colour and the SVG's own fill attribute, whatever the button sits in. */
.socials svg { width: 55%; height: 55%; display: block; fill: #fff; }
.socials a:hover { transform: translateY(-2px); opacity: .92;
    box-shadow: 0 6px 16px rgba(15, 40, 40, .22); }
.socials a:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.socials a.s-fb { background: #1877F2; }
.socials a.s-ig { background: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285aeb 90%); }
.socials a.s-tt { background: #010101; }
.socials a.s-yt { background: #FF0000; }
.socials a.s-tg { background: #229ED9; }
.footer-bottom { margin-top: 46px; padding-top: 22px; border-top: 1px solid var(--footer-border); display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; color: var(--footer-muted); font-size: .85rem; }

/* =============================================================
   GALAXY-GLASS SYSTEM (shared by every page)
   Frosted panels that sit over the starfield, an accent "nebula"
   card variant, and the floating space decorations.
   ============================================================= */
.gx-sec { position: relative; padding: clamp(30px, 4.5vw, 60px) 0; }
.gx-head { text-align: center; max-width: 720px; margin: 0 auto clamp(26px, 4vw, 44px); position: relative; z-index: 1; }
.gx-head .eyebrow { margin-bottom: 14px; }
.gx-head h2 { font-size: clamp(1.9rem, 4.4vw, 3rem); color: #fff; }
.gx-head p { color: var(--text-soft); margin: 12px auto 0; }

/* Original navy fill kept. Separation from the dark (often purple-glowing) background
   comes from a brighter border + a faint outer ring and ambient shadow, so the card
   edge reads on all sides without changing the panel colour. */
.gx-glass { position: relative; border-radius: var(--r-lg); z-index: 1;
    background: linear-gradient(180deg, rgba(48, 58, 112,.95), rgba(28, 36, 74,.97));
    border: 1px solid rgba(125,222,208,.5);
    box-shadow: 0 20px 55px rgba(2,5,22,.55), 0 0 0 1px rgba(140, 170, 255,.1),
                0 0 34px rgba(20,150,150,.12), inset 0 1px 0 rgba(255,255,255,.16);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); }
.gx-glass:hover { border-color: rgba(120,220,205,.55);
    box-shadow: 0 28px 70px rgba(2,5,22,.6), 0 0 60px rgba(40,180,175,.24), inset 0 1px 0 rgba(255,255,255,.14); }
.gx-glass h3, .gx-glass h4 { color: #fff; }
.gx-glass p { color: #dbe1f5; }
.gx-glass strong { color: #fff; }
.gx-glass a:not(.btn) { color: #7fdcd0; }
/* an eyebrow sitting directly inside a card needs breathing room above the title */
.gx-glass > .eyebrow { display: flex; width: fit-content; margin-bottom: 14px; }
.gx-glass > .section-title { margin-bottom: .5em; }
.gx-pad { padding: clamp(24px, 3.4vw, 44px); }

/* ---- Nebula card: accent bloom + star speckle + icon as light source ----
   Drive the accent per card with style="--a:#hex". */
.gx-neb { position: relative; overflow: hidden;
    transition: transform .3s ease, border-color .3s ease, box-shadow .3s ease; }
.gx-neb::before { content: ""; position: absolute; top: -46%; right: -32%; width: 80%; aspect-ratio: 1; border-radius: 50%;
    background: radial-gradient(circle, var(--a, var(--brand)), transparent 70%);
    opacity: .34; filter: blur(26px); z-index: 0; pointer-events: none;
    transition: opacity .4s ease, transform .4s ease; }
.gx-neb::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: .6;
    background-repeat: no-repeat;
    background-image:
        radial-gradient(1.6px 1.6px at 18% 24%, #fff, transparent),
        radial-gradient(1.2px 1.2px at 67% 14%, rgba(255,255,255,.85), transparent),
        radial-gradient(1.1px 1.1px at 86% 58%, rgba(255,255,255,.7), transparent),
        radial-gradient(1.4px 1.4px at 30% 80%, rgba(255,255,255,.75), transparent),
        radial-gradient(1px 1px at 54% 46%, rgba(255,255,255,.55), transparent),
        radial-gradient(1.2px 1.2px at 9% 62%, rgba(255,255,255,.6), transparent),
        radial-gradient(1px 1px at 78% 86%, rgba(255,255,255,.5), transparent); }
.gx-neb > * { position: relative; z-index: 1; }
.gx-neb:hover { transform: translateY(-6px); border-color: rgba(255,255,255,.34);
    box-shadow: 0 34px 80px rgba(2,5,22,.65), 0 0 54px -12px var(--a, var(--brand)); }
.gx-neb:hover::before { opacity: .55; transform: scale(1.18); }
/* the icon glows in the card's accent — it reads as the light source */
.gx-neb .halo, .gx-neb .gx-icon {
    background: radial-gradient(circle at 50% 38%, var(--a, var(--brand)), rgba(18, 24, 54,.35) 74%);
    border-color: rgba(255,255,255,.3);
    box-shadow: 0 0 38px -6px var(--a, var(--brand)), inset 0 1px 0 rgba(255,255,255,.4); }
.gx-neb .gx-emoji { filter: drop-shadow(0 6px 16px var(--a, var(--brand))); }



/* .prose is written for the old light panels (dark ink). Inside a glass panel it must
   flip to light-on-dark, otherwise the copy is unreadable. */
.gx-glass.prose, .gx-glass .prose { color: #dbe1f5; }
.gx-glass.prose h2, .gx-glass .prose h2,
.gx-glass.prose h3, .gx-glass .prose h3,
.gx-glass.prose h4, .gx-glass .prose h4 { color: #fff; }
/* slightly smaller so long achievement titles fit one line on desktop
   (e.g. "…(IRAO) 2022"); still wraps gracefully on narrow screens. */
.gx-glass.prose h2, .gx-glass .prose h2 { font-size: var(--fs-h3); line-height: var(--lh-snug); }
.gx-glass.prose p, .gx-glass .prose p,
.gx-glass.prose li, .gx-glass .prose li { color: #dbe1f5; }
.gx-glass.prose strong, .gx-glass .prose strong,
.gx-glass.prose b, .gx-glass .prose b { color: #fff; }
/* :not(.btn) — without it this outranks .btn-primary and tints button labels teal
   on their own teal fill (warm-theme.css already excludes buttons the same way) */
.gx-glass.prose a:not(.btn), .gx-glass .prose a:not(.btn) { color: #7fdcd0; }
.gx-glass.prose th, .gx-glass .prose th { background: rgba(37,203,187,.18); color: #fff; }
.gx-glass.prose th, .gx-glass .prose th,
.gx-glass.prose td, .gx-glass .prose td { border-color: rgba(110,215,200,.22); }
.gx-glass.prose img, .gx-glass .prose img { box-shadow: 0 14px 34px rgba(0,0,0,.45); }

/* boxed icon used inside glass cards */
.gx-icon { width: 54px; height: 54px; border-radius: 15px; display: grid; place-items: center; font-size: 1.6rem;
    background: linear-gradient(140deg, rgba(37,203,187,.30), rgba(34,182,200,.30));
    border: 1px solid rgba(110,215,200,.36); box-shadow: 0 0 24px rgba(60,210,196,.24); }
/* SVG icon (<x-gx-icon>) inside the boxed icon — white on the accent-tinted box */
.gx-icon svg { width: 26px; height: 26px; color: #fff; filter: drop-shadow(0 2px 6px var(--a, var(--brand))); }

/* centred icon-over-text card (objectives etc.) */
.gx-obj { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
.gx-obj .gx-icon { margin-bottom: 14px; }

/* ---- floating space decorations (never block clicks) ---- */
.deco { position: absolute; pointer-events: none; user-select: none; z-index: 0; line-height: 1;
    filter: drop-shadow(0 8px 18px rgba(0,0,0,.35)); }
.deco.f  { animation: decoFloat 7s ease-in-out infinite; }
.deco.f2 { animation: decoFloat 9.5s ease-in-out infinite; }
.deco.spin { animation: decoSpin 30s linear infinite; }
.deco.tw { animation: decoTw 3.4s ease-in-out infinite; }
@keyframes decoFloat { 0%,100% { transform: translateY(0) } 50% { transform: translateY(-22px) } }
@keyframes decoSpin { to { transform: rotate(360deg) } }
@keyframes decoTw { 0%,100% { opacity: 1; transform: scale(1) } 50% { opacity: .35; transform: scale(.72) } }
.section > .container { position: relative; z-index: 1; }
@media (max-width: 900px) { .deco.hide-sm { display: none; } }
@media (prefers-reduced-motion: reduce) { .deco, .gx-neb, .gx-neb::before { animation: none !important; transition: none !important; } }

/* Global reduced-motion safety net. Stops looping animations (hero float/glow,
   timeline pulse, spinners) and collapses transition/scroll motion, while letting
   one-shot state changes settle instantly. .reveal is forced visible so its
   opacity:0 start state can never strand content for these users. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
    html { scroll-behavior: auto !important; }
    .reveal { opacity: 1 !important; transform: none !important; }
}

/* ---------- Inner page hero ---------- */
/* Top gap: min raised to 44px so the badge/eyebrow clears the sticky header with
   breathing room on mobile (was 28px, felt cramped under the nav). */
/* kept tight on purpose — the hero is badge + eyebrow + title only, so generous
   padding here just pushed the first content card down past the fold */
.page-hero { padding: clamp(20px, 2.6vw, 30px) 0 clamp(8px, 1.4vw, 16px); text-align: center; position: relative; }
/* breathing room between the eyebrow pill and the title (matches .gx-head's eyebrow gap) —
   without this the page-hero eyebrow sits flush against the h1 on every inner page. */
.page-hero .eyebrow { margin-bottom: 12px; }
.page-hero h1 { font-size: var(--fs-h1); }
/* The h1's bottom margin exists to hold the lead off it. On a hero with no lead
   it is just dead air above the first panel, so drop it — :last-child means this
   un-applies by itself the moment a lead is added back. */
.page-hero h1:last-child { margin-bottom: 0; }
.page-hero .lead { margin: 12px auto 0; }
.page-hero .badge-mini { width: 88px; margin: 0 auto 10px; filter: drop-shadow(0 12px 30px rgba(30, 170, 100,.5)); }

/* ---------- Prose (rich text inside panels) ---------- */
.prose { color: #4a5578; font-size: 1.02rem; }
.prose h2 { color: #161d3d; font-size: 1.6rem; margin: 1.4em 0 .5em; }
.prose h3 { color: #1c244a; font-size: 1.25rem; margin: 1.2em 0 .4em; }
.prose p { color: #4a5578; margin: 0 0 1rem; max-width: var(--measure); line-height: var(--lh-relaxed); }
.prose li { max-width: var(--measure); }
/* :not(.btn) so a button placed inside prose (e.g. the IBO "Official Website" CTA)
   keeps its own white-on-brand styling — .prose a would otherwise out-specify .btn
   and repaint the label teal, fusing it into the button's teal background. */
.prose a:not(.btn) { color: var(--brand-600); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.prose ul, .prose ol { margin: 0 0 1.2rem; padding-left: 1.3em; }
.prose li { margin-bottom: .5em; color: #4a5578; }
.prose strong, .prose b { color: #1c244a; }
.prose img { border-radius: var(--r-md); margin: 1em 0; box-shadow: 0 14px 34px rgba(20, 26, 60,.16); }
/* Emoji shipped as <img> by the old WordPress content (s.w.org SVGs) carry NO intrinsic
   size, so the rule above blew them up to the full column width — complete with card
   shadow and rounded corners. Render them as what they are: an inline glyph on the text
   baseline, at text size. */
.prose img[src*="/emoji/"], .prose img[src*="s.w.org"] {
    display: inline; width: 1em; height: 1em; margin: 0 .18em 0 0;
    vertical-align: -.14em; border-radius: 0; box-shadow: none;
}
.prose table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: .95rem; }
.prose th, .prose td { border: 1px solid #e3e8fb; padding: 10px 12px; text-align: left; }
.prose th { background: #eef2ff; color: #1c244a; }

/* ---------- FAQ accordion ---------- */
.faq { max-width: 860px; margin: 0 auto; }
/* accordions sit inside a dark glass panel — keep them dark, or the light prose
   text inside .faq-answer becomes invisible on a white card. */
.faq-item { border: 1px solid rgba(110,215,200,.2); border-radius: var(--r-md); margin-bottom: 14px; overflow: hidden;
    background: rgba(12,17,42,.5); transition: box-shadow .25s, border-color .25s, background .25s; }
.faq-item:hover { border-color: rgba(120,220,205,.4); }
.faq-item[open] { border-color: rgba(120,220,205,.5); background: rgba(18, 24, 58,.65); box-shadow: 0 14px 34px rgba(0,0,0,.4); }
.faq-item summary { list-style: none; cursor: pointer; padding: 20px 24px; display: flex; align-items: center; gap: 14px;
    font-family: var(--font-display); font-weight: 600; color: #fff; font-size: 1.05rem; }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary .q-num { flex: 0 0 30px; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
    background: var(--grad-brand); color: #fff; font-size: .85rem; }
.faq-item summary .chev { margin-left: auto; transition: transform .25s; color: #7fdcd0; }
.faq-item[open] summary .chev { transform: rotate(180deg); }
.faq-answer { padding: 0 24px 22px 68px; }
.faq-answer > :first-child { margin-top: 0; }
/* Wide tables (the key-dates table in "What is the important date…") sized to their
   content, overflowed the card and were silently CLIPPED by .faq-item's
   overflow:hidden — the last column was unreachable. Fixed layout + wrapping cells
   make every column fit the card; overflow-x is the safety net for anything wider. */
.faq-answer { overflow-x: auto; }
.faq-answer:has(table) { padding-left: 24px; }   /* tables reclaim the text indent */
/* Tables keep their normal prose size — the LAYOUT gives them room (single column,
   full card width), rather than shrinking the content to fit a narrow column. */
.faq-answer table { margin: .2em 0 0; }
/* pasted URLs are one long unbreakable "word" — let them wrap instead of pushing the
   answer wider than its card */
.faq-answer a { overflow-wrap: anywhere; }
/* Deliberate exception to the site's 66ch measure cap: an expanded item spans the full
   card, so capping the text left the right half of the card empty. Answers fill it,
   justified (user's call 2026-07-28 — the rest of the site stays left-aligned). */
.faq-answer :is(p, li) { max-width: none; }
.faq-answer p { text-align: justify; }

/* ---------- Contact ---------- */
/* auto-fit, not repeat(3): locked at 3 columns these cards kept narrowing with the
   viewport until the email and the icon row had to wrap. 320px is the floor that
   keeps both on one line; below that the grid drops to 2 columns instead. */
.contact-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 22px; }
.contact-card { text-align: center; }
/* gx-pad's 44px a side is tuned for full-width panels; on a ~330px card it ate a
   quarter of the width, which is what forced the wrapping in the first place. */
.contact-card.gx-pad { padding: 28px 18px; }
/* sized to sit on one line in the narrowest 3-column case. No nowrap on purpose:
   if the address is ever changed to a longer one, it breaks at the <wbr> before
   the @ rather than overflowing the card. */
.contact-card .c-mail { font-size: .9rem; font-weight: 600; }
/* margin-top was an inline style before the row moved into partials/socials.blade.php */
.contact-card .socials { gap: 10px; justify-content: center; margin-top: 16px; }
.contact-card .socials a { width: 40px; height: 40px; }
/* …but not below the 44px tap target on touch devices. Must live here, next to the
   rule it overrides — the touch-target block earlier in this file has the same
   specificity and would lose on source order. */
@media (pointer: coarse) { .contact-card .socials a { width: 44px; height: 44px; } }
.contact-card .gx-icon { margin: 0 auto 16px; }
/* long values (address, email) must wrap inside the card rather than overflow it.
   break-word (not anywhere) so the email prefers its <wbr> at the "@". */
.contact-card p, .contact-card a { overflow-wrap: break-word; }
.map-embed { border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--border); box-shadow: var(--shadow-md); }
.map-embed iframe { display: block; width: 100%; height: 420px; border: 0; }

/* ---------- Results ---------- */
.result-hero { text-align: center; position: relative; overflow: hidden; }
/* Congratulations ribbon (shared by both results pages). The notched ends come from
   clip-path; the folded tails are the wrapper's pseudo-elements, since anything
   inside the banner would be cut away by its own clip. */
.res-ribbon { position: relative; display: inline-flex; margin: 0 0 24px; }
.res-ribbon::before, .res-ribbon::after { content: ""; position: absolute; bottom: -14px;
    width: 24px; height: 24px; background: #0a5f57; z-index: 0; }
.res-ribbon::before { left: 4px;  clip-path: polygon(0 0, 100% 0, 100% 58%); }
.res-ribbon::after  { right: 4px; clip-path: polygon(0 0, 100% 0, 0 58%); }
.res-banner { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 12px;
    margin: 0; padding: 16px clamp(30px, 5vw, 58px); color: #fff; background: var(--grad-brand);
    font-size: clamp(1.25rem, 2.8vw, 1.9rem); font-weight: var(--fw-bold); line-height: 1.2;
    box-shadow: 0 14px 34px var(--brand-glow);
    clip-path: polygon(0 0, 100% 0, calc(100% - 22px) 50%, 100% 100%, 0 100%, 22px 50%); }
/* keep the copy above the confetti layer */
.result-hero > *:not(.res-confetti) { position: relative; z-index: 1; }

/* Mascot swoops in from across the card, shrinks, and parks in the top-right corner,
   then gently idle-floats. The card's overflow:hidden keeps the whole flight on-card.
   (img.result-mascot outranks the position:relative rule above, so absolute wins.) */
.result-hero > img.result-mascot {
    position: absolute; top: 10px; right: 12px; z-index: 3;
    width: clamp(60px, 11vw, 92px); height: auto; pointer-events: none;
    filter: drop-shadow(0 10px 22px rgba(20,150,150, .5));
    transform-origin: center;
    /* flies in once and then stays put — the endless idle bob was distracting */
    animation: mascotFlyIn 1.5s cubic-bezier(.16, .84, .3, 1) both;
}
@keyframes mascotFlyIn {
    0%   { opacity: 0; transform: translate(-150px, 118px) scale(2.1) rotate(-16deg); }
    30%  { opacity: 1; }
    100% { opacity: 1; transform: translate(0, 0) scale(1) rotate(0); }
}
@media (prefers-reduced-motion: reduce) {
    .result-hero > img.result-mascot { animation: none; opacity: 1; }
}


/* Confetti, confined to the medallion band at the top of the card.
   The fade MUST be an absolute length, not a percentage: these two cards differ
   ~3x in height, so a percentage that clears the copy on the short one lands in
   the middle of the paragraphs on the tall one. Fully transparent by 162px —
   the first heading starts at 179px on both cards, so this clears it. */
.res-confetti { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
    -webkit-mask: linear-gradient(#000 0px, #000 68px, transparent 162px);
    mask: linear-gradient(#000 0px, #000 68px, transparent 162px); }
/* One-time celebratory burst: each piece falls once with a natural side-to-side sway and
   tumble, then fades and stops (iteration 1 + fill:forwards). Mixed shapes + mirrored sway
   on alternate pieces so it reads as real confetti, not a uniform grid. */
.res-confetti i { position: absolute; top: -18px; width: 8px; height: 14px; border-radius: 2px; opacity: 0;
    animation-name: confetti-fall; animation-timing-function: ease-in;
    animation-iteration-count: 1; animation-fill-mode: forwards; }
.res-confetti i:nth-child(3n)   { width: 9px; height: 9px; border-radius: 50%; }   /* circles */
.res-confetti i:nth-child(3n+1) { width: 8px; height: 8px; border-radius: 1px; }   /* squares */
.res-confetti i:nth-child(even) { animation-name: confetti-fall-b; }               /* mirrored sway */
@keyframes confetti-fall {
    0%   { transform: translate(0, -18px) rotate(0deg);        opacity: 0; }
    12%  { opacity: .92; }
    30%  { transform: translate(16px, 46px) rotate(180deg); }
    60%  { transform: translate(-10px, 104px) rotate(400deg); opacity: .92; }
    100% { transform: translate(8px, 196px) rotate(700deg);   opacity: 0; }
}
@keyframes confetti-fall-b {
    0%   { transform: translate(0, -18px) rotate(0deg);         opacity: 0; }
    12%  { opacity: .92; }
    30%  { transform: translate(-16px, 44px) rotate(-160deg); }
    60%  { transform: translate(12px, 108px) rotate(-380deg);  opacity: .92; }
    100% { transform: translate(-8px, 198px) rotate(-680deg);  opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .res-confetti { display: none; }
}
.btn-view { font-size: 1.15rem; padding: 18px 40px; }

/* ---------- PYQ list ---------- */
.pyq-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.pyq-item { display: flex; align-items: center; gap: 14px; padding: 16px 18px; border-radius: var(--r-md);
    background: rgba(12,17,42,.5); border: 1px solid rgba(110,215,200,.2); color: #dbe1f5;
    transition: transform .25s, border-color .25s, box-shadow .25s; }
.pyq-item:hover { transform: translateY(-4px); border-color: rgba(120,220,205,.45); box-shadow: 0 16px 36px rgba(0,0,0,.45); }
/* deeper red so the white "PDF" clears AA */
.pyq-item .pyq-ico { flex: 0 0 42px; width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
    background: linear-gradient(135deg,#d43f2a,#a82a1a); color: #fff; font-size: 1.1rem; font-weight: 700; }
.pyq-item b { display: block; color: #fff; font-size: .98rem; }
.pyq-item span { color: #9fb0dd; font-size: .82rem; }

/* ---------- Reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(30px); transition: opacity .7s ease, transform .7s cubic-bezier(.2,.8,.2,1); }
.reveal.in { opacity: 1; transform: none; }

/* ---------- Responsive ---------- */
@media (max-width: 980px) {
    .footer-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 600px) {
    .eyebrow { white-space: normal; letter-spacing: .12em; font-size: .72rem; text-align: left; }
    .section { padding: clamp(26px, 7vw, 44px) 0; }
    .container { padding: 0 18px; }
    .section-title { font-size: clamp(1.7rem, 8vw, 2.4rem); }
    .lead { font-size: 1rem; }
    .page-hero .lead, .center .lead { max-width: 100%; }
}
/* Nav collapses to the burger at 1024px — with the two-line wordmark the
   horizontal menu crowds (Contact Us wraps) below ~1024, so switch to the burger
   before that. Grid/footer collapse stays at 760px (below). */
@media (max-width: 1024px) {
    .nav-links { position: fixed; inset: var(--nav-h) 0 auto 0; flex-direction: column; align-items: stretch;
        background: rgba(8,11,30,.98); border-bottom: 1px solid var(--border); padding: 14px; gap: 4px;
        /* visibility:hidden when closed removes the off-screen links from the tab order —
           transform alone leaves them focusable, so keyboard focus flew off-screen. */
        transform: translateY(-120%); visibility: hidden; transition: transform .35s, visibility .35s;
        max-height: calc(100vh - var(--nav-h)); overflow-y: auto; }
    .nav-links.open { transform: none; visibility: visible; }
    .nav-links a { padding: 14px; }
    /* scope the dropdown's own visibility:visible to the OPEN menu, or it would
       re-expose the 5 submenu links even while the menu is closed. */
    .nav-links.open .dropdown { position: static; opacity: 1; visibility: visible; transform: none; box-shadow: none; background: transparent; border: 0; padding: 0 0 0 14px; }
    .dropdown { position: static; opacity: 1; transform: none; box-shadow: none; background: transparent; border: 0; padding: 0 0 0 14px; }
    .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
}
@media (max-width: 760px) {
    /* the wordmark used to be hidden here because the horizontal nav crowded it.
       The nav is a burger from 1024px down now, so there is room for the two-line
       lockup at every width — which is what the Kancil spec expects. */
    .grid-2 { grid-template-columns: minmax(0,1fr); }
    .footer-grid { grid-template-columns: 1fr; gap: 22px; }
    .site-footer { padding: 32px 0 14px; margin-top: 40px; }
    .footer-brand p { max-width: none; }
    .nav-cta .btn:not(.nav-toggle) { display: none; }
}
