/* Grapevine LMS — design layer.
 *
 * Sits on top of the eDocs template (style.css + doc.css) and adds the course
 * player components. Everything visual is driven by the custom properties in
 * the token block below, so a reskin means overriding tokens — not rewriting
 * component rules. The markup the JS drives is addressed by data-* attributes
 * (data-gvl-module / data-gvl-panel / data-gvl-lesson), never by these class
 * names, so a theme is free to replace the markup wholesale.
 */

/* ── Design tokens ──────────────────────────────────────────────────────── */
:root {
    --gvl-primary:        #0b50d8;
    --gvl-primary-dark:   #093c8e;
    --gvl-primary-light:  #a4c2f4;
    --gvl-primary-wash:   #eef3fe;

    --gvl-title:          #212121;
    --gvl-text:           #555555;
    --gvl-muted:          #909193;

    --gvl-surface:        #ffffff;
    --gvl-surface-sunk:   #f9f9fa;
    --gvl-line:           #f1f2f3;
    --gvl-line-strong:    #e7e8e9;

    --gvl-accent:         #0fb07a;   /* completion / progress */

    /* Per-kind tints. Tokens like everything else, so a skin recolours the
       cards along with the rest rather than being left with three stray
       palettes it has to override by selector. */
    --gvl-kind-reflect:      #c97a17;
    --gvl-kind-reflect-wash: #fdf3e7;
    --gvl-kind-quiz:         #6541c4;
    --gvl-kind-quiz-wash:    #f0ecfd;
    --gvl-kind-reading:      #0fb07a;
    --gvl-kind-reading-wash: #e9f6f1;

    --gvl-radius:         0.5rem;
    --gvl-radius-lg:      1rem;
    --gvl-shadow:         0 2px 6px rgba(33, 33, 33, .05);
    --gvl-shadow-lift:    0 12px 28px rgba(11, 80, 216, .13);
    --gvl-shadow-modal:   0 24px 70px rgba(0, 0, 0, .28);

    --gvl-font:           'Poppins', sans-serif;

    /* Layout — the template's own measurements, named so a reskin can move them. */
    --gvl-sidebar-w:      300px;
    --gvl-topbar-h:       90px;
}


/* ── Progress (sidebar) ─────────────────────────────────────────────────── */
.gvl-progress__title {
    font-family: var(--gvl-font);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--gvl-muted);
    margin: 0 0 10px;
}

.gvl-progress__track {
    height: 6px;
    border-radius: 3px;
    background-color: var(--gvl-line-strong);
    overflow: hidden;
}

.gvl-progress__bar {
    height: 100%;
    width: 0;
    border-radius: 3px;
    background-color: var(--gvl-accent);
    transition: width .35s ease;
}

.gvl-progress__label {
    font-size: 12px;
    color: var(--gvl-muted);
    margin: 10px 0 0;
}


/* ── Lesson cards ───────────────────────────────────────────────────────── */
.gvl-lessongrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 26px;
}

.gvl-lessoncard {
    display: flex;
    flex-direction: column;
    text-align: left;
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius-lg);
    box-shadow: var(--gvl-shadow);
    padding: 0;
    overflow: hidden;
    cursor: pointer;
    transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}

.gvl-lessoncard:hover {
    transform: translateY(-4px);
    border-color: var(--gvl-primary-light);
    box-shadow: var(--gvl-shadow-lift);
}

.gvl-lessoncard__media {
    position: relative;
    aspect-ratio: 16 / 9;
    background-color: var(--gvl-primary-wash);
    background-size: cover;
    background-position: center;
    display: flex;
    align-items: center;
    justify-content: center;
}

.gvl-lessoncard__media--image::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(9, 60, 142, .55) 0%, rgba(9, 60, 142, 0) 55%);
}

.gvl-lessoncard__label {
    font-family: var(--gvl-font);
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background-color: rgba(255, 255, 255, .94);
    color: var(--gvl-primary);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    padding: 5px 10px;
    border-radius: 100px;
}

/* Play / type badge in the centre of the thumbnail. */
.gvl-lessoncard__icon {
    position: relative;
    z-index: 2;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, .92);
    color: var(--gvl-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 14px rgba(0, 0, 0, .16);
    transition: transform .2s ease, background-color .2s ease, color .2s ease;
}

.gvl-lessoncard:hover .gvl-lessoncard__icon {
    transform: scale(1.08);
    background-color: var(--gvl-primary);
    color: #fff;
}

.gvl-lessoncard__body {
    padding: 18px 20px 20px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1 1 auto;
}

.gvl-lessoncard__title {
    font-family: var(--gvl-font);
    font-size: 15px;
    font-weight: 600;
    color: var(--gvl-title);
    line-height: 1.4;
    margin: 0;
}

.gvl-lessoncard__meta {
    font-size: 12px;
    color: var(--gvl-muted);
    margin: 0;
}

/* Non-video item kinds re-tint the media panel rather than changing its shape. */
.gvl-lessoncard--reflection .gvl-lessoncard__media { background-color: var(--gvl-kind-reflect-wash); }
.gvl-lessoncard--reflection .gvl-lessoncard__icon  { color: var(--gvl-kind-reflect); }
.gvl-lessoncard--reflection:hover .gvl-lessoncard__icon { background-color: var(--gvl-kind-reflect); color: #fff; }
.gvl-lessoncard--reflection .gvl-lessoncard__label { color: var(--gvl-kind-reflect); }

.gvl-lessoncard--quiz .gvl-lessoncard__media,
.gvl-lessoncard--essay .gvl-lessoncard__media { background-color: var(--gvl-kind-quiz-wash); }
.gvl-lessoncard--quiz .gvl-lessoncard__icon,
.gvl-lessoncard--essay .gvl-lessoncard__icon  { color: var(--gvl-kind-quiz); }
.gvl-lessoncard--quiz:hover .gvl-lessoncard__icon,
.gvl-lessoncard--essay:hover .gvl-lessoncard__icon { background-color: var(--gvl-kind-quiz); color: #fff; }
.gvl-lessoncard--quiz .gvl-lessoncard__label,
.gvl-lessoncard--essay .gvl-lessoncard__label { color: var(--gvl-kind-quiz); }

.gvl-lessoncard--reading .gvl-lessoncard__media,
.gvl-lessoncard--document .gvl-lessoncard__media { background-color: var(--gvl-kind-reading-wash); }
.gvl-lessoncard--reading .gvl-lessoncard__icon,
.gvl-lessoncard--document .gvl-lessoncard__icon  { color: var(--gvl-kind-reading); }
.gvl-lessoncard--reading:hover .gvl-lessoncard__icon,
.gvl-lessoncard--document:hover .gvl-lessoncard__icon { background-color: var(--gvl-kind-reading); color: #fff; }
.gvl-lessoncard--reading .gvl-lessoncard__label,
.gvl-lessoncard--document .gvl-lessoncard__label { color: var(--gvl-kind-reading); }

/* Completion tick, set by the runtime from progress state. */
.gvl-lessoncard__done {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 2;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background-color: var(--gvl-accent);
    color: #fff;
    display: none;
    align-items: center;
    justify-content: center;
}

.gvl-lessoncard.is-complete .gvl-lessoncard__done {
    display: flex;
}

.gvl-empty {
    font-size: 15px;
    color: var(--gvl-muted);
    padding: 40px 0;
    margin: 0;
}


/* ── Player modal ───────────────────────────────────────────────────────── */
.gvl-player-backdrop {
    position: fixed;
    inset: 0;
    z-index: 10000;
    background-color: rgba(15, 20, 32, .72);
    backdrop-filter: blur(3px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
}

.gvl-player-backdrop[hidden] {
    display: none;
}

.gvl-player {
    width: 100%;
    max-width: 1100px;
    max-height: 100%;
    display: flex;
    flex-direction: column;
    background-color: var(--gvl-surface);
    border-radius: var(--gvl-radius-lg);
    box-shadow: var(--gvl-shadow-modal);
    overflow: hidden;
}

.gvl-player__head {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 18px 22px;
    border-bottom: 1px solid var(--gvl-line);
    flex: 0 0 auto;
}

.gvl-player__title {
    font-family: var(--gvl-font);
    flex: 1 1 auto;
    min-width: 0;
    font-size: 17px;
    font-weight: 600;
    color: var(--gvl-title);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.gvl-player__step {
    flex: 0 0 auto;
    font-size: 12px;
    font-weight: 500;
    color: var(--gvl-muted);
    white-space: nowrap;
}

.gvl-player__close {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border: 0;
    border-radius: 50%;
    background-color: var(--gvl-line);
    color: var(--gvl-text);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background-color .18s ease, color .18s ease;
}

.gvl-player__close:hover {
    background-color: var(--gvl-primary);
    color: #fff;
}

.gvl-player__stage {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 26px;
}

/* The three stage layouts the runtime picks between. */
.gvl-stage--video-only .gvl-stage__video {
    width: 100%;
}

.gvl-stage--split {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: 28px;
    align-items: start;
}

.gvl-stage__video {
    position: relative;
    aspect-ratio: 16 / 9;
    background-color: #000;
    border-radius: var(--gvl-radius);
    overflow: hidden;
}

.gvl-stage__video iframe,
.gvl-stage__video video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

.gvl-stage__thumb {
    width: 100%;
    border-radius: var(--gvl-radius);
    display: block;
}

.gvl-stage__text {
    font-size: 15px;
    line-height: 1.75;
    color: var(--gvl-text);
}

.gvl-stage__text > :first-child { margin-top: 0; }
.gvl-stage__text > :last-child  { margin-bottom: 0; }

/* The template resets list markers globally; restore them inside lesson copy. */
.gvl-stage__text ul,
.gvl-stage__text ol {
    padding-left: 1.3em;
    margin: 0 0 1em;
}

.gvl-stage__text ul { list-style: disc; }
.gvl-stage__text ol { list-style: decimal; }

.gvl-stage__text li {
    margin-bottom: .4em;
}

.gvl-stage__text h1,
.gvl-stage__text h2,
.gvl-stage__text h3,
.gvl-stage__text h4 {
    color: var(--gvl-title);
    font-weight: 600;
}

/* "You're up to date" — the player's third state, beside finished and stuck.
 * Centred in the stage where the lesson would have been, so it reads as an
 * answer rather than as an error. */
.gvl-caught {
    text-align: center;
    padding: 56px 24px;
    max-width: 44ch;
    margin: 0 auto;
}

.gvl-caught__mark {
    font-size: 26px;
    color: var(--gvl-enroll, #2e7d32);
    margin: 0 0 12px;
}

.gvl-caught__h {
    font-size: 19px;
    font-weight: 600;
    color: var(--gvl-title);
    margin: 0 0 8px;
    line-height: 1.3;
}

.gvl-caught__p {
    font-size: 14px;
    line-height: 1.6;
    color: var(--gvl-muted);
    margin: 0;
}

.gvl-stage__loading {
    padding: 60px 0;
    text-align: center;
    color: var(--gvl-muted);
    font-size: 14px;
}

/* Reflection / response box. */





.gvl-player__explore {
    padding: 0 26px 20px;
    flex: 0 0 auto;
}

.gvl-player__explore[hidden] {
    display: none;
}

.gvl-player__explore-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--gvl-muted);
    margin: 0 0 10px;
}

.gvl-player__explore a {
    display: inline-block;
    font-size: 13px;
    color: var(--gvl-primary);
    background-color: var(--gvl-primary-wash);
    padding: 7px 14px;
    border-radius: 100px;
    margin: 0 8px 8px 0;
}

.gvl-player__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 22px;
    border-top: 1px solid var(--gvl-line);
    background-color: var(--gvl-surface-sunk);
    flex: 0 0 auto;
}


/* ── Responsive ─────────────────────────────────────────────────────────── */
@media (max-width: 1200px) {
    .gvl-stage--split {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 991px) {
    .gvl-topbar-title {
        display: none;
    }

    .gvl-player-backdrop {
        padding: 0;
    }

    .gvl-player {
        max-width: none;
        height: 100%;
        max-height: none;
        border-radius: 0;
    }

    /* On mobile the rail is the off-canvas drawer; let it scroll as one column. */
    .gvl-progress {
        margin-top: 24px;
    }
}

@media (max-width: 575px) {
    .gvl-lessongrid {
        grid-template-columns: 1fr;
        gap: 18px;
    }
}


/* ── Quiz ───────────────────────────────────────────────────────────────── */









/* Verdict. Never colour alone: the mark carries the meaning for anyone who
   cannot distinguish the two hues. */







/* ── Essay / response extras ────────────────────────────────────────────── */




/* ── Document ───────────────────────────────────────────────────────────── */
.gvl-doc {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

.gvl-doc__frame {
    width: 100%;
    height: min(70vh, 760px);
    border: 1px solid var(--gvl-line-strong);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface-sunk);
}

.gvl-doc__download {
    align-self: flex-start;
}


/* ── Admin bar ──────────────────────────────────────────────────────────────
 * WordPress offsets the document for its own fixed bar, but the modal is
 * position:fixed and still starts at viewport 0, sliding underneath it. */
.admin-bar .gvl-player-backdrop {
    top: 32px;
}

@media screen and (max-width: 782px) {
    .admin-bar .gvl-player-backdrop {
        top: 46px;
    }
}


/* ── Course page ────────────────────────────────────────────────────────────
 * The eDocs `article.html` shape: a coloured page header, a main column and a
 * sticky sidebar. */
.gvl-pagehead {
    padding: 54px 0 46px;
    background-color: var(--gvl-primary);
    color: #fff;
}

.gvl-pagehead__title {
    font-family: var(--gvl-font);
    font-size: clamp(26px, 4vw, 38px);
    font-weight: 600;
    line-height: 1.2;
    color: #fff;
    margin: 0 0 12px;
}

.gvl-crumbs {
    font-size: 13px;
    color: rgba(255, 255, 255, .78);
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.gvl-crumbs a {
    color: rgba(255, 255, 255, .9);
    text-decoration: none;
}

.gvl-crumbs a:hover {
    color: #fff;
    text-decoration: underline;
}

.gvl-coursepage {
    padding: 48px 0 80px;
    background-color: var(--gvl-surface-sunk);
}

/* Padding matched to .gvl-sidecol__block deliberately.
 *
 * The two cards' borders already lined up, but the vendored .promo-box carries
 * 2.5rem of padding against the sidebar's 22px — so the first line of text in
 * each sat 23px apart, and the pair read as misaligned even though the boxes
 * were not. Aligning the boxes is not the same as aligning what you can see. */
.gvl-coursepage__intro {
    padding: 22px 28px;
    /* The sidebar cards are bordered; without a matching border this one's
       content sat a pixel high, and the two read as different kinds of card. */
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius-lg);
    margin-bottom: 30px;
    font-size: 15px;
    line-height: 1.75;
    color: var(--gvl-text);
}

.gvl-coursepage__intro-body > :first-child { margin-top: 0; }
.gvl-coursepage__intro-body > :last-child  { margin-bottom: 0; }


/* ── Syllabus accordion ─────────────────────────────────────────────────── */
.gvl-syllabus__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

.gvl-syllabus__heading {
    min-width: 0;
}

/* Progress and its Continue button, on the same row as the heading. */
.gvl-syllabus__progress {
    display: flex;
    align-items: center;
    gap: 16px;
    flex: 0 1 auto;
    min-width: 0;
}

.gvl-syllabus__progressbar {
    min-width: 160px;
    flex: 0 1 220px;
}

.gvl-syllabus__continue {
    flex: 0 0 auto;
    white-space: nowrap;
    padding: 10px 22px !important;
}

.gvl-syllabus__title {
    font-family: var(--gvl-font);
    font-size: 22px;
    font-weight: 600;
    color: var(--gvl-title);
    margin: 0;
}

.gvl-syllabus__meta {
    font-size: 13px;
    color: var(--gvl-muted);
    margin: 4px 0 0;
}

/* The same totals, on the right of the header when nothing else claims that
   side — so the row is balanced either way. */
.gvl-syllabus__meta--right {
    margin: 0;
    flex: 0 0 auto;
}

.gvl-accordion__item {
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius-lg);
    margin-bottom: 14px;
    overflow: hidden;
}

.gvl-accordion__heading {
    margin: 0;
}

.gvl-accordion__button {
    display: flex;
    align-items: center;
    gap: 16px;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    padding: 20px 22px;
    font-family: var(--gvl-font);
    cursor: pointer;
    transition: background-color .18s ease;
}

.gvl-accordion__button:hover {
    background-color: var(--gvl-surface-sunk);
}

.gvl-accordion__num {
    flex: 0 0 30px;
    height: 30px;
    border-radius: 50%;
    background-color: var(--gvl-primary-wash);
    color: var(--gvl-primary);
    font-size: 13px;
    font-weight: 600;
    line-height: 30px;
    text-align: center;
}

.gvl-accordion__label {
    flex: 1 1 auto;
    min-width: 0;
}

.gvl-accordion__name {
    display: block;
    font-size: 16px;
    font-weight: 600;
    color: var(--gvl-title);
    line-height: 1.35;
}

.gvl-accordion__count {
    display: block;
    font-size: 12px;
    color: var(--gvl-muted);
    margin-top: 3px;
}

/* "Opens in 5 days" on a module heading. Beside the lesson count rather than
 * under it, because the two together are one line of fact about the module —
 * and it is set apart by weight rather than by colour so it reads as
 * information and not as a warning. */
.gvl-accordion__when {
    display: inline-block;
    margin-top: 3px;
    font-size: 12px;
    font-weight: 600;
    color: var(--gvl-muted);
}

.gvl-accordion__count + .gvl-accordion__when {
    margin-left: 10px;
    padding-left: 10px;
    border-left: 1px solid var(--gvl-line);
}

.gvl-accordion__chevron {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--gvl-muted);
    transition: transform .2s ease;
}

.gvl-accordion__button.collapsed .gvl-accordion__chevron {
    transform: rotate(-90deg);
}

.gvl-accordion__body {
    padding: 0 22px 18px;
}

.gvl-accordion__desc {
    font-size: 14px;
    line-height: 1.7;
    color: var(--gvl-text);
    margin: 0 0 16px;
}

.gvl-lessonlist {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Lessons with no module, sitting directly in Course Content — a single-video
   course, or a standalone lesson.

   Styled as a card so it reads as a peer of the modules around it rather than
   as loose text that lost its container, and padded to the same 22px as
   .gvl-accordion__body so a loose lesson's title lines up with the title of a
   lesson inside a module. */
.gvl-lessonlist--loose {
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius-lg);
    margin-bottom: 14px;
    padding: 6px 22px;
    overflow: hidden;
}

.gvl-lessonrow {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    border-top: 1px solid var(--gvl-line);
    padding: 14px 4px;
    font-family: var(--gvl-font);
    cursor: pointer;
    transition: background-color .15s ease;
}

.gvl-lessonlist li:first-child .gvl-lessonrow {
    border-top: 0;
}

.gvl-lessonrow:hover {
    background-color: var(--gvl-primary-wash);
}

.gvl-lessonrow__icon {
    flex: 0 0 34px;
    height: 34px;
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-primary-wash);
    color: var(--gvl-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
}

.gvl-lessonrow--reflection .gvl-lessonrow__icon { background-color: var(--gvl-kind-reflect-wash); color: var(--gvl-kind-reflect); }
.gvl-lessonrow--quiz .gvl-lessonrow__icon,
.gvl-lessonrow--essay .gvl-lessonrow__icon      { background-color: var(--gvl-kind-quiz-wash);    color: var(--gvl-kind-quiz); }
.gvl-lessonrow--reading .gvl-lessonrow__icon,
.gvl-lessonrow--document .gvl-lessonrow__icon   { background-color: var(--gvl-kind-reading-wash); color: var(--gvl-kind-reading); }

.gvl-lessonrow__text {
    flex: 1 1 auto;
    min-width: 0;
}

.gvl-lessonrow__title {
    display: block;
    font-size: 15px;
    font-weight: 500;
    color: var(--gvl-title);
    line-height: 1.4;
}

.gvl-lessonrow__kind {
    display: block;
    font-size: 12px;
    color: var(--gvl-muted);
    margin-top: 2px;
}

.gvl-lessonrow__done {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background-color: var(--gvl-accent);
    color: #fff;
    font-size: 10px;
    display: none;
    align-items: center;
    justify-content: center;
}

.gvl-lessonrow.is-complete .gvl-lessonrow__done {
    display: flex;
}


/* ── Course sidebar ─────────────────────────────────────────────────────────
 * Bootstrap's .sticky-top sets z-index:1020, which is above a themed sticky
 * header — so the sidebar scrolled OVER the header instead of beneath it.
 * Dropped to 1 so any chrome the theme layers above it wins, and the sticky
 * offset clears the bar rather than parking the panel behind it.
 *
 * --gvl-topbar-h is exactly the token for this: the plugin does not know how
 * tall a given skin's header is, but the skin declares it. */
.gvl-sidecol {
    top: calc(var(--gvl-topbar-h) + 24px);
    z-index: 1;
    /* No margin or padding of its own: the first panel has to line up with the
       top of the course description card in the column beside it. */
    margin: 0;
    padding: 0;
}

/* WordPress's own bar pushes everything down again for signed-in staff. */
.admin-bar .gvl-sidecol {
    top: calc(var(--gvl-topbar-h) + 56px);
}

@media screen and (max-width: 782px) {
    .admin-bar .gvl-sidecol {
        top: calc(var(--gvl-topbar-h) + 70px);
    }
}

.gvl-sidecol__block {
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius-lg);
    padding: 22px;
    margin-bottom: 18px;
}

/* The small-caps card heading. Shared by the sidebar cards and the course
 * description, so the two columns start with the same thing on the same line —
 * which is what makes them line up structurally rather than by matched
 * padding that a later edit could quietly break. */
.gvl-cardtitle,
.gvl-sidecol .widget-title {
    font-family: var(--gvl-font);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--gvl-muted);
    margin: 0 0 14px;
}

.gvl-enrollbox__state {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--gvl-accent);
    margin: 0 0 14px;
}

.gvl-enrollbox__state--muted {
    color: var(--gvl-muted);
    margin-bottom: 6px;
}

.gvl-enrollbox__hint,
.gvl-enrollbox__message {
    font-size: 13px;
    color: var(--gvl-muted);
    margin: 0;
}

.gvl-enrollbox__message[hidden] {
    display: none;
}

.gvl-sidefacts {
    margin: 0;
}

.gvl-sidefacts__row {
    padding: 10px 0;
    border-top: 1px solid var(--gvl-line);
}

.gvl-sidefacts__row:first-child {
    border-top: 0;
    padding-top: 0;
}

.gvl-sidefacts dt {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--gvl-muted);
    margin: 0 0 3px;
}

.gvl-sidefacts dd {
    font-size: 14px;
    font-weight: 500;
    color: var(--gvl-title);
    margin: 0;
}

.gvl-instructors {
    list-style: none;
    margin: 0;
    padding: 0;
}

.gvl-instructors li {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 0;
    border-top: 1px solid var(--gvl-line);
}

.gvl-instructors li:first-child {
    border-top: 0;
    padding-top: 0;
}

/* flex-basis alone did not hold the size — the vendored base's own img rules
 * win on `width`, so the avatar rendered at its intrinsic 96px. Explicit
 * dimensions, plus object-fit so a non-square photo crops rather than
 * distorts. */
.gvl-instructors img {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    object-fit: cover;
    border-radius: 50%;
}

.gvl-instructors__name {
    display: block;
    font-size: 14px;
    font-weight: 600;
    color: var(--gvl-title);
}

/* The role under the name, since the panel holds instructors and assistants
 * both. Smaller and quieter than the name, louder than the bio: it is a label,
 * not a sentence. */
.gvl-instructors__role {
    display: block;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--gvl-muted);
    margin-top: 2px;
}

.gvl-instructors__bio {
    display: block;
    font-size: 12px;
    line-height: 1.55;
    color: var(--gvl-muted);
    margin-top: 3px;
}

/* ── The release schedule, in course settings ────────────────────────────
 *
 * The whole schedule as one list, because a sequence has to be edited as one:
 * a twelve-module course set through twelve modals is a drip whose shape its
 * author has never seen. */
.gvl-release {
    margin-top: 18px;
}

.gvl-release__grid {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.gvl-release__grid th {
    text-align: left;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--gvl-muted);
    padding: 0 10px 8px 0;
    border-bottom: 1px solid var(--gvl-line);
}

.gvl-release__grid td {
    padding: 9px 10px 9px 0;
    border-bottom: 1px solid var(--gvl-line);
    vertical-align: middle;
}

.gvl-release__grid tr:last-child td {
    border-bottom: 0;
}

.gvl-release__name {
    display: block;
    font-weight: 500;
    color: var(--gvl-title);
}

.gvl-release__sub {
    display: block;
    font-size: 12px;
    color: var(--gvl-muted);
}

.gvl-release__field {
    display: inline-flex;
    align-items: center;
    gap: 7px;
}

/* Width by content, not by the grid: a date input and a two-digit number have
 * no business being the same size, and `width: 100%` from the form base made
 * both of them span the column. */
.gvl-release__num {
    width: 5.5em;
    display: inline-block;
}

.gvl-release__date {
    width: auto;
    display: inline-block;
}

.gvl-release__unit {
    font-size: 12.5px;
    color: var(--gvl-muted);
    white-space: nowrap;
}

/* The computed column. Tabular figures so a run of dates lines up, and the two
 * states that are not simply a date are coloured — "open already" reads as
 * fine, "before the module above it" as something to look at. */
.gvl-release__when {
    font-size: 12.5px;
    color: var(--gvl-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.gvl-release__when.is-now {
    color: var(--gvl-enroll, #2e7d32);
}

.gvl-release__when.is-warning {
    color: #a5620b;
    font-weight: 600;
}

.gvl-release__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 9px;
    margin-top: 14px;
    padding-top: 13px;
    border-top: 1px dashed var(--gvl-line);
    font-size: 13px;
}

.gvl-release__tools > span {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 9px;
}

.gvl-release__tools label {
    margin: 0;
    font-size: 13px;
    color: var(--gvl-muted);
}

/* ── The preview ─────────────────────────────────────────────────────────
 * Staff see every lesson, so this is the only way an author can look at their
 * own drip. Set apart from the table above it, because it is an answer rather
 * than a field. */
.gvl-release__preview {
    margin-top: 16px;
    padding: 14px;
    border: 1px dashed var(--gvl-line);
    border-radius: 6px;
    background: rgba(0, 0, 0, .015);
}

.gvl-release__scrub {
    display: flex;
    align-items: center;
    gap: 11px;
    flex-wrap: wrap;
}

.gvl-release__scrub label {
    margin: 0;
    font-size: 13px;
    color: var(--gvl-muted);
    white-space: nowrap;
}

.gvl-release__range {
    flex: 1 1 160px;
    min-width: 120px;
    accent-color: var(--gvl-primary, #1d5a9c);
}

.gvl-release__read {
    font-size: 13px;
    font-weight: 600;
    color: var(--gvl-title);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.gvl-release__preview-list {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.gvl-release__preview-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    border-radius: 4px;
    font-size: 13.5px;
    background: var(--gvl-card, #fff);
}

.gvl-release__preview-row i {
    font-size: 11px;
    color: var(--gvl-enroll, #2e7d32);
    width: 13px;
    text-align: center;
}

.gvl-release__preview-row.is-shut {
    color: var(--gvl-muted);
    background: transparent;
}

.gvl-release__preview-row.is-shut i {
    color: var(--gvl-muted);
    opacity: .7;
}

.gvl-release__preview-state {
    margin-left: auto;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--gvl-muted);
}

/* The table scrolls rather than the modal, on a narrow screen. */
@media (max-width: 575px) {
    .gvl-release {
        overflow-x: auto;
    }

    .gvl-release__grid {
        min-width: 420px;
    }
}

.gvl-manage {
    border-style: dashed;
}

.gvl-manage__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.gvl-manage__list li {
    margin-bottom: 10px;
}

.gvl-manage__list li:last-child {
    margin-bottom: 0;
}

.gvl-manage__list a {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 14px;
    color: var(--gvl-text);
    text-decoration: none;
}

.gvl-manage__list a:hover {
    color: var(--gvl-primary);
}

.gvl-manage__list i {
    flex: 0 0 16px;
    font-size: 12px;
    color: var(--gvl-muted);
}

@media (max-width: 991px) {
    .gvl-sidecol {
        position: static;
        margin-top: 30px;
    }
}


/* ── Enroll, on the Enrollment row ──────────────────────────────────────────
 * The action sits with the fact it acts on, rather than in a card of its own
 * repeating what the fact already says. */
.gvl-sidefacts__row--action {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* Green regardless of the skin's primary: this is a go action, and the one
   thing on the page a visitor is meant to press. Kept as its own token so a
   skin can still recolour it deliberately. */
.gvl-enrollbtn,
.gvl-enrollbtn:focus {
    flex: 0 0 auto;
    background-color: var(--gvl-enroll, #1f9254) !important;
    border-color: var(--gvl-enroll, #1f9254) !important;
    color: #fff !important;
    font-weight: 600;
    padding: 7px 18px !important;
    white-space: nowrap;
    text-decoration: none;
}

.gvl-enrollbtn:hover,
.gvl-enrollbtn:active {
    background-color: var(--gvl-enroll-dark, #17703f) !important;
    border-color: var(--gvl-enroll-dark, #17703f) !important;
    color: #fff !important;
}

.gvl-checkoutpanel {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--gvl-line);
}

.gvl-checkoutpanel[hidden] {
    display: none;
}


/* ── Join-a-course modal ────────────────────────────────────────────────── */

/* Deliberately NOT setting a border. It used to say `border: 0` to drop
   Bootstrap's default, which was written when a border was something to remove.
   The theme now gives every .modal-content a visible edge — its backdrop is
   white, so a card with no edge has nothing to separate it from the page — and
   this rule was quietly opting one modal out of that at a higher specificity. */
.gvl-authmodal .modal-content {
    border-radius: var(--gvl-radius-lg);
}

/* The vendored base sets .modal-header to display:block with 2.5rem padding,
   which drops the close button below the title. */
.gvl-authmodal .modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 22px 24px 16px;
    border-bottom: 1px solid var(--gvl-line);
}

.gvl-authmodal .modal-title {
    font-family: var(--gvl-font);
    font-size: 18px;
    font-weight: 600;
    color: var(--gvl-title);
}

.gvl-authmodal .modal-body {
    padding: 22px 24px 26px;
}

.gvl-authpane[hidden] {
    display: none;
}

.gvl-field {
    margin-bottom: 16px;
}

.gvl-field--pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.gvl-field label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--gvl-title);
    margin-bottom: 5px;
}

.gvl-field input {
    width: 100%;
    height: 44px;
    padding: 0 14px;
    font-family: var(--gvl-font);
    font-size: 15px;
    color: var(--gvl-text);
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line-strong);
    border-radius: var(--gvl-radius);
}

.gvl-field input:focus {
    outline: none;
    border-color: var(--gvl-primary-light);
    box-shadow: 0 0 0 3px var(--gvl-primary-wash);
}

/* Filled from the account and not editable. Greyed rather than hidden: the
   point is to show WHICH account is being bought for, which a hidden field
   cannot do. */
.gvl-field input.is-locked,
.gvl-field input:disabled {
    color: var(--gvl-muted);
    background-color: var(--gvl-surface-sunk);
    cursor: not-allowed;
}

.gvl-field__hint {
    display: block;
    font-size: 12px;
    color: var(--gvl-muted);
    margin-top: 5px;
}

/* ── Checkout ────────────────────────────────────────────────────────────────
   gvl-platform draws the payment form into the join modal, using the same
   .gvl-field as the panes around it so the card does not arrive looking like a
   form from somewhere else. These are the parts .gvl-field cannot cover: the
   card lives inside Stripe's iframe, which cannot be styled from here — only
   the box around it can, and it has to match an input exactly. */
.gvl-checkout__card {
    height: 44px;
    padding: 13px 14px 0;
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line-strong);
    border-radius: var(--gvl-radius);
}

/* Stripe adds this class to the host when the field inside has focus; without
   it the card is the one control on the form that never lights up. */
.gvl-checkout__card.StripeElement--focus {
    border-color: var(--gvl-primary-light);
    box-shadow: 0 0 0 3px var(--gvl-primary-wash);
}

.gvl-checkout__card.StripeElement--invalid {
    border-color: #c0392b;
}

.gvl-checkout__pay {
    width: 100%;
    margin-top: 4px;
    padding: 12px !important;
}

/* Something that needs doing, at the top of the Billing panel. Red because it
   is money that was taken and not delivered, which is the one thing on this
   screen worth interrupting somebody for. */
.gvl-billing__alert {
    margin: 0 0 1.5rem;
    padding: 1rem 1.125rem;
    border: 1px solid #f0b4b4;
    border-left-width: 4px;
    border-radius: var(--gvl-radius, 6px);
    background: #fdf3f3;
}

.gvl-billing__alert strong {
    display: block;
    margin-bottom: .25rem;
    color: #8a1f1f;
}

.gvl-billing__alert p {
    margin: 0;
    font-size: .875rem;
    line-height: 1.5;
    color: var(--gvl-ink, #1d2b36);
}

/* ── Waiting for the webhook ─────────────────────────────────────────────────
   Between Stripe confirming the card and our side hearing about it. Usually
   under a second, occasionally longer, and the one thing it must not look like
   is a page that has stopped. */

.gvl-checkout__waiting {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: 1.25rem 0;
}

.gvl-checkout__spinner {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    border: 2px solid var(--gvl-rule, #e7ecf1);
    /* One arc in the brand colour is what makes it read as turning. */
    border-top-color: var(--gvl-primary, #0b50d8);
    border-radius: 50%;
    animation: gvl-spin .7s linear infinite;
}

.gvl-checkout__waitingtext {
    margin: 0;
    font-size: .9375rem;
    line-height: 1.5;
    color: var(--gvl-ink, #1d2b36);
}

@keyframes gvl-spin {
    to { transform: rotate(360deg); }
}

/* A spinner is motion with no information in it, so somebody who has asked for
   less of that loses nothing by it stopping. The text already says what is
   happening — which is why the message is never carried by the animation. */
@media (prefers-reduced-motion: reduce) {
    .gvl-checkout__spinner { animation: none; }
}

.gvl-checkout__status {
    margin: 12px 0 0;
    font-size: 14px;
    color: var(--gvl-muted);
}

.gvl-checkout__status.is-error {
    color: #c0392b;
}

.gvl-authmodal__submit {
    width: 100%;
    margin-top: 4px;
    padding: 12px !important;
}

.gvl-authmodal__switch {
    font-size: 13px;
    color: var(--gvl-muted);
    text-align: center;
    margin: 14px 0 0;
}

/* A control that reads as a link but is a button, because it performs an
   action on this page rather than navigating anywhere. */
.gvl-linkbtn {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--gvl-primary);
    text-decoration: underline;
    cursor: pointer;
}

.gvl-linkbtn:hover {
    color: var(--gvl-primary-dark);
}

.gvl-authmodal__message {
    font-size: 14px;
    line-height: 1.55;
    padding: 12px 14px;
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-primary-wash);
    color: var(--gvl-primary-dark);
    margin: 0 0 18px;
}

.gvl-authmodal__message.is-error {
    background-color: #fdeeee;
    color: #a3312d;
}

.gvl-authmodal__message[hidden] {
    display: none;
}

.gvl-billing {
    position: relative;
    padding: 16px;
    margin-bottom: 18px;
    background-color: var(--gvl-surface-sunk);
    border-radius: var(--gvl-radius);
}

/* Top right of the sunk panel, in its padding rather than in the first field's
   line — the section it closes is the whole block, not the field beside it. */
.gvl-billing__close {
    position: absolute;
    top: 10px;
    right: 10px;
    padding: 6px;
    opacity: .45;
}

.gvl-billing__close:hover,
.gvl-billing__close:focus {
    opacity: .85;
}

/* The first row has to clear the button, or a two-up pair of labels runs
   underneath it. */
.gvl-billing > .gvl-field:first-of-type {
    padding-right: 28px;
}

.gvl-billing[hidden] {
    display: none;
}

@media (max-width: 480px) {
    .gvl-field--pair {
        grid-template-columns: 1fr;
    }
}


/* ── Locked rows ────────────────────────────────────────────────────────────
 * The outline of a course you cannot open yet. Listed so you can see what it
 * covers, but not dressed as something clickable — no hover, no pointer, and a
 * lock where the completion tick would be. */
.gvl-lessonrow--locked {
    cursor: default;
    background: none;
}

.gvl-lessonrow--locked:hover {
    background: none;
}

.gvl-lessonrow--locked .gvl-lessonrow__title {
    color: var(--gvl-text);
    font-weight: 400;
}

.gvl-lessonrow--locked .gvl-lessonrow__icon {
    opacity: .55;
}

.gvl-lessonrow__lock {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--gvl-muted);
    opacity: .6;
}

/* The padlock's replacement when there is something to say. Sized and spaced
 * like the Done tick it sits where, so a list of rows with mixed states keeps
 * one right-hand column rather than three ragged ones. */
.gvl-lessonrow__when {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 600;
    color: var(--gvl-muted);
    white-space: nowrap;
}

@media (max-width: 767px) {
    .gvl-syllabus__progress {
        width: 100%;
    }

    .gvl-syllabus__progressbar {
        flex: 1 1 auto;
    }
}


/* ── /login/ page ───────────────────────────────────────────────────────── */
.gvl-loginpage {
    padding: 56px 24px 96px;
    background-color: var(--gvl-surface-sunk);
}

.gvl-loginpage__card {
    max-width: 420px;
    margin: 0 auto;
    padding: 30px;
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius-lg);
    box-shadow: var(--gvl-shadow);
}

/* The one line that says why a password form is on screen at all. */
.gvl-authmodal__support {
    color: var(--gvl-muted);
    text-align: left;
    margin: 0 0 16px;
}


/* "Course full" where the Enroll button would otherwise sit. */
.gvl-sidefacts__note {
    align-self: center;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--gvl-muted, #6c757d);
    white-space: nowrap;
}


/* ── Front-end management forms ──────────────────────────────────────────────
   Tokens only, no hard-coded brand colours, so a skin restyles these the same
   way it restyles everything else. */
.gvl-form__row { margin-bottom: 1.75rem; }

/* The split row nests its labels one level deeper, so a direct-child selector
   missed them and the date fields were styled by the browser default while
   every other label was not. */
.gvl-form__row > label,
.gvl-form__row--split > div > label {
    display: block;
    margin-bottom: .35rem;
    font-weight: 600;
    font-size: .9375rem;
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-form__row--split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

@media (max-width: 575.98px) {
    .gvl-form__row--split { grid-template-columns: 1fr; }
}

/* ── A "?" beside a label ────────────────────────────────────────────────────
   Shares the popover mechanism with the learner's "waiting to be graded" note;
   only the trigger differs. Small and quiet: it is there for the person who
   wonders, and invisible to everybody else. */
/* The label and its "?" on one line. The label keeps its own bottom margin, so
   the row carries it instead. */
.gvl-form__labelrow {
    display: flex;
    align-items: center;
    gap: .125rem;
    margin-bottom: .35rem;
}

.gvl-form__labelrow > label { margin-bottom: 0; }

.gvl-help {
    position: relative;
    display: inline-block;
    margin-left: .375rem;
    vertical-align: middle;
}

.gvl-help__trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.125rem;
    height: 1.125rem;
    padding: 0;
    font-size: .6875rem;
    font-weight: 700;
    line-height: 1;
    color: var(--gvl-muted, #5c6b7a);
    background: var(--gvl-tint, #eef4f8);
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: 50%;
    cursor: pointer;
}

.gvl-help__trigger:hover,
.gvl-help__trigger:focus-visible,
.gvl-help[data-gvl-note].is-open .gvl-help__trigger {
    color: #fff;
    background: var(--gvl-primary, #0b50d8);
    border-color: var(--gvl-primary, #0b50d8);
}

/* Normal weight — the label it sits beside is bold, and the explanation is not. */
.gvl-help__note { font-weight: 400; }

/* An explanation with more than one case in it needs those cases separated. */
.gvl-help__note p { margin: 0 0 .75rem; }
.gvl-help__note p:last-child { margin-bottom: 0; }

.gvl-help__heading {
    margin: 1rem 0 .25rem;
    font-size: .8125rem;
    font-weight: 700;
}

.gvl-help__heading:first-child { margin-top: 0; }

/* The one actionable thing in the popover, so it looks like one. */
.gvl-help__note a {
    color: var(--gvl-primary, #0b50d8);
    text-decoration: underline;
}

/* Wider than the learner's one-line note: this one carries two sections. */
.gvl-help .gvl-help__note { max-width: 22rem; }

.gvl-form__hint {
    display: block;
    margin-top: .35rem;
    font-size: .8125rem;
    line-height: 1.45;
    color: var(--gvl-muted, #6c757d);
}

.gvl-form__check {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    font-weight: 600;
    font-size: .9375rem;
    cursor: pointer;
}

.gvl-form__check input { margin-top: .2rem; }

.gvl-form__message {
    margin: 1rem 0 0;
    padding: .6rem .8rem;
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #eef4f8);
    color: var(--gvl-heading, #1f2d3d);
    font-size: .875rem;
}

.gvl-form__message.is-error {
    background: var(--gvl-danger-tint, #fdecea);
    color: var(--gvl-danger, #a4292c);
}


/* ── A course that is one video ──────────────────────────────────────────────
   A poster that opens the full-width player, rather than an inline embed. */


/* ── Management modal chrome ─────────────────────────────────────────────────
   The vendored eDocs stylesheet sets .modal-header and .modal-footer to
   display:block, which drops Bootstrap's flex layout. Two things broke: the
   close button stopped being pushed to the right and sat under the title, and
   the footer stopped being held out of the scrolling area — with
   .modal-content overflow:hidden it was simply clipped, so on a 900px-tall
   screen the Save button was rendered at y=1112 and never seen.

   Scoped to this modal rather than fixed globally: the template's own modals
   are built for that stylesheet, and re-flexing all of them to suit ours is how
   a vendored theme starts fighting back. */
#gvl-course-edit .modal-content,
#gvl-add-module .modal-content,
#gvl-add-lesson .modal-content,
#gvl-enrollment .modal-content {
    max-height: calc(100vh - 3.5rem);
}

/* The form wraps the header, body and footer so everything posts together —
   which makes IT .modal-content's only flex child, and leaves the three of them
   with no flex context to size in. Bootstrap's scrollable modal assumes those
   are direct children of .modal-content. Making the form the column flex
   container puts that back: without this the body never shrinks, and the footer
   is pushed past the clipped edge of the dialog. */
#gvl-course-edit .modal-content > form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

#gvl-course-edit .modal-header,
#gvl-add-module .modal-header,
#gvl-add-lesson .modal-header,
#gvl-enrollment .modal-header,
#gvl-grade .modal-header,
#gvl-gradebook .modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-shrink: 0;
    padding: 1.75rem 2rem 1.25rem;
    border-bottom: 1px solid var(--gvl-rule, #e7ecf1);
}

#gvl-course-edit .modal-header .btn-close,
#gvl-add-module .modal-header .btn-close,
#gvl-add-lesson .modal-header .btn-close,
#gvl-enrollment .modal-header .btn-close,
#gvl-grade .modal-header .btn-close,
#gvl-gradebook .modal-header .btn-close {
    display: block;
    flex: 0 0 auto;
    margin: 0;
    opacity: .6;
}

#gvl-course-edit .modal-header .btn-close:hover,
#gvl-add-module .modal-header .btn-close:hover,
#gvl-add-lesson .modal-header .btn-close:hover,
#gvl-enrollment .modal-header .btn-close:hover,
#gvl-grade .modal-header .btn-close:hover,
#gvl-gradebook .modal-header .btn-close:hover { opacity: 1; }

/* min-height:0 is what actually lets a flex child scroll — without it the body
   refuses to shrink below its content and pushes the footer out. */
#gvl-course-edit .modal-body,
#gvl-add-module .modal-body,
#gvl-add-lesson .modal-body,
#gvl-enrollment .modal-body,
#gvl-grade .modal-body,
#gvl-gradebook .modal-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 1.75rem 2rem;
}

#gvl-course-edit .modal-footer,
#gvl-add-module .modal-footer,
#gvl-add-lesson .modal-footer,
#gvl-enrollment .modal-footer,
#gvl-grade .modal-footer,
#gvl-gradebook .modal-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: .6rem;
    flex-shrink: 0;
    padding: 1.25rem 2rem 1.5rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

/* Delete sits apart from Cancel/Save so it is not fumbled for one of them. */
#gvl-course-edit .modal-footer [data-gvl-course-delete] { margin-right: auto; }

/* ── Modal footers on a phone ───────────────────────────────────────────────
 *
 * .btn is 9.25rem wide before it contains any text and carries 2.5rem of
 * padding either side — deliberate, and the right call on a desktop. On a
 * 412px phone the Site Admin footer's two buttons needed 364px of a 330px row,
 * so they wrapped onto separate lines and the footer grew to 183px: a third of
 * the screen spent on two buttons, which is what they looked like.
 *
 * Dividing the row between them is narrower AND the ordinary phone treatment,
 * so it reads as intended rather than as a squeeze. */
@media (max-width: 575px) {

    .modal-footer .btn {
        flex: 1 1 0;
        min-width: 0;
        padding-left: .875rem;
        padding-right: .875rem;
    }

    /* A destructive action keeps its distance by taking a row of its own.
     * Sharing one with Cancel and Save would put Delete a thumb's width from
     * Save on the smallest screen — margin-right:auto buys no separation once
     * everything is stretching to fill the line. */
    #gvl-course-edit .modal-footer [data-gvl-course-delete] {
        flex: 1 0 100%;
        margin-right: 0;
        margin-bottom: .25rem;
    }
}

/* A breathing gap between "what the course is" and "how people get onto it". */
.gvl-form__section {
    margin-top: 2.5rem;
    padding-top: 2rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

/* The same gap without the rule. Space alone separates these groups; a line as
   well made the form read as more divided than it is. */
.gvl-form__section--plain {
    padding-top: 0;
    border-top: 0;
}

/* Settings need more room between them than the fields inside one setting do.
   Each of these carries a control plus the sentence explaining it, and at the
   old spacing a hint sat nearer the NEXT label than its own control. */
#gvl-course-edit .gvl-form__row { margin-bottom: 2.5rem; }

.gvl-form__row:last-child { margin-bottom: 0; }

/* ── Management modals: the register ─────────────────────────────────────── */
.gvl-addmember { margin-bottom: 1.75rem; }

.gvl-addmember__fields {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: .75rem;
    align-items: stretch;
}

@media (max-width: 575.98px) {
    .gvl-addmember__fields { grid-template-columns: 1fr; }
}

/* The search field, its results panel and the role button are one row of equal
   height. The panel is positioned, not in flow, so opening it does not shove
   the button and the list below it down the modal. */
.gvl-addmember__search { position: relative; min-width: 0; }


/* The field wrapper exists only so the "Add New User" link can be placed
   inside the input. It is the flex child now, so it carries the growth and the
   input fills it — otherwise the input loses its matched height. */
.gvl-addmember__field { position: relative; display: flex; flex: 1 1 auto; }
.gvl-addmember__field .form-control { width: 100%; }

.gvl-addmember__new {
    position: absolute;
    right: .5rem;
    top: 50%;
    transform: translateY(-50%);
    padding: .15rem .3rem;
    border: 0;
    border-radius: 4px;
    background: transparent;
    font-size: .8125rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    color: var(--gvl-accent, #3399cc);
    cursor: pointer;
}

.gvl-addmember__new:hover { text-decoration: underline; }

/* Room for the link. JS replaces this with the measured width once the modal is
   open, because the label is translated and "Add New User" is not its longest
   rendering; this value only has to be close enough before that runs. */
.gvl-addmember__field:not(.is-bare) .form-control { padding-right: 7rem; }

/* Somebody already uses the address typed into the new-person form. */
.gvl-people__dupe {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .25rem .5rem;
    margin: .5rem 0 0;
    font-size: .875rem;
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-people__dupe-add {
    padding: 0;
    border: 0;
    background: transparent;
    font-size: inherit;
    font-weight: 600;
    color: var(--gvl-accent, #3399cc);
    text-decoration: underline;
    cursor: pointer;
}

.gvl-addmember__search .form-control.is-chosen {
    font-weight: 600;
    border-color: var(--gvl-accent, #3399cc);
}

/* All three controls are the same height, and that is arranged rather than
   guessed at: every column is a flex column of label-then-control, the row
   stretches, and the control takes whatever height is left. Pinning a height on
   the buttons instead gave 44px against the input's 56px, because the theme
   pads .btn-primary differently from .btn-outline-secondary. */
.gvl-addmember__search,
.gvl-addmember__role,
.gvl-addmember__action { display: flex; flex-direction: column; }

.gvl-addmember__search .form-control,
.gvl-addmember__role .btn,
.gvl-addmember__action .btn { flex: 1 1 auto; }

.gvl-addmember__role .btn,
.gvl-addmember__action .btn { white-space: nowrap; }

/* The action column has no label of its own; an empty one keeps its control on
   the same line as the other two. */
.gvl-addmember__action .gvl-addmember__spacer { visibility: hidden; }

.gvl-addmember__role .dropdown-menu { min-width: 100%; }

.gvl-addmember__panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + .25rem);
    left: 0;
    right: 0;
    max-height: 19rem;
    overflow-y: auto;
    padding: .35rem;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-card, #fff);
    box-shadow: 0 10px 28px rgba(0, 0, 0, .14);
}

.gvl-people { list-style: none; margin: 0; padding: 0; }

.gvl-people__row {
    display: flex;
    align-items: center;
    gap: .75rem;
    width: 100%;
    padding: .5rem .6rem;
    border: 0;
    border-radius: 4px;
    background: transparent;
    text-align: left;
    cursor: pointer;
}

.gvl-people__row:hover:not(:disabled),
.gvl-people__row:focus-visible { background: var(--gvl-tint, #eef4f8); }

.gvl-people__row:disabled { cursor: default; opacity: .6; }
.gvl-people__who { flex: 1 1 auto; min-width: 0; }

.gvl-people__already {
    flex: 0 0 auto;
    font-size: .75rem;
    color: var(--gvl-muted, #6c757d);
    white-space: nowrap;
}

/* The "nobody matched" form, inside the same panel so the answer to a failed
   search is right where the search was. */
/* In the form's own flow, not in the results dropdown. It was in the panel
   once; the panel is absolutely positioned and the vendored stylesheet sets
   .modal-content overflow:hidden, so the form's last row — the one offering an
   existing user — was cut off by the dialog edge. Inline, the modal grows. */
.gvl-people__new {
    display: grid;
    gap: .5rem;
    margin-top: .75rem;
    padding: .75rem;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #f7fafc);
}

.gvl-people__none {
    margin: 0;
    font-size: .8125rem;
    color: var(--gvl-muted, #6c757d);
}

.gvl-people__newfields { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }

@media (max-width: 575.98px) {
    .gvl-people__newfields { grid-template-columns: 1fr; }
}

.gvl-addmember__fields label {
    display: block;
    margin-bottom: .35rem;
    font-weight: 600;
    font-size: .9375rem;
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-members__row {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: .75rem 0;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

.gvl-members__who { flex: 1 1 auto; min-width: 0; }

.gvl-members__name {
    display: block;
    font-weight: 600;
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-members__email {
    display: block;
    font-size: .8125rem;
    color: var(--gvl-muted, #6c757d);
    overflow-wrap: anywhere;
}

.gvl-members__role {
    flex: 0 0 auto;
    font-size: .8125rem;
    color: var(--gvl-muted, #6c757d);
}

/* ── Management modals: the gradebook ────────────────────────────────────── */
.gvl-gradebook-modal .modal-body { padding: 1.5rem 2rem 2.5rem; }

/* The grid is the one thing here that genuinely needs width, and it still
   outgrows it — so it scrolls inside its own box rather than the page. */
.gvl-gradebook__scroll {
    overflow-x: auto;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
}

.gvl-gradebook {
    border-collapse: collapse;
    width: 100%;
    font-size: .875rem;
}

.gvl-gradebook th,
.gvl-gradebook td {
    padding: .6rem .75rem;
    border-bottom: 1px solid var(--gvl-rule, #e7ecf1);
    text-align: center;
    vertical-align: middle;
}

/* The learner column stays put while the lessons scroll past it — without it
   you lose track of whose row you are reading two screens along. */
.gvl-gradebook__learner {
    position: sticky;
    left: 0;
    z-index: 2;
    min-width: 200px;
    text-align: left;
    background: var(--gvl-card, #fff);
    box-shadow: 1px 0 0 var(--gvl-rule, #e7ecf1);
}

.gvl-gradebook thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--gvl-tint, #eef4f8);
    font-weight: 600;
}

.gvl-gradebook thead .gvl-gradebook__learner { z-index: 3; }

/* Module columns are a fixed width so that a whole number of them fits the
   frame at every size — three wide, two medium, one narrow. The pager reads
   this width back off the rendered column and steps by exactly that many, so
   a page never ends halfway through a module.

   A fixed width rather than a max: `max-width` lets a short module name make a
   narrow column, and then "how many fit" has a different answer per course. */
.gvl-gradebook__lesson,
.gvl-gradebook__module {
    width: var(--gvl-gb-col, 200px);
    min-width: var(--gvl-gb-col, 200px);
    max-width: var(--gvl-gb-col, 200px);
}

.gvl-gradebook__lesson span,
.gvl-gradebook__module span {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A module cell stacks three facts, which is why it is not a one-line cell:
   how far in, what scored, and what is still waiting on somebody. */
.gvl-gradebook__cell {
    display: table-cell;
    min-width: 150px;
}

.gvl-gradebook__opened,
.gvl-gradebook__score {
    display: block;
    white-space: nowrap;
}

.gvl-gradebook__opened {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-gradebook__opened.is-done { color: var(--gvl-primary, #0b50d8); }

.gvl-gradebook__done { font-weight: 600; }

.gvl-gradebook__score {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.gvl-gradebook__score.is-empty { font-weight: 400; color: var(--gvl-muted, #5c6b7a); }

.gvl-gradebook__cell--total { background: var(--gvl-tint, #eef4f8); }

.gvl-gradebook__cell.is-complete { background: var(--gvl-success-tint, #e8f5e9); }
.gvl-gradebook__cell.is-pending  { background: var(--gvl-warning-tint, #fff8e1); }

/* Overall sticks to the right edge the way Student sticks to the left. Paging
   through eight modules to check a total, then paging back, is the thing the
   two sticky columns exist to prevent — you are always looking at a name and a
   total with the modules moving between them. */
.gvl-gradebook__total {
    font-weight: 600;
    white-space: nowrap;
    position: sticky;
    right: 0;
    z-index: 2;
    background: var(--gvl-card, #fff);
    box-shadow: -1px 0 0 var(--gvl-rule, #e7ecf1);
}

.gvl-gradebook thead .gvl-gradebook__total {
    z-index: 3;
    background: var(--gvl-tint, #eef4f8);
}

/* ── Finding and paging through the STUDENTS ────────────────────────────── */

/* Above the grid, and not scrolling with it: these controls change which rows
   the server sends, where the module pager below only moves columns already on
   the page. Putting them at opposite ends of the grid is the clearest way to
   say they are not the same control. */

.gvl-gradebook__tools {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}

.gvl-gradebook__find {
    position: relative;
    flex: 1 1 220px;
    max-width: 320px;
}

.gvl-gradebook__find .form-control {
    padding-left: 34px;
    height: 34px;
    font-size: .875rem;
}

/* Inside the field, vertically centred on the field rather than on its text,
   so it does not drift when the control's height changes. */
.gvl-gradebook__findicon {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    color: var(--gvl-muted, #5c6b7a);
    font-size: .8125rem;
}

/* Fixed width, because the label swaps between "A–Z" and "Z–A" and a button
   that changes size when clicked moves everything after it. Wide enough for
   the longer of the two in both. */
.gvl-gradebook__sort {
    white-space: nowrap;
    min-width: 9.5rem;
    text-align: center;
}

/* Pushed to the far end: the count and its arrows answer "where am I in the
   list", which is a different question from "which student", and the gap keeps
   them from being read as one control. */
.gvl-gradebook__people {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

.gvl-gradebook__count {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

@media (max-width: 575px) {
    /* The count wraps to its own line before the arrows get cramped — the
       arrows are what gets tapped. */
    .gvl-gradebook__people { margin-left: 0; width: 100%; }
    .gvl-gradebook__find { max-width: none; }
}

/* ── Paging through the modules ─────────────────────────────────────────── */

.gvl-gradebook__frame { position: relative; }

.gvl-gradebook__pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 10px;
}

.gvl-gradebook__at {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
    font-variant-numeric: tabular-nums;
}

.gvl-gradebook__page {
    min-width: 34px;
    line-height: 1;
    font-size: 1rem;
}

.gvl-gradebook__page:disabled { opacity: .4; cursor: default; }

/* Fallback widths only. The real column width is computed and set as
   --gvl-gb-col by the pager, which is the only thing that can measure the
   space available and divide it into three, two or one — see sizeColumns().
   These values are what a column is before that runs, and what it stays if
   there is no pager because every module already fits. */
@media (max-width: 991px) {
    .gvl-gradebook__lesson,
    .gvl-gradebook__module { --gvl-gb-col: 190px; }
    .gvl-gradebook__learner { min-width: 160px; }
}

@media (max-width: 767px) {
    .gvl-gradebook__lesson,
    .gvl-gradebook__module { --gvl-gb-col: 170px; }
    .gvl-gradebook__learner { min-width: 130px; }
    .gvl-gradebook th,
    .gvl-gradebook td { padding: .5rem .5rem; }
}

/* ── "Waiting to be graded" ──────────────────────────────────────────────────
   One note, two readers: the learner meets it on the syllabus, the instructor in
   the gradebook. Click rather than hover, so it works by touch and by keyboard —
   which also means it needs somewhere to sit without shifting the layout. */
.gvl-pending {
    position: relative;
    display: inline-block;
}

.gvl-pending__badge,
.gvl-gradebook__flag {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    padding: .125rem .5rem .125rem .375rem;
    font-size: .75rem;
    font-weight: 600;
    line-height: 1.6;
    color: #7a5a00;
    background: var(--gvl-warning-tint, #fff8e1);
    border: 1px solid var(--gvl-warning, #e6a700);
    border-radius: 999px;
    cursor: pointer;
}

.gvl-pending__badge:hover,
.gvl-pending__badge:focus-visible,
.gvl-gradebook__flag:hover,
.gvl-gradebook__flag:focus-visible { background: #fff2cc; }

/* This one does something when you press it, so it says so. */
.gvl-gradebook__flag { margin-top: .375rem; }

.gvl-pending__dot {
    width: .4375rem;
    height: .4375rem;
    border-radius: 50%;
    background: var(--gvl-warning, #e6a700);
    flex: 0 0 auto;
}

/* Fixed, not absolute: in the gradebook this sits inside a table with
   overflow-x:auto, which clips the vertical axis too — see place() in
   gvl-lms.js, which sets top/left from the badge's position on screen. */
.gvl-pending__note {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 1080;
    display: none;
    width: max-content;
    max-width: 17rem;
    padding: .625rem .75rem;
    font-size: .8125rem;
    font-weight: 400;
    line-height: 1.45;
    text-align: left;
    white-space: normal;
    color: var(--gvl-ink, #1d2b36);
    background: var(--gvl-card, #fff);
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    box-shadow: 0 4px 14px rgba(0, 0, 0, .12);
}

/* Keyed on the shared data attribute, not on either component's wrapper class.
   The "?" popover reuses .gvl-pending__note for its looks but sits inside
   .gvl-help, so a rule naming .gvl-pending matched the note and never the
   wrapper — it opened in the DOM and stayed invisible. */
[data-gvl-note].is-open [data-gvl-note-body] { display: block; }

/* The learner's own points, under the progress bar. */
.gvl-progress__points {
    margin: .25rem 0 .5rem;
    font-size: .875rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* ── Reading one answer ──────────────────────────────────────────────────────
   The answer is the content here, so it gets the room and the type; the two
   fields under it are small because awarding a number is a small act. */
.gvl-grade__heading { display: grid; gap: .125rem; }

/* The heading here is two lines, so the close button lines up with the name
   rather than floating level with the gap under it. */
#gvl-grade .modal-header { align-items: flex-start; }

.gvl-grade__position {
    margin: 0;
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-grade__prompt {
    margin: 0 0 .75rem;
    font-weight: 600;
    font-size: 1.0625rem;
}

.gvl-grade__answer {
    margin-bottom: 1.5rem;
    padding: 1rem 1.125rem;
    max-height: 22rem;
    overflow-y: auto;
    white-space: pre-wrap;        /* their paragraphs are theirs */
    line-height: 1.65;
    border-left: 3px solid var(--gvl-primary, #0b50d8);
    border-radius: 0 var(--gvl-radius, 6px) var(--gvl-radius, 6px) 0;
    background: var(--gvl-tint, #eef4f8);
}

.gvl-grade__award .gvl-grade__scoreline {
    display: flex;
    align-items: center;
    gap: .625rem;
}

.gvl-grade__scoreline input { max-width: 7rem; }

.gvl-grade__outof {
    font-size: .875rem;
    color: var(--gvl-muted, #5c6b7a);
    white-space: nowrap;
}

/* Already marked, and being looked at again rather than for the first time.
   Said in words, not in colour: --gvl-primary is itself green in this theme, so
   a green "graded" border was indistinguishable from the ordinary one. */
.gvl-grade__marked {
    display: none;
    margin-left: .5rem;
    padding: .0625rem .4375rem;
    font-size: .6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #fff;
    background: var(--gvl-success, #2e7d32);
    border-radius: 999px;
    vertical-align: .1em;
}

[data-gvl-grade].is-graded .gvl-grade__marked { display: inline-block; }

/* Previous / Save / Next, with the arrows pushed to the edges they point at. */
.gvl-grade__foot { justify-content: space-between; }
.gvl-grade__foot [data-gvl-grade-save] { margin: 0 auto; }

.gvl-document__note {
    margin: .75rem 0 0;
    font-size: .875rem;
    color: var(--gvl-muted, #5c6b7a);
}

/* ── The profile modal ───────────────────────────────────────────────────────
   Reachable from every page, so it reuses the form rules above rather than
   carrying its own copy of them. */
#gvl-profile .modal-header,
#gvl-profile .modal-body,
#gvl-profile .modal-footer { padding-left: 2rem; padding-right: 2rem; }

#gvl-profile .modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-top: 1.75rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--gvl-rule, #e7ecf1);
}

#gvl-profile .modal-body { padding-top: 1.75rem; padding-bottom: 1.75rem; }

#gvl-profile .modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: .6rem;
    padding-top: 1.25rem;
    padding-bottom: 1.5rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

/* Text, not a disabled input. A greyed-out field invites people to try it and
   then wonder why it does nothing. */
.gvl-profile__email {
    margin: 0;
    font-weight: 600;
    word-break: break-all;
}

.gvl-profile__courses {
    margin-top: 2rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

.gvl-profile__heading {
    margin: 0 0 .75rem;
    font-weight: 600;
    font-size: .9375rem;
}

.gvl-profile__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.gvl-profile__course {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: .625rem 0;
    border-bottom: 1px solid var(--gvl-rule, #e7ecf1);
}

.gvl-profile__course:last-child { border-bottom: 0; }

.gvl-profile__coursename { font-weight: 600; }

/* The part they play, not a status — quiet, and the same width-ish every time
   so the column reads down the list. */
.gvl-profile__role {
    flex: 0 0 auto;
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
}

/* ── Site settings ───────────────────────────────────────────────────────────
   Drawn from a schema, so the rules here are about SHAPES of field rather than
   about any particular setting. */
#gvl-site-settings .modal-header,
#gvl-site-settings .modal-body,
#gvl-site-settings .modal-footer { padding-left: 2rem; padding-right: 2rem; }

#gvl-site-settings .modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-top: 1.75rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--gvl-rule, #e7ecf1);
}

#gvl-site-settings .modal-body { padding-top: 1.75rem; padding-bottom: 1.75rem; }

#gvl-site-settings .modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: .6rem;
    padding-top: 1.25rem;
    padding-bottom: 1.5rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

/* ── Billing ─────────────────────────────────────────────────────────────────
   Drawn by gvl-platform into a container this plugin provides, so the styles
   live here with the rest of the modal while the Stripe code lives there. */
/* Two different kinds of money, so two sections with their own headings:
   Stripe (what students pay the site) and the card (what Grapevine charges the
   site). They were one undifferentiated panel and read as one thing. */
.gvl-billing__section + .gvl-billing__section {
    margin-top: 2rem;
    padding-top: 1.75rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

.gvl-billing__heading {
    margin: 0 0 1rem;
    font-weight: 600;
    font-size: .9375rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-billing__lede {
    margin: 0 0 1.25rem;
    line-height: 1.6;
}

/* A section's explanation and the state underneath it are two separate thoughts
   — "this is what the card does" and "there isn't one" — so they get the same
   gap as the one before the button, rather than reading as one paragraph that
   happens to wrap. Written on the lede rather than the hint: adjacent margins
   collapse to the larger of the two, and setting it here is the half that is
   obvious when somebody changes the other. */
.gvl-billing__lede + .gvl-form__hint,
.gvl-billing__lede + .gvl-billing__testmode { margin-top: 1.75rem; }

.gvl-billing__section .gvl-form__hint { margin-bottom: 0; }

.gvl-billing__testmode {
    display: inline-block;
    margin: 0 0 1.25rem;
    padding: .25rem .625rem;
    font-size: .75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #7a5a00;
    background: var(--gvl-warning-tint, #fff8e1);
    border: 1px solid var(--gvl-warning, #e6a700);
    border-radius: 999px;
}

/* The Stripe section's two facts — the account id and whether it can take
   payments — stay a list. They ARE label/value pairs about a remote record,
   with no physical object to draw. */
.gvl-billing__card {
    margin: 0 0 1.5rem;
    border: 1px solid var(--gvl-line-strong, #e7e8e9);
    border-radius: var(--gvl-radius, .5rem);
    overflow: hidden;
}

.gvl-billing__row {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: .75rem 1rem;
    border-bottom: 1px solid var(--gvl-line-strong, #e7e8e9);
}

.gvl-billing__row:last-child { border-bottom: 0; }
.gvl-billing__row dt { margin: 0; font-weight: 600; }
.gvl-billing__row dd { margin: 0; font-variant-numeric: tabular-nums; }

/* ── The card on file ────────────────────────────────────────────────────────
   Drawn as the card rather than as a table about it. Three of its fields are
   printed on an object the reader is holding, so laying them out the way they
   sit on that object means they are recognised instead of read.

   Sized to an object, not to the panel: a full-width band of five label/value
   rows read as a data table, which is the shape for comparing many records —
   there is only ever one card here. */
.gvl-cardface {
    position: relative;
    max-width: 23rem;
    margin: 0 0 1.5rem;
    padding: 1.125rem 1.25rem 1.25rem;
    border: 1px solid var(--gvl-line-strong, #e7e8e9);
    border-radius: var(--gvl-radius-lg, 1rem);
    background: var(--gvl-surface-sunk, #f9f9fa);
}

/* Top right, where the brand mark sits on the real thing. */
.gvl-cardface__brand {
    position: absolute;
    top: 1.125rem;
    right: 1.25rem;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--gvl-muted, #909193);
}

/* Smaller than a card's printed number would be, because the line now carries
   a sentence as well as the digits — set at display size it would shout, and
   there is nothing here to shout over. */
.gvl-cardface__number {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
    color: var(--gvl-title, #212121);
}

.gvl-cardface__expiry {
    margin: .3125rem 0 0;
    font-size: .8125rem;
    font-variant-numeric: tabular-nums;
    color: var(--gvl-muted, #909193);
}

/* The billing identity is a second thought about the same object, so it shares
   the card and is divided from its face rather than floated off on its own. */
.gvl-cardface__billing {
    margin-top: 1.125rem;
    padding-top: .9375rem;
    border-top: 1px solid var(--gvl-line-strong, #e7e8e9);
}

.gvl-cardface__label {
    display: block;
    margin-bottom: .25rem;
    font-size: .625rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--gvl-muted, #909193);
}

.gvl-cardface__name {
    margin: 0;
    font-size: .875rem;
    font-weight: 600;
    color: var(--gvl-title, #212121);
}

/* The formatter returns real line breaks, so the address is drawn the way it
   would be written rather than run together with commas. pre-line keeps it one
   text node, which means it still copies as an address. */
.gvl-cardface__address {
    margin: .125rem 0 0;
    font-size: .8125rem;
    line-height: 1.5;
    white-space: pre-line;
    color: var(--gvl-text, #555555);
}

/* Same gap the explanation gets from anything else that follows it: the
   sentence above says what the card is FOR, and the card is a separate
   thought, not a caption on that sentence. */
.gvl-billing__lede + .gvl-cardface,
.gvl-billing__testmode + .gvl-cardface { margin-top: 1.75rem; }

/* Stripe draws its own form inside an iframe here — this page cannot read what
   is typed into it, which is the point. Given room rather than styled. */
.gvl-billing__address { margin-bottom: 1.25rem; }

/* Matches the label Stripe draws over its own address fields, so the card row
   does not read as a different form from the three above it. */
.gvl-billing__label {
    display: block;
    margin-bottom: .3125rem;
    font-size: .875rem;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-billing__element {
    margin-bottom: 1.5rem;
    min-height: 3rem;
}

/* Room between what a section says and what it offers. The explanation and the
   button that acts on it were a line apart, which read as one block and made the
   destructive buttons feel attached to the sentence above them. */
.gvl-billing__actions {
    display: flex;
    align-items: center;
    gap: .625rem;
    flex-wrap: wrap;
    margin-top: 1.75rem;
}

/* The Connect link is a bare <a>, not in an actions row — same gap. */
.gvl-billing__section > .btn { margin-top: 1.75rem; }

/* Status text sits between what a section says and what it offers, so every
   section reads the same way down the page: the instruction, then the state,
   then the button. The slot is empty most of the time, so its spacing belongs
   to the message inside it — a margin on the slot itself would hold a gap open
   with nothing in it. */
.gvl-billing__status .gvl-form__message { margin: 1.25rem 0 0; }

/* The destructive one sits apart from the one somebody actually came for. */
.gvl-billing__actions .btn-outline-danger { margin-left: auto; }

/* The promotional video, at the head of the description card. Room beneath it
   so the prose does not look like a caption on the player. */
.gvl-coursepage__promo {
    margin-bottom: 1.25rem;
    border-radius: var(--gvl-radius, .5rem);
    overflow: hidden;
    background-color: #000;
}

.gvl-coursepage__promo iframe,
.gvl-coursepage__promo video {
    width: 100%;
    height: 100%;
    border: 0;
}

/* ── The course's picture ────────────────────────────────────────────────────
   An icon or an image, never both, so the control shows one set of choices at a
   time rather than two that have to be mentally ranked. */
.gvl-art__preview {
    position: relative;
    width: 100%;
    max-width: 260px;
    margin-bottom: .75rem;
    border: 1px solid var(--gvl-line-strong, #e7e8e9);
    border-radius: var(--gvl-radius, .5rem);
    overflow: hidden;
}

.gvl-art__preview img {
    display: block;
    width: 100%;
    /* The catalogue card crops to this shape, so the preview does too — an
       image that looks right here and wrong there is worse than no preview. */
    aspect-ratio: 16 / 9;
    object-fit: cover;
}

.gvl-art__clear {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 6px;
    background-color: rgba(255, 255, 255, .9);
    border-radius: 50%;
}

.gvl-art__icons {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: .75rem;
}

.gvl-art__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    font-size: 1.125rem;
    color: var(--gvl-muted, #909193);
    background-color: var(--gvl-surface, #fff);
    border: 1px solid var(--gvl-line-strong, #e7e8e9);
    border-radius: var(--gvl-radius, .5rem);
    cursor: pointer;
}

.gvl-art__icon:hover {
    color: var(--gvl-title, #212121);
    border-color: var(--gvl-primary-light, #a4c2f4);
}

/* The chosen one, stated in colour AND in weight of border — a single tint is
   hard to pick out of a row of twelve at a glance. */
.gvl-art__icon.is-on {
    color: var(--gvl-primary, #0b50d8);
    background-color: var(--gvl-primary-wash, #eef3fe);
    border-color: var(--gvl-primary, #0b50d8);
    box-shadow: inset 0 0 0 1px var(--gvl-primary, #0b50d8);
}

.gvl-art__actions {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
    margin-bottom: .375rem;
}

.gvl-art__actions .gvl-form__hint { margin: 0; }
.gvl-art__actions .gvl-form__hint.is-error { color: #c0392b; }

/* ── Pages ───────────────────────────────────────────────────────────────────
   A list that is also the navigation, so it is drawn as an outline rather than
   a table: indentation is the only thing that says "this sits under that". */
.gvl-pages__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
}

.gvl-pages__head .gvl-form__hint { margin: 0; max-width: 34em; }

.gvl-pages__row {
    display: flex;
    align-items: center;
    gap: .625rem;
    padding: .625rem .75rem;
    border: 1px solid var(--gvl-line-strong, #e7e8e9);
    border-radius: var(--gvl-radius, .5rem);
    background-color: var(--gvl-surface, #fff);
    margin-bottom: .375rem;
}

/* One level in. The line is what makes a nested page read as nested while it
   is being dragged, when the indent alone is ambiguous. */
.gvl-pages__row.is-child {
    margin-left: 2rem;
    border-left: 3px solid var(--gvl-primary-light, #a4c2f4);
}

.gvl-pages__children:empty { min-height: 0; }

/* A landing strip under every parent, so a page can be dropped INTO one that
   has no children yet. Without it there is nothing to aim at. */
.gvl-pages__children {
    min-height: .75rem;
    margin-left: 2rem;
}

.gvl-pages__grip {
    color: var(--gvl-muted, #909193);
    cursor: grab;
}

/* Indent / outdent. Quiet until the row is hovered, because the list should
   read as a list of pages rather than a row of controls. */
.gvl-pages__nest {
    padding: .25rem .375rem;
    font-size: .8125rem;
    color: var(--gvl-muted, #909193);
    background: none;
    border: 0;
    border-radius: var(--gvl-radius, .5rem);
    cursor: pointer;
    opacity: .5;
}

.gvl-pages__row:hover .gvl-pages__nest { opacity: 1; }

.gvl-pages__nest:hover:not(:disabled) {
    color: var(--gvl-primary, #0b50d8);
    background-color: var(--gvl-primary-wash, #eef3fe);
}

.gvl-pages__nest:disabled {
    opacity: .2;
    cursor: not-allowed;
}

/* The drop strips are nearly invisible at rest and open up while something is
   being dragged — a target you cannot hit is the same as no target. */
.gvl-pages__list.is-dragging .gvl-pages__children {
    min-height: 2.25rem;
    border: 1px dashed var(--gvl-primary-light, #a4c2f4);
    border-radius: var(--gvl-radius, .5rem);
    margin-bottom: .375rem;
}

.gvl-pages__title {
    flex: 1 1 auto;
    padding: 0;
    text-align: left;
    font-size: .9375rem;
    font-weight: 600;
    color: var(--gvl-title, #212121);
    background: none;
    border: 0;
    cursor: pointer;
}

.gvl-pages__title:hover { color: var(--gvl-primary, #0b50d8); }

.gvl-pages__badge {
    padding: .125rem .5rem;
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--gvl-muted, #909193);
    background-color: var(--gvl-surface-sunk, #f9f9fa);
    border-radius: 1rem;
}

.gvl-pages__view {
    font-size: .8125rem;
    color: var(--gvl-muted, #909193);
}

.gvl-pages__remove { color: #c0392b !important; }
.gvl-pages__note.is-error { color: #c0392b; }

/* ── Pills ───────────────────────────────────────────────────────────────────
   One panel at a time. This was a stack with a heading over each group, which
   works for two settings and stops working the moment one group is a list of
   every person on the site. */
.gvl-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
    margin-bottom: 1.75rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--gvl-rule, #e7ecf1);
}

.gvl-tabs__pill {
    padding: .4375rem .9375rem;
    font-size: .875rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--gvl-muted, #5c6b7a);
    background: none;
    border: 1px solid transparent;
    border-radius: 999px;
    cursor: pointer;
}

.gvl-tabs__pill:hover {
    color: var(--gvl-ink, #1d2b36);
    background: var(--gvl-tint, #eef4f8);
}

.gvl-tabs__pill.is-on {
    color: #fff;
    background: var(--gvl-primary, #0b50d8);
    border-color: var(--gvl-primary, #0b50d8);
}

/* ── A setting that is a switch ──────────────────────────────────────────────
   The Features pane of course bundletings: a row per thing the course can turn on,
   with the explanation under the name rather than beside the control. Built as
   a LIST of rows because the next feature is a second row and not a redesign. */

.gvl-toggles {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

.gvl-toggle {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.5rem;
}

/* The name and its explanation stay together and take the width; the switch is
   a fixed object at the end of the row. */
.gvl-toggle__text {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    min-width: 0;
}

.gvl-toggle__text label { margin: 0; cursor: pointer; }
.gvl-toggle__text .gvl-form__hint { margin: 0; }

/* A real checkbox, made invisible but left in place — not display:none, which
   takes it out of the tab order and out of form.elements. It covers the track
   so the whole switch is the control, and the track is painted from :checked. */
.gvl-toggle__switch {
    position: relative;
    flex: 0 0 auto;
    width: 44px;
    height: 24px;
    margin-top: .125rem;
}

.gvl-toggle__switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    z-index: 1;
}

.gvl-toggle__track {
    position: absolute;
    inset: 0;
    border-radius: 999px;
    background: var(--gvl-rule, #e7ecf1);
    transition: background .15s ease;
    pointer-events: none;
}

/* The knob. */
.gvl-toggle__track::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
    transition: transform .15s ease;
}

.gvl-toggle__switch input:checked + .gvl-toggle__track {
    background: var(--gvl-primary, #0b50d8);
}

.gvl-toggle__switch input:checked + .gvl-toggle__track::after {
    transform: translateX(20px);
}

/* Keyboard focus has to be visible on the track, because the input it belongs
   to is transparent. */
.gvl-toggle__switch input:focus-visible + .gvl-toggle__track {
    outline: 2px solid var(--gvl-primary, #0b50d8);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .gvl-toggle__track,
    .gvl-toggle__track::after { transition: none; }
}

/* A label carrying more weight than the rows around it — the one setting in a
   group that decides something the others only qualify. */
.gvl-form__label--strong {
    font-weight: 700;
}

/* ── The people on this site ─────────────────────────────────────────────────
   A list that is also an editor. Each row stays a row until somebody asks to
   change it, so twenty people read as twenty lines rather than twenty forms. */
/* The search and the Add button share a row: they are the two things somebody
   opens this panel to do, and Add buried under twenty rows means scrolling past
   everybody to reach it. */
.gvl-users__search {
    display: flex;
    align-items: center;
    gap: .625rem;
    margin-bottom: 1rem;
}

.gvl-users__search input { flex: 1 1 auto; }
.gvl-users__add { flex: 0 0 auto; white-space: nowrap; }

/* Closed until wanted, and set apart from the list below it so a half-typed
   new person is not mistaken for a row. */
.gvl-users__addform {
    margin-bottom: 1.25rem;
    padding: 1.25rem;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #eef4f8);
}

.gvl-users__email-field { margin-bottom: 1rem; }

.gvl-users__list { min-height: 4rem; }

.gvl-users__row { border-bottom: 1px solid var(--gvl-rule, #e7ecf1); }

.gvl-users__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: .75rem 0;
}

.gvl-users__who {
    display: grid;
    gap: .125rem;
    min-width: 0;
}

.gvl-users__name { font-weight: 600; }

.gvl-users__email {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
    word-break: break-all;
}

.gvl-users__meta {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex: 0 0 auto;
    flex-wrap: wrap;
}

.gvl-users__role {
    padding: .125rem .5rem;
    font-size: .75rem;
    font-weight: 600;
    line-height: 1.6;
    color: var(--gvl-muted, #5c6b7a);
    background: var(--gvl-tint, #eef4f8);
    border-radius: 999px;
    white-space: nowrap;
}

/* A capability this plugin granted, as distinct from a WordPress role. */
.gvl-users__role--granted {
    color: #7a5a00;
    background: var(--gvl-warning-tint, #fff8e1);
    border: 1px solid var(--gvl-warning, #e6a700);
}

.gvl-users__courses {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.gvl-users__edit {
    padding: 0;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--gvl-primary, #0b50d8);
    background: none;
    border: 0;
    cursor: pointer;
    text-decoration: underline;
}

/* Closed until asked for. */
.gvl-users__editor { display: none; }
.gvl-users__row.is-open .gvl-users__editor {
    display: block;
    padding: 0 0 1.25rem;
}

.gvl-users__names {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .75rem;
    margin-bottom: 1rem;
}

.gvl-users__caps {
    display: grid;
    gap: .625rem;
    margin-bottom: 1.125rem;
}

/* Checkbox, name, and what it lets them do — the third part is why somebody can
   tick the right box without having to ask. */
.gvl-users__cap {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: .25rem .625rem;
    margin: 0;
    cursor: pointer;
}

.gvl-users__caplabel { font-weight: 600; font-size: .875rem; }

.gvl-users__caphint {
    grid-column: 2;
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-users__actions {
    display: flex;
    align-items: center;
    gap: .625rem;
}

/* Pushed away from Save, so the destructive one is not fumbled for the other. */
.gvl-users__remove { margin-left: auto; }

.gvl-users__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-top: 1.25rem;
}

.gvl-users__count {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-users__pager {
    display: flex;
    align-items: center;
    gap: .625rem;
}

.gvl-users__page {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 575px) {
    .gvl-users__names { grid-template-columns: 1fr; }
}

.gvl-settings__section + .gvl-settings__section {
    margin-top: 2rem;
    padding-top: 1.75rem;
    border-top: 1px solid var(--gvl-rule, #e7ecf1);
}

.gvl-settings__heading {
    margin: 0 0 1.25rem;
    font-weight: 600;
    font-size: .9375rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--gvl-muted, #5c6b7a);
}

.gvl-settings__media {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* Sized to what a logo actually is — wide and short — so the preview shows the
   proportions it will have in the bar rather than a square crop of it. */
.gvl-settings__thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 180px;
    height: 56px;
    flex: 0 0 auto;
    padding: .375rem .625rem;
    border: 1px dashed var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #eef4f8);
    overflow: hidden;
}

.gvl-settings__thumb img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.gvl-settings__empty {
    font-size: .8125rem;
    color: var(--gvl-muted, #5c6b7a);
}

/* The swatch IS the control, so it is sized to be clicked rather than tucked
   beside a text field. The native picker takes a hex value for anyone pasting a
   brand color, which is why there is no second input next to it. */
.gvl-settings__color {
    display: flex;
    align-items: center;
    gap: .875rem;
}

.gvl-settings__swatch {
    width: 64px;
    height: 40px;
    padding: 0;
    flex: 0 0 auto;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: none;
    cursor: pointer;
}

/* Browsers wrap the actual colour in their own chrome; these strip it so the
   swatch fills the box it was given. */
.gvl-settings__swatch::-webkit-color-swatch-wrapper { padding: 3px; }
.gvl-settings__swatch::-webkit-color-swatch { border: 0; border-radius: 3px; }
.gvl-settings__swatch::-moz-color-swatch { border: 0; border-radius: 3px; }

/* Nothing chosen: the swatch shows the theme's colour, and says so by being
   quieter than a deliberate choice. */
.gvl-settings__color.is-default .gvl-settings__swatch { opacity: .55; }

.gvl-settings__mediabuttons {
    display: flex;
    align-items: center;
    gap: .5rem;
}

.gvl-settings__remove { color: var(--gvl-danger, #b3261e); }

/* ── Authoring controls ──────────────────────────────────────────────────────
   Quiet until wanted: the outline is read far more often than it is edited, so
   the tools sit at low contrast and come up on hover or keyboard focus. They
   stay visible on touch, where there is no hover to reveal them. */
.gvl-authortools {
    display: inline-flex;
    align-items: center;
    gap: .15rem;
    margin-left: .35rem;
    opacity: .35;
    transition: opacity .15s ease;
}

li:hover > .gvl-authortools,
.gvl-accordion__heading:hover .gvl-authortools,
.gvl-authortools:focus-within { opacity: 1; }

@media (hover: none) {
    .gvl-authortools { opacity: .8; }
}

.gvl-authortools__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: var(--gvl-muted, #6c757d);
    font-size: .8125rem;
    cursor: pointer;
}

.gvl-authortools__btn:hover { background: var(--gvl-tint, #eef4f8); color: var(--gvl-heading, #1f2d3d); }
.gvl-authortools__btn--danger:hover { background: var(--gvl-danger-tint, #fdecea); color: var(--gvl-danger, #a4292c); }
.gvl-authortools__btn[data-gvl-handle],
.gvl-authortools__btn[data-gvl-module-handle] { cursor: grab; }
.gvl-authortools__btn:active[data-gvl-handle] { cursor: grabbing; }

/* A lesson row and its tools share one line. */
.gvl-lessonlist li { display: flex; align-items: center; gap: .25rem; }
.gvl-lessonlist li > .gvl-lessonrow { flex: 1 1 auto; min-width: 0; }

/* A module with no lessons still needs somewhere to drop one. */
.gvl-lessonlist--empty {
    min-height: 2.5rem;
    margin: 0;
    border: 1px dashed var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
}

.gvl-accordion__heading { display: flex; align-items: center; }
.gvl-accordion__heading > .gvl-accordion__btn,
.gvl-accordion__heading > button:first-child { flex: 1 1 auto; min-width: 0; }
.gvl-authortools--module { margin-right: .5rem; }

/* Dragging */
.gvl-sorting__ghost { opacity: .4; }
.gvl-sorting__ghost .gvl-lessonrow { border-style: dashed; }

.gvl-order-status {
    margin: 0 0 .75rem;
    padding: .4rem .7rem;
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #eef4f8);
    color: var(--gvl-muted, #6c757d);
    font-size: .8125rem;
}

/* Player tools, beside the close button. */
.gvl-player__tool {
    border: 0;
    background: transparent;
    color: inherit;
    opacity: .6;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 4px;
    cursor: pointer;
}

.gvl-player__tool:hover { opacity: 1; background: rgba(127, 127, 127, .14); }
.gvl-player__tool--danger:hover { color: var(--gvl-danger, #a4292c); }

/* ── The two video blocks, as the player renders them ────────────────────── */
.gvl-block-video { margin: 0 0 1.5rem; }
.gvl-block-video__frame { width: 100%; }
.gvl-block-video__frame iframe,
.gvl-block-video__frame video { width: 100%; height: 100%; border: 0; }

.gvl-block-video figcaption {
    margin-top: .5rem;
    font-size: .875rem;
    color: var(--gvl-muted, #6c757d);
}

.gvl-block-videotext {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 1.75rem;
    align-items: start;
    margin: 0 0 1.75rem;
}

/* Which side the video sits on is an authored choice, so it is a class on the
   block rather than something inferred from the order of the markup. */
.gvl-block-videotext--right .gvl-block-videotext__media { order: 2; }

@media (max-width: 767.98px) {
    .gvl-block-videotext { grid-template-columns: 1fr; gap: 1rem; }
    .gvl-block-videotext--right .gvl-block-videotext__media { order: 0; }
}

.gvl-block-videotext__text > :first-child { margin-top: 0; }
.gvl-block-videotext__text > :last-child { margin-bottom: 0; }

/* The media stays put while its notes scroll.
 *
 * Only the text column ever has more than a screenful in it, so scrolling to
 * read the notes dragged the video off the top of the stage — the thing being
 * described left the screen exactly when somebody started reading about it.
 * 189 of the 287 migrated lessons are this layout, so it is most of the course.
 *
 * `position: sticky` rather than giving the text column its own scrollbar:
 *
 *   - it releases at the end of its own block, so a lesson holding two of these
 *     does not pin the first video over the second one's notes;
 *   - nothing nests a scroll region inside a scroll region, which is what makes
 *     a wheel or a trackpad swipe do different things depending on where the
 *     pointer happens to be, and what strands the rest of the lesson when the
 *     inner pane swallows the gesture;
 *   - it needs no height to be measured or guessed, so it is right at every
 *     window size and stays right when the player is resized.
 *
 * The scrolling ancestor is .gvl-player__stage. `align-items: start` on the
 * grid above is load-bearing: a stretched grid item fills the row and has
 * nowhere to move to, which makes sticky silently do nothing.
 */
@media (min-width: 768px) {

    .gvl-player__stage .gvl-block-videotext__media {
        position: sticky;
        top: 0;
    }

    /* A picture can be any shape, and one taller than the stage would stick
       with its own bottom permanently out of reach — scrolling could never
       bring it back. Capped to what the stage can show, allowing for the
       player's head, foot and padding. Video needs no such cap: the frame is
       16:9 of a column narrower than the stage is tall. */
    .gvl-player__stage .gvl-block-videotext__media .gvl-block-image {
        max-height: calc(100vh - 260px);
        width: auto;
        max-width: 100%;
        /* block, or margin-inline:auto does nothing to an inline image and a
           narrow portrait picture hugs the left edge of a wide column. */
        display: block;
        margin-inline: auto;
    }
}

/* Below 768px the grid above is a single column, so there is no "other side"
   to keep still — a sticky video there would sit on top of the words. The rule
   is inside the min-width query rather than undone in a max-width one, so
   there is one place that decides it. */

/* ── The blocks as the EDITOR shows them ─────────────────────────────────────
   A labelled still, not a live embed: an iframe in the canvas swallows the
   clicks meant for the block, so selecting or dragging it becomes a hunt for a
   few safe pixels of border. */
.gvl-blockedit__preview {
    display: flex;
    flex-direction: column;
    gap: .3rem;
    padding: 1.1rem 1.25rem;
    border: 1px dashed var(--gvl-rule, #cfd8e3);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #eef4f8);
}

.gvl-blockedit__badge {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--gvl-accent, #3399cc);
}

.gvl-blockedit__url {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .8125rem;
    color: var(--gvl-muted, #6c757d);
    overflow-wrap: anywhere;
}

.gvl-blockedit__caption { font-size: .875rem; color: var(--gvl-muted, #6c757d); }

.gvl-blockedit--split {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
}

.gvl-blockedit--split.gvl-blockedit--right .gvl-blockedit__preview,
.gvl-blockedit--split.gvl-blockedit--right .gvl-blockedit__media { order: 2; }

@media (max-width: 767.98px) {
    .gvl-blockedit--split { grid-template-columns: 1fr; }
    .gvl-blockedit--split.gvl-blockedit--right .gvl-blockedit__preview,
    .gvl-blockedit--split.gvl-blockedit--right .gvl-blockedit__media { order: 0; }
}

/* ── The four lesson layouts, in the canvas ──────────────────────────────────
   The point of these is that the author edits what the reader sees, so the two
   columns here line up with the two the player draws. The media half is still a
   labelled still rather than a live embed, for the same reason as above. */
.gvl-blockedit__still {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    padding: 1.1rem 1.25rem;
    border: 1px dashed var(--gvl-rule, #cfd8e3);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #eef4f8);
    min-height: 8rem;
    justify-content: center;
}

.gvl-blockedit__label {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--gvl-accent, #3399cc);
}

.gvl-blockedit__image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--gvl-radius, 6px);
}

/* The media half is edited in place, because this editor has no inspector
   sidebar to put the fields in — see the note in gvl-blocks.js. */
.gvl-blockedit__field {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    width: 100%;
}

.gvl-blockedit__field + .gvl-blockedit__field { margin-top: .6rem; }

.gvl-blockedit__input {
    width: 100%;
    padding: .45rem .6rem;
    border: 1px solid var(--gvl-rule, #cfd8e3);
    border-radius: 4px;
    background: var(--gvl-card, #fff);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .8125rem;
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-blockedit__input:focus {
    outline: 2px solid var(--gvl-accent, #3399cc);
    outline-offset: -1px;
}

.gvl-blockedit__still--image { gap: .6rem; }
.gvl-blockedit__choose { align-self: flex-start; }

/* The real embed in the canvas. The frame holds the 16:9; the pane around it
   stops pretending to be one, so the URL field sits under the video rather than
   floating in the middle of it. */
.gvl-blockedit__frame {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: 4px;
    overflow: hidden;
    background: #000;
}

.gvl-blockedit__frame iframe,
.gvl-blockedit__frame video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

/* Takes the click that selects the block, then disappears once it is selected
   so the video can actually be played. */
.gvl-blockedit__shield {
    position: absolute;
    inset: 0;
    cursor: pointer;
}

.gvl-blockedit__still--playing {
    background: transparent;
    border-style: solid;
    padding: .6rem;
    gap: .6rem;
}

.gvl-blockedit__media { min-width: 0; }

/* The content column. It holds real blocks, so it needs the list and heading
   rules the canvas otherwise strips — without them an author writes a list and
   sees unindented, unmarked lines, which is exactly the "what I see is not what
   I get" this work set out to remove. */
.gvl-blockedit__body { min-width: 0; }

.gvl-blockedit__body ul,
.gvl-blockedit__body ol {
    margin: 0 0 1rem;
    padding-left: 1.4rem;
}

.gvl-blockedit__body ul { list-style: disc; }
.gvl-blockedit__body ol { list-style: decimal; }
.gvl-blockedit__body li { margin-bottom: .25rem; }

.gvl-blockedit__body > :first-child { margin-top: 0; }
.gvl-blockedit__body > :last-child { margin-bottom: 0; }

/* A layout with nothing in its content side yet should still read as a column
   waiting for text, not as empty space. */
.gvl-blockedit__body .block-editor-inner-blocks { min-height: 3rem; }

/* The text-only layout is one column, so it should fill the width. */
.gvl-blockedit--text .gvl-blockedit__body { max-width: none; }

/* ── While a lesson is being dragged ─────────────────────────────────────────
   Collapsed modules read as drop targets rather than as furniture, so it is
   obvious there is somewhere to aim. The panel itself opens after a moment's
   hover — see springWatch() in gvl-authoring.js. */
.gvl-is-sorting .gvl-accordion__item .collapse:not(.show) + .gvl-accordion__heading,
.gvl-is-sorting .gvl-accordion__heading {
    outline: 1px dashed transparent;
    outline-offset: -3px;
    transition: outline-color .15s ease, background-color .15s ease;
}

.gvl-is-sorting .gvl-accordion__heading:hover {
    outline-color: var(--gvl-accent, #3399cc);
    background: var(--gvl-tint, #eef4f8);
}

/* The grab cursor should follow the pointer everywhere during a drag, not only
   over the handle it started on. */
.gvl-is-sorting, .gvl-is-sorting * { cursor: grabbing !important; }

/* The module summary lines up under the module TITLE, not under the number
   badge beside it.

   The heading lays out as: 22px button padding | 30px badge | 16px gap | title.
   The body starts at the same 22px, so the summary needs the badge and the gap
   back — written as that sum rather than as 46px, so it still adds up if either
   value changes. */
.gvl-accordion__desc {
    margin-left: calc(30px + 16px);
}

@media (max-width: 575.98px) {
    /* At this width the indent costs more in line length than it earns in
       alignment. */
    .gvl-accordion__desc { margin-left: 0; }
}

/* ── Course status: Active / Hidden / Inactive ───────────────────────────── */
.gvl-status {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}

.gvl-status__choice {
    padding: .5rem 1rem;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-card, #fff);
    font-size: .9375rem;
    font-weight: 600;
    color: var(--gvl-muted, #5c6b7a);
    cursor: pointer;
}

.gvl-status__choice:hover { border-color: var(--gvl-accent, #3399cc); }

.gvl-status__choice.is-on {
    border-color: var(--gvl-accent, #3399cc);
    background: var(--gvl-accent, #3399cc);
    color: #fff;
}

/* The hint changes with the choice, so it needs room for the longest of them
   rather than reflowing the dialog each time one is picked. */
.gvl-form__row [data-gvl-status-hint] { display: block; min-height: 2.5em; }

/* ── The lesson editor, shaped like the player ───────────────────────────────
   The shared editor's canvas is a 720px article sheet — right for a slot
   description or a newsletter, wrong for a lesson, which the reader meets as a
   wide landscape panel with media beside text. Editing a lesson in a narrow
   column means the thing being edited never looks like the thing being made.

   Scoped to gvl-lms's own overlay root, NOT changed in sp-integrations: the
   720px sheet is what spa-2026 and spc-2026 are built around, and this editor
   is shared with them.

   1100px and 1.15fr/1fr are the player's own numbers, so the split an author
   arranges here lands in the same proportion the learner sees. */
#gvl-lesson-editor-root .sp-block-editor-canvas {
    max-width: 1100px;
    padding: 2rem 2.25rem 2.5rem;
}

/* The layouts drive their own two columns, so the canvas should not also be
   imposing an article measure on what sits inside them. */
#gvl-lesson-editor-root .gvl-blockedit--split {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 1.5rem;
}

/* A landscape media pane rather than a letterbox of text: 16:9 is what every
   embed in the player resolves to, so the author sees the shape they will get. */
#gvl-lesson-editor-root .gvl-blockedit__still {
    aspect-ratio: 16 / 9;
    min-height: 0;
    justify-content: center;
}

/* Once a video is actually embedded the frame inside owns the 16:9, so the pane
   has to size to its contents or the field is pushed out of the box. */
#gvl-lesson-editor-root .gvl-blockedit__still--playing {
    aspect-ratio: auto;
    justify-content: flex-start;
}

/* An image fills that frame the way the player's does. */
#gvl-lesson-editor-root .gvl-blockedit__still--image {
    aspect-ratio: auto;
    min-height: 10rem;
}

/* Lessons are short. Without this the writing area collapses to the height of
   whatever is in it and the panel looks half-built. */
#gvl-lesson-editor-root .gvl-blockedit__body { min-height: 12rem; }

@media (max-width: 991.98px) {
    #gvl-lesson-editor-root .gvl-blockedit--split { grid-template-columns: 1fr; }
    #gvl-lesson-editor-root .gvl-blockedit__still { aspect-ratio: 16 / 9; }
}

/* ── A question, as the reader meets it ──────────────────────────────────── */
.gvl-question {
    margin: 1.25rem 0;
    padding: 1.1rem 1.25rem;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #f7fafc);
}

.gvl-question__prompt {
    margin: 0 0 .85rem;
    font-weight: 600;
    color: var(--gvl-heading, #1f2d3d);
}

/* `.gvl-stage__text ul` (0,2,0) sets list-style: disc on prose lists in the
   player, and a bare `.gvl-question__options` (0,1,0) loses to it — the options
   rendered as a bulleted list. Scoped here so it wins wherever the question is
   drawn, rather than only inside the stage. */
.gvl-question__options,
.gvl-stage__text .gvl-question__options {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

/* Bootstrap owns how these look — colour, border, radius, hover, focus and the
   disabled state all come from .btn and its variant. This only does the two
   things Bootstrap has no opinion about: an answer runs the width of the
   question, and its text reads from the left like a sentence rather than being
   centred like a label. */
.gvl-question__option {
    display: block;
    width: 100%;
    text-align: left;
    white-space: normal;
}

/* An answered question keeps its verdict visible rather than greying out — the
   learner should still be able to read what they chose. */
.gvl-question__option:disabled { opacity: 1; }
.gvl-question__option.btn-success:disabled,
.gvl-question__option.btn-danger:disabled { color: #fff; }
.gvl-question__option.is-chosen { font-weight: 600; }

.gvl-question__result { margin: .85rem 0 0; font-size: .9375rem; }
.gvl-question__result.is-correct .gvl-question__verdict { color: #198754; font-weight: 700; }
.gvl-question__result.is-wrong   .gvl-question__verdict { color: #dc3545; font-weight: 700; }
.gvl-question__result.is-error   { color: #dc3545; }
.gvl-question__feedback { color: var(--gvl-muted, #5c6b7a); }

/* ── And as the author writes it ─────────────────────────────────────────── */
.gvl-qedit {
    padding: 1rem;
    border: 1px dashed var(--gvl-rule, #cfd8e3);
    border-radius: var(--gvl-radius, 6px);
    background: var(--gvl-tint, #f7fafc);
}

.gvl-qedit__field { display: block; margin-bottom: .85rem; }

.gvl-qedit__label {
    display: block;
    margin-bottom: .25rem;
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--gvl-accent, #3399cc);
}

.gvl-qedit__input {
    width: 100%;
    padding: .45rem .6rem;
    border: 1px solid var(--gvl-rule, #cfd8e3);
    border-radius: 4px;
    background: var(--gvl-card, #fff);
    font-size: .9375rem;
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-qedit__input:focus { outline: 2px solid var(--gvl-accent, #3399cc); outline-offset: -1px; }

.gvl-qedit__options { list-style: none; margin: 0 0 .85rem; padding: 0; }

.gvl-qedit__option {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "pick text remove" "pick feedback remove";
    gap: .35rem .6rem;
    align-items: center;
    padding: .6rem;
    margin-bottom: .5rem;
    border: 1px solid var(--gvl-rule, #e7ecf1);
    border-radius: 4px;
    background: var(--gvl-card, #fff);
}

/* The correct one reads as correct at a glance, so an author scanning a long
   question does not have to find the selected radio. */
.gvl-qedit__option.is-correct { border-color: #198754; background: #f2faf5; }

.gvl-qedit__pick {
    grid-area: pick;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .2rem;
    cursor: pointer;
}

.gvl-qedit__picklabel {
    font-size: .625rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--gvl-muted, #6c757d);
    text-align: center;
    max-width: 5.5rem;
}

.gvl-qedit__option.is-correct .gvl-qedit__picklabel { color: #198754; font-weight: 700; }

/* Both selectors have to be able to beat each other's specificity, or the two
   inputs land in the same grid cell and the second paints over the first —
   which is exactly what happened: the option fields showed the FEEDBACK text
   and the options themselves were underneath it, invisible.
   `.gvl-qedit__option > .gvl-qedit__input` is 0,2,0 and plain
   `.gvl-qedit__input--feedback` was 0,1,0, so the generic rule won. */
.gvl-qedit__option > .gvl-qedit__input { grid-area: text; }
.gvl-qedit__option > .gvl-qedit__input--feedback { grid-area: feedback; font-size: .8125rem; }
.gvl-qedit__remove { grid-area: remove; }

.gvl-qedit__foot { display: flex; align-items: flex-end; gap: 1rem; }
.gvl-qedit__points { display: block; }
.gvl-qedit__input--points { width: 5rem; }

/* ── Short answer ────────────────────────────────────────────────────────── */

/* The space belongs to the FOOT, not the textarea: putting it on both is how a
   gap ends up doubled when the result message wraps onto its own line. */
.gvl-question__write { margin-bottom: 0; }

/* Outlined at rest, solid on hover.
   The theme restyles Bootstrap's outline variant to its own green but gives it
   no hover fill, so that part is ours. From var(--gvl-primary) rather than a
   literal: the plugin's default is blue and each skin overrides it — a hardcoded
   green here would be the one button that ignored the skin. */
.gvl-question__submit:hover:not(:disabled),
.gvl-question__submit:focus-visible:not(:disabled) {
    background-color: var(--gvl-primary);
    border-color: var(--gvl-primary);
    color: #fff;
}

.gvl-question__foot {
    display: flex;
    align-items: center;
    gap: .85rem;
    flex-wrap: wrap;
    margin-top: 1.25rem;
}

/* Their own words, read back. Set apart so it is obviously what WAS written
   rather than somewhere still to write. */
.gvl-question__answer {
    margin: 0 0 .75rem;
    padding: .75rem .9rem;
    border-left: 3px solid var(--gvl-rule, #cfd8e3);
    background: var(--gvl-card, #fff);
    color: var(--gvl-heading, #1f2d3d);
}

.gvl-question__answer > :first-child { margin-top: 0; }
.gvl-question__answer > :last-child { margin-bottom: 0; }

.gvl-question__result.is-pending .gvl-question__verdict { color: var(--gvl-muted, #5c6b7a); font-weight: 700; }
.gvl-question__result.is-graded .gvl-question__verdict { color: #198754; font-weight: 700; }

/* The author's dead preview of the answer box: visibly not for typing in. */
.gvl-qedit__input--dead {
    background: var(--gvl-tint, #f1f5f8);
    color: var(--gvl-muted, #8a97a4);
    cursor: not-allowed;
}

.gvl-qedit__note {
    margin: 0;
    font-size: .8125rem;
    color: var(--gvl-muted, #6c757d);
}

.gvl-qedit--short .gvl-qedit__foot { align-items: flex-end; }

/* "No image", first in the icon group and the default.
   Set apart by a rule rather than by colour: it is not one of the icons, it is
   the absence of them, and a reader scanning the row should be able to see
   that before reading the tooltip. */
.gvl-art__icon--none {
    margin-right: 10px;
    border-style: dashed;
}

.gvl-art__icon--none i { opacity: .55; }
.gvl-art__icon--none.is-on i { opacity: 1; }

/* ── Course Bundles, in Site Admin ──────────────────────────────────────────────
   Borrows the Pages panel's row furniture rather than restating it: the two are
   the same kind of thing — a short list that owns its own saving — and a second
   set of nearly-identical rules is a second place for them to drift apart. */
.gvl-bundles__list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }

.gvl-bundles__row {
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface);
}

.gvl-bundles__bar { display: flex; align-items: center; gap: 10px; padding: 10px 12px; }

.gvl-bundles__count {
    margin-left: auto;
    font-size: 12px;
    color: var(--gvl-muted);
    white-space: nowrap;
}

.gvl-bundles__editor {
    border-top: 1px solid var(--gvl-line);
    padding: 14px 12px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.gvl-bundles__editor[hidden] { display: none; }

.gvl-bundles__courses { display: flex; flex-direction: column; gap: 6px; }

.gvl-bundles__course {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 10px;
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface-sunk);
}

/* Tabular so the numbers line up as the list grows past nine. */
.gvl-bundles__ordinal {
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--gvl-muted);
    min-width: 1.5em;
}

.gvl-bundles__coursename { flex: 1 1 auto; min-width: 0; }

/* ── A dropdown you can search ───────────────────────────────────────────────
   A button that opens a panel holding a search box above a scrolling list. The
   panel is absolutely positioned so opening it does not push the rest of the
   editor down — a list that shoves the thing you were reading off the screen is
   worse than no list. */
.gvl-picker { position: relative; }

.gvl-picker__button { width: 100%; text-align: left; }

.gvl-picker__panel {
    position: absolute;
    z-index: 5;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background-color: var(--gvl-surface);
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    box-shadow: var(--gvl-shadow-modal, 0 10px 30px rgba(15, 20, 32, .18));
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.gvl-picker__panel[hidden] { display: none; }

/* Bounded, and scrolls inside itself: a catalogue of ninety courses must not
   make a dropdown taller than the modal containing it. */
.gvl-picker__list {
    max-height: 15rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.gvl-picker__item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    border-radius: var(--gvl-radius);
    padding: 7px 9px;
    cursor: pointer;
    font: inherit;
    color: inherit;
}

.gvl-picker__item:hover,
.gvl-picker__item:focus-visible { background-color: var(--gvl-surface-sunk); }

.gvl-picker__itemtitle { flex: 1 1 auto; min-width: 0; }

.gvl-picker__itemnote {
    flex: 0 0 auto;
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--gvl-muted);
}

.gvl-picker__empty { margin: 4px 2px; }

/* A disabled arrow stays in place rather than disappearing: a row whose buttons
   move depending on where it sits is a row you have to look at twice. */
.gvl-bundles__course .gvl-pages__action[disabled] { opacity: .3; cursor: default; }

/* A sentence above the sign-up form, shown only to somebody arriving on a seat
   link — the one case where the form needs to explain itself. */
.gvl-authpane__lede {
    margin: 0 0 1rem;
    font-size: .9rem;
    line-height: 1.55;
    color: var(--gvl-muted, #6c757d);
}

/* ── Invitations, in Site Admin ──────────────────────────────────────────────
   Same furniture as the Course Bundles panel beside it: a short list of rows that
   open. Its own class hooks alongside the shared ones, because two panels
   answering one selector is how a click meant for one lands on the other. */
.gvl-seats__list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }

.gvl-seats__new {
    position: relative;
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface-sunk);
    padding: 14px;
    margin-top: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.gvl-seats__new[hidden] { display: none; }

/* Out of the flow, so dismissing the form costs it no vertical space. It sits
 * beside the first field's label, which is short in every language this is in
 * so far; should one ever reach it, the label wraps rather than runs under. */
/* Marked out from the fields around it: it is not another thing to fill in,
 * it is something the form has noticed. */
.gvl-seats__openwarn {
    border-left: 3px solid var(--gvl-warning, #b7791f);
    padding-left: 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.gvl-seats__openwarn[hidden] { display: none; }

.gvl-seats__cancel {
    position: absolute;
    top: 10px;
    right: 10px;
}

/* The LABEL yields to it, not the field. Giving the whole first block the
 * padding shortened its input by 24px, so one control in a stack of five
 * stopped short of the others — the sort of misalignment that is read as a
 * rendering fault rather than as room made for a button. */
.gvl-seats__new > :first-child + * > .gvl-form__label { padding-right: 24px; }

.gvl-seats__row {
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface);
}

.gvl-seats__bar { display: flex; align-items: center; gap: 10px; padding: 10px 12px; }

/* Tabular, because "3 of 5 left" is the number somebody scans down the column
   for and ragged digits make that harder than it needs to be. */
.gvl-seats__count {
    margin-left: auto;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--gvl-muted);
    white-space: nowrap;
}

.gvl-seats__detail {
    border-top: 1px solid var(--gvl-line);
    padding: 14px 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.gvl-seats__detail[hidden] { display: none; }

.gvl-seats__link { display: flex; gap: 8px; align-items: center; }
.gvl-seats__link input { flex: 1 1 auto; min-width: 0; font-size: 13px; }

.gvl-seats__people { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }

.gvl-seats__people li {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 10px;
    align-items: baseline;
    padding: 6px 10px;
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface-sunk);
}

.gvl-seats__person { font-size: 13px; }
.gvl-seats__when { font-size: 12px; color: var(--gvl-muted); margin-left: auto; }

/* A withdrawn or finished invitation is not an error — it is an invitation
   that has done its job, or been stopped on purpose. */
.gvl-seats__row .gvl-pages__badge { flex: 0 0 auto; }

/* A hint that is a warning rather than an explanation. Colour and weight only —
   it sits where the ordinary hint sits, so moving it would make the two look
   like different kinds of thing when they are both "what this choice means". */
.gvl-form__hint.is-warning {
    color: var(--gvl-danger, #a4292c);
    font-weight: 500;
}


/* ── The page somebody buys places on ───────────────────────────────────────
 *
 * Reached from an email by a person who has never seen this site, so it is one
 * column and nothing else: what they are buying, what it costs, and the card.
 */
.gvl-buy {
    max-width: 34rem;
    margin: 0 auto;
    padding: 48px 20px 64px;
}

.gvl-buy__title {
    margin: 0 0 12px;
    font-size: 1.5rem;
    line-height: 1.25;
    text-wrap: balance;
}

.gvl-buy__lede {
    margin: 0 0 12px;
    color: var(--gvl-ink-soft, #555);
}

.gvl-buy__pay { margin-top: 28px; }

.gvl-buy__label {
    margin: 28px 0 6px;
    font-weight: 600;
}

/* The link is read, copied and forwarded, so it wraps rather than running off
 * the side of a phone — and breaks anywhere, because a token has no spaces in
 * it to break at. */
.gvl-buy__link {
    margin: 0 0 16px;
    padding: 12px 14px;
    border: 1px solid var(--gvl-line, #ddd);
    border-radius: var(--gvl-radius, 6px);
    background-color: var(--gvl-surface-sunk, #f7f7f7);
    overflow-wrap: anywhere;
    word-break: break-all;
}


/* ── Importing a file of people ─────────────────────────────────────────── */

.gvl-users__importform {
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface-sunk);
    padding: 14px;
    margin-top: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.gvl-users__importform[hidden] { display: none; }

/* The problems, as a list somebody works down with their spreadsheet open
 * beside them — so it scrolls rather than pushing the button off the screen. */
.gvl-users__importfound:not(:empty) {
    max-height: 14rem;
    overflow-y: auto;
}

.gvl-users__importbad { margin: 0 0 4px; }

/* The number being agreed to. Not a warning colour — it is a fact, not a
 * problem — but not a footnote either. */
.gvl-users__importcost {
    margin: 10px 0 0;
    font-weight: 600;
    color: var(--gvl-title);
}


/* ── A reseller's customers ─────────────────────────────────────────────── */

.gvl-customers__head { margin-bottom: 12px; }

.gvl-customers__new {
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface-sunk);
    padding: 14px;
    margin-bottom: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.gvl-customers__new[hidden] { display: none; }

.gvl-customers__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--gvl-line);
    border-radius: var(--gvl-radius);
    background-color: var(--gvl-surface);
    margin-bottom: 8px;
}

/* The name and what is on the site, stacked, so a row reads as one customer
 * rather than as three columns that happen to line up. */
.gvl-customers__who {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.gvl-customers__name {
    font-weight: 600;
    overflow-wrap: anywhere;
}

.gvl-customers__counts {
    font-size: 0.875rem;
    color: var(--gvl-ink-soft, #666);
    font-variant-numeric: tabular-nums;
}
