﻿/* ---------- 2-column workspace: source/form settings | results to review. Used by both
   AiActivityGenerator and WebContentLab — lives here (global, unscoped) since Blazor CSS
   isolation can't share one scoped rule across two different components. ---------- */
.build-3col-grid {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
}

@media (max-width: 992px) {
    .build-3col-grid {
        grid-template-columns: 1fr;
    }
}

/* ---------- Compose page: the same grid grows a third column for the basket.
   A modifier rather than a change to the base rule, because the Build tab drawer
   and the Web Content lab both use the two-column form and have no basket. ---------- */
/* Widths are deliberately not Quizora's. The parameter form here has paired fields
   (difficulty next to thinking level, type next to option count) that go cramped
   under about 300px, so it gets more room. The basket only ever holds one-line
   previews, so it gives some back. The centre column keeps the difference, because
   it is the one that has to hold code blocks and answer options. */
.build-3col-grid.build-3col-with-basket {
    grid-template-columns: 320px minmax(0, 1fr) 250px;
    gap: 1.25rem;
}

/* Both rails stay put while the suggestions scroll, so the teacher can add a
   question and see the count move without scrolling back up. 5rem clears the
   sticky 68px top bar with a little air. */
.build-3col-grid.build-3col-with-basket > .build-3col-col1,
.build-3col-grid.build-3col-with-basket > .build-3col-col3 {
    position: sticky;
    top: 5rem;
}

/* Writing a question by hand: one wide column and the basket. The generator's two columns
   are hidden rather than removed, so they leave the grid flow and this template lines the
   replacement up against the basket, which keeps the width and position it has everywhere
   else. */
.build-3col-grid.build-3col-with-basket.build-3col-replaced {
    grid-template-columns: minmax(0, 1fr) 250px;
}

/* The parameter rail is a stack of cards, one per section, rather than one tall
   panel. Each section is a thing you either need or do not, so giving each its own
   edge makes a closed one read as a shut drawer instead of missing content. The
   column itself is therefore just the frame: no border of its own.

   It has no height cap and no scrollbar. It does not need one, because only a
   single section is ever open, so the rail cannot grow past a viewport. */

.build-3col-grid.build-3col-with-basket .aigen-section {
    background: var(--surface-card);
    border: 1px solid var(--border-medium);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: var(--space-3);
}


.build-3col-col3 {
    min-width: 0;
}

/* Each column reads as its own surface, so the three roles stay legible: what you
   are asking for, what came back, and what you have kept. Scoped to the basket
   variant, because the Build tab drawer already sits on a panel of its own and a
   second border inside it would just be a box in a box. The border is one step
   darker than a normal panel's, since these are the page's primary division. */
.build-3col-grid.build-3col-with-basket > .build-3col-col2,
.build-3col-grid.build-3col-with-basket .build-3col-col3 > .ex-panel,
.build-3col-grid.build-3col-with-basket .build-3col-col3 > .compose-basket-wrap > .ex-panel {
    background: var(--surface-card);
    border: 1px solid var(--border-medium);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.build-3col-grid.build-3col-with-basket > .build-3col-col2 {
    padding: var(--space-4);
}

/* The centre column is now a bordered panel in its own right, so the empty state's
   dashed box would draw a second outline inside the first. Drop it and let the
   column's own border carry the shape. */
.build-3col-grid.build-3col-with-basket .build-results-empty {
    border: 0;
    min-height: 420px;
}

@media (max-width: 1400px) {
    .build-3col-grid.build-3col-with-basket {
        grid-template-columns: 275px minmax(0, 1fr) 225px;
        gap: 1rem;
    }
}

@media (max-width: 992px) {
    .build-3col-grid.build-3col-with-basket {
        grid-template-columns: 1fr;
    }

    /* Stacked, sticky rails would follow the reader down the whole suggestion list
       and cover it, and neither needs its own scroll once it has the full width. */
    .build-3col-grid.build-3col-with-basket > .build-3col-col1,
    .build-3col-grid.build-3col-with-basket > .build-3col-col3 {
        position: static;
        max-height: none;
        overflow-y: visible;
    }
}

/* ---------- Basket ---------- */
/* Two lines per row. The number and the start of the question share the first, so the text gets
   the whole width; what the question is worth and what can be done with it sit under the text. */
.compose-basket-item {
    display: grid;
    grid-template-columns: 1.5rem minmax(0, 1fr);
    column-gap: .5rem;
    row-gap: .25rem;
    align-items: start;
    padding: .5rem 0;
    border-bottom: 1px solid var(--bs-border-color);
}
.compose-basket-meta {
    grid-column: 2;
    display: flex;
    align-items: center;
    gap: .35rem;
}

.compose-basket-item:last-child {
    border-bottom: 0;
}

.compose-basket-num {
    flex: 0 0 1.5rem;
    height: 1.5rem;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--bs-tertiary-bg);
    font-size: var(--fs-2xs);
    font-weight: 600;
}

.compose-basket-text {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-xs);
    line-height: 1.4;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The whole question, beside the basket, while a row is hovered. inset-inline-end puts it on
   the side facing the rest of the page in both directions: to the right of a left-hand basket in
   Hebrew, to the left of a right-hand one in English. Long questions fade out at the bottom
   rather than scroll, since the card never takes the pointer. */
.compose-basket-wrap { position: relative; }
.compose-peek {
    position: absolute;
    top: 0;
    inset-inline-end: calc(100% + .75rem);
    z-index: 20;
    width: min(34rem, 55vw);
    max-height: min(70vh, 36rem);
    overflow: hidden;
    padding: var(--space-3);
    background: var(--surface-card);
    border: 1px solid var(--border-medium);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    pointer-events: none;
}
.compose-peek::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 2.5rem;
    background: linear-gradient(transparent, var(--surface-card));
}
@media (max-width: 992px) {
    .compose-peek { display: none; }
}

/* A question's time, first on the row's second line. Never wraps or shrinks: a range cut in
   half reads as a different number. */
.compose-basket-time {
    flex: 0 0 auto;
    /* First on the second line; the actions are pushed to its far end. */
    margin-inline-end: auto;
    padding: .05rem .4rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--radius-pill, 999px);
    background: var(--bs-tertiary-bg);
    font-size: var(--fs-2xs);
    font-weight: 600;
    white-space: nowrap;
}

/* -- Quiz settings step --
   Settings on the start side in cards that fold, the quiz on the wide side. The settings
   column stays put while the quiz scrolls, so a points change can be checked against the
   total without scrolling back up. */
.details-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 1.25rem;
    align-items: start;
}
/* The cards as capsule tabs across the top: a step number, or a done or missing mark, and the name. */
.details-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin-bottom: 1rem;
}
.details-tab {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding-block: .3rem;
    padding-inline: .35rem .85rem;
    border: 1px solid var(--border-light);
    border-radius: 999px;
    background: var(--surface-card, #fff);
    color: var(--ink-primary);
    font-size: var(--fs-sm);
    font-weight: 600;
    white-space: nowrap;
}
button.details-tab:hover { border-color: var(--primary-500, var(--bs-primary)); }
.details-tab.is-selected { background: var(--bs-primary); border-color: var(--bs-primary); color: #fff; }
.details-tab.is-soon {
    padding-inline: .75rem;
    border-style: dashed;
    background: transparent;
    color: var(--ink-secondary);
    cursor: default;
}
.details-tab-step {
    display: inline-grid;
    place-items: center;
    width: 1.45rem;
    height: 1.45rem;
    border-radius: 50%;
    background: var(--surface-subtle);
    color: var(--ink-secondary);
    font-size: var(--fs-2xs);
    font-weight: 700;
}
.details-tab-step.is-ok { background: var(--bs-success-bg-subtle); color: var(--bs-success-text-emphasis); }
.details-tab-step.is-missing { background: var(--bs-danger-bg-subtle); color: var(--bs-danger-text-emphasis); }
.details-tab.is-selected .details-tab-step { background: rgba(255, 255, 255, .22); color: #fff; }
.details-tab-step .bi { font-size: .75rem; line-height: 1; }
.details-tab-count {
    padding: 0 .4rem;
    border-radius: 999px;
    background: var(--surface-subtle);
    color: var(--ink-secondary);
    font-size: var(--fs-2xs);
    font-weight: 700;
    line-height: 1.5;
}
.details-tab.is-selected .details-tab-count { background: rgba(255, 255, 255, .22); color: #fff; }
@media (max-width: 768px) {
    /* One row that scrolls sideways on a phone, rather than tabs stacked three deep. */
    .details-tabs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: .25rem; }
}

/* The end side: the basket. Sticky, so saving stays in reach beside a long card. */
.details-basket-col { position: sticky; top: 5rem; }
@media (max-width: 992px) {
    /* minmax(0, ...) and not 1fr: a plain 1fr grows to its longest unbroken line. */
    .details-grid { grid-template-columns: minmax(0, 1fr); }
    .details-basket-col { position: static; }
}
.details-pane,
.details-basket-col { min-width: 0; }

/* The wide side: the chosen card's work. A column whose body takes the free height, so the foot with
   the way on sits at the bottom of the panel however short the card is. */
.details-pane {
    display: flex;
    flex-direction: column;
    min-height: 24rem;
}
.details-pane-body { flex: 1 0 auto; min-width: 0; }
.details-pane-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
}
.details-pane-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1rem;
    margin-bottom: .75rem;
}
.details-pane-toolbar .details-action-row > .btn { flex: 0 0 auto; }
.details-pane-foot {
    display: flex;
    justify-content: flex-end;
    margin-top: 1.25rem;
    padding-top: .9rem;
    border-top: 1px solid var(--border-light);
}
/* Lists and pickers that read badly stretched across a wide screen. */
.details-pane-narrow { max-width: 44rem; }
.details-form-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .9rem 1.5rem;
    max-width: 52rem;
}
.details-form-wide { grid-column: 1 / -1; }
@media (max-width: 768px) {
    .details-form-grid { grid-template-columns: 1fr; }
}
.details-subhead {
    max-width: 52rem;
    margin: 1.25rem 0 .75rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border-light);
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--ink-secondary);
}

/* Scoring: a line per question - its number, the start of its text and time, its points. */
.details-score-list { list-style: none; margin: 0; padding: 0; }
.details-score-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: .4rem .75rem;
    padding: .55rem 0;
    border-bottom: 1px solid var(--border-light);
}
.details-score-row:last-child { border-bottom: 0; }
.details-score-text {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    text-align: start;
}
.details-score-text:hover .details-score-prompt { color: var(--bs-primary); }
/* A line with nothing to open - a PDF paper's question number - reads as plain text. */
.details-score-text:disabled { cursor: default; opacity: 1; color: inherit; }
.details-score-text:disabled:hover .details-score-prompt { color: inherit; }
.details-score-prompt {
    font-size: var(--fs-sm);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.details-score-meta { font-size: var(--fs-2xs); color: var(--ink-secondary); }
.details-score-points { display: flex; align-items: center; gap: .3rem; }
.details-score-full {
    grid-column: 1 / -1;
    padding: .6rem .8rem;
    border-radius: .4rem;
    background: var(--surface-subtle);
}
.details-points { width: 5rem; }
.details-total.is-off { color: var(--bs-danger-text-emphasis); }

/* Minutes are one or two digits. Full-width boxes made the range read as two free-text fields. */
.qfields-minutes { max-width: 6rem; }


/* -- Question sources --
   One row of entry points above the three panels. The disabled ones stay in the row rather
   than being hidden: a teacher hunting for "import my old exam" needs to see that it is
   planned, not conclude it does not exist. */
.compose-manual-preview {
    border-top: 1px solid var(--border-light);
    padding-top: .6rem;
}

.compose-sources {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: 1.75rem;
}
.compose-source {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .45rem .85rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-pill, 999px);
    background: var(--surface-card, #fff);
    color: var(--ink-secondary);
    font-size: var(--fs-sm);
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
}
.compose-source:hover:not(:disabled):not(.is-active) {
    border-color: var(--primary-500, var(--bs-primary));
    color: var(--bs-primary);
}
.compose-source.is-active {
    background: var(--bs-primary);
    border-color: var(--bs-primary);
    color: #fff;
    cursor: default;
}
.compose-source:disabled { opacity: .55; cursor: not-allowed; }
.compose-source-soon {
    padding: .05rem .4rem;
    border-radius: var(--radius-pill, 999px);
    background: var(--surface-subtle);
    color: var(--ink-secondary);
    font-size: var(--fs-2xs);
    font-weight: 600;
}

/* Only the list scrolls, so the count above it and the continue button below it
   stay visible no matter how many questions are in the basket. Sized against the
   viewport so a short screen does not push the button off the bottom. */
.compose-basket-list {
    max-height: clamp(8rem, calc(100vh - 26rem), 26rem);
    overflow-y: auto;
}

.build-3col-col2 {
    min-width: 0;
}

/* ---------- Results empty state — before generation runs, the center column would
   otherwise sit completely blank next to the form column, which reads as broken
   rather than "nothing here yet". Same icon+title+description convention used
   for empty states elsewhere in the app. ---------- */
.build-results-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: .5rem;
    min-height: 320px;
    padding: 2rem 1.5rem;
    border: 1px dashed var(--border-medium);
    border-radius: var(--radius-md);
    color: var(--ink-muted);
}

.build-results-empty i {
    font-size: var(--fs-xl);
    color: var(--primary-500);
}

.build-results-empty strong {
    color: var(--ink-primary);
    font-size: var(--fs-md);
}

.build-results-empty span {
    font-size: var(--fs-sm);
    max-width: 22rem;
    line-height: 1.5;
}

/* ── Blazor validation (keep — Bootstrap doesn't cover these Blazor-specific selectors) ── */
h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--primary-600);
}

.invalid {
    outline: 1px solid var(--bs-danger);
}

.validation-message {
    color: var(--bs-danger);
    font-size: var(--fs-xs);
}

.blazor-error-boundary {
    background: var(--bs-danger);
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

.blazor-error-boundary::after {
    content: "An error has occurred.";
}

#blazor-error-ui {
    background: var(--bs-danger);
    color: white;
    padding: .75rem 1.25rem;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1080;
    display: none;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    float: inline-end;
}

.darker-border-checkbox.form-check-input {
    border-color: var(--border-strong);
}

/* ── Export-to-PDF (window.print): show only the element marked .print-area, hide the rest ── */
@media print {
    body * {
        visibility: hidden;
    }

    .print-area, .print-area * {
        visibility: visible;
    }

    .print-area {
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
    }
}

/* Float placeholder to end while unfocused (form-floating RTL/LTR) */
.form-floating > .form-control-plaintext::placeholder,
.form-floating > .form-control::placeholder {
    color: var(--ink-faint);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder,
.form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ── App-wide utilities ── */

/* Cursor helpers */
.cursor-pointer { cursor: pointer; }

/* Prevent text selection on interactive UI elements */
.no-select { user-select: none; }

/* Full-page centered flex — used by auth pages */
.page-centered {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Card shadow variant slightly stronger than Bootstrap's shadow-sm */
.card-lifted {
    box-shadow: var(--shadow-md);
    border: 1px solid var(--border-light);
}

/* Status badge dots */
.status-dot {
    display: inline-block;
    width: .55rem;
    height: .55rem;
    border-radius: 50%;
    vertical-align: middle;
    margin-inline-end: .3rem;
}

.status-dot.active   { background: var(--primary-500); }
.status-dot.idle     { background: var(--accent-amber); }
.status-dot.offline  { background: var(--ink-faint); }
.status-dot.danger   { background: var(--accent-coral); }

/* Was a hardcoded violet with no relation to the palette; now the deep brand tone. */
.bg-purple { background-color: var(--primary-900) !important; }

/* ── Markdown-rendered question prompts (build-md) ── */
/* Keep prose tight in compact lists, and give embedded code blocks a dark, scrollable pane. */
.build-md p:last-child { margin-bottom: 0; }
.build-md p { margin-bottom: .5rem; }
/* Mixed-language prose: each block picks its own direction from its own content
   (English paragraph → LTR, Hebrew → RTL) even when the page is RTL. */
.build-md p,
.build-md li,
.build-md h1, .build-md h2, .build-md h3, .build-md h4,
.build-md blockquote,
.build-md td, .build-md th {
    unicode-bidi: plaintext;
}
/* -- Answer options --
   Rows, not list items. The key box holds the correct-answer mark, so nothing trails the
   text and nothing can wrap onto a line by itself. The row takes the exam's direction from
   its container; the text box isolates whatever runs are inside it, which is how a code
   token keeps its own left-to-right order inside a Hebrew sentence. */
.q-option {
    display: flex;
    align-items: flex-start;
    gap: .4rem;
    padding: .2rem .4rem;
    border-radius: var(--radius-xs);
}
.q-option + .q-option { margin-top: .15rem; }
.q-option-key {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    margin-top: .1rem;
    border: 1px solid var(--border-light);
    border-radius: 50%;
    background: var(--surface-subtle);
    color: var(--ink-secondary);
    font-size: var(--fs-2xs);
    font-weight: 600;
    line-height: 1;
}
/* min-width:0 so a long unbroken code token wraps inside the row instead of widening it. */
.q-option-text { unicode-bidi: isolate; min-width: 0; }
/* With no check mark beside it, the tint and the filled key box are the only thing saying
   which answer is right, so the row carries a border as well — a flat tint alone reads as
   a hover state on a card that already has several. */
.q-option.is-correct {
    background: var(--bs-success-bg-subtle);
    border: 1px solid var(--bs-success-border-subtle);
}
.q-option.is-correct .q-option-text { color: var(--bs-success-text-emphasis); font-weight: 600; }
.q-option.is-correct .q-option-key {
    background: var(--bs-success);
    border-color: var(--bs-success);
    color: #fff;
}
.build-md pre {
    margin: .5rem 0 0;
    border-radius: var(--radius-xs);
    overflow: auto;
    max-height: 320px;
    max-width: 100%;
    width: 100%;
    background: var(--surface-subtle);
    border: 1px solid var(--border-light);
    padding: .75rem 1rem;
    direction: ltr;
    text-align: left;
}
/* highlight.js's own .hljs class carries the atom-one-dark theme's dark background straight
   onto the <code> element, which otherwise sits on top of (and hides) the lighter <pre>
   background above — the two were never actually contesting the same property before. */
.build-md pre code { font-size: var(--fs-sm); color: var(--ink-primary); background: transparent !important; }

/* A suggestion card exists to be judged, and the 320px cap cut even a 16-line snippet, so
   deciding whether to keep a code question meant scrolling inside the block first. Raised
   to fit a realistic exam snippet of roughly 26 lines whole. Still capped rather than
   removed: a 40-line block would push the other suggestions off the screen entirely, and
   at that length scrolling is the right answer. The saved-question list in the Build tab
   keeps the tighter cap, where compact rows matter more than reading the code in place. */
.build-3col-col2 .build-md pre {
    max-height: 560px;
}
/* Inline code keeps its own LTR run inside RTL prose. */
.build-md :not(pre) > code {
    font-size: .82em;
    direction: ltr;
    unicode-bidi: isolate;
    color: inherit;
    font-family: monospace;
}
/* Plain (uncolored) inline code still gets the tint — only text carrying its own <font color>
   (applied via the editor's color picker, or pasted in) should override it. Bootstrap's
   "subtle" triad (bg/border/text-emphasis) is already tuned per-theme for light AND dark mode,
   unlike a hardcoded rgba() tint which only ever looked right in one of the two. */
.build-md :not(pre) > code:not(:has(> font[color])) { color: var(--bs-danger-text-emphasis); }
/* MathLive's default virtual-keyboard layer (105) sits behind Bootstrap modals (1055).
   Formula entry is also available inside Student Preview, so keep the keyboard above it. */
body {
    --keyboard-zindex: 2000;
}

/* ── Teacher library ───────────────────────────────────────────────────────────────────────
   One row per item: what it is, the name and the student-facing description, then where it
   comes from and how many quizzes hold a copy. Actions sit at the row's end. */
.library-page { max-width: 64rem; }
.library-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.library-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: .75rem;
    padding: .6rem .9rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md, .5rem);
    background: var(--surface-card, #fff);
}
.library-row.no-icon { grid-template-columns: minmax(0, 1fr) auto; }
.library-row-icon {
    width: 2rem;
    height: 2rem;
    display: grid;
    place-items: center;
    border-radius: .5rem;
    background: var(--surface-subtle);
    color: var(--bs-primary);
    font-size: 1rem;
}
.library-row-body { min-width: 0; }
.library-row-title { font-size: var(--fs-sm); font-weight: 600; line-height: 1.35; }
.library-row-desc {
    margin-top: .15rem;
    font-size: var(--fs-xs);
    color: var(--ink-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* One line that never wraps, so no line can start with a separator dot; the course list in the
   middle is what gives way, cut with an ellipsis. */
.library-row-meta {
    margin-top: .2rem;
    min-width: 0;
    overflow: hidden;
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: .15rem .45rem;
    font-size: var(--fs-2xs);
    color: var(--ink-secondary);
}
.library-row-meta > span { flex-shrink: 0; white-space: nowrap; }
.library-row-source {
    margin-top: .1rem;
    font-size: var(--fs-2xs);
    color: var(--ink-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.library-row-used { color: var(--bs-success-text-emphasis); font-weight: 600; }
/* The facts on a row read as one line, each set apart by a dot rather than by wide gaps. */
.library-row-meta > span + span::before {
    content: "·";
    margin-inline-end: .45rem;
    color: var(--ink-secondary);
    opacity: .6;
    font-weight: 400;
}
.library-row-actions { display: flex; gap: .15rem; }
/* Row actions stay quiet until pointed at, so the item, not its buttons, is what the eye finds. */
.library-action {
    width: 2rem;
    height: 2rem;
    padding: 0;
    display: inline-grid;
    place-items: center;
    border: 0;
    border-radius: .4rem;
    background: transparent;
    color: var(--ink-secondary);
}
.library-action:hover,
.library-action:focus-visible { background: var(--surface-subtle); color: var(--ink-primary); }
.library-action.is-danger:hover,
.library-action.is-danger:focus-visible { background: var(--bs-danger-bg-subtle); color: var(--bs-danger-text-emphasis); }

/* The tools catalog is the main-menu entry meant to catch the eye: when it is not the current page
   its label reads darker than its neighbours and its icon carries the brand colour. */
.ex-tab.ex-tab-feature:not(.active) { color: var(--ink-primary); font-weight: 600; }
.ex-tab.ex-tab-feature i { color: var(--primary-700); }

/* The course a quiz is filed under, one line under the page title on the question-assembly page. */
.compose-field-row { display: flex; align-items: center; gap: .35rem; }
.compose-course-select { flex: 1 1 auto; width: auto; min-width: 0; }

/* ── Create menu ──────────────────────────────────────────────────────────────────────────
   A main-menu entry that opens a panel of activity kinds: a card each with a CSS-drawn picture of
   what building it looks like, then plain links to the less common kinds. */
.ex-create-menu { display: flex; align-items: stretch; }
.ex-create-caret { font-size: .65em; opacity: .7; }
.ex-create-panel {
    width: min(58rem, calc(100vw - 2rem));
    padding: 1rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg, .75rem);
    white-space: normal;
}
.ex-create-head { margin-bottom: .75rem; }
.ex-create-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: .75rem;
}
.ex-create-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md, .5rem);
    background: var(--surface-card, #fff);
    color: inherit;
    text-decoration: none;
    transition: var(--transition-fast);
}
.ex-create-card:hover,
.ex-create-card:focus-visible {
    color: inherit;
    border-color: var(--primary-500, var(--bs-primary));
    box-shadow: 0 4px 14px rgba(0, 0, 0, .08);
    transform: translateY(-1px);
}
.ex-create-preview {
    height: 6.5rem;
    padding: .75rem 1rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .35rem;
    background: var(--surface-subtle);
    border-bottom: 1px solid var(--border-light);
}
.ex-create-card-body { padding: .65rem .75rem .8rem; }
.ex-create-card-title {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--primary-900);
}
.ex-create-card-desc {
    margin-top: .25rem;
    font-size: var(--fs-2xs);
    line-height: 1.45;
    color: var(--ink-secondary);
}
.ex-create-more {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem .9rem;
    margin-top: .85rem;
    padding-top: .75rem;
    border-top: 1px solid var(--border-light);
}
.ex-create-more-link {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--ink-primary);
    text-decoration: none;
}
.ex-create-more-link:hover { color: var(--bs-primary); }

/* The pictures: shapes only, no text, so they read the same in every language. */
.mock-w-90 { width: 90%; }
.mock-w-80 { width: 80%; }
.mock-w-70 { width: 70%; }
.mock-w-60 { width: 60%; }
.mock-w-55 { width: 55%; }
.mock-line { display: block; height: .45rem; border-radius: 999px; background: var(--border-light); }
.mock-opt {
    display: flex;
    align-items: center;
    gap: .35rem;
    height: .8rem;
    padding-inline: .3rem;
    border: 1px solid var(--border-light);
    border-radius: .25rem;
    background: var(--surface-card, #fff);
}
.mock-opt i { width: .45rem; height: .45rem; border-radius: 50%; background: var(--border-light); flex-shrink: 0; }
.mock-opt::after { content: ""; flex: 1; max-width: 60%; height: .3rem; border-radius: 999px; background: var(--border-light); }
.mock-opt.is-correct { border-color: var(--bs-success-border-subtle); background: var(--bs-success-bg-subtle); }
.mock-opt.is-correct i { background: var(--bs-success); }
.mock-bubble {
    display: block;
    align-self: flex-start;
    height: 1.1rem;
    border: 1px solid var(--border-light);
    border-radius: .6rem;
    background: var(--surface-card, #fff);
}
.mock-bubble.is-me { align-self: flex-end; border-color: transparent; background: var(--primary-100); }
.mock-step { display: flex; align-items: center; gap: .45rem; }
.mock-sheet { display: flex; gap: .5rem; height: 100%; }
.mock-page {
    flex: 1.2;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .3rem;
    padding: .4rem .5rem;
    background: var(--surface-card);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-xs);
}
.mock-answer {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .35rem;
    padding: .4rem .5rem;
    border: 1px dashed var(--primary-200);
    border-radius: var(--radius-xs);
    background: var(--primary-50);
}
.mock-answer i { display: block; height: .3rem; border-radius: 999px; background: var(--primary-200); }
.mock-answer i:last-child { width: 60%; }
.mock-step i {
    width: .8rem;
    height: .8rem;
    flex-shrink: 0;
    border: 2px solid var(--border-light);
    border-radius: 50%;
    background: var(--surface-card, #fff);
}
.mock-step::after { content: ""; flex: 1; max-width: 75%; height: .4rem; border-radius: 999px; background: var(--border-light); }
.mock-step.is-done i { border-color: var(--primary-700); background: var(--primary-700); }
.mock-step.is-current i { border-color: var(--primary-700); }
.mock-step.is-current::after { background: var(--primary-100); }


/* Materials and tools on the quiz-settings cards: one compact row each, the name and the
   student-facing description on one line apiece, actions at the end. */
.details-attached {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.details-attached-item {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    padding: .4rem .55rem;
    border: 1px solid var(--border-light);
    border-radius: .4rem;
    background: var(--surface-card, #fff);
}
.details-attached-item.is-hidden { opacity: .6; }
.details-attached-icon { margin-top: .1rem; color: var(--bs-primary); }
.details-attached-body { flex: 1; min-width: 0; }
.details-attached-title,
.details-attached-desc {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.details-attached-title { font-size: var(--fs-xs); font-weight: 600; }
.details-attached-desc { font-size: var(--fs-2xs); color: var(--ink-secondary); }

/* Library toolbar and groups: search, a course filter and a sort on one line, then the items in
   collapsible groups with a count. */
.library-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: .75rem;
}
.library-search { position: relative; flex: 1 1 14rem; max-width: 22rem; }
.library-search > i {
    position: absolute;
    inset-inline-start: .65rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ink-secondary);
    pointer-events: none;
}
.library-search > input { padding-inline-start: 2rem; }
.library-filter { width: auto; max-width: 15rem; }
.library-group { margin-bottom: 1rem; }
.library-group-head {
    display: flex;
    align-items: center;
    gap: .45rem;
    width: 100%;
    padding: .2rem 0;
    border: 0;
    background: transparent;
    color: var(--ink-secondary);
    font-size: var(--fs-xs);
    font-weight: 600;
    text-align: start;
}
.library-group-head:hover { color: var(--ink-primary); }
.library-group-icon { font-size: .95rem; line-height: 1; }
.library-group-title { white-space: nowrap; }
.library-group-count {
    padding: .05rem .45rem;
    border-radius: 999px;
    background: var(--surface-subtle);
    font-size: var(--fs-2xs);
    line-height: 1.4;
}
.library-group-rule { flex: 1; height: 1px; background: var(--border-light); }
.library-group-caret { font-size: .7rem; }
.library-group .library-list { margin-top: .4rem; }
.library-row-meta > .library-row-courses {
    flex-shrink: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Tool preview: a large window, the tool filling everything under its name. */
.tool-view { display: flex; flex-direction: column; height: 100%; min-height: 0; }
/* Quiz settings, empty tools card: common tools as one-click chips. */
.details-tool-suggestions { display: flex; flex-wrap: wrap; gap: .35rem; }
.details-tool-chip {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .25rem .6rem;
    border: 1px solid var(--border-light);
    border-radius: 999px;
    background: var(--surface-card, #fff);
    color: var(--ink-primary);
    font-size: var(--fs-xs);
}
.details-tool-chip:hover { border-color: var(--primary-500, var(--bs-primary)); }

/* Editing a quiz's tool: the settings in a row across the top, the tool itself filling the whole
   width under them. The tool is what is being judged, so it gets the page; the settings are a strip.
   Only the strip scrolls. */
.tool-edit-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    overflow: hidden;
}
.tool-edit-fields {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: 0 .7rem .55rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md, .5rem);
    background: var(--surface-card, #fff);
    font-size: var(--fs-xs);
}
/* The fields themselves: even columns on one baseline, so the strip reads as one row of answers
   rather than a pile of boxes of different heights. */
.tool-fields-grid {
    flex: 1 1 auto;
    min-width: 0;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-content: start;
    gap: .4rem .6rem;
}
.tool-field { min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.tool-field.is-double { grid-column: span 2; }
.tool-field.is-triple { grid-column: span 3; }
.tool-field.is-wide { grid-column: 1 / -1; }
.tool-field.is-crop { grid-column: span 2; }
.tool-field-scroll { max-height: 8rem; overflow-y: auto; }
@media (max-width: 900px) {
    .tool-fields-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .tool-field.is-double,
    .tool-field.is-triple,
    .tool-field.is-crop { grid-column: 1 / -1; }
}

/* A label is one line: the name of the field on the start side, a way out to the provider on the
   other. Anything longer lives in the field's own tooltip. */
.tool-edit-fields .form-label {
    display: flex;
    align-items: baseline;
    gap: .35rem;
    margin: 0;
    font-size: var(--fs-2xs);
    color: var(--ink-secondary);
    white-space: nowrap;
}
.tool-field-link { margin-inline-start: auto; font-size: var(--fs-2xs); text-decoration: none; }
.tool-field-link:hover { text-decoration: underline; }
.tool-edit-fields .form-text { font-size: var(--fs-2xs); line-height: 1.25; margin: 0; }
.tool-edit-fields .form-control,
.tool-edit-fields .form-select { font-size: var(--fs-xs); padding: .25rem .45rem; }
.tool-edit-fields .btn { font-size: var(--fs-2xs); }

/* What a pasted address means, under the address: a word for it and a line saying what students get. */
.tool-mode { display: flex; align-items: center; gap: .35rem; min-width: 0; }
.tool-mode-chip {
    flex-shrink: 0;
    padding: .05rem .4rem;
    border-radius: 999px;
    font-weight: 600;
    background: var(--surface-subtle);
    color: var(--ink-secondary);
    border: 1px solid var(--border-light);
}
.tool-mode-chip.is-user { background: var(--primary-100); color: var(--primary-900); border-color: var(--primary-700, #257A59); }
.tool-mode-chip.is-starter { background: #fef3c7; color: #92400e; border-color: #fcd34d; }

/* The four edges of a provider's frame, trimmed in one short row. */
.tool-crop { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .35rem; }
.tool-crop-side { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.tool-crop-side > span { font-size: var(--fs-2xs); color: var(--ink-secondary); }
.tool-crop-side .form-control { width: 100%; padding-inline: .25rem; text-align: center; }
.tool-edit-fields .form-control-color { width: 2.2rem; padding: .15rem; }
.tool-edit-fields .form-check-label { font-size: var(--fs-2xs); }
.tool-edit-fields code { font-size: var(--fs-2xs); }

/* Cancel and confirm under the fields they answer, at the end of the row, every time. */
.tool-settings-actions {
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    gap: .4rem;
    padding-top: .5rem;
    border-top: 1px solid var(--border-light);
}
.tool-settings-actions .btn { white-space: nowrap; }

/* The other shape, for the menu: the settings stand beside the tool instead of over it. Browsing the
   catalogue means looking at the tool while it is set up, so the two sit side by side; a quiz's
   settings step has already chosen, and gives the tool the width. */
.tool-edit-body.is-side { flex-direction: row; }
.is-side .tool-edit-fields { flex: 0 0 17rem; flex-direction: column; max-height: none; }
.is-side .tool-fields-grid { grid-template-columns: minmax(0, 1fr); }
.is-side .tool-field.is-double,
.is-side .tool-field.is-triple,
.is-side .tool-field.is-wide,
.is-side .tool-field.is-crop { grid-column: span 1; }
.is-side .tool-edit-fields { overflow-y: auto; }
.is-side .tool-settings-actions { margin-top: auto; }
.is-side .tool-settings-actions .btn { flex: 1 1 auto; }
@media (max-width: 900px) {
    .tool-edit-body.is-side { flex-direction: column; }
    .is-side .tool-edit-fields { flex: 0 0 auto; max-height: 45%; }
    .is-side .tool-fields-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.tool-edit-preview {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md, .5rem);
    background: var(--surface-subtle);
    overflow: hidden;
}
/* Both panes are titled the same way, so they read as a pair: what the teacher decides on one side,
   what it turns into on the other. The settings scroll under their line; the tool's stays put. */
.tool-pane-label {
    padding: .4rem .75rem;
    border-bottom: 1px solid var(--border-light);
    font-size: var(--fs-2xs);
    color: var(--ink-secondary);
    background: var(--surface-subtle);
}
.tool-edit-fields .tool-pane-label {
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0 -.7rem;
    border-radius: var(--radius-md, .5rem) var(--radius-md, .5rem) 0 0;
}
.tool-edit-preview .tool-view { flex: 1; min-height: 0; background: var(--surface-card, #fff); }


/* A page's heading and the picker's tabs on one line: the title on the start side, the categories
   filling the space beside it and wrapping under it only when they must. */
.tool-picker-head {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    margin-bottom: .35rem;
}
.tool-picker-tabs { display: flex; flex-wrap: wrap; gap: .35rem; }
/* Under a heading the tabs run across the middle of the page; alone - a quiz's settings card -
   they start where everything else on the card starts. */
.tool-picker-head > .tool-picker-tabs:not(:first-child) { justify-content: center; }
/* Inside the head the row is one line of a heading, not a section of its own. */
.tool-picker-head .compose-sources { margin-bottom: 0; }
.tool-picker-head .ex-page-head { margin-bottom: 0; }
.tool-picker-head .ex-page-sub { max-width: 30rem; }

/* A page of handwriting, in a quiz and in the checking screen: as tall as a sheet of working, and
   the tool's own chrome above it. */
.answer-pad, .check-ink { height: min(78vh, 46rem); display: flex; flex-direction: column; }
.answer-pad .handwriting-pad,
.check-ink .handwriting-pad {
    flex: 1;
    min-height: 0;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md, .5rem);
    overflow: hidden;
}
.check-ink { height: auto; max-height: 40rem; overflow-y: auto; }
/* The page a handwriting question hands over, shown at a glance while the quiz is being built. */
.build-preview-pad .handwriting-pad {
    height: 11rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md, .5rem);
    overflow: hidden;
}
.check-ink .handwriting-pad { min-height: 18rem; }

/* Writing at full size: the pad over everything else, with a little air around it. CSS rather than
   the Fullscreen API, which a locked exam is already holding and watching for exits. */
/* Above everything the exam itself puts on screen - its header, a floating tool, the teacher's
   preview banner - and below the lockdown warnings, which must never be covered. */
.handwriting-backdrop {
    position: fixed;
    inset: 0;
    z-index: 10000;
    background: rgba(15, 23, 42, .45);
}
.handwriting-pad.handwriting-expanded {
    position: fixed;
    top: 4.5rem;
    bottom: 3rem;
    left: 3rem;
    right: 3rem;
    z-index: 10001;
    height: auto;
    border: 1px solid var(--border-medium);
    border-radius: var(--radius-lg, .75rem);
    background: var(--surface-card, #fff);
    box-shadow: 0 1.5rem 3rem rgba(15, 23, 42, .25);
    overflow: hidden;
}
@media (max-width: 768px) {
    .handwriting-pad.handwriting-expanded { top: 3rem; bottom: 1rem; left: 1rem; right: 1rem; }
}

/* A checked paper shows every page at once, one under the other, each with its number. */
.handwriting-pages { display: flex; flex-direction: column; gap: .5rem; overflow-y: auto; }
.handwriting-page { display: flex; flex-direction: column; }
.handwriting-page .handwriting-surface { width: 100%; aspect-ratio: 210 / 297; }
.handwriting-page-label { padding: .15rem .4rem; color: var(--ink-secondary); }
.handwriting-page-count { min-width: 2.2rem; text-align: center; color: var(--ink-secondary); }

/* The tools of a category, as capsules: one press opens its settings, a second closes them. */
.tool-capsules { display: flex; flex-wrap: wrap; gap: .4rem; margin: .6rem 0; }
/* On a page of its own the row runs across the middle, under the categories it belongs to; inside a
   quiz's card it starts where the card starts. */
.tool-capsules.is-centred { justify-content: center; }
.tool-capsule {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .35rem .7rem;
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--ink-primary);
    background: var(--surface-card, #fff);
    border: 1px solid var(--border-light);
    border-radius: 999px;
}
.tool-capsule:hover { border-color: var(--border-medium); }
.tool-capsule.is-open {
    color: var(--primary-900);
    background: var(--primary-100);
    border-color: var(--primary-700, #257A59);
}
.tool-capsule.is-added { opacity: .7; }
.tool-capsule-note { display: inline-flex; align-items: center; gap: .15rem; color: var(--ink-secondary); font-weight: 500; }
/* A saved tool and the x that forgets it travel together, so the x cannot wrap onto its own line. */
.tool-capsule-pair { display: inline-flex; align-items: center; gap: .25rem; }
/* What the page asks beside the tool's own fields - in the menu, which quiz it joins. */
.tool-settings-extra .form-text { margin-top: .15rem; }

/* A tool's settings where the tool is: the row gives up its own layout and holds the pane. */
.details-attached-item.is-editing { display: block; padding: 0; }
.tool-settings { display: flex; flex-direction: column; gap: .5rem; }
.tool-settings-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: 0 .1rem;
}
/* In a quiz's settings the strip takes the height it needs and the tool has a height of its own
   under it; beside each other, in the menu, the two share one. */
.tool-settings-body { overflow: visible; }
.tool-settings-body:not(.is-side) .tool-edit-preview { flex: 0 0 auto; height: min(62vh, 30rem); }
.tool-settings-body.is-side { height: min(72vh, 34rem); }

/* Quiz settings, tools card: two pickers side by side, the add button under them, above the list. */
.details-tool-add {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .35rem;
    margin-bottom: .75rem;
}
.details-tool-add .btn { grid-column: 1 / -1; }

/* Quiz settings panel: explanations behind an info icon, beside the label or button they explain. */
.details-field-head { display: flex; align-items: center; gap: .25rem; margin-bottom: .25rem; }
.details-action-row { display: flex; align-items: center; gap: .35rem; }
.details-action-row > .btn { flex: 1; }
.details-info {
    display: inline-grid;
    place-items: center;
    width: 1.4rem;
    height: 1.4rem;
    flex-shrink: 0;
    border-radius: 50%;
    color: var(--ink-secondary);
    font-size: .8rem;
    cursor: help;
}
.details-info:hover,
.details-info:focus-visible { color: var(--bs-primary); background: var(--surface-subtle); outline: none; }

/* Quiz settings: the basket from the assembly step, on the end side. */
.details-basket-head {
    display: flex;
    align-items: flex-start;
    gap: .45rem;
    font-size: var(--fs-sm);
    font-weight: 700;
}
.details-basket-head > .bi { color: var(--primary-700); }
.details-basket-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .15rem .8rem;
    margin-top: .35rem;
    font-size: var(--fs-xs);
    color: var(--ink-secondary);
}
.details-basket-list {
    list-style: none;
    margin: .6rem 0 0;
    padding: 0;
    max-height: 18rem;
    overflow-y: auto;
}
.details-basket-list li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: start;
    gap: .5rem;
    padding: .4rem 0;
    border-bottom: 1px solid var(--border-light);
}
.details-basket-list li:last-child { border-bottom: 0; }
.details-basket-text {
    font-size: var(--fs-xs);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.details-basket-pts { font-size: var(--fs-2xs); color: var(--ink-secondary); white-space: nowrap; }
.details-basket-pts.is-off { color: var(--bs-danger-text-emphasis); }
.details-basket-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .35rem;
    margin-top: .65rem;
}
.details-basket-actions .btn { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.details-basket-save {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    margin-top: .75rem;
    padding-top: .75rem;
    border-top: 1px solid var(--border-light);
}

/* Making a quiz: the way back one step, where a page has one, and a small line of where the page sits.
   The line keeps one place on every step - at the end side, whether or not a back button is there - so
   it does not jump between pages. The row is as tall as a small button even where there is none, so the
   title below starts at the same height too. */
.details-flow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    min-height: calc(1.5 * .875rem + .5rem + 2px);
    gap: .5rem 1rem;
    margin-bottom: 1rem;
}
.details-flow-steps {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .25rem;
    margin: 0;
    margin-inline-start: auto;
    padding: 0;
    list-style: none;
    font-size: var(--fs-xs);
    color: var(--ink-secondary);
}
.details-flow-steps li { display: inline-flex; align-items: center; gap: .25rem; }
/* The page's title shares the line: title at the start, the steps at the end, and a way back, where
   the page has one, beside the steps - so the steps sit where they always do. */
.details-flow > .ex-page-title { margin: 0; }
.details-flow > .btn { margin-inline-start: auto; }
.details-flow > .btn + .details-flow-steps { margin-inline-start: 0; }
/* The separator mirrors with the page direction, so it points the way the steps go. */
.details-flow-steps li + li::before { content: "\203A"; margin-inline-end: .25rem; opacity: .6; }
.details-flow-steps li.is-done { color: var(--bs-success-text-emphasis); }
.details-flow-steps li.is-current { color: var(--primary-900); font-weight: 700; }
/* The current step is underlined the way the main menu marks its active tab - a rounded primary bar -
   thinner here, to suit the smaller text. */
.details-flow-steps li.is-current > span { position: relative; }
.details-flow-steps li.is-current > span::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: -.3rem;
    height: 2px;
    border-radius: var(--radius-pill);
    background: var(--primary-700);
}

/* Summary and publishing: sections down the wide pane, the published state's code and QR first. */
.publish-section + .publish-section {
    margin-top: 1.25rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--border-light);
}
.publish-section-title {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0 0 .65rem;
    font-size: var(--fs-sm);
    font-weight: 700;
}
.publish-section-title > .bi { color: var(--primary-700); }
.publish-ready {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .45rem;
    font-size: var(--fs-sm);
    font-weight: 600;
}
.publish-ready.is-ok { color: var(--bs-success-text-emphasis); }
.publish-ready.is-blocked { color: var(--bs-danger-text-emphasis); }
.publish-muted { color: var(--ink-secondary); font-weight: 400; }
.publish-issues { list-style: none; margin: .5rem 0 0; padding: 0; }
.publish-issues li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: .55rem;
    padding: .45rem 0;
    border-bottom: 1px solid var(--border-light);
    font-size: var(--fs-xs);
}
.publish-issues li:last-child { border-bottom: 0; }
.publish-issues .is-block { color: var(--bs-danger-text-emphasis); }
.publish-issues .is-warn { color: var(--bs-warning-text-emphasis); }

.publish-summary {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
    gap: .65rem;
}
.publish-block {
    min-width: 0;
    padding: .65rem .75rem;
    background: var(--surface-subtle);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
}
.publish-block-head {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin-bottom: .3rem;
    font-size: var(--fs-xs);
    font-weight: 700;
}
.publish-block-head > .bi { color: var(--primary-700); }
.publish-block-edit { margin-inline-start: auto; font-size: var(--fs-2xs); font-weight: 600; }
.publish-block-edit.is-disabled { color: var(--ink-secondary); opacity: .6; cursor: not-allowed; }
.publish-block-line {
    overflow: hidden;
    font-size: var(--fs-xs);
    color: var(--ink-secondary);
    white-space: nowrap;
    text-overflow: ellipsis;
}
.live-draft-note {
    display: inline-flex;
    align-items: center;
    font-size: var(--fs-xs);
    color: var(--ink-secondary);
}
.live-draft-note.is-ready { color: var(--primary-700, #257A59); font-weight: 600; }

/* ── Grading ───────────────────────────────────────────────────────────────────────────
   The class or one paper, never both, in the pane the building steps use; the numbers keep
   the narrow column beside either. */
.grade-state { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .6rem; }
.grade-state-line { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.grade-state-actions { display: inline-flex; flex-wrap: wrap; gap: .4rem; }

.grade-filters { display: flex; gap: .4rem; margin-bottom: .6rem; }
.grade-filters input[type="search"] { flex: 1 1 auto; min-width: 0; }
.grade-filters select { flex: 0 0 auto; width: auto; font-size: var(--fs-xs); }

.grade-list { list-style: none; margin: 0; padding: 0; }
.grade-row { border-bottom: 1px solid var(--border-light); }
.grade-row:last-child { border-bottom: 0; }
.grade-row-open {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto 1rem;
    align-items: center;
    gap: .6rem;
    width: 100%;
    padding: .5rem .35rem;
    text-align: start;
    background: none;
    border: 0;
    border-inline-start: 3px solid transparent;
}
button.grade-row-open:hover { background: var(--surface-subtle); }
.grade-row[data-state="pass"] .grade-row-open      { border-inline-start-color: var(--bs-success); }
.grade-row[data-state="fail"] .grade-row-open      { border-inline-start-color: var(--bs-danger); }
.grade-row[data-state="ungraded"] .grade-row-open  { border-inline-start-color: var(--border-medium); }
.grade-row.is-absent .grade-row-open { opacity: .6; }
.grade-row-flags { display: inline-flex; gap: .3rem; }
.grade-row-score { display: inline-flex; align-items: baseline; gap: .4rem; }
.grade-percent { font-size: var(--fs-lg); font-weight: 800; color: var(--ink-primary); }
.grade-row[data-state="fail"] .grade-percent { color: var(--bs-danger-text-emphasis); }
.grade-row[data-state="pass"] .grade-percent { color: var(--primary-700, #257A59); }
.grade-row-chevron { color: var(--ink-faint); font-size: var(--fs-xs); }
.grade-back { background: none; border: 0; padding: 0; margin-bottom: .5rem; }

.grade-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem .8rem; margin-top: .5rem; }
.grade-facts > div { display: flex; flex-direction: column; }
.grade-fact-value { font-size: var(--fs-xl); font-weight: 800; line-height: 1.1; color: var(--ink-primary); }
.grade-fact-label { font-size: var(--fs-2xs); color: var(--ink-secondary); }
.grade-spread-title { margin-top: .9rem; font-size: var(--fs-2xs); color: var(--ink-secondary); }
.grade-spread { display: flex; gap: 1px; height: 1.4rem; margin-top: .3rem; border-radius: var(--radius-sm, 5px); overflow: hidden; }
.grade-spread-key { display: flex; flex-wrap: wrap; gap: .2rem .6rem; margin-top: .35rem; font-size: var(--fs-2xs); color: var(--ink-secondary); }
.grade-spread-key .bi { margin-inline-end: .2rem; }

.check-paper-head { padding: 0 0 .7rem; border-bottom: 1px solid var(--border-light); }
.check-paper-body { padding-top: .8rem; }

.publish-preview { display: flex; flex-wrap: wrap; gap: .5rem; }


.join-qr {
    display: block;
    padding: .35rem;
    background: #fff;
    border: 1px solid var(--border-medium);
    border-radius: var(--radius-lg);
}

/* Activities list: flat cards grouped by month or by course, with the one action each state calls for. */
.acts-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1rem;
    margin-bottom: 1rem;
    padding: .6rem 1rem;
    background: var(--bs-success-bg-subtle);
    border: 1px solid var(--bs-success-border-subtle);
    border-radius: var(--radius-lg);
    color: var(--bs-success-text-emphasis);
    font-size: var(--fs-sm);
    font-weight: 600;
}
.acts-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem 1rem;
    margin-bottom: 1.1rem;
}
.acts-search { flex: 1 1 14rem; max-width: 22rem; }
.acts-filters { display: flex; flex-wrap: wrap; gap: .35rem; }
.acts-filter {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .2rem .7rem;
    background: var(--surface-card);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-pill);
    color: var(--ink-secondary);
    font-size: var(--fs-xs);
    font-weight: 600;
}
.acts-filter.is-selected { border-color: var(--primary-700); color: var(--primary-900); }
.acts-filter-count { font-size: var(--fs-2xs); opacity: .7; }

.acts-group + .acts-group { margin-top: 1.4rem; }
.acts-group-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin: 0 0 .5rem;
    color: var(--ink-secondary);
    font-size: var(--fs-sm);
    font-weight: 700;
}
.acts-group-count { font-size: var(--fs-2xs); font-weight: 600; opacity: .7; }
.acts-list { display: flex; flex-direction: column; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.acts-card {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: .6rem 1.25rem;
    padding: .7rem 1rem;
    background: var(--surface-card);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
}
.acts-card:hover { border-color: var(--border-medium); }
.acts-card.is-highlight { border-color: var(--primary-700); box-shadow: 0 0 0 3px var(--bs-success-bg-subtle); }
.acts-card-top { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.acts-card-title {
    margin: 0;
    overflow: hidden;
    font-size: var(--fs-md, 1rem);
    font-weight: 700;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.acts-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: .15rem .9rem;
    margin-top: .25rem;
    color: var(--ink-secondary);
    font-size: var(--fs-2xs);
}
.acts-card-meta .bi { margin-inline-end: .25rem; opacity: .6; }
.acts-card-code {
    font-family: var(--bs-font-monospace);
    font-size: var(--fs-sm);
    font-weight: 700;
    letter-spacing: .08em;
    color: var(--primary-900);
}
.acts-card-actions { display: flex; align-items: center; gap: .35rem; }
@media (max-width: 768px) {
    .acts-card { grid-template-columns: minmax(0, 1fr); }
    .acts-card-actions { flex-wrap: wrap; }
}

.acts-group-title { min-width: 0; margin: 0; font-size: inherit; font-weight: inherit; color: inherit; overflow-wrap: anywhere; }


.acts-page-head { margin-bottom: 1rem; }

/* My activities: the course on its own line, then searching and reading the list. */
.acts-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: .75rem;
}
.acts-bar-course { display: flex; flex: 1 1 16rem; align-items: center; gap: .15rem; min-width: 0; }
.acts-bar-course > .form-select { min-width: 0; }
.acts-select { flex: 0 0 auto; width: auto; }
.acts-view {
    display: inline-flex;
    gap: .2rem;
    padding: .2rem;
    background: var(--surface-subtle);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-pill);
}
.acts-view button {
    padding: .2rem .55rem;
    background: transparent;
    border: 0;
    border-radius: var(--radius-pill);
    color: var(--ink-secondary);
}
.acts-view button.is-selected { background: var(--surface-card); color: var(--primary-900); box-shadow: var(--shadow-sm); }


/* Wide cards instead of rows: the same card, stacked, several to a row. */
.acts-list.is-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
    gap: .6rem;
}
.acts-list.is-cards .acts-card {
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    height: 100%;
}
.acts-list.is-cards .acts-card-actions { margin-top: .5rem; }

/* Sorting, grouping and the state filters, folded away under their own button. */
.acts-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .75rem; }
.acts-tools-toggle {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .3rem .6rem;
    background: var(--surface-card);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-pill);
    color: var(--ink-secondary);
    font-size: var(--fs-xs);
    font-weight: 600;
}
.acts-tools-toggle:hover { border-color: var(--border-medium); }
.acts-tools-toggle.is-open { border-color: var(--primary-700); color: var(--primary-900); }
.acts-tools-count {
    padding: 0 .4rem;
    border-radius: var(--radius-pill);
    background: var(--primary-700);
    color: #fff;
    font-size: var(--fs-2xs);
}
.acts-tools-panel {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem .75rem;
    margin-bottom: .75rem;
    padding: .6rem .75rem;
    background: var(--surface-subtle);
    border-radius: var(--radius-lg);
}

/* The first card is the activity not made yet: the same shape, drawn as an outline. */
.acts-chip-new { border-color: var(--primary-700); color: var(--primary-900); }

/* The name and the class, changed on the card itself. */
.acts-title-input { max-width: 20rem; }
.acts-title-edit {
    padding: 0 .2rem;
    background: transparent;
    border: 0;
    color: var(--ink-secondary);
    font-size: var(--fs-xs);
    opacity: 0;
}
.acts-card:hover .acts-title-edit,
.acts-title-edit:focus-visible { opacity: 1; }
.acts-card-title.is-untitled { color: var(--ink-secondary); font-weight: 400; }
.acts-meta-edit {
    padding: 0;
    background: transparent;
    border: 0;
    color: inherit;
    font: inherit;
    text-decoration: underline dotted;
    text-underline-offset: .2em;
}
.acts-meta-edit:hover { color: var(--primary-900); }
.acts-course-select { width: auto; max-width: 16rem; }

/* Starting an activity of a known kind: a name, then the kind, above the list. */
.acts-create-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    margin-bottom: .75rem;
    padding: .55rem .7rem;
    background: var(--surface-subtle);
    border-radius: var(--radius-lg);
}
.acts-create-label { color: var(--ink-secondary); font-size: var(--fs-xs); font-weight: 600; }
.acts-create-row .acts-create-label { flex: 0 0 100%; }
.acts-create-grid {
    flex: 1 1 100%;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: .5rem;
}
/* One kind: its icon and name on a line, and what it is under them. */
.acts-create-card {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: .5rem;
    row-gap: .15rem;
    align-items: center;
    padding: .6rem .75rem;
    text-align: start;
    background: var(--surface-card);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md);
    color: var(--ink-primary);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.acts-create-card:hover:not(:disabled) { border-color: var(--primary-400); box-shadow: var(--shadow-sm); }
.acts-create-card:disabled { opacity: .6; }
.acts-create-card > .bi { color: var(--primary-700); font-size: var(--fs-lg); }
.acts-create-card-name { font-weight: 700; font-size: var(--fs-sm); }
.acts-create-card.is-wip { background: transparent; border-style: dashed; }
.acts-create-card-wip {
    margin-inline-start: .35rem;
    padding: 0 .4rem;
    border-radius: var(--radius-pill);
    background: var(--accent-amber-bg);
    color: var(--accent-amber);
    font-size: var(--fs-2xs);
    font-weight: 600;
}
.acts-create-card-desc { grid-column: 1 / -1; font-size: var(--fs-2xs); color: var(--ink-secondary); line-height: 1.4; }

/* The quiz's name, over the class it belongs to, at the head of the basket. */
.compose-title-input { width: 100%; }

/* The kind of each activity, first on its card in its own colour. A kind with no colour of its own
   still gets the tile, in grey, so a new kind reads as a kind before anyone picks its colour. */
.acts-kind {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .15rem;
    width: 5.5rem;
    min-height: 3.2rem;
    padding: .35rem .3rem;
    background: var(--kind-bg, var(--surface-subtle));
    border-radius: var(--radius-lg);
    color: var(--kind-fg, var(--ink-secondary));
    text-align: center;
}
.acts-kind > .bi { font-size: 1.15rem; line-height: 1; }
.acts-kind-label { font-size: var(--fs-2xs); font-weight: 700; line-height: 1.2; }
.acts-kind[data-kind="structured"]   { --kind-bg: #E6F1FB; --kind-fg: #185FA5; }
.acts-kind[data-kind="chat"]         { --kind-bg: #EEEDFE; --kind-fg: #534AB7; }
.acts-kind[data-kind="lab"]          { --kind-bg: #FAEEDA; --kind-fg: #854F0B; }
.acts-kind[data-kind="url"]          { --kind-bg: #E1F5EE; --kind-fg: #0F6E56; }
.acts-kind[data-kind="pdf-document"] { --kind-bg: #FAECE7; --kind-fg: #993C1D; }
.acts-kind[data-kind="creative"]     { --kind-bg: #FBEAF0; --kind-fg: #993556; }

/* On wide cards, and on a narrow screen, the kind sits as a small label over the name instead. */
.acts-list.is-cards .acts-kind {
    flex-direction: row;
    justify-self: start;
    width: auto;
    min-height: 0;
    padding: .2rem .55rem;
}
@media (max-width: 768px) {
    .acts-kind {
        flex-direction: row;
        justify-self: start;
        width: auto;
        min-height: 0;
        padding: .2rem .55rem;
    }
}

/* Low fields: small type and tight padding, so the questions below start higher - each with a small
   label tight over it. */
.compose-id-field { display: flex; flex-direction: column; gap: .1rem; }
.compose-id-label { margin: 0; color: var(--ink-secondary); font-size: var(--fs-2xs); font-weight: 600; line-height: 1.2; }
.compose-id-control { padding-block: .15rem; font-size: var(--fs-xs); line-height: 1.5; }
.compose-field-row .btn-sm { padding: .15rem .45rem; }

/* How many questions, and how long they take, on one line above them. */
.compose-basket-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    margin-bottom: .6rem;
    font-size: var(--fs-xs);
    font-weight: 700;
}

/* The same name and class, to read, at the head of the basket on the later steps. */
/* The name has its own line and may take two, rather than being cut after a word or two. */
.details-basket-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-height: 1.35;
}
.details-basket-course {
    margin-top: .15rem;
    overflow: hidden;
    color: var(--ink-secondary);
    font-size: var(--fs-xs);
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* A step line that carries the subtitle under the title: the line aligns to its top, and the way back
   and the steps sit level with the title rather than centred on both lines. */
.details-flow.has-sub { align-items: flex-start; }
.details-flow.has-sub > .details-flow-steps { margin-top: .9rem; }
.details-flow.has-sub > .btn { margin-top: .45rem; }
/* The title block takes the room that is left and wraps its subtitle inside it, so the way back and the
   steps are never pushed onto a line of their own. */
.details-flow-head { flex: 1 1 0; min-width: 0; }
.details-flow-head .ex-page-title { margin: 0; }
.details-flow-head .ex-page-sub { margin: .15rem 0 0; }
/* On a narrow screen the title takes the whole line, and the way back and the steps go under it. */
@media (max-width: 992px) {
    .details-flow.has-sub { flex-wrap: wrap; }
    .details-flow-head { flex-basis: 100%; }
    .details-flow.has-sub > .btn,
    .details-flow.has-sub > .details-flow-steps { margin-top: 0; }
}

/* Question assembly: the name and class in one low line under the title. */
.compose-identity-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem 1rem; margin-bottom: .9rem; }
.compose-id-name { width: 18rem; max-width: 100%; }
.compose-id-course { width: 26rem; max-width: 100%; }

/* An activity's own page: its kind, class and state under its name, the one thing to do now, how
   students come in, and how many have. */
.hub-back { display: inline-flex; align-items: center; gap: .3rem; margin-bottom: .2rem; color: var(--ink-secondary); font-size: var(--fs-xs); font-weight: 600; text-decoration: none; }
.hub-back:hover { color: var(--primary-900); text-decoration: underline; }
.hub-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .75rem; }
.hub-now { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem; }
.hub-now-text { font-size: var(--fs-sm); font-weight: 600; }
.hub-code { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem 1.5rem; }
.hub-label { color: var(--ink-secondary); font-size: var(--fs-xs); font-weight: 600; }
.hub-code-value { color: var(--primary-900); font-family: var(--bs-font-monospace); font-size: 1.6rem; font-weight: 800; letter-spacing: .16em; }
.hub-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .35rem;
    margin-top: .75rem;
    padding-top: .75rem;
    border-top: 1px solid var(--border-light);
    text-align: center;
}
.details-basket > .hub-stats:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* Running it: the step now, the clock while it runs, and the students as a list that colours itself. */
.live-control { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.25rem; }
.live-control-text { flex: 1 1 14rem; min-width: 0; }
.live-control-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-inline-start: auto; }
.live-confirm-text { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-primary); }
.live-clock { display: grid; justify-items: center; gap: .1rem; min-width: 9rem; }
.live-clock-value { color: var(--primary-900); font-family: var(--bs-font-monospace); font-size: 1.9rem; font-weight: 800; line-height: 1; }
.live-clock-sub { color: var(--ink-secondary); font-size: var(--fs-2xs); }
.live-clock-bar { display: block; inline-size: 100%; block-size: 3px; border-radius: var(--radius-pill); background: var(--surface-subtle); overflow: hidden; }
.live-clock-bar > span { display: block; block-size: 100%; background: var(--primary-700); }
.live-clock.is-low .live-clock-value { color: var(--bs-danger-text-emphasis); }
.live-clock.is-low .live-clock-bar > span { background: var(--bs-danger); }
.live-pulse { display: inline-flex; align-items: center; gap: .3rem; margin-inline-start: .35rem; color: var(--bs-success-text-emphasis); font-size: var(--fs-2xs); font-weight: 600; }
.live-pulse > span { inline-size: .45rem; block-size: .45rem; border-radius: 50%; background: var(--bs-success); animation: live-pulse 1.6s ease-in-out infinite; }
@keyframes live-pulse { 50% { opacity: .35; } }
.live-filters { margin-bottom: .6rem; }
.live-list { list-style: none; margin: 0; padding: 0; max-height: 34rem; overflow-y: auto; }
.live-row {
    display: grid;
    /* The name a set width, so every state lines up in one column right beside it; breaches next,
       the actions at the far end. */
    grid-template-columns: auto minmax(12rem, 22rem) 7.5rem auto minmax(0, 1fr);
    align-items: center;
    gap: .35rem .75rem;
    padding: .5rem .25rem;
    border-bottom: 1px solid var(--border-light);
}
.live-row:last-child { border-bottom: 0; }
.live-dot { inline-size: .55rem; block-size: .55rem; border-radius: 50%; border: 1.5px solid var(--ink-faint); }
.live-who { display: flex; align-items: baseline; gap: .5rem; min-width: 0; }
.live-name { min-width: 0; font-size: var(--fs-sm); font-weight: 600; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.live-id { flex-shrink: 0; color: var(--ink-muted); font-size: var(--fs-2xs); white-space: nowrap; }
.live-status {
    justify-self: start;
    padding: .1rem .55rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-pill);
    background: var(--surface-subtle);
    color: var(--ink-secondary);
    font-size: var(--fs-2xs);
    font-weight: 700;
    white-space: nowrap;
}
.live-row-actions { grid-column: 5; display: inline-flex; gap: .3rem; justify-self: end; min-inline-size: 2rem; }
.live-flags { grid-column: 4; display: inline-flex; flex-wrap: wrap; gap: .3rem; }
.live-extra-label { align-self: center; color: var(--ink-secondary); font-size: var(--fs-xs); font-weight: 600; }
/* A message to one student opens under their row. */
.live-message { grid-column: 1 / -1; display: flex; gap: .4rem; padding-inline-start: 1.3rem; }
.live-message .form-control { max-inline-size: 32rem; }
.live-message-sent { grid-column: 1 / -1; padding-inline-start: 1.3rem; color: var(--bs-success-text-emphasis); font-size: var(--fs-xs); font-weight: 600; }
/* A message to everyone, under the step. */
.live-announce { margin-top: .9rem; padding-top: .8rem; border-top: 1px dashed var(--border-light); }
.live-announce-label { display: block; margin-bottom: .35rem; color: var(--ink-secondary); font-size: var(--fs-xs); font-weight: 700; }
.live-announce-row { display: flex; gap: .4rem; }
.live-announce-row .form-control { max-inline-size: 36rem; }
.live-announce-current {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .5rem;
    margin-top: .5rem; padding: .45rem .65rem; border-radius: var(--radius-md, .5rem);
    background: var(--bs-warning-bg-subtle); color: var(--bs-warning-text-emphasis); font-size: var(--fs-xs);
}
.live-announce-current-label { font-weight: 700; }
.live-announce-current-text { flex: 1 1 12rem; min-width: 0; }
.live-row[data-status="waiting"] .live-dot { border-color: var(--bs-info); background: var(--bs-info); }
.live-row[data-status="working"] .live-dot { border-color: var(--bs-success); background: var(--bs-success); }
.live-row[data-status="working"] .live-status { background: var(--bs-success-bg-subtle); border-color: var(--bs-success-border-subtle); color: var(--bs-success-text-emphasis); }
.live-row[data-status="waiting"] .live-status { background: var(--bs-info-bg-subtle); border-color: var(--bs-info-border-subtle); color: var(--bs-info-text-emphasis); }
.live-row[data-status="submitted"] .live-status { background: var(--primary-100); border-color: var(--primary-200, var(--border-light)); color: var(--primary-800, var(--primary-900)); }
.live-row[data-status="dropped"] .live-dot,
.live-row[data-status="locked"] .live-dot { border-color: var(--bs-danger); background: var(--bs-danger); }
.live-row[data-status="dropped"] .live-status,
.live-row[data-status="locked"] .live-status,
.live-row[data-status="unsubmitted"] .live-status { background: var(--bs-danger-bg-subtle); border-color: var(--bs-danger-border-subtle); color: var(--bs-danger-text-emphasis); }
.live-row[data-status="locked"] { background: var(--bs-danger-bg-subtle); }
.live-row[data-status="submitted"] .live-dot { border-color: var(--primary-700); background: var(--primary-700); }
.live-row[data-status="out"] .live-name { color: var(--ink-secondary); font-weight: 500; }
.live-empty { display: flex; align-items: center; gap: .5rem; padding: 1rem .25rem; color: var(--ink-secondary); font-size: var(--fs-sm); }
@media (max-width: 576px) {
    .live-row { grid-template-columns: auto minmax(0, 1fr) auto; }
    .live-status { justify-self: end; }
    .live-flags, .live-row-actions { grid-column: 2 / -1; justify-self: start; }
    .live-message, .live-message-sent { padding-inline-start: 0; }
}
/* A section looked at less, folded under its title. */
.hub-fold > summary { cursor: pointer; margin-bottom: 0; list-style: none; }
.hub-fold > summary::-webkit-details-marker { display: none; }
.hub-fold > summary::after { content: "\F282"; font-family: "bootstrap-icons"; margin-inline-start: auto; color: var(--ink-secondary); transition: transform .15s; }
.hub-fold[open] > summary { margin-bottom: .65rem; }
.hub-fold[open] > summary::after { transform: rotate(180deg); }
/* Under the step while students come in: the class screen, and the lock. */
.hub-entry-hint { display: flex; align-items: center; gap: .35rem; margin-top: .5rem; color: var(--ink-muted); font-size: var(--fs-xs); }
.hub-entry-hint strong { color: var(--ink-secondary); }
.hub-entry-facts { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.25rem; margin-top: .75rem; color: var(--ink-secondary); font-size: var(--fs-xs); }
/* The class screen: the entry, large, over everything. */
.hub-project {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2rem;
    padding: 2rem 1rem;
    background: var(--surface-card, #fff);
    overflow: auto;
}
.hub-project-close { position: absolute; inset-block-start: 1rem; inset-inline-end: 1rem; }
.hub-project-title { font-size: clamp(1.5rem, 3vw, 2.4rem); font-weight: 800; color: var(--primary-900); text-align: center; }
.hub-project-body { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2rem 4rem; }
.hub-project-entry { display: grid; justify-items: center; gap: .25rem; text-align: center; }
.hub-project-entry .hub-label { font-size: var(--fs-lg, 1.15rem); }
.hub-project-url { font-size: clamp(1.2rem, 2.4vw, 1.8rem); font-weight: 700; color: var(--ink-primary); }
.hub-project-code { font-family: var(--bs-font-monospace); font-size: clamp(3rem, 9vw, 6.5rem); font-weight: 800; letter-spacing: .16em; color: var(--primary-900); line-height: 1.1; }
/* ── The paper on the class screen, one question at a time ─────────────────────────── */
.review-screen {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    background: var(--surface-page, #FAF8F3);
    outline: none;
}
.review-bar {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: .8rem 1.2rem;
    border-bottom: 1px solid var(--border-light);
}
.review-count { font-size: var(--fs-lg); font-weight: 800; color: var(--primary-700, #257A59); }
.review-title { flex: 1 1 auto; min-width: 0; font-weight: 700; color: var(--ink-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.review-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1.4rem;
    padding: 1.5rem clamp(1rem, 6vw, 5rem);
    overflow-y: auto;
}
.review-prompt { font-size: clamp(1.3rem, 2.6vw, 2.1rem); font-weight: 700; line-height: 1.4; color: var(--ink-primary); }
.review-options { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
.review-option {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: .7rem 1rem;
    font-size: clamp(1rem, 1.8vw, 1.4rem);
    background: var(--surface-card, #fff);
    border: 2px solid var(--border-light);
    border-radius: var(--radius-lg);
}
.review-option.is-right { border-color: var(--bs-success); background: var(--bs-success-bg-subtle); }
.review-option.is-taken { border-color: var(--bs-danger-border-subtle); }
.review-option-text { flex: 1 1 auto; }
.review-option-share { position: relative; flex: 0 0 8rem; height: 1.5rem; background: var(--surface-subtle); border-radius: 999px; overflow: hidden; }
.review-bar-fill { position: absolute; inset-block: 0; inset-inline-start: 0; background: var(--primary-200, #C7E5D5); }
.review-option-count { position: absolute; inset: 0; display: grid; place-items: center; font-size: var(--fs-xs); font-weight: 700; color: var(--ink-primary); }
.review-verdict { display: flex; align-items: baseline; flex-wrap: wrap; gap: .6rem; }
.review-score { font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800; color: var(--primary-700, #257A59); line-height: 1; }
.review-score-label { color: var(--ink-secondary); }
.review-answer {
    padding: .9rem 1.1rem;
    background: var(--surface-card, #fff);
    border: 2px solid var(--primary-200, #C7E5D5);
    border-radius: var(--radius-lg);
}
.review-answer.is-from-class { border-style: dashed; }
.review-answer-label { font-size: var(--fs-xs); font-weight: 700; color: var(--primary-700, #257A59); margin-bottom: .3rem; }
.review-answer-body { font-size: clamp(1rem, 1.6vw, 1.3rem); line-height: 1.5; color: var(--ink-primary); }
.review-answer-none { font-size: var(--fs-sm); color: var(--ink-secondary); }
.review-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 1rem;
    border-top: 1px solid var(--border-light);
}

.hub-stat-num { display: block; font-size: var(--fs-lg, 1.15rem); font-weight: 800; }
.hub-stat-label { color: var(--ink-secondary); font-size: var(--fs-2xs); }

/* A card's name opens its activity. */
.acts-card-link { color: inherit; text-decoration: none; }
.acts-card-link:hover { color: var(--primary-900); text-decoration: underline; }

/* A link off Examin, not followed in a locked activity: a word about it, briefly, at the foot of the page. */
.link-guard-toast {
    position: fixed;
    inset-inline: 0;
    bottom: 1.25rem;
    width: fit-content;
    max-width: calc(100% - 2rem);
    margin-inline: auto;
    padding: .55rem 1rem;
    border-radius: 999px;
    background: #1f2937;
    color: #fff;
    font-size: var(--fs-sm);
    box-shadow: 0 6px 20px rgba(15, 23, 42, .25);
    z-index: 2000;
    opacity: 0;
    transform: translateY(.5rem);
    pointer-events: none;
    transition: opacity .2s, transform .2s;
}
.link-guard-toast.is-shown { opacity: 1; transform: none; }

/* PDF sheet: the window a paper is made in from text or a link (PdfSheetCompose). Global, since
   its content is drawn inside TopLayerDialog's <dialog>, out of the page component's scope. */
.sheet-source-dialog { width: min(960px, 94vw); }
.sheet-source-head,
.sheet-source-foot {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .7rem 1rem;
    border-bottom: 1px solid var(--border-light);
}
.sheet-source-head { justify-content: space-between; }
.sheet-source-foot {
    justify-content: flex-end;
    border-bottom: 0;
    border-top: 1px solid var(--border-light);
}
.sheet-source-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 1rem;
    overflow: auto;
}
.sheet-source-editor { min-height: 0; overflow: auto; }
