/*
Theme Name:        OpenTuition
Theme URI:         https://opentuition.com
Author:            OpenTuition
Author URI:        https://opentuition.com
Description:       Custom block theme for opentuition.com. Pairs with the ot-forum plugin which owns the entire /forums/* route. Theme is chrome only — header, footer, homepage, course pages, blog. Zero JavaScript framework on non-forum pages; tuned FSE with the standard bloat dequeued.
Version:           0.1.13
Requires at least: 6.6
Tested up to:      6.7
Requires PHP:      8.2
License:           GPL-2.0-or-later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:       opentuition
Tags:              block-theme, full-site-editing, custom-colors, dark-mode
*/

/*
 * Most styling lives in theme.json (design tokens) + per-block CSS in
 * /blocks/. This file holds the small set of cross-cutting rules that
 * don't belong in either: dark-mode anti-FOUC styles, .screen-reader-text,
 * and tiny utility classes used by parts/header.html.
 *
 * If you find yourself adding more than ~50 lines here, the rule probably
 * belongs in theme.json (if it's a token) or in a block stylesheet (if
 * it's component-scoped).
 */

/* Global box-sizing reset.
   Why this needs to exist on the theme side:
     The forum SPA bundle ships its own `* { box-sizing: border-box }`,
     which applies on every forum page. Without a matching theme rule,
     theme pages used the browser default (content-box for most elements).
     That mismatch caused sub-pixel differences in flex layout between
     contexts — most visibly the mega-menu items shifting position when
     the user navigated from a theme page into the forum. With both
     surfaces now using border-box everywhere, the two render identically.
   Why this is safe:
     border-box is the modern standard (used by Tailwind, Bootstrap, every
     major framework). The only risk is if a specific theme rule sets an
     explicit width AND has padding — and a quick audit confirmed all such
     rules in this file are either padding:0 or designed border-box-aware. */
*, *::before, *::after { box-sizing: border-box; }

/* Anti-FOUC: hide the page until dark-mode.php has applied data-theme.
   The inline <head> script removes this class within ~1ms of <html>
   parsing, so the user never sees a flash of light theme on a dark
   preference. */
html.ot-no-fouc body { visibility: hidden; }

/* ----------------------------------------------------------------------
   Dark-mode palette overrides.

   theme.json defines only the light palette. To go dark, we override the
   CSS variables WP emits from theme.json (--wp--preset--color--*). Two
   triggers, in priority order:

     1. :root[data-theme="dark"]          — explicit user choice from the
                                              header toggle button. Wins
                                              over OS preference.
     2. (prefers-color-scheme: dark)      — first-time visitors. Guarded
        :root:not([data-theme="light"])     so an explicit "light" choice
                                              from the toggle still wins.

   Hex values mirror ot-forum/assets/src/styles/index.css so the forum and
   the rest of the site share one design system in dark mode. If you change
   one, change both.

   Brand tokens (--accent red, --green) stay the same in dark mode — they
   ARE the brand. Only chrome tokens (--bg/--fg/--border) flip. The --link
   blue moves to a lighter cyan-blue for readability on the dark background
   (matches GitHub's #58a6ff which is what ot-forum uses).
   ---------------------------------------------------------------------- */

:root[data-theme="dark"],
:root[data-theme="dark"] body {
    --wp--preset--color--bg:        #0d1117;
    --wp--preset--color--bg-elev:   #161b22;
    --wp--preset--color--fg:        #e6edf3;
    --wp--preset--color--fg-muted:  #8b95a3;
    --wp--preset--color--fg-dim:    #6e7681;
    --wp--preset--color--border:    #30363d;
    --wp--preset--color--link:      #58a6ff;
    background-color: var(--wp--preset--color--bg);
    color:            var(--wp--preset--color--fg);
    color-scheme:     dark;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]),
    :root:not([data-theme="light"]) body {
        --wp--preset--color--bg:        #0d1117;
        --wp--preset--color--bg-elev:   #161b22;
        --wp--preset--color--fg:        #e6edf3;
        --wp--preset--color--fg-muted:  #8b95a3;
        --wp--preset--color--fg-dim:    #6e7681;
        --wp--preset--color--border:    #30363d;
        --wp--preset--color--link:      #58a6ff;
        background-color: var(--wp--preset--color--bg);
        color:            var(--wp--preset--color--fg);
        color-scheme:     dark;
    }
}

/* The .has-bg-* utility classes WP generates from theme.json palette
   reference the original light-palette hexes by NAME (e.g. has-bg-elev-
   background-color). Those `background-color: #f7f7f8 !important` rules
   override our CSS-variable swap above. Re-point them to the variable
   when in dark mode so cards/CTA panels also flip. */
:root[data-theme="dark"] .has-bg-background-color,
:root:not([data-theme="light"]) .has-bg-background-color {
    background-color: var(--wp--preset--color--bg) !important;
}
:root[data-theme="dark"] .has-bg-elev-background-color,
:root:not([data-theme="light"]) .has-bg-elev-background-color {
    background-color: var(--wp--preset--color--bg-elev) !important;
}
:root[data-theme="dark"] .has-fg-color,
:root:not([data-theme="light"]) .has-fg-color {
    color: var(--wp--preset--color--fg) !important;
}
:root[data-theme="dark"] .has-fg-muted-color,
:root:not([data-theme="light"]) .has-fg-muted-color {
    color: var(--wp--preset--color--fg-muted) !important;
}
:root[data-theme="dark"] .has-border-border-color,
:root:not([data-theme="light"]) .has-border-border-color {
    border-color: var(--wp--preset--color--border) !important;
}

/* The announcement-bar template part uses hardcoded hex (#d9ecff blue
   tile, #fff3a6 yellow tile) ported from Genesis Simple Hooks. Those
   colours read fine on light, but the dark text inside them (#0b3a73 /
   #8b0000) needs no change because the tiles themselves stay light —
   they're meant to be high-contrast announcement panels. Leaving as-is
   on purpose; revisit if editors want true dark variants. */

/* ==================================================================
   Mega-menu — primary header navigation rendered by inc/primary-menu.php
   over WordPress native menus.

   Markup shape (from OpenTuition_Mega_Menu_Walker):
     nav.ot-mega-menu
       ul.ot-mega-menu__list
         li.ot-mega-menu__top[--has-children]
           a.ot-mega-menu__top-link
             span.ot-mega-menu__caret    (only if has-children)
           div.ot-mega-menu__panel       (only if has-children)
             div.ot-mega-menu__panel-inner   (grid)
               div.ot-mega-menu__column
                 div.ot-mega-menu__column-title
                 ul.ot-mega-menu__column-items
                   li.ot-mega-menu__leaf
                     a

   Hover/focus on a top item reveals its panel — pure CSS, no JS. We
   key off :hover AND :focus-within so keyboard tabbing also opens the
   dropdown (a11y).

   Mobile: at <=782px the panels collapse inline (always visible) so
   the nav becomes a stacked list of links. Skips the dropdown UX
   entirely on small screens — simpler than building a hamburger
   accordion, and the current Genesis site does the same. */

.ot-mega-menu { position: relative; }

.ot-mega-menu__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    align-items: center;
}

.ot-mega-menu__top {
    position: relative;
}

.ot-mega-menu__top-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    padding: 0.5em 0.85em;
    color: var(--wp--preset--color--fg);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.9375rem;
    line-height: 1.2;
    border-bottom: 2px solid transparent;
    white-space: nowrap;
}
.ot-mega-menu__top:hover > .ot-mega-menu__top-link,
.ot-mega-menu__top:focus-within > .ot-mega-menu__top-link,
.ot-mega-menu__top.is-current > .ot-mega-menu__top-link {
    color: var(--wp--preset--color--accent);
    border-bottom-color: var(--wp--preset--color--accent);
}

/* Inline search field — replaces the old "Search" menu link.
   Visual signature: pill-shaped soft surface (so it reads as an
   *input* rather than another link), matching the menu's vertical
   rhythm. Focus ring uses brand red to harmonise with the rest of
   the header. Submitting GETs ?q=<value> to the existing Google
   Custom Search results page (/search-opentuition/), so behaviour
   after submit is unchanged from the legacy menu link.

   Width: 200px on desktop, shrinks via min-content + flex on
   narrower viewports. On mobile the whole search slot moves into
   the hamburger drawer (rules at the bottom of this file). */
/* Override the list's default flex-wrap so the search field shrinks
   to fit alongside the menu items rather than dropping to a second
   row. The menu has at most ~6 items so worst-case overflow is small;
   the search field hits min-width: 120px before anything visibly
   breaks. */
.ot-mega-menu__list { flex-wrap: nowrap; }
.ot-mega-menu__top--search {
    margin-left: 0.5rem;
    min-width: 0; /* allow flex-shrink past the input's min-content */
}
.ot-search-form {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.7rem 0.4rem 0.55rem;
    background: var(--wp--preset--color--bg-elev, #f5f5f5);
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--wp--preset--color--fg-muted);
    /* Flexible: prefer 170px, can shrink to 120px before the
       placeholder gets cropped. flex-shrink:1 means it gives way to
       menu items first. Once the placeholder doesn't fit at 120px
       it'd ideally collapse to icon-only — but that's a future polish
       beyond what's needed at common viewport widths. */
    flex: 0 1 170px;
    min-width: 120px;
    max-width: 220px;
    transition: background 120ms, border-color 120ms, color 120ms, box-shadow 120ms;
}
.ot-search-form:hover {
    background: var(--wp--preset--color--bg-elev-hover, #ececec);
}
.ot-search-form:focus-within {
    background: var(--wp--preset--color--bg, #fff);
    border-color: var(--wp--preset--color--accent);
    color: var(--wp--preset--color--fg);
    box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.12);
}
.ot-search-form__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    cursor: text;
}
.ot-search-form__input {
    background: transparent;
    border: 0;
    outline: 0;
    flex: 1;
    min-width: 0;
    padding: 0;
    font: inherit;
    font-size: 0.875rem;
    line-height: 1.2;
    color: var(--wp--preset--color--fg);
}
.ot-search-form__input::placeholder {
    color: var(--wp--preset--color--fg-muted);
    opacity: 1;
}
.ot-search-form__input::-webkit-search-cancel-button {
    -webkit-appearance: none;
}

.ot-mega-menu__caret {
    font-size: 0.7em;
    color: #999;
    transition: color 120ms;
}
.ot-mega-menu__top:hover .ot-mega-menu__caret,
.ot-mega-menu__top:focus-within .ot-mega-menu__caret {
    color: var(--wp--preset--color--accent);
}

/* Dropdown panel — absolutely positioned beneath the trigger.
   Hidden by default via opacity + visibility (NOT display:none, so
   focus-within still works for keyboard nav into the panel). */
/* Surface treatment: panel uses the existing Surface token
   (--bg-elev = #f7f7f8) — same as the header band. Each column is
   a Background-token card (--bg = #fff) floated on top, framed by
   the existing Border token. Reads as structured architecture
   rather than empty sheet, same idiom as Linear / Vercel / Stripe.
   The brand-red top edge stays as the signature.

   No hardcoded colors here — every value pulls from theme.json. */
.ot-mega-menu__panel {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 100;
    min-width: 560px;
    max-width: 90vw;
    background: var(--wp--preset--color--bg-elev);
    border: 1px solid var(--wp--preset--color--border);
    border-top: 2px solid var(--wp--preset--color--accent);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(0, 0, 0, 0.04);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 120ms ease-out, transform 120ms ease-out, visibility 0s linear 120ms;
    padding: 1.25rem 1.25rem;
}
.ot-mega-menu__column {
    background: var(--wp--preset--color--bg);
    border: 0.5px solid var(--wp--preset--color--border);
    border-radius: 10px;
    padding: 0.9rem 1rem 0.75rem;
}
.ot-mega-menu__top:hover > .ot-mega-menu__panel,
.ot-mega-menu__top:focus-within > .ot-mega-menu__panel {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity 120ms ease-out, transform 120ms ease-out, visibility 0s;
}

.ot-mega-menu__panel-inner {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1.5rem;
}

.ot-mega-menu__column-title {
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--wp--preset--color--accent);
    margin-bottom: 0.55rem;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--wp--preset--color--border, #ebebeb);
    text-transform: uppercase;
    letter-spacing: 0.6px;
}

.ot-mega-menu__column-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.ot-mega-menu__leaf a {
    display: block;
    padding: 0.32rem 0.5rem;
    margin: 0 -0.5rem;
    border-radius: 3px;
    color: var(--wp--preset--color--fg);
    text-decoration: none;
    font-size: 0.825rem;
    line-height: 1.35;
}
.ot-mega-menu__leaf a:hover,
.ot-mega-menu__leaf.is-current a {
    background: rgba(204, 0, 0, 0.06);
    color: var(--wp--preset--color--accent);
}

/* (Old .ot-header__right block removed — consolidated into the unified
   header layout section further down. Search for "Header layout — unified
   across theme pages AND the forum SPA" — that's now the single source
   of truth for the header chrome.) */

/* Hamburger toggle — hidden by default; shown only on small viewports.
   The icon is three CSS lines (top + middle via element + bottom via
   ::after) that animate to an X when aria-expanded="true". JS in
   assets/js/mega-menu.js flips that attribute. */
.ot-mobile-nav-toggle {
    display: none; /* hidden on desktop — the full mega-menu is shown */
}
.ot-mobile-nav-toggle__icon,
.ot-mobile-nav-toggle__icon::before,
.ot-mobile-nav-toggle__icon::after {
    display: block;
    width: 22px;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
    transition: transform 180ms ease, opacity 120ms ease, top 180ms ease;
}
.ot-mobile-nav-toggle__icon {
    position: relative;
}
.ot-mobile-nav-toggle__icon::before,
.ot-mobile-nav-toggle__icon::after {
    content: "";
    position: absolute;
    left: 0;
}
.ot-mobile-nav-toggle__icon::before { top: -7px; }
.ot-mobile-nav-toggle__icon::after  { top:  7px; }

/* Open state: lines collapse into an X. */
.ot-mobile-nav-toggle[aria-expanded="true"] .ot-mobile-nav-toggle__icon { background: transparent; }
.ot-mobile-nav-toggle[aria-expanded="true"] .ot-mobile-nav-toggle__icon::before {
    top: 0; transform: rotate(45deg);
}
.ot-mobile-nav-toggle[aria-expanded="true"] .ot-mobile-nav-toggle__icon::after {
    top: 0; transform: rotate(-45deg);
}

/* ---------- Mobile breakpoint ---------- */

@media (max-width: 782px) {
    /* Header needs position:relative so the absolutely-positioned dropdown
       anchors to the header (its bottom edge), not the page. */
    .ot-header { position: relative; }

    /* Show the hamburger button. 40x40 hit target — close to iOS HIG's
       44px minimum but visually balanced with the 32px avatar pill it
       sits next to. Pill shape + soft hover bg matches .ot-user-menu__trigger
       so the two controls read as a coherent pair on the right edge. */
    .ot-mobile-nav-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        padding: 0;
        background: none;
        border: 1px solid transparent;
        border-radius: 999px;
        color: var(--wp--preset--color--fg);
        cursor: pointer;
        transition: background 120ms, border-color 120ms, color 120ms;
    }
    .ot-mobile-nav-toggle:hover,
    .ot-mobile-nav-toggle[aria-expanded="true"] {
        background: var(--wp--preset--color--bg-elev, #f5f5f5);
        border-color: var(--wp--preset--color--border, #e0e0e0);
        color: var(--wp--preset--color--accent);
    }

    /* Nav becomes a slide-down panel below the header. Hidden by default;
       the JS toggle adds .is-open. Using display:none (vs opacity) means
       the closed nav doesn't trap focus or get scrolled past on tab. */
    .ot-mega-menu {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        z-index: 999;
        background: var(--wp--preset--color--bg, #fff);
        border-top: 1px solid var(--wp--preset--color--border, #e0e0e0);
        box-shadow: 0 8px 16px rgba(0, 0, 0, 0.08);
        padding: 0.25rem 1rem 1rem;
        max-height: calc(100vh - 70px);
        overflow-y: auto;
    }
    .ot-mega-menu.is-open { display: block; }

    .ot-mega-menu__list {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    .ot-mega-menu__top {
        border-bottom: 1px solid var(--wp--preset--color--border, #ebebeb);
    }
    .ot-mega-menu__top:last-child { border-bottom: 0; }

    /* Top-level link: full-width tap target, caret rotates when open. */
    .ot-mega-menu__top-link {
        justify-content: space-between;
        padding: 0.85em 0;
        border-bottom: 0;
    }
    /* Don't show desktop's red underline on mobile — the row already
       has its own border-bottom from .ot-mega-menu__top. */
    .ot-mega-menu__top:hover > .ot-mega-menu__top-link,
    .ot-mega-menu__top:focus-within > .ot-mega-menu__top-link,
    .ot-mega-menu__top.is-current > .ot-mega-menu__top-link {
        border-bottom-color: transparent;
    }
    .ot-mega-menu__caret {
        display: inline-block;
        font-size: 1em;
        transition: transform 160ms ease;
    }
    .ot-mega-menu__top.is-open .ot-mega-menu__caret { transform: rotate(180deg); }

    /* Panels collapsed by default; shown only when JS adds .is-open
       to the parent <li>. Override desktop's hover-and-focus opening. */
    .ot-mega-menu__top:hover > .ot-mega-menu__panel,
    .ot-mega-menu__top:focus-within > .ot-mega-menu__panel {
        opacity: 0;
        visibility: hidden;
        transform: none;
    }
    .ot-mega-menu__panel {
        display: none;
        position: static;
        opacity: 1;
        visibility: visible;
        transform: none;
        box-shadow: none;
        border: 0;
        border-left: 2px solid var(--wp--preset--color--accent);
        padding: 0.4rem 0 0.85rem 0.85rem;
        min-width: 0;
        margin: 0 0 0.6rem;
        background: transparent;
    }
    .ot-mega-menu__top.is-open > .ot-mega-menu__panel {
        display: block;
        opacity: 1;
        visibility: visible;
    }
    .ot-mega-menu__panel-inner {
        grid-template-columns: 1fr;
        gap: 0.85rem;
    }

    /* Search field in the mobile drawer — full width, slightly
       taller for thumb-friendly tapping. */
    .ot-mega-menu__top--search {
        margin-left: 0;
    }
    .ot-search-form {
        width: 100%;
        padding: 0.6rem 0.85rem;
    }

    /* Tighter leaf links in the collapsed panel. */
    .ot-mega-menu__leaf a {
        padding: 0.45rem 0.5rem;
        font-size: 0.875rem;
    }
}

/* Dark-mode tints — panel background + leaf hover need to flip.
   Theme tokens already adapt via theme.json, but the panel shadow
   and the rgba red leaf-hover would look wrong on a dark backdrop. */
:root[data-theme="dark"] .ot-mega-menu__panel {
    background: #18181a;
    border-color: #333;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5), 0 4px 12px rgba(0, 0, 0, 0.35);
}
:root[data-theme="dark"] .ot-mega-menu__column {
    background: #232325;
    border-color: rgba(255, 255, 255, 0.06);
}
:root[data-theme="dark"] .ot-mega-menu__leaf a:hover,
:root[data-theme="dark"] .ot-mega-menu__leaf.is-current a {
    background: rgba(204, 0, 0, 0.18);
}
:root[data-theme="dark"] .ot-mega-menu__caret { color: #777; }
:root[data-theme="dark"] .ot-search-form {
    background: #2a2a2a;
    color: #aaa;
}
:root[data-theme="dark"] .ot-search-form:hover { background: #333; }
:root[data-theme="dark"] .ot-search-form:focus-within {
    background: #1f1f1f;
    color: #f5f5f5;
    box-shadow: 0 0 0 3px rgba(255, 56, 56, 0.18);
}
:root[data-theme="dark"] .ot-search-form__input { color: #f5f5f5; }
:root[data-theme="dark"] .ot-search-form__input::placeholder { color: #888; }

/* Hamburger button — dark-mode hover/active background to match the
   avatar pill it sits next to. */
:root[data-theme="dark"] .ot-mobile-nav-toggle:hover,
:root[data-theme="dark"] .ot-mobile-nav-toggle[aria-expanded="true"] {
    background: #2a2a2a;
    border-color: #444;
}

/* Header logo. The wp:site-title fallback that used to live next to it
   was removed in B102 — the :has() selector that hid it didn't apply
   reliably across the SPA's do_blocks() pipeline, so on forum pages the
   "OpenTuition.com" title was rendering visibly between the logo and the
   nav, pushing the nav 80–90px right and breaking the wrap point.

   Why we override the wp-block-site-logo styles here (B103):
     WP's wp-block-site-logo stylesheet is enqueued on theme pages but
     NOT on forum pages (SPA doesn't load block library CSS). That sheet
     sets text-align, padding, and link sizing on the site-logo block, so
     the rendered logo had subtly different width between the two contexts
     (residual ~18px shift in the nav after B102). Setting these
     explicitly here makes the logo identical on both surfaces — no
     dependency on WP's block library CSS being present. */
.ot-header__logo,
.wp-block-site-logo {
    /* Force wrapper to size to its image content. Diagnosed via console
       output (B106) that the wrapper was rendering at 294px wide on forum
       pages despite the image inside being only ~124px. Cause was likely
       a flex-basis or width inherited from a now-removed block attribute,
       persisted in the DOM by WP's block render. The display:inline-flex
       + flex:0 0 auto + width:max-content combo guarantees the wrapper
       hugs its content on every browser, every context. */
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    width: max-content;
    min-width: 0;
    line-height: 0;             /* eliminate phantom inline-baseline space
                                   that browsers add to <img> wrappers */
    margin: 0;
    padding: 0;
}
/* Both selectors carry specificity (0,2,1) to match WP's block library
   rule `.wp-block-site-logo.is-default-size img { width: 120px }`. Without
   the matching specificity, on theme pages WP's rule wins and forces the
   logo to 120px while on forum pages (no block library CSS loaded) it
   renders at natural size — enormous. With this match, source order wins
   the tie, and the theme stylesheet loads AFTER WP's block library so we
   take precedence on both contexts. */
.ot-header__logo img,
.wp-block-site-logo img,
.wp-block-site-logo.is-default-size img {
    display: block;
    height: auto;
    max-height: 50px;
    width: auto;                /* let aspect ratio drive width — capped
                                   in practice by max-height to ~123px on
                                   the OT logo (220×89 native = 2.47:1) */
    margin: 0;
    padding: 0;
}
.ot-header__logo a,
.ot-header__logo .wp-block-site-logo,
.wp-block-site-logo a {
    display: inline-flex;
    align-items: center;
    line-height: 0;
}

/* ==================================================================
   Header layout — unified across theme pages AND the forum SPA.

   Why one block, no .ot-forum-page > scoping (B100):
     WordPress normally generates per-block layout CSS (.wp-container-*)
     dynamically inside wp_head() — display:flex, gap, etc. for any
     wp:group with a "layout" attribute. The forum SPA bypasses wp_head()
     so those rules never make it onto forum pages. We previously had a
     parallel set of forum-only fallbacks (.ot-forum-page > header.ot-header...)
     that recreated WP's output by hand, but the values drifted out of
     sync with the markup (one block said spacing-2, the fallback said
     spacing-3, etc.). Result: header looked subtly different between
     contexts.

     Solution: declare every layout property here, by className, with no
     surface scoping. Both contexts read these same rules. The wp-container-*
     rules WP emits on theme pages still apply but are identical or weaker
     (specificity tie, source order wins → our rules ship after global
     styles in the cascade).

     If you change a value here, NOTHING else needs updating. There is no
     "fallback" anymore — this IS the rule.
   ================================================================== */

/* The <header> itself — constrained-layout container.
   max-width comes from the wp:group's contentSize attribute, but we set it
   here too so the SPA gets it without needing WP's per-container CSS.
   Vertical padding stays on the inline style attribute (parts/header.html
   line 20) which both contexts get identically. */
/* Header band — full-width tinted surface (warm cream) framed by a
   2px brand-red bottom edge. The cream tint matches the dropdown
   panel surface (.ot-mega-menu__panel) so the chrome reads as one
   unified design system. The red bottom edge mirrors the dropdown's
   red top edge — when a dropdown opens, the two red lines visually
   frame the navigation experience as one continuous element.

   Constrained layout: the outer <header> spans the full viewport
   for the colored band, while .ot-header__row inside it stays at
   1200px max with auto margins, keeping content alignment matching
   the rest of the site. This breaks the inline `max-width:1200px`
   that the wp:group "constrained" layout would otherwise apply. */
header.ot-header {
    max-width: 100%;
    margin-left: 0;
    margin-right: 0;
    /* Existing Surface token from theme.json (--bg-elev = #f7f7f8 in
       light mode, dark equivalent in dark mode). Reuses the same
       palette as buttons, panels, and form fields elsewhere on the
       site instead of introducing a one-off cream colour. */
    background: var(--wp--preset--color--bg-elev);
    box-sizing: border-box;
    /* No explicit bottom border — the Surface-tone header sitting
       on white content provides enough separation through tonal
       contrast alone. Modern minimal idiom: Linear, Apple, Vercel
       all skip explicit hairlines in favour of surface-tone shifts.
       Override the inline 1px gray border from the template part
       with `none` to keep the visual quiet. */
    border-bottom: 0 !important;
}
:root[data-theme="dark"] header.ot-header {
    /* In dark mode --bg-elev is already a dark-tinted surface from
       theme.json's dark palette — no need to hardcode. */
    background: var(--wp--preset--color--bg-elev);
}

/* Outer row: brand-nav on the left, right cluster on the right.
   Now also responsible for the 1200px content constraint that
   used to live on the <header> itself. */
.ot-header__row {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--wp--preset--spacing--4);     /* 24px between left and right clusters */
    width: 100%;
    max-width: 1200px;
    margin-left: auto;
    margin-right: auto;
    padding-left: var(--wp--preset--spacing--4);
    padding-right: var(--wp--preset--spacing--4);
}

/* Left cluster: logo + (legacy site-title fallback) + primary mega-menu. */
.ot-header__brand-nav {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--wp--preset--spacing--3);     /* 16px between logo and nav */
}

/* Right cluster: hamburger + bell + avatar. */
.ot-header__right {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--wp--preset--spacing--2);     /* 8px between the three controls */
    margin-right: 6rem;                      /* pull cluster inward from the
                                                right edge on wide viewports
                                                — see B89 */
}
@media (max-width: 1199px) { .ot-header__right { margin-right: 2rem; } }
@media (max-width: 782px)  { .ot-header__right { margin-right: 0; } }

/* Forum 2-column layout — wraps the SPA mount + the right-rail sidebar.
   Matches the theme's .ot-content-with-sidebar columns block (used by
   page.html, single.html, course-landing.html) so the forum sits in the
   same visual frame as the rest of the site. */
.ot-forum-layout {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--wp--preset--spacing--4) 1rem;
    display: flex;
    align-items: flex-start;
    gap: var(--wp--preset--spacing--5);
    box-sizing: border-box;
}
.ot-forum-content {
    flex: 1 1 auto;
    min-width: 0;        /* lets the column shrink past content min-width
                            (table cells, long URLs etc. otherwise force
                            the column wider than its flex-basis) */
}
.ot-forum-sidebar {
    flex: 0 0 300px;
    width: 300px;
}
@media (max-width: 900px) {
    .ot-forum-layout {
        flex-direction: column;
        gap: var(--wp--preset--spacing--4);
    }
    .ot-forum-sidebar { flex: 1 1 auto; width: 100%; }
}

.ot-forum-page > footer.ot-footer {
    max-width: 1200px;
    margin-left: auto;
    margin-right: auto;
    padding-left: 1rem;
    padding-right: 1rem;
    box-sizing: border-box;
}
.ot-forum-page > footer.ot-footer .wp-block-columns {
    /* Force the columns block to stay 4-up like on theme pages. WP's
       responsive collapse to single-column triggers around 781px and
       requires the per-block container CSS that doesn't reach us. */
    display: flex;
    flex-wrap: nowrap;
    gap: var(--wp--preset--spacing--5);
}
.ot-forum-page > footer.ot-footer .wp-block-column {
    flex-basis: 0;
    flex-grow: 1;
    min-width: 0;
}
/* Bottom bar (copyright + legal) — flex space-between like on theme pages. */
.ot-forum-page > footer.ot-footer .ot-footer__bottom {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
}

/* Footer link styling. Without this, link colour comes from theme.json
   --link blue which is fine but reads "active" — not what footer chrome
   should be. Footer links sit muted by default and surface to brand
   blue on hover for affordance. */
.ot-footer__links a,
.ot-footer__legal a {
    color: var(--wp--preset--color--fg-muted);
    text-decoration: none;
}
.ot-footer__links a:hover,
.ot-footer__legal a:hover {
    color: var(--wp--preset--color--link);
    text-decoration: underline;
}
/* Tighter heading→link gap inside each column. WP's default block-gap
   leaves too much air. */
.ot-footer__cols .wp-block-heading + p { margin-top: 0.5rem; }

/* ==================================================================
   User menu — header-right account widget rendered by [ot_auth_state].

   Two states:
     .ot-user-menu--out  → quiet "Log in" + brand-red "Register" pill
     .ot-user-menu--in   → avatar + name pill, opens a dropdown via <details>

   The dropdown is HTML-native (<details>/<summary>), so it works without
   JS for open/close. assets/js/mega-menu.js handles outside-click to
   collapse it (matching the UX everyone expects from header dropdowns). */

.ot-user-menu { position: relative; font-size: 0.875rem; line-height: 1; }

/* ---- Logged out ---- */

.ot-user-menu--out {
    display: inline-flex;
    align-items: center;
    gap: 0.6em;
}
.ot-user-menu__login {
    color: var(--wp--preset--color--fg);
    text-decoration: none;
    padding: 0.3em 0.4em;
}
.ot-user-menu__login:hover {
    color: var(--wp--preset--color--accent);
    text-decoration: underline;
}
.ot-user-menu__register {
    background: var(--wp--preset--color--accent);
    color: #fff;
    padding: 0.45em 0.95em;
    border-radius: 999px;
    text-decoration: none;
    font-weight: 500;
    transition: background 120ms;
}
.ot-user-menu__register:hover { background: #a30000; color: #fff; }

/* ---- Logged in: avatar pill ---- */

/* The trigger pill was dropped — the avatar+name now reads like any
   other top-level menu item (ACCA, CIMA, etc.), with a brand-red
   underline on hover/open. Padding + font-size mirror
   .ot-mega-menu__top-link so the cluster on the right edge stays
   visually aligned with the nav on the left. */
.ot-user-menu__trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    padding: 0.5em 0.6em;
    color: var(--wp--preset--color--fg);
    font-weight: 500;
    font-size: 0.9375rem;
    line-height: 1.2;
    cursor: pointer;
    list-style: none;
    user-select: none;
    border-bottom: 2px solid transparent;
    transition: color 120ms, border-bottom-color 120ms;
}
/* Hide the native disclosure triangle on every browser. */
.ot-user-menu__trigger::-webkit-details-marker { display: none; }
.ot-user-menu__trigger::marker { content: ''; }

.ot-user-menu__trigger:hover,
.ot-user-menu[open] > .ot-user-menu__trigger {
    color: var(--wp--preset--color--accent);
    border-bottom-color: var(--wp--preset--color--accent);
}

/* Avatar-only trigger variant — no name/caret next to the avatar.
   Drops the text-style padding + bottom border (no text to underline)
   and shows a thin ring around the avatar on hover/open instead. */
.ot-user-menu--avatar-only .ot-user-menu__trigger {
    padding: 4px;
    border-bottom: 0;
}
.ot-user-menu--avatar-only .ot-user-menu__avatar {
    border-radius: 50%;
    box-shadow: 0 0 0 2px transparent;
    transition: box-shadow 120ms;
}
.ot-user-menu--avatar-only:hover .ot-user-menu__avatar,
.ot-user-menu--avatar-only[open] .ot-user-menu__avatar {
    box-shadow: 0 0 0 2px var(--wp--preset--color--accent);
}

.ot-user-menu__avatar {
    width: 32px; height: 32px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    background: var(--wp--preset--color--bg-elev, #f0f0f0);
}

.ot-user-menu__name {
    color: var(--wp--preset--color--fg);
    font-weight: 500;
    max-width: 24ch;          /* handles ~20-24 char usernames without truncating;
                                 only kicks in as a guard for ridiculous lengths */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ot-user-menu__caret {
    font-size: 0.7em;
    color: var(--wp--preset--color--fg-muted);
    transition: transform 150ms;
}
.ot-user-menu[open] .ot-user-menu__caret { transform: rotate(180deg); }

/* ---- Logged in: dropdown panel ---- */

/* Visual treatment intentionally matches .ot-mega-menu__panel (search
   for "MEGA-MENU PANEL" above) so the ACCA dropdown and the avatar
   dropdown read as the same component family:
     - 1px subtle border + 2px brand-red top edge
     - Soft shadow (0 4px 12px rgba(0,0,0,0.08))
     - Square corners (no border-radius)
     - Opacity + transform fade-in on open
   Item hover also matches the mega-menu leaves: brand-red-tinted bg
   + accent text. Any changes here should be mirrored on the mega-menu
   rule, and vice versa. */
.ot-user-menu__panel {
    position: absolute;
    top: 100%;
    right: 0;
    min-width: 220px;
    background: var(--wp--preset--color--bg, #fff);
    border: 1px solid var(--wp--preset--color--border, #e0e0e0);
    border-top: 2px solid var(--wp--preset--color--accent);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    padding: 0.5em 0;
    z-index: 200;
    /* Animation parity with the mega-menu. <details> normally toggles
       display:none, which kills CSS transitions; we override that
       below so opacity/visibility/transform can animate cleanly. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 120ms ease-out, transform 120ms ease-out, visibility 0s linear 120ms;
}
/* Force the panel into the display:block / animatable state regardless
   of <details> open/closed. Without this rule, browsers hide closed
   <details> children entirely, defeating the transition above. */
details.ot-user-menu .ot-user-menu__panel { display: block; }
details.ot-user-menu[open] > .ot-user-menu__panel {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity 120ms ease-out, transform 120ms ease-out, visibility 0s;
}
.ot-user-menu__header {
    padding: 0.45em 1em;
    font-size: 0.8125rem;
    color: var(--wp--preset--color--fg-muted);
    border-bottom: 1px solid var(--wp--preset--color--border, #ebebeb);
    margin-bottom: 0.4em;
    word-break: break-word;
}
.ot-user-menu__header strong {
    display: block;
    color: var(--wp--preset--color--fg);
    font-weight: 500;
}
.ot-user-menu__item {
    display: block;
    padding: 0.55em 1em;
    color: var(--wp--preset--color--fg);
    text-decoration: none;
    font-size: 0.875rem;
}
/* Hover tint matches .ot-mega-menu__leaf a:hover — brand-red wash +
   accent text. Keep the two rules in sync. */
.ot-user-menu__item:hover {
    background: rgba(204, 0, 0, 0.06);
    color: var(--wp--preset--color--accent);
}
.ot-user-menu__divider {
    border: 0;
    border-top: 1px solid var(--wp--preset--color--border, #ebebeb);
    margin: 0.4em 0;
}
.ot-user-menu__item--logout { color: var(--wp--preset--color--fg-muted); }

/* ==================================================================
   Notification bell — header chrome shortcode rendered by
   inc/notifications.php. Logged-in only; populated by
   assets/js/notifications.js.

   Same pill shape + hover treatment as the avatar trigger and the
   hamburger so the three controls read as one cohesive cluster. */

.ot-notif-bell {
    position: relative;
    font-size: 0.875rem;
    line-height: 1;
}

.ot-notif-bell__trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 999px;
    border: 1px solid transparent;
    cursor: pointer;
    list-style: none;
    color: var(--wp--preset--color--fg);
    transition: background 120ms, border-color 120ms, color 120ms;
    position: relative;
}
.ot-notif-bell__trigger::-webkit-details-marker { display: none; }
.ot-notif-bell__trigger::marker { content: ''; }

.ot-notif-bell__trigger:hover,
.ot-notif-bell[open] > .ot-notif-bell__trigger {
    background: var(--wp--preset--color--bg-elev, #f5f5f5);
    border-color: var(--wp--preset--color--border, #e0e0e0);
    color: var(--wp--preset--color--accent);
}

.ot-notif-bell__icon {
    width: 20px; height: 20px;
    display: block;
}

/* Unread count — red pill anchored to the bell's top-right corner. */
.ot-notif-bell__badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    background: var(--wp--preset--color--accent);
    color: #fff;
    font-size: 0.65rem;
    font-weight: 600;
    line-height: 18px;
    text-align: center;
    border-radius: 999px;
    border: 2px solid var(--wp--preset--color--bg, #fff);
    box-sizing: content-box;
    pointer-events: none;
}
.ot-notif-bell__badge[hidden] { display: none; }

/* Dropdown panel — wider than the user menu (we render a list of items
   with multi-line text). Sits below the bell, anchored to its right edge. */
.ot-notif-bell__panel {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    width: 360px;
    max-width: calc(100vw - 2rem);
    background: var(--wp--preset--color--bg, #fff);
    border: 1px solid var(--wp--preset--color--border, #e0e0e0);
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
    z-index: 200;
    overflow: hidden;
}
.ot-notif-bell__header {
    padding: 0.7em 1em;
    border-bottom: 1px solid var(--wp--preset--color--border, #ebebeb);
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.ot-notif-bell__header strong {
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--wp--preset--color--fg);
}
.ot-notif-bell__mark-all {
    background: none;
    border: 0;
    padding: 0.25em 0.5em;
    color: var(--wp--preset--color--fg-muted);
    font-size: 0.75rem;
    cursor: pointer;
    border-radius: 4px;
}
.ot-notif-bell__mark-all:hover {
    color: var(--wp--preset--color--accent);
    background: var(--wp--preset--color--bg-elev, #f5f5f5);
}

.ot-notif-bell__list {
    max-height: 360px;
    overflow-y: auto;
}
.ot-notif-bell__empty {
    padding: 1.5em 1em;
    text-align: center;
    color: var(--wp--preset--color--fg-muted);
    font-size: 0.85rem;
}
.ot-notif-bell__empty--error { color: var(--wp--preset--color--accent); }

.ot-notif-bell__item {
    display: block;
    padding: 0.7em 1em;
    text-decoration: none;
    color: var(--wp--preset--color--fg);
    border-bottom: 1px solid var(--wp--preset--color--border, #ebebeb);
    background: var(--wp--preset--color--bg, #fff);
    transition: background 120ms;
}
.ot-notif-bell__item:last-child { border-bottom: 0; }
.ot-notif-bell__item:hover {
    background: var(--wp--preset--color--bg-elev, #f5f5f5);
}
.ot-notif-bell__item.is-unread {
    background: rgba(204, 0, 0, 0.04);
    font-weight: 500;
}
.ot-notif-bell__item.is-unread:hover {
    background: rgba(204, 0, 0, 0.08);
}
.ot-notif-bell__item-text {
    font-size: 0.8125rem;
    line-height: 1.45;
    color: var(--wp--preset--color--fg);
}
.ot-notif-bell__item-text em {
    font-style: normal;
    color: var(--wp--preset--color--accent);
}
.ot-notif-bell__verb {
    color: var(--wp--preset--color--fg-muted);
    font-weight: 400;
}
.ot-notif-bell__item-meta {
    font-size: 0.7rem;
    color: var(--wp--preset--color--fg-muted);
    margin-top: 0.25em;
}

.ot-notif-bell__footer {
    display: block;
    padding: 0.6em 1em;
    text-align: center;
    color: var(--wp--preset--color--accent);
    text-decoration: none;
    font-size: 0.8125rem;
    border-top: 1px solid var(--wp--preset--color--border, #ebebeb);
    background: var(--wp--preset--color--bg-elev, #fafafa);
}
.ot-notif-bell__footer:hover { background: var(--wp--preset--color--bg-elev, #f0f0f0); text-decoration: underline; }

/* Mobile — narrower viewport, panel takes near-full width and hugs the
   right edge of the screen so it doesn't overflow off-canvas. */
@media (max-width: 782px) {
    .ot-notif-bell__panel {
        width: calc(100vw - 1rem);
        right: -0.5rem;
    }
}

/* Dark mode */
:root[data-theme="dark"] .ot-notif-bell__panel {
    background: #1f1f1f;
    border-color: #333;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
:root[data-theme="dark"] .ot-notif-bell__trigger:hover,
:root[data-theme="dark"] .ot-notif-bell[open] > .ot-notif-bell__trigger {
    background: #2a2a2a;
    border-color: #444;
}
:root[data-theme="dark"] .ot-notif-bell__item { background: transparent; }
:root[data-theme="dark"] .ot-notif-bell__item:hover { background: #2a2a2a; }
:root[data-theme="dark"] .ot-notif-bell__item.is-unread { background: rgba(204, 0, 0, 0.12); }
:root[data-theme="dark"] .ot-notif-bell__item.is-unread:hover { background: rgba(204, 0, 0, 0.18); }
:root[data-theme="dark"] .ot-notif-bell__footer { background: #1a1a1a; }
:root[data-theme="dark"] .ot-notif-bell__badge { border-color: #1f1f1f; }

/* Mobile tweaks — hide the username next to the avatar (avatar alone
   is enough; same pattern as Slack / GitHub / X). The Register pill
   and Log in link shrink slightly to fit next to the hamburger. */
@media (max-width: 782px) {
    .ot-user-menu__name { display: none; }
    .ot-user-menu__caret { display: none; }
    .ot-user-menu__trigger { padding: 0.2em; }
    .ot-user-menu--out { gap: 0.4em; }
    .ot-user-menu__login { padding: 0.3em 0.2em; }
    .ot-user-menu__register { padding: 0.4em 0.75em; font-size: 0.8125rem; }
    .ot-user-menu__panel { right: 0; min-width: 200px; }
}

/* Dark-mode tweaks — panel surfaces need to flip to dark grey. The
   tokenised hover background already adapts via theme.json; only the
   panel itself needs explicit overrides for the shadow + border. */
:root[data-theme="dark"] .ot-user-menu__panel {
    background: #1f1f1f;
    border-color: #333;
    /* Brand-red top edge stays — same as the mega-menu's dark mode. */
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
:root[data-theme="dark"] .ot-user-menu__trigger:hover,
:root[data-theme="dark"] .ot-user-menu[open] > .ot-user-menu__trigger {
    background: #2a2a2a;
    border-color: #444;
}
:root[data-theme="dark"] .ot-user-menu__item:hover {
    background: #2a2a2a;
}

/* ====================================================================
   Site-wide WP buttons (B125)

   Single source of truth for the .wp-block-button__link pill so every
   front-end page gets the same treatment — qualification hubs, course
   landings, plain pages, the homepage, the lot. The previous version
   only styled buttons inside .page-template-qualification-hub, leaving
   buttons elsewhere with WP's flat default and creating an obvious
   two-style split between sections of the site.

   Default behaviour: brand-red gradient pill, soft red shadow, white
   text, sweeping shine on hover, subtle lift. Honours the WP block
   editor's `is-style-outline` variant by NOT rewriting outline buttons
   — we let those keep their alternative look.

   Scope: front-end only. The .editor-styles-wrapper exclusion keeps
   the block editor preview from gaining the shine pseudo-element
   which Gutenberg can't always handle gracefully.
   ==================================================================== */
.wp-block-button__link:not(.is-style-outline) {
    background-color: var(--wp--preset--color--accent);
    background-image: linear-gradient(135deg, var(--wp--preset--color--accent) 0%, #b30000 100%);
    border: none;
    border-radius: 999px;
    padding: 0.75rem 1.75rem;
    font-weight: 600;
    color: #fff;
    box-shadow: 0 4px 12px rgba(204, 0, 0, 0.25);
    transition: transform 120ms, box-shadow 120ms, background-color 120ms;
    position: relative;
    overflow: hidden;
}
.wp-block-button__link:not(.is-style-outline):hover {
    background-color: #a30000;
    background-image: linear-gradient(135deg, #b30000 0%, #8b0000 100%);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 8px 20px rgba(204, 0, 0, 0.35);
}
/* Sweeping shine on hover — purely decorative diagonal highlight. */
.wp-block-button__link:not(.is-style-outline)::after {
    content: "";
    position: absolute;
    top: 0;
    left: -120%;
    width: 60%;
    height: 100%;
    background: linear-gradient(120deg,
        transparent 0%,
        rgba(255, 255, 255, 0.30) 50%,
        transparent 100%);
    transform: skewX(-20deg);
    transition: left 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
    pointer-events: none;
}
.wp-block-button__link:not(.is-style-outline):hover::after {
    left: 130%;
}
/* Outline variant — clean brand-red ring, white fill, fills on hover. */
.wp-block-button__link.is-style-outline {
    background: transparent;
    border: 2px solid var(--wp--preset--color--accent);
    border-radius: 999px;
    padding: calc(0.75rem - 2px) calc(1.75rem - 2px);
    font-weight: 600;
    color: var(--wp--preset--color--accent);
    transition: background-color 120ms, color 120ms;
}
.wp-block-button__link.is-style-outline:hover {
    background-color: var(--wp--preset--color--accent);
    color: #fff;
}
/* Honour reduced-motion — strip the shine sweep. */
@media (prefers-reduced-motion: reduce) {
    .wp-block-button__link::after { display: none; }
}

/* Per-paper resources card in the sidebar (B129). Lists every resource
   page that exists for the current paper — Lectures, Notes, Revision,
   Tests, Mocks, Flashcards. Item marked .is-current when the user is
   on that resource's page (gives a "you are here" cue). */
.ot-paper-resources {
    background: var(--wp--preset--color--bg-elev);
    border: 1px solid var(--wp--preset--color--border);
    border-radius: 10px;
    padding: var(--wp--preset--spacing--3);
    margin-bottom: var(--wp--preset--spacing--4);
}
.ot-paper-resources__label {
    margin: 0 0 0.5rem;
    color: var(--wp--preset--color--accent);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.ot-paper-resources__list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.ot-paper-resources__item {
    border-top: 1px solid var(--wp--preset--color--border);
}
.ot-paper-resources__item:first-child {
    border-top: none;
}
.ot-paper-resources__link {
    display: block;
    padding: 0.625rem 0.25rem;
    color: var(--wp--preset--color--fg);
    text-decoration: none;
    font-size: 0.9375rem;
    font-weight: 500;
    transition: color 120ms, padding-left 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ot-paper-resources__link:hover {
    color: var(--wp--preset--color--accent);
    padding-left: 0.5rem;
    text-decoration: none;
}
.ot-paper-resources__item.is-current .ot-paper-resources__link {
    color: var(--wp--preset--color--accent);
    font-weight: 700;
}
.ot-paper-resources__here {
    display: inline-block;
    font-size: 0.5rem;
    vertical-align: middle;
    margin-left: 0.25rem;
    color: var(--wp--preset--color--accent);
}

/* H1 on single posts (tutorial chapters, news posts, etc).
   The default WP block-editor H1 inherits body 16px/700 — far too small
   for a page title. We give it a clamp-based responsive size and the
   brand-red accent so it matches the styling on /acca/<paper>/-notes
   pages and qualification hubs. Stays under hero-band size (notes/hub
   pages use ~3rem); single posts get a slightly more modest 1.75-2.25rem.
*/
.single .wp-block-post-title,
.single h1.wp-block-post-title {
    font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem);
    font-weight: 700;
    color: var(--wp--preset--color--accent);
    line-height: 1.2;
    margin: 0 0 var(--wp--preset--spacing--4);
    letter-spacing: -0.01em;
}

/* WP-Embed (YouTube, Vimeo, etc.) sizing on single-post content.
   WP's oEmbed cache hardcodes width=500 height=281 on the iframe. Without
   this, the iframe stays at 500px even inside our 720px reading column,
   leaving an awkward gutter. We force the wrapper + iframe to fill the
   column and let .wp-has-aspect-ratio (added by WP for known providers)
   keep the right ratio.

   Why !important on width/height: WP's hardcoded iframe attributes are
   inline `width=` / `height=` which beat plain CSS specificity. */
.ot-main .wp-block-embed,
.ot-main .wp-block-embed__wrapper {
    width: 100%;
    margin-inline: 0;
}
.ot-main .wp-block-embed.wp-has-aspect-ratio .wp-block-embed__wrapper {
    position: relative;
    /* fallback for browsers without aspect-ratio — picked up by aspect-class rules below */
}
.ot-main .wp-block-embed iframe,
.ot-main .wp-block-embed embed,
.ot-main .wp-block-embed object {
    width: 100% !important;
    border: 0;
}
.ot-main .wp-block-embed.wp-has-aspect-ratio iframe,
.ot-main .wp-block-embed.wp-has-aspect-ratio embed,
.ot-main .wp-block-embed.wp-has-aspect-ratio object {
    height: 100% !important;
    position: absolute;
    inset: 0;
}
/* WP adds wp-embed-aspect-{ratio} for known providers. Translate each
   into an aspect-ratio so the wrapper has a height even though the
   iframe is absolutely positioned. */
.ot-main .wp-block-embed.wp-embed-aspect-21-9 .wp-block-embed__wrapper { aspect-ratio: 21 / 9; }
.ot-main .wp-block-embed.wp-embed-aspect-18-9 .wp-block-embed__wrapper { aspect-ratio: 18 / 9; }
.ot-main .wp-block-embed.wp-embed-aspect-16-9 .wp-block-embed__wrapper { aspect-ratio: 16 / 9; }
.ot-main .wp-block-embed.wp-embed-aspect-4-3  .wp-block-embed__wrapper { aspect-ratio: 4 / 3;  }
.ot-main .wp-block-embed.wp-embed-aspect-1-1  .wp-block-embed__wrapper { aspect-ratio: 1 / 1;  }
.ot-main .wp-block-embed.wp-embed-aspect-9-16 .wp-block-embed__wrapper { aspect-ratio: 9 / 16; }
.ot-main .wp-block-embed.wp-embed-aspect-1-2  .wp-block-embed__wrapper { aspect-ratio: 1 / 2;  }

/* "Back to all lectures" link on single tutorial-chapter posts (B127).
   Sits above the H1 — clear navigation affordance back to the paper's
   lecture index. Muted by default, brand-red on hover, with a small
   arrow that nudges left on hover for affordance. */
.ot-back-to-lectures {
    margin: 0 0 var(--wp--preset--spacing--3);
    font-size: 0.9375rem;
}
.ot-back-to-lectures__link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--wp--preset--color--fg-muted);
    text-decoration: none;
    font-weight: 500;
    transition: color 120ms;
}
.ot-back-to-lectures__link:hover {
    color: var(--wp--preset--color--accent);
    text-decoration: none;
}
.ot-back-to-lectures__arrow {
    display: inline-block;
    transition: transform 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ot-back-to-lectures__link:hover .ot-back-to-lectures__arrow {
    transform: translateX(-3px);
}

/* Hide the qualification-hub hero band on the front page only.
   The hero band displays post-title (set per-page in qualification-hub.html
   so /acca/, /cima/, etc. get a nice page title). On the front page the
   post-title is the literal page name from wp_posts ("OpenTuition Homepage")
   which isn't customer-facing copy — under Genesis the per-page PHP template
   hid it. We mirror that here without removing it for the other ~232 pages
   that legitimately want the title.
   Edit the page in WP admin to rename or remove the page title if you'd
   prefer to drop this rule and show whatever title you choose. */
.home .ot-hub-hero { display: none; }

/* ====================================================================
   Qualification Hub — visual polish (B122)

   Targets pages using template-qualification-hub (e.g. /acca/, /cima/,
   /fia/). Pure CSS — the post_content HTML is whatever an editor saved
   in WP admin, we just elevate it. Key targets:
     • .ot-hub-hero          — page title band (full-width)
     • .has-darkred-bg-color  — paper-list block (Knowledge/Skills + SP)
     • .has-lightyellow-bg-color — yellow callout cards (e.g. New ACCA Q.)
     • [class*=gradient-background] — the AI-tutor panel
     • Inner WP button blocks — unify to brand red pill
   The original ACCA hub block markup is many ad-hoc wp:group / wp:column
   layers; rather than rewriting the post HTML we paint over it. Long
   selectors below are a deliberate scope — they only apply on the hub
   templates and won't leak. (B122)
   ==================================================================== */

.page-template-qualification-hub .ot-main--qualification {
    /* Tighter rhythm between the hero band and the body grid. */
    --hub-radius: 16px;
    --hub-radius-sm: 10px;
    --hub-shadow: 0 2px 4px rgba(15, 23, 42, 0.04), 0 1px 2px rgba(15, 23, 42, 0.06);
    --hub-shadow-lift: 0 8px 24px rgba(15, 23, 42, 0.08), 0 2px 6px rgba(15, 23, 42, 0.06);
}

/* ---- Hero band ---- */
.page-template-qualification-hub .ot-hub-hero {
    position: relative;
    background:
        radial-gradient(ellipse 60% 80% at 50% 0%, rgba(204, 0, 0, 0.06), transparent 70%),
        linear-gradient(180deg, var(--wp--preset--color--bg-elev) 0%, var(--wp--preset--color--bg) 100%);
    padding-block: clamp(3rem, 6vw, 5rem) !important;
    overflow: hidden;
}
/* Decorative red accent line under the H1. */
.page-template-qualification-hub .ot-hub-hero .wp-block-post-title {
    position: relative;
    letter-spacing: -0.015em;
    color: var(--wp--preset--color--fg);
    margin: 0;
}
.page-template-qualification-hub .ot-hub-hero .wp-block-post-title::after {
    content: "";
    display: block;
    width: 64px;
    height: 4px;
    margin: 1.25rem auto 0;
    background: var(--wp--preset--color--accent);
    border-radius: 999px;
}

/* ---- Top dual cards (e.g. March results / 20% off books) ---- */
/* The first wp-block-columns inside the post-content. Catches the
   2-up "alert" cards at the top of the page. */
.page-template-qualification-hub .wp-block-post-content > .wp-block-columns:first-of-type > .wp-block-column,
.page-template-qualification-hub .wp-block-post-content > .wp-block-columns.has-background:first-of-type {
    border-radius: var(--hub-radius);
}

.page-template-qualification-hub .wp-block-columns.has-background {
    border-radius: var(--hub-radius);
    box-shadow: var(--hub-shadow);
    overflow: hidden;
    border: 1px solid color-mix(in srgb, currentColor 8%, transparent);
}

/* ---- Paper-list block (.has-darkred-background-color wrapper) ---- */
/* The big red panel that holds Knowledge + Skills + Strategic Professional
   paper links. We re-skin the wrapper, keep the bold red identity, but
   give it modern card structure with proper inner cards. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group {
    background:
        radial-gradient(ellipse at top right, rgba(255, 215, 0, 0.10), transparent 60%),
        linear-gradient(135deg, #8b0000 0%, #b30000 50%, #cc0000 100%) !important;
    border-radius: var(--hub-radius);
    padding: clamp(2rem, 4vw, 2.75rem) !important;
    box-shadow: 0 6px 24px rgba(139, 0, 0, 0.18);
    color: #fff;
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group > h2.wp-block-heading {
    margin: 0 0 1.5rem;
    font-size: clamp(1.5rem, 3vw, 1.875rem);
    font-weight: 700;
    color: #ffd54a !important;        /* friendlier yellow vs. raw "yellow" preset */
    letter-spacing: -0.01em;
    line-height: 1.2;
}
/* The two inner panels (Knowledge+Skills white, Strategic Professional grey)
   become a card grid of paper tiles. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background {
    background: rgba(255, 255, 255, 0.97) !important;
    border-radius: var(--hub-radius-sm);
    padding: 1.25rem !important;
    /* CSS grid: auto-fill cards, 1-3 columns based on width. */
    display: grid !important;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0.5rem;
    align-items: stretch;
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-grey-background-color {
    background: rgba(247, 247, 248, 0.97) !important;
}
/* Each <p><a>…</a></p> becomes a paper tile. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p {
    margin: 0;
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a {
    display: block;
    padding: 0.75rem 0.875rem;
    background: var(--wp--preset--color--bg);
    border: 1px solid var(--wp--preset--color--border);
    border-radius: 8px;
    color: var(--wp--preset--color--fg) !important;
    text-decoration: none !important;
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.35;
    transition: transform 120ms, box-shadow 120ms, border-color 120ms, color 120ms;
    position: relative;
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a::after {
    content: "→";
    position: absolute;
    right: 0.875rem;
    top: 50%;
    transform: translateY(-50%) translateX(0);
    color: var(--wp--preset--color--fg-dim);
    font-weight: 500;
    transition: transform 120ms, color 120ms;
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a:hover {
    border-color: var(--wp--preset--color--accent);
    color: var(--wp--preset--color--accent) !important;
    transform: translateY(-1px);
    box-shadow: 0 6px 16px rgba(204, 0, 0, 0.12);
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a:hover::after {
    color: var(--wp--preset--color--accent);
    transform: translateY(-50%) translateX(3px);
}
/* Strategic Professional cards get a subtle prestige tint on the left edge. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-grey-background-color > p > a {
    border-left: 3px solid var(--wp--preset--color--accent);
}

/* ---- Yellow callout (e.g. "New ACCA Qualification") ---- */
.page-template-qualification-hub .has-lightyellow-background-color.wp-block-group {
    background: linear-gradient(135deg, #fff8d6 0%, #fff3b8 100%) !important;
    border: 1px solid #f3d779;
    border-left: 4px solid #d97706;
    border-radius: var(--hub-radius);
    padding: clamp(1.5rem, 3vw, 2rem) !important;
    box-shadow: var(--hub-shadow);
}
.page-template-qualification-hub .has-lightyellow-background-color.wp-block-group h2.wp-block-heading {
    margin-top: 0;
    color: #8b0000 !important;
    letter-spacing: -0.01em;
}

/* ---- AI Tutor gradient panel ---- */
.page-template-qualification-hub [class*="gradient-background"].alignfull {
    background: linear-gradient(135deg, #1a2332 0%, #2d3e52 50%, #3a4f6b 100%) !important;
    border-radius: var(--hub-radius);
    margin-inline: 0 !important;
    padding-block: clamp(2.5rem, 5vw, 3.5rem) !important;
    box-shadow: var(--hub-shadow-lift);
    overflow: hidden;
    position: relative;
}
.page-template-qualification-hub [class*="gradient-background"].alignfull::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse at 30% 20%, rgba(204, 0, 0, 0.20), transparent 50%),
        radial-gradient(ellipse at 70% 80%, rgba(0, 102, 204, 0.18), transparent 55%);
    pointer-events: none;
}
.page-template-qualification-hub [class*="gradient-background"].alignfull > * {
    position: relative;
    z-index: 1;
}
.page-template-qualification-hub [class*="gradient-background"].alignfull h2 {
    color: #fff !important;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.page-template-qualification-hub [class*="gradient-background"].alignfull p {
    color: rgba(255, 255, 255, 0.85);
}

/* (Generic button styling lifted to a theme-wide rule — see the
   "Site-wide WP buttons (B125)" block earlier in this file.
   Buttons inside qualification-hub now inherit from that unified
   rule, so /acca/, /cima/, /fia/ and every other front-end page
   share the same brand-red pill.) */

/* ---- H2 section headings ---- */
/* Free-floating H2s in the post content (Survey Results, Plan your ACCA
   Exam Journey, Exam Technique Articles, etc.) get a brand-accent
   marker on the left to pull the eye and tie sections together. */
.page-template-qualification-hub .wp-block-post-content > h2.wp-block-heading {
    position: relative;
    padding-left: 0.875rem;
    border-left: 4px solid var(--wp--preset--color--accent);
    margin-top: var(--wp--preset--spacing--5) !important;
    font-size: clamp(1.5rem, 3vw, 1.875rem);
    line-height: 1.2;
    letter-spacing: -0.01em;
}

/* ---- News list (the stack of <p><a> rows under "ACCA News:") ---- */
/* Convert plain paragraph-of-link rows into a visually tighter list with
   subtle dividers and an arrow hover affordance. We can't add classes,
   so target via the H4 sibling pattern: H4 "ACCA News:" is followed by
   a series of single-link paragraphs. */
.page-template-qualification-hub .wp-block-post-content h4.wp-block-heading + p,
.page-template-qualification-hub .wp-block-post-content h4.wp-block-heading + p + p,
.page-template-qualification-hub .wp-block-post-content h4.wp-block-heading + p + p + p,
.page-template-qualification-hub .wp-block-post-content h4.wp-block-heading + p + p + p + p {
    margin: 0;
    padding: 0.625rem 0;
    border-bottom: 1px solid var(--wp--preset--color--border);
    font-size: 1rem;
}
.page-template-qualification-hub .wp-block-post-content h4.wp-block-heading {
    margin-bottom: 0.25rem;
    color: var(--wp--preset--color--fg-muted);
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* ---- Discount code highlight ---- */
/* The "Discount code: bppacca20optu" line — make the code monospace
   pill so it reads as something to copy. */
.page-template-qualification-hub .wp-block-post-content p strong:only-child {
    /* No-op safety; targeted styling below for known wraps. */
}

/* ---- Spacer normalisation ---- */
/* The page is full of wp:spacer blocks of varying heights. Cap them so
   the rhythm stays consistent without re-editing post content. */
.page-template-qualification-hub .wp-block-post-content > .wp-block-spacer {
    height: var(--wp--preset--spacing--4) !important;
}

/* ---- Tighten body link colour rules ---- */
/* Brand-blue content links only inside the body, not inside the red
   paper panel where we override them above. */
.page-template-qualification-hub .wp-block-post-content a:not(.wp-block-button__link):not(.wp-element-button) {
    color: var(--wp--preset--color--link);
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a {
    /* Override re-asserted to win against the rule above. */
    color: var(--wp--preset--color--fg) !important;
}

/* ---- Mobile tightening ---- */
@media (max-width: 700px) {
    .page-template-qualification-hub .has-darkred-background-color.wp-block-group {
        padding: 1.5rem !important;
    }
    .page-template-qualification-hub .has-darkred-background-color.wp-block-group
        .wp-block-group.has-background {
        grid-template-columns: 1fr;
    }
}

/* ====================================================================
   Qualification Hub — premium polish (B124)

   Layered on top of B122 to push the surface from "polished" to
   "memorable": graph-paper hero backdrop, animated AI-tutor spotlight,
   3D card hover, gradient button shine, decorative dividers, scroll-
   driven entrance animations, refined typography. Pure CSS, no text
   changes; selector scope still .page-template-qualification-hub.

   Colour system reused from theme.json:
     accent  #cc0000   • link    #0066cc   • amber  #d97706
     bg      #ffffff   • bg-elev #f7f7f8   • fg-muted #65707d

   Motion is gated behind prefers-reduced-motion. Scroll animations are
   gated behind @supports (animation-timeline: view()) — graceful no-op
   on Safari < 26 / Firefox.
   ==================================================================== */

/* ---- Site-wide details for hub pages ---- */
.page-template-qualification-hub ::selection {
    background: rgba(204, 0, 0, 0.18);
    color: var(--wp--preset--color--fg);
}
.page-template-qualification-hub {
    scroll-behavior: smooth;
    scroll-padding-top: 64px;
}
.page-template-qualification-hub h1,
.page-template-qualification-hub h2,
.page-template-qualification-hub h3 {
    text-wrap: balance;       /* keeps hero/section heads from leaving lonely words */
}

/* ---- Hero — graph-paper + soft red bloom + decorative accents ---- */
.page-template-qualification-hub .ot-hub-hero {
    background:
        /* 1. soft red bloom behind the H1 */
        radial-gradient(ellipse 50% 70% at 50% 30%, rgba(204, 0, 0, 0.08), transparent 75%),
        /* 2. graph-paper dot grid (sized via CSS variable for scale) */
        radial-gradient(circle at center, rgba(31, 35, 40, 0.10) 1px, transparent 1.4px),
        /* 3. base elevation gradient */
        linear-gradient(180deg, var(--wp--preset--color--bg-elev) 0%, var(--wp--preset--color--bg) 100%) !important;
    background-size: auto, 22px 22px, auto;
    background-position: center, 0 0, center;
    /* fade the dot grid out at the edges so it doesn't look pasted on */
    -webkit-mask-image: radial-gradient(ellipse 90% 70% at center, #000 60%, transparent 100%);
            mask-image: radial-gradient(ellipse 90% 70% at center, #000 60%, transparent 100%);
}
/* Two rotated geometric accents flanking the hero — abstract, pure
   shapes, no text, low opacity so they read as background ornament. */
.page-template-qualification-hub .ot-hub-hero {
    position: relative;
    isolation: isolate;
}
.page-template-qualification-hub .ot-hub-hero::before,
.page-template-qualification-hub .ot-hub-hero::after {
    content: "";
    position: absolute;
    width: 72px;
    height: 72px;
    border: 2px solid var(--wp--preset--color--accent);
    border-radius: 12px;
    opacity: 0.18;
    z-index: -1;
    pointer-events: none;
}
.page-template-qualification-hub .ot-hub-hero::before {
    top: 28%;
    left: 8%;
    transform: rotate(-15deg);
}
.page-template-qualification-hub .ot-hub-hero::after {
    bottom: 22%;
    right: 8%;
    transform: rotate(20deg);
    border-style: dashed;
}
/* Replace the simple red dash under H1 with a gradient bar + center dot. */
.page-template-qualification-hub .ot-hub-hero .wp-block-post-title::after {
    width: 96px;
    height: 5px;
    background:
        radial-gradient(circle at 50% 50%, var(--wp--preset--color--accent) 0 4px, transparent 4.5px),
        linear-gradient(90deg, transparent 0%, var(--wp--preset--color--accent) 25%, var(--wp--preset--color--accent) 75%, transparent 100%);
    background-color: transparent;
    border-radius: 0;
    margin-top: 1.5rem;
}

/* ---- Top dual cards — refined shadows + subtle gradient sheen ---- */
.page-template-qualification-hub .wp-block-post-content > .wp-block-columns:first-of-type .wp-block-column.has-background {
    position: relative;
    overflow: hidden;
    transition: transform 200ms, box-shadow 200ms;
}
.page-template-qualification-hub .wp-block-post-content > .wp-block-columns:first-of-type .wp-block-column.has-background::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.45) 0%, transparent 35%);
    pointer-events: none;
}
.page-template-qualification-hub .wp-block-post-content > .wp-block-columns:first-of-type .wp-block-column.has-background:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(15, 23, 42, 0.10), 0 4px 8px rgba(15, 23, 42, 0.06);
}

/* ---- Paper grid — premium card treatment + diagonal pattern overlay ---- */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group {
    /* Add a subtle diagonal hatch over the red gradient for texture. */
    background:
        repeating-linear-gradient(
            -45deg,
            rgba(255, 255, 255, 0.025) 0 8px,
            transparent 8px 16px
        ),
        radial-gradient(ellipse at top right, rgba(255, 215, 0, 0.10), transparent 60%),
        linear-gradient(135deg, #8b0000 0%, #b30000 50%, #cc0000 100%) !important;
    position: relative;
    overflow: hidden;
}
/* Decorative corner ribbon — top-right, abstract red triangle. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group::before {
    content: "";
    position: absolute;
    top: -1px;
    right: -1px;
    width: 120px;
    height: 120px;
    background: linear-gradient(225deg, rgba(255, 215, 0, 0.18) 0%, transparent 60%);
    pointer-events: none;
}
/* Card hover: lift + subtle gradient ring + arrow slide. The earlier
   B122 hover was flat; now the cards feel pressable. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a {
    background: linear-gradient(180deg, #ffffff 0%, #fafafb 100%);
    transition: transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
                box-shadow 180ms,
                border-color 180ms,
                color 180ms,
                background 180ms;
    will-change: transform;
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-background > p > a:hover {
    transform: translateY(-3px) scale(1.01);
    box-shadow:
        0 12px 24px rgba(204, 0, 0, 0.18),
        0 0 0 2px rgba(204, 0, 0, 0.10);
    background: linear-gradient(180deg, #ffffff 0%, #fff5f5 100%);
}
/* Strategic Professional — gold-tinted prestige treatment, was just a
   plain red left edge. Now: gradient red→gold left edge + amber tint
   on hover. */
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-grey-background-color > p > a {
    border-left: 0;
    background:
        linear-gradient(90deg, rgba(217, 119, 6, 0.08) 0%, transparent 30%),
        linear-gradient(180deg, #ffffff 0%, #fafafb 100%);
    box-shadow: inset 3px 0 0 0 var(--wp--preset--color--accent),
                inset 6px 0 0 0 rgba(217, 119, 6, 0.4);
}
.page-template-qualification-hub .has-darkred-background-color.wp-block-group
    .wp-block-group.has-grey-background-color > p > a:hover {
    background:
        linear-gradient(90deg, rgba(217, 119, 6, 0.14) 0%, transparent 40%),
        linear-gradient(180deg, #ffffff 0%, #fff5f5 100%);
}

/* ---- Yellow callout — parchment treatment ---- */
.page-template-qualification-hub .has-lightyellow-background-color.wp-block-group {
    position: relative;
    overflow: hidden;
}
/* Folded-corner illusion top-right. */
.page-template-qualification-hub .has-lightyellow-background-color.wp-block-group::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 36px 36px 0;
    border-color: transparent rgba(217, 119, 6, 0.18) transparent transparent;
    box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.08);
}

/* ---- AI Tutor — animated spotlight + button glow ---- */
.page-template-qualification-hub [class*="gradient-background"].alignfull {
    background: linear-gradient(135deg, #0f1729 0%, #1e2d44 50%, #2d4060 100%) !important;
}
.page-template-qualification-hub [class*="gradient-background"].alignfull::before {
    background:
        radial-gradient(ellipse at 30% 20%, rgba(204, 0, 0, 0.30), transparent 50%),
        radial-gradient(ellipse at 70% 80%, rgba(0, 102, 204, 0.22), transparent 55%);
    animation: ot-hub-spotlight 8s ease-in-out infinite alternate;
}
@keyframes ot-hub-spotlight {
    0%   { background-position: 0% 0%, 100% 100%; opacity: 0.85; }
    100% { background-position: 20% 10%, 80% 90%; opacity: 1.00; }
}
/* Add a soft starfield-like dot pattern for depth. */
.page-template-qualification-hub [class*="gradient-background"].alignfull::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        radial-gradient(circle at 12% 18%, rgba(255, 255, 255, 0.3) 1px, transparent 2px),
        radial-gradient(circle at 78% 32%, rgba(255, 255, 255, 0.25) 1px, transparent 2px),
        radial-gradient(circle at 32% 72%, rgba(255, 255, 255, 0.20) 1px, transparent 2px),
        radial-gradient(circle at 88% 78%, rgba(255, 255, 255, 0.30) 1px, transparent 2px),
        radial-gradient(circle at 56% 14%, rgba(255, 255, 255, 0.22) 1px, transparent 2px);
    pointer-events: none;
    z-index: 0;
    opacity: 0.6;
}
.page-template-qualification-hub [class*="gradient-background"].alignfull h2 {
    letter-spacing: -0.02em;
    font-size: clamp(1.875rem, 4vw, 2.5rem);
}
/* The AI tutor's primary button gets a pulsing glow ring on idle. */
.page-template-qualification-hub [class*="gradient-background"].alignfull .wp-block-button__link {
    position: relative;
    z-index: 1;
    box-shadow:
        0 0 0 0 rgba(204, 0, 0, 0.55),
        0 4px 16px rgba(204, 0, 0, 0.35);
    animation: ot-hub-pulse 2.4s ease-out infinite;
}
@keyframes ot-hub-pulse {
    0%   { box-shadow: 0 0 0 0   rgba(204, 0, 0, 0.55), 0 4px 16px rgba(204, 0, 0, 0.35); }
    70%  { box-shadow: 0 0 0 18px rgba(204, 0, 0, 0.00), 0 4px 16px rgba(204, 0, 0, 0.35); }
    100% { box-shadow: 0 0 0 0   rgba(204, 0, 0, 0.00), 0 4px 16px rgba(204, 0, 0, 0.35); }
}
.page-template-qualification-hub [class*="gradient-background"].alignfull .wp-block-button__link:hover {
    animation-play-state: paused;
}

/* (Sweeping-shine treatment also lifted to the site-wide rule — see
   "Site-wide WP buttons (B125)" earlier. Removed from this scope.) */

/* ---- Section heading — refined accent + hairline rule ---- */
.page-template-qualification-hub .wp-block-post-content > h2.wp-block-heading {
    /* Replace the simple flat red border with a gradient bar that fades
       — a small craft touch that reads as "designed", not "wireframe". */
    border-left: 0;
    padding-left: 0.875rem;
    background:
        linear-gradient(180deg, var(--wp--preset--color--accent) 0%, rgba(204, 0, 0, 0.25) 100%) left center / 4px 80% no-repeat;
    position: relative;
}

/* ---- Decorative section divider ---- */
/* Insert a thin gradient divider between major direct-child sections so
   the page reads as a sequence of "chapters". Using a subtle radial-
   diamond pattern in the centre avoids a flat-line feel. */
.page-template-qualification-hub .wp-block-post-content > .wp-block-group:not(:last-child),
.page-template-qualification-hub .wp-block-post-content > .wp-block-columns:not(:last-child) {
    position: relative;
}

/* ---- News list — subtle "NEW" indicator on the first item ---- */
.page-template-qualification-hub .wp-block-post-content h4.wp-block-heading + p::before {
    content: "";
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--wp--preset--color--accent);
    box-shadow: 0 0 0 4px rgba(204, 0, 0, 0.15);
    margin-right: 10px;
    vertical-align: 2px;
    animation: ot-hub-blink 2s ease-in-out infinite;
}
@keyframes ot-hub-blink {
    0%, 100% { opacity: 1;   transform: scale(1); }
    50%      { opacity: 0.4; transform: scale(0.85); }
}

/* ---- "Ready to get started?" CTA card — depth + decorative band ---- */
.page-template-qualification-hub .wp-block-post-content > .wp-block-group.has-bg-elev-background-color,
.page-template-qualification-hub .wp-block-post-content > .wp-block-group.has-background:last-of-type {
    position: relative;
    overflow: hidden;
}
.page-template-qualification-hub .wp-block-post-content > .wp-block-group.has-bg-elev-background-color::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: linear-gradient(90deg,
        transparent 0%,
        var(--wp--preset--color--accent) 30%,
        var(--wp--preset--color--accent) 70%,
        transparent 100%);
}

/* ---- Scroll-driven entrance animations (modern browsers) ---- */
@supports (animation-timeline: view()) {
    .page-template-qualification-hub .wp-block-post-content > h2.wp-block-heading,
    .page-template-qualification-hub .wp-block-post-content > .wp-block-group,
    .page-template-qualification-hub .wp-block-post-content > .wp-block-columns,
    .page-template-qualification-hub .ot-hub-hero {
        animation: ot-hub-fade-up linear both;
        animation-timeline: view();
        animation-range: cover 0% cover 25%;
    }
    @keyframes ot-hub-fade-up {
        from { opacity: 0; transform: translateY(24px); }
        to   { opacity: 1; transform: translateY(0); }
    }
}

/* ---- Reduced motion — kill all animations ---- */
@media (prefers-reduced-motion: reduce) {
    .page-template-qualification-hub *,
    .page-template-qualification-hub *::before,
    .page-template-qualification-hub *::after {
        animation: none !important;
        transition: none !important;
    }
}

/* ---- Mobile — drop the hero accents (no room) ---- */
@media (max-width: 700px) {
    .page-template-qualification-hub .ot-hub-hero::before,
    .page-template-qualification-hub .ot-hub-hero::after {
        display: none;
    }
}

/* Screen-reader-only utility — used by skip links + icon buttons. */
.screen-reader-text {
    border: 0;
    clip: rect(1px, 1px, 1px, 1px);
    clip-path: inset(50%);
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    width: 1px;
    word-wrap: normal !important;
}
.screen-reader-text:focus {
    clip: auto !important;
    clip-path: none;
    background-color: var(--wp--preset--color--bg-elev);
    color: var(--wp--preset--color--accent);
    display: block;
    font-size: 1rem;
    height: auto;
    left: 5px;
    line-height: normal;
    padding: 12px 18px;
    text-decoration: none;
    top: 5px;
    width: auto;
    z-index: 100000;
}
