/*
 * NCRA accessibility layer — theme-independent styles.
 *
 * Reference: NCRA-A11Y-2026-07, Phase 2.
 *
 * Everything here is accessibility-critical: it must keep working if the theme
 * is switched, upgraded or replaced, so it lives in the plugin rather than in
 * the child theme. Purely presentational rules (block layout, colour fields,
 * topic tables) live in theme_ncra_a11y instead, because presentation is
 * theme-scoped by definition.
 *
 * Plain CSS on purpose. No SCSS pipeline to re-enter and no build step to
 * repeat on upgrade — the file that ships is the file that runs.
 *
 * CASCADE POSITION — read before adding a rule
 * --------------------------------------------
 * Moodle emits this stylesheet from the head hook, which places it BEFORE the
 * active theme's compiled sheet, not after. Verified on Moodle 5.1.3: this file
 * is link #5 in <head> and theme/styles.php is #7. So a rule written at the
 * same specificity as a theme rule LOSES.
 *
 * Every override below therefore earns its weight deliberately, by scoping to a
 * page landmark id (#region-main, #page-footer) or by :root, rather than by
 * !important. Two exceptions are marked in place and argued where they appear:
 * the focus indicator and the reduced-motion stand-down, where being
 * unconditionally applied is the entire point of the rule.
 *
 * Not relying on load order is also the more update-proof choice: if a future
 * Moodle moves hook output later in <head>, nothing here changes behaviour.
 */

/* ---------------------------------------------------------------------------
 * Accessibility tokens
 *
 * One place to change a value that has been measured. Every ratio quoted in
 * this file was taken from the live staging site with the computed colours the
 * browser actually resolved, not from the source hex, because the surface a
 * link sits on is what decides whether it passes.
 * ------------------------------------------------------------------------ */
:root {
    /* Link colour — see the contrast section below for the measurements. */
    --ncra-link: #3450dc;
    --ncra-link-hover: #2a40b4;

    /* Focus indicator. Dark ring, white inner gap, so the pair stays visible
       on a white page and on the brand-blue navbar alike. */
    --ncra-focus-ring: #0b1020;
    --ncra-focus-gap: #ffffff;

    /* WCAG 2.5.8 floor. Percepta asks for 44px on touch platforms; 24 is the
       conformance minimum and the most that can be applied site-wide here
       without redesigning Moodle's own controls. */
    --ncra-target-min: 24px;
}

/* ---------------------------------------------------------------------------
 * Screen-reader-only utility
 *
 * Defined here rather than relying on Bootstrap's .visually-hidden, so the
 * JavaScript layer keeps working regardless of which Bootstrap version the
 * active theme ships.
 * ------------------------------------------------------------------------ */
.ncra-visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---------------------------------------------------------------------------
 * WCAG 1.4.3 Contrast (Minimum) — link colour
 *
 * The Phase 1 audit reported the brand colour #5F78FB at 3.5–3.8:1 and the
 * punch-list moved it to #4C68FB, described as 4.50:1. Re-measured live on
 * 19 August 2026, #4C68FB resolves to:
 *
 *     on #FFFFFF (page)              4.49:1   FAILS (the 4.50 was rounded up)
 *     on #F8F9FA (course cards)      4.26:1   FAILS
 *     on #F0F8FF (aliceblue blocks)  4.18:1   FAILS
 *
 * The third one matters most: aliceblue is the field behind every audio,
 * alert, reflection, resource and activity block in every course, so almost
 * every link a learner meets inside course content was the failing case.
 *
 * A fourth surface turned up only once the quiz pages were tested: the
 * question field #CCE6EA, a pale teal, where the same link measured 3.43:1.
 * That is the darkest ground a content link actually lands on anywhere on this
 * site, so it, not white, is what the colour has to be chosen against.
 *
 * #3450DC, the same blue deeper again, measures:
 *
 *     on #FFFFFF   6.31:1      on #F8F9FA   5.99:1
 *     on #F0F8FF   5.88:1      on #CCE6EA   4.83:1
 *
 * One colour for every surface rather than a different one per background:
 * fewer rules, and no new surface can quietly reintroduce the failure.
 *
 * Scoped to content landmarks so it cannot reach the navbar or the drawers.
 * Buttons that paint their own fill are excluded, because they set their own
 * foreground against it — but .btn-link is deliberately INCLUDED. Despite the
 * class it renders as a link, with no background of its own, and Moodle uses it
 * for real content controls: "Clear my choice" inside every multiple-choice
 * question is a .btn-link, and it was still failing at 3.43:1 after the first
 * pass of this rule excluded it by name.
 * ------------------------------------------------------------------------ */
#region-main a:not(.btn):not(.badge):not([class*="btn-"]),
#page-footer a:not(.btn):not(.badge):not([class*="btn-"]),
.course-content a:not(.btn):not(.badge):not([class*="btn-"]),
.summarytext a:not(.btn):not(.badge),
.activity-altcontent a:not(.btn):not(.badge),
#region-main a.btn-link,
.course-content a.btn-link,
#region-main button.btn-link,
.course-content button.btn-link {
    color: var(--ncra-link);
}

#region-main a:not(.btn):not(.badge):not([class*="btn-"]):hover,
#page-footer a:not(.btn):not(.badge):not([class*="btn-"]):hover,
.course-content a:not(.btn):not(.badge):not([class*="btn-"]):hover,
.summarytext a:not(.btn):not(.badge):hover,
.activity-altcontent a:not(.btn):not(.badge):hover,
#region-main a.btn-link:hover,
.course-content a.btn-link:hover,
#region-main button.btn-link:hover,
.course-content button.btn-link:hover {
    color: var(--ncra-link-hover);
}

/* Badges painted with the brand fill.
 *
 * Moodle's question-version badge is .badge.bg-primary.text-light: #F8F9FA on
 * #4C68FB measures 4.26:1, so the badge failed even though the link beside it
 * passed. A badge owns both of its colours, so the fix is the pair, not the
 * text: the same deeper blue carries near-white at 5.99:1.
 *
 * !important is required rather than chosen. Bootstrap ships its background
 * utilities as `.bg-primary { background-color: ... !important }`, so nothing
 * without it can win, at any specificity. Verified: a :root-prefixed rule at
 * 0-3-0 was ignored and the badge stayed at 4.26:1. */
:root .badge.bg-primary {
    background-color: var(--ncra-link) !important;
}

/* ---------------------------------------------------------------------------
 * WCAG 2.4.7 Focus Visible / 1.4.11 Non-text Contrast — the focus indicator
 *
 * Percepta, foundation "focus": the indicator must be perceivable against
 * every background it can land on. Moodle and Bootstrap between them set
 * `outline: 0` on several focusable containers and replace it with a tinted
 * box-shadow that disappears on a coloured surface.
 *
 * A dark ring alone vanishes on the brand-blue navbar; a white ring alone
 * vanishes on the page. So this is a two-tone indicator: a white gap held by
 * box-shadow, and a dark ring outside it. One of the two always contrasts,
 * whatever it lands on.
 *
 * !important is deliberate and is confined to this block. This file loads
 * before the theme (see the header note), and a focus indicator that a theme
 * rule can silently cancel is the single defect most likely to strand a
 * keyboard user. Losing this argument to the cascade is not an acceptable
 * outcome, so it does not enter the argument.
 * ------------------------------------------------------------------------ */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible,
[role="link"]:focus-visible,
[role="menuitem"]:focus-visible,
[role="tab"]:focus-visible,
[contenteditable="true"]:focus-visible {
    outline: 3px solid var(--ncra-focus-ring) !important;
    outline-offset: 2px !important;
    box-shadow: 0 0 0 2px var(--ncra-focus-gap) !important;
    border-radius: 2px;
}

/* The skip links are positioned off-screen until focused. They must not be
   clipped by the ring, and they are a keyboard user's first impression. */
.visually-hidden-focusable:focus-visible,
.skip-block:focus-visible {
    outline-offset: 0 !important;
}

/* Measured once focused, the skip link rendered 163x22 — two pixels under the
   2.5.8 floor. It is the very first target a keyboard user meets, so it is
   also the one least worth leaving marginal. */
.visually-hidden-focusable:focus,
.skip-block,
.skip-block:focus {
    min-height: var(--ncra-target-min);
    display: inline-flex;
    align-items: center;
}

/* ---------------------------------------------------------------------------
 * WCAG 2.5.8 Target Size (Minimum)
 *
 * Measured live on a course section page: 14 controls rendered under 24x24
 * CSS pixels, of which the recurring ones are structural rather than
 * content-authored, so they appear on every course in the site:
 *
 *   8x  .courseindex-chevron            21x27   section expand/collapse
 *   3x  .btn-sm inside notification and message popovers   34x22
 *
 * Inline links inside a sentence are exempt under 2.5.8 and are deliberately
 * left alone: resizing them would break the line box for everyone.
 *
 * Size is added as minimum box size plus centring, never as font growth, so
 * the glyph is unchanged and the layout does not move.
 * ------------------------------------------------------------------------ */
.courseindex-chevron,
.courseindex-item .icons-collapse-expand,
.btn-sm.btn-link,
.mark-all-read-button,
.btn.btn-icon,
.icon-no-margin.btn {
    min-width: var(--ncra-target-min);
    min-height: var(--ncra-target-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Checkboxes, radios and switches — the controls a learner actually operates
 * to answer a quiz question. Bootstrap draws them at 1em, which measured 36x18
 * for the switch and 16x16 for a checkbox: both under the floor, and both are
 * exactly the "small target, high stakes" case 2.5.8 exists for.
 *
 * Only the box grows. Font size, label position and line height are untouched,
 * so a quiz page keeps its layout and gains a target a shaking hand can hit. */
.form-check-input,
input[type="checkbox"]:not(.visually-hidden):not([class*="visually-hidden"]),
input[type="radio"]:not(.visually-hidden):not([class*="visually-hidden"]) {
    min-width: var(--ncra-target-min);
    min-height: var(--ncra-target-min);
}

/* Boost Union's activity-chooser category rail and the drawer controls are
   icon-only buttons; the same floor applies. */
[data-action="show-category"],
[data-action="cancel-search"],
.drawertoggle,
.icon-back-in-drawer {
    min-width: var(--ncra-target-min);
    min-height: var(--ncra-target-min);
}

/* ---------------------------------------------------------------------------
 * WCAG 2.3.3 Animation from Interactions — reduced motion
 *
 * Nothing in the theme or in Moodle core honoured prefers-reduced-motion:
 * measured 172 elements carrying a running animation and 3 carrying a
 * transition on a single course page, with zero prefers-reduced-motion blocks
 * anywhere in the loaded CSS.
 *
 * Percepta, foundation "motion": non-essential motion is removed or damped
 * when the reader asks for stillness.
 *
 * !important is again deliberate and confined here: the point of the rule is
 * to overrule animation declared anywhere else, including inside Moodle core
 * and any third-party plugin, none of which this file loads after.
 *
 * Loading indicators are exempt. A spinner frozen mid-turn reads as a hung
 * page, which trades one accessibility problem for a worse one; motion that
 * communicates state is essential motion.
 * ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
    *:not(.spinner-border):not(.spinner-grow):not(.loading-icon):not(.fa-spin),
    *:not(.spinner-border):not(.spinner-grow):not(.loading-icon):not(.fa-spin)::before,
    *:not(.spinner-border):not(.spinner-grow):not(.loading-icon):not(.fa-spin)::after {
        animation-duration: 0.001ms !important;
        animation-delay: 0ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        transition-delay: 0ms !important;
    }

    html {
        scroll-behavior: auto !important;
    }
}

/* ---------------------------------------------------------------------------
 * Callout icons
 *
 * Previously injected with content: url(https://radioaction.ca/...). Two
 * problems with that:
 *
 *   1. A pseudo-element image carries no text alternative, so "this is a
 *      warning" was conveyed visually only. The JavaScript layer now supplies
 *      that signal as real text, which makes the image purely decorative — and
 *      a background-image is unambiguously decorative in a way that
 *      content: url() is not.
 *   2. The files were hotlinked from another domain. If radioaction.ca moves or
 *      lapses, every alert on the site silently loses its icon.
 *
 * Both PNGs are 80x80. content: url() rendered them at that intrinsic size, so
 * the dimensions below are pinned to match exactly — the rendering is
 * identical. The original padding-right: 10px becomes margin-right: 10px, which
 * occupies the same 90px of horizontal space.
 *
 * Both the legacy name selector and the structural data-ncra-type selector are
 * listed, so the icon resolves locally whichever one is matching.
 * ------------------------------------------------------------------------ */
[data-activityname^="alert" i] p:first-of-type::before,
[data-ncra-type="alert"] p:first-of-type::before {
    content: "";
    display: block;
    float: left;
    width: 80px;
    height: 80px;
    margin-right: 10px;
    padding-right: 0;
    background-image: url(../pix/warning-sign.png);
    background-repeat: no-repeat;
    background-position: center center;
    background-size: 80px 80px;
}

[data-activityname^="reflect" i] p:first-of-type::before,
[data-ncra-type="reflect"] p:first-of-type::before {
    content: "";
    display: block;
    float: left;
    width: 80px;
    height: 80px;
    margin-right: 10px;
    padding-right: 0;
    background-image: url(../pix/thinking.png);
    background-repeat: no-repeat;
    background-position: center center;
    background-size: 80px 80px;
}

/* ---------------------------------------------------------------------------
 * Native HTML5 media players
 *
 * The JavaScript layer replaces the VideoJS wrapper with a plain <audio> or
 * <video> element, handing control back to the browser. Native controls are
 * keyboard accessible, labelled by the platform, respect the user's operating
 * system media and caption preferences, and cannot drift out of contrast
 * compliance on a future VideoJS upgrade.
 *
 * Only layout is styled. The controls themselves are deliberately left alone —
 * restyling native controls is exactly how the 2.43:1 contrast problem was
 * introduced in the first place.
 * ------------------------------------------------------------------------ */
.ncra-native-player {
    display: block;
    width: 100%;
    max-width: 100%;
    margin: 0 auto;
}

audio.ncra-native-player {
    height: 54px;   /* enough for every current browser's control bar */
}

/* ---------------------------------------------------------------------------
 * Transcript disclosure
 *
 * WCAG 1.2.1 needs a transcript, which is content the site does not yet have
 * for its 96 media files. What the framework can do is make sure that the
 * moment an author does supply one, it is exposed correctly rather than
 * needing a developer.
 *
 * The JavaScript layer wires any element an author marks with
 * data-ncra-transcript to the player above it as a native <details>. This
 * styles that disclosure; it renders nothing at all when no transcript exists.
 * ------------------------------------------------------------------------ */
.ncra-transcript {
    margin: 0.5rem 0 1rem;
    font-size: 0.95rem;
}

.ncra-transcript > summary {
    cursor: pointer;
    padding: 4px 0;
    min-height: var(--ncra-target-min);
    display: flex;
    align-items: center;
    font-weight: 600;
}

.ncra-transcript > .ncra-transcript-body {
    padding: 0.5rem 0.75rem;
    border-left: 3px solid var(--ncra-link);
    margin-top: 0.25rem;
}

/* ---------------------------------------------------------------------------
 * VideoJS fallback — WCAG 1.4.11 Non-text Contrast
 *
 * Retained deliberately. If a VideoJS player survives anywhere the JavaScript
 * does not reach — a cached fragment, a plugin rendering its own player, or the
 * layer being switched off for diagnosis — its control bar must still clear the
 * 3:1 required for interface components.
 *
 * #89aac7 behind white icons measures 2.43:1. #4a6c8a is 5.52:1 in the same
 * blue family.
 *
 * Scoped through :root so it outweighs the theme's own VideoJS rules despite
 * loading before them. Without that, this fallback was decorative.
 * ------------------------------------------------------------------------ */
:root .mediaplugin .vjs-control-bar {
    background-color: #4a6c8a;
}

:root .mediaplugin .vjs-volume-level,
:root .mediaplugin .vjs-play-progress {
    background-color: #ffffff;
}

:root .mediaplugin .vjs-load-progress,
:root .mediaplugin .vjs-slider {
    background-color: rgba(255, 255, 255, 0.35);
}

:root .mediaplugin .video-js .vjs-button:focus-visible,
:root .mediaplugin .video-js .vjs-slider:focus-visible {
    outline: 3px solid #ffffff;
    outline-offset: -3px;
    box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.75);
}

/* ---------------------------------------------------------------------------
 * Repaired data tables — WCAG 1.3.1
 *
 * The JavaScript layer promotes a genuine data table's first row and first
 * column to <th> and gives the table a <caption>. The caption is for screen
 * readers: sighted users already read that context from the heading above.
 * ------------------------------------------------------------------------ */
caption.ncra-table-caption {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Percepta, component "table": a wide table scrolls inside its own container so
 * it never forces the whole page sideways. That is also WCAG 1.4.10 Reflow —
 * at 320px the four-column skills matrix is wider than the viewport, and
 * without this the entire page gains a horizontal scrollbar and every other
 * line of text runs off the screen with it.
 *
 * tabindex="0" is added by the JavaScript layer, not here: a scrollable region
 * that cannot be reached by keyboard is a region a keyboard user cannot scroll. */
.ncra-table-scroll {
    overflow-x: auto;
    max-width: 100%;
}

.ncra-table-scroll:focus-visible {
    outline: 3px solid var(--ncra-focus-ring) !important;
    outline-offset: -3px !important;
}

/* A promoted <th> must not suddenly look different from the <td> it replaced:
 * the repair is semantic, and a visual jump would be a regression on a live
 * course. Browsers default <th> to bold and centred, so that is undone unless
 * the author's own stylesheet says otherwise. */
th.ncra-promoted-header {
    font-weight: inherit;
    text-align: inherit;
}
