/* The deck player, the merged meeting page's own frame, and the live-room door
   inside the student area. PUBLIC bundle, like student.css: the student pages
   render outside the admin shell, so this file must never gain an "admin"
   prefix (bundle-css.mjs partitions the two bundles on the literal `/css/admin`,
   and a mis-named file arrives UNSTYLED in production while dev looks fine).

   WHAT THIS FILE OWNS after the 2026-09-04 redesign:
     - `.stu-deck*`     the player, its notes strip, its jump index, the compact
                        modifier used by the homework excerpt;
     - `.stu-meeting*`  the merged meeting page: the tab bar's placement, the
                        panel, the deck-not-open card;
     - `.stu-meetnav*`  previous / next meeting;
     - `.stu-session-head*` and `.stu-attendance__done*`, the two additions the
                        meeting page's own head and attendance block needed;
     - `.stu-room-door*` the live-room door.
   The meeting page's frame lives here rather than in student-pages.css or
   student-materials.css because it exists to hold the player and because those
   two sheets belong to other workstreams; nothing here re-declares a rule from
   either of them. `public/css/student.css` is READ-ONLY and every rule below is
   additive to it.

   Tokens only. The `--stu-*` family is declared once, on `.stu-shell`, in
   student-shell.css; this file uses them and never re-declares one.

   RTL is global, so list indentation is padding-right. Breakpoints are the
   house pair: 1024px and 767px. */

/* ── THE PLAYER ──────────────────────────────────────────────────────────────
   The single OPAQUE surface on a page of translucent ones. That contrast alone
   is the focus signal: no accent bar, no coloured border, no size increase. */

.stu-deck {
    position: relative;
    margin-bottom: var(--space-xl);
    border: 1px solid rgba(98, 98, 234, 0.10);
    border-radius: var(--radius-xl);
    background: var(--bg-surface);
    box-shadow: var(--stu-lift-3, 0 24px 64px rgba(0, 60, 130, 0.13), 0 2px 6px rgba(0, 60, 130, 0.05));
    overflow: clip;
}

/* ── Head. It names the MEETING and the deck. The SLIDE names itself, inside
   the stage: one title per band. */

.stu-deck__head {
    padding: var(--stu-s-5, 20px) var(--space-lg) var(--space-md);
    border-bottom: 1px solid var(--border-light);
    background: linear-gradient(180deg, rgba(239, 246, 255, 0.55), rgba(255, 255, 255, 0));
}

.stu-deck__headmain {
    min-width: 0;
}

.stu-deck__eyebrow {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 9px;
    margin: 0 0 var(--space-xs);
    font-size: var(--stu-t-xs, 12px);
    letter-spacing: 0;
    color: var(--text-secondary);
}

.stu-deck__eyebrow i {
    width: 15px;
    color: var(--text-tertiary);
}

/* The separator is CSS, so no caller has to choose a punctuation mark for it. */
.stu-deck__eyebrow-part + .stu-deck__eyebrow-part::before {
    content: '';
    display: inline-block;
    width: 3px;
    height: 3px;
    margin-inline-end: 9px;
    border-radius: 50%;
    background: var(--border-default);
    vertical-align: middle;
}

.stu-deck__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--stu-t-xl, 21px);
    font-weight: 500;
    letter-spacing: 0;
    line-height: 1.35;
    color: var(--text-color);
}

/* ── Nothing to read yet ────────────────────────────────────────────────── */

.stu-deck__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-xl) var(--space-lg);
    text-align: center;
}

.stu-deck__empty-mascot {
    width: 128px;
    height: 128px;
}

.stu-deck__empty-mascot svg {
    width: 100%;
    height: 100%;
    display: block;
}

.stu-deck__empty .stu-state {
    margin: 0;
}

/* ── Progress. SOLID, never the brand gradient: the page's gradient budget is
   spent on the marks. The fill is anchored at the track's start edge and grows
   toward its end, which under RTL means it grows leftward on its own. */

.stu-deck__progress {
    height: 2px;
    background: rgba(0, 113, 227, 0.10);
}

.stu-deck__progress > i {
    display: block;
    height: 100%;
    background: var(--primary-color);
    border-start-end-radius: 2px;
    border-end-end-radius: 2px;
    transition: width var(--stu-dur-3, 0.28s) var(--stu-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
}

/* ── THE STAGE, and the projector-slide problem ──────────────────────────────
   Slides are authored for a projector, so a slide taller than the viewport is
   NORMAL, not an error. The rule: the SLIDE scrolls, the PAGE does not move.

     max-height              caps the stage against the viewport
     overflow-y: auto        gives the slide its own scroller
     overscroll-behavior     stops the chain to the page when the slide bottoms
       : contain             out - which is what "the page fighting it" means
     scrollbar-gutter        no 8px reflow when a short slide follows a tall one
     container-type          authored bodies size themselves in cqw units
       : inline-size         (clamp(24px, 3.2cqw, 62px) and forty more like it).
                             Without a size container they resolve against the
                             VIEWPORT instead of against the player. */

.stu-deck__viewport {
    position: relative;
}

.stu-deck__stage {
    container-type: inline-size;
    max-height: min(58vh, 560px);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    scroll-behavior: smooth;
    padding: var(--stu-s-7, 32px) 34px var(--space-xl);
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 60, 130, 0.16) transparent;
}

.stu-deck__stage::-webkit-scrollbar {
    width: 8px;
}

.stu-deck__stage::-webkit-scrollbar-thumb {
    background: rgba(0, 60, 130, 0.16);
    border-radius: 4px;
}

/* THE RING ON A FULL-BLEED CONTROL HAS TO BE DRAWN INSIDE IT.
   `.stu-deck` is `overflow: clip` and both of these controls fill the card from
   edge to edge, so the area-wide ring in student-shell.css - `outline-offset:
   2px` plus a 3px outer box-shadow - is cut away on both flanks. All a keyboard
   reader saw when the stage took focus was a blue line across its top, which on
   this card reads as the progress rail rather than as focus. A negative offset
   puts the outline on the control's own pixels, inside the clip.

   THE CARD CLASS IS IN THE SELECTOR ONLY TO WIN THE CASCADE, and it is why this
   rule works where the bare `.stu-deck__stage:focus-visible` it replaces did
   not. `.stu-shell :is(a, button, summary, input, select, textarea,
   [tabindex]):focus-visible` measures (0,3,0) - `:is()` takes its most specific
   argument and `[tabindex]` is an attribute selector - so the (0,2,0) rule that
   used to sit here lost silently and the offset stayed at +2px. Two classes
   plus the pseudo tie at (0,3,0), and this sheet is linked after
   student-shell.css, so source order decides in its favour. Measured in the
   browser at 1440, not reasoned about. */
.stu-deck .stu-deck__stage:focus-visible,
.stu-deck .stu-deck__counter:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: -3px;
}

/* THE FADE IS A STATE, NEVER A DECORATION. `.is-overflowing` is set by a
   ResizeObserver plus a scroll listener and cleared the moment the reader
   reaches the end. Applied unconditionally it dims the last line of every short
   slide, and on a phone's 420px stage it eats a whole bullet.

   A MASK, not a white overlay: authored slides carry their own backgrounds,
   images and tinted panels, and a hard #fff wash smears a band across them. */
.stu-deck__stage.is-overflowing {
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 44px), transparent 100%);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 44px), transparent 100%);
}

/* The escape. One press drops the cap AND the mask together, and the slide
   scrolls with the page like an article. */
.stu-deck__stage.is-open {
    max-height: none;
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
}

/* The continuation cue. Gated on the same state, and moved OUT of the text
   column to the inline end, so it never sits on the last visible line. */
.stu-deck__more {
    position: absolute;
    bottom: 10px;
    inset-inline-end: var(--space-md);
    display: none;
    align-items: center;
    gap: 6px;
    margin: 0;
    padding: var(--space-xs) 11px;
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.92);
    box-shadow: 0 1px 4px rgba(0, 60, 130, 0.10), 0 0 0 1px rgba(98, 98, 234, 0.10);
    font-size: var(--stu-t-xs, 12px);
    color: var(--text-secondary);
    pointer-events: none;
}

.stu-deck__stage.is-overflowing ~ .stu-deck__more {
    display: inline-flex;
}

.stu-deck__slide-title {
    margin: 0 0 var(--space-md);
    font-family: var(--font-display);
    font-size: var(--stu-t-2xl, 28px);
    font-weight: 300;
    letter-spacing: -0.02em;
    line-height: 1.25;
    color: var(--text-color);
}

/* ── The authored body ──────────────────────────────────────────────────────
   Staff-written HTML, so the shapes it can arrive in are bounded only by what
   the deck editor allows. Give each of them a readable default and, above all,
   keep a wide one INSIDE the card: a code block or a table that overflows must
   scroll itself, never the page.

   Authored bodies ship their own <style> blocks and their own classes, and
   v-html injects that CSS document-globally, so every rule below is a FALLBACK
   at element selectivity that authored classes beat - never a fight the player
   tries to win. */

.stu-deck__body {
    font-size: var(--stu-t-lg, 17px);
    line-height: 1.78;
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

.stu-deck__body strong {
    color: var(--text-color);
    font-weight: 500;
}

.stu-deck__body > * + * {
    margin-top: var(--space-md);
}

.stu-deck__body h1,
.stu-deck__body h2,
.stu-deck__body h3,
.stu-deck__body h4 {
    font-family: var(--font-display);
    font-weight: 400;
    line-height: 1.35;
    color: var(--text-color);
}

.stu-deck__body h1 { font-size: 24px; }
.stu-deck__body h2 { font-size: 21px; }
.stu-deck__body h3 { font-size: 18px; }
.stu-deck__body h4 { font-size: 16px; }

.stu-deck__body ul,
.stu-deck__body ol {
    padding-right: var(--space-lg);
}

.stu-deck__body li + li {
    margin-top: 6px;
}

.stu-deck__body a {
    color: var(--primary-color);
    text-decoration: none;
}

.stu-deck__body a:hover {
    opacity: 0.75;
}

.stu-deck__body code {
    font-family: var(--font-mono);
    font-size: 0.92em;
    background: var(--primary-light);
    border-radius: var(--radius-sm);
    padding: 1px 5px;
}

/* A code block is the ONE place a mono face appears, and it holds no Hebrew. */
.stu-deck__body pre {
    direction: ltr;
    text-align: left;
    font-family: var(--font-mono);
    font-size: var(--stu-t-sm, 13px);
    line-height: 1.6;
    background: #F5F8FD;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md);
    padding: var(--space-md);
    overflow-x: auto;
}

.stu-deck__body pre code {
    background: none;
    padding: 0;
}

.stu-deck__body img,
.stu-deck__body video {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-md);
}

.stu-deck__body table {
    display: block;
    width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
}

.stu-deck__body th,
.stu-deck__body td {
    border: 1px solid var(--border-light);
    padding: 6px 10px;
    text-align: start;
}

.stu-deck__body blockquote {
    border-inline-start: 3px solid var(--primary-border);
    padding-inline-start: var(--space-md);
    color: var(--text-secondary);
}

/* ── An interactive slide, summarised ─────────────────────────────────────
   Content ABOUT the slide rather than the slide, so it is a recess inside the
   opaque player. */

.stu-deck__ask {
    margin-top: var(--space-lg);
    border-radius: var(--radius-md);
    background: var(--stu-well-tint, rgba(8, 148, 255, 0.055));
    box-shadow: var(--stu-well, inset 0 1px 3px rgba(0, 60, 130, 0.08), inset 0 0 0 1px rgba(98, 98, 234, 0.09));
    padding: var(--space-md);
}

/* Was mono + uppercase + 0.12em tracking on a HEBREW word, which is the least
   legible type the area could produce: uppercase is a no-op on a unicameral
   script and tracked caps is a Latin small-caps convention. Size and colour
   carry the label instead. */
.stu-deck__ask-kind {
    margin: 0 0 var(--space-xs);
    font-family: var(--font-body);
    font-size: var(--stu-t-xs, 12px);
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--primary-color);
}

.stu-deck__ask-prompt {
    margin: 0 0 var(--space-md);
    font-size: 17px;
    line-height: 1.6;
    color: var(--text-color);
}

.stu-deck__ask-note {
    margin: var(--space-md) 0 0;
    font-size: 13px;
    color: var(--text-secondary);
}

.stu-deck__options {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.stu-deck__option {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 4px var(--space-sm);
    background: var(--bg-surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
    padding: 10px var(--space-md);
    font-size: 15px;
    color: var(--text-color);
}

.stu-deck__option--correct {
    border-color: var(--primary-border);
}

.stu-deck__option-mark {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--primary-color);
}

.stu-deck__option-mark i {
    width: 14px;
}

/* tabular-nums in the BODY font, not the mono stack: this figure sits beside
   Hebrew and a mono face would fall back to Courier for it. */
.stu-deck__option-share {
    font-family: var(--font-body);
    font-variant-numeric: tabular-nums;
    font-size: 13px;
    color: var(--text-secondary);
}

/* The bar spans both columns, under the label. Its width is the one thing here
   that is a live figure, so it arrives as a computed :style binding. */
.stu-deck__option-bar {
    grid-column: 1 / -1;
    display: block;
    height: 4px;
    border-radius: var(--radius-full);
    background: rgba(0, 113, 227, 0.10);
    overflow: hidden;
}

.stu-deck__option-fill {
    display: block;
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--primary-color);
}

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

.stu-deck__answers li {
    background: var(--bg-surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
    padding: 8px var(--space-md);
    font-size: 15px;
    color: var(--text-color);
    overflow-wrap: anywhere;
}

/* ── The outbound links a slide can carry ───────────────────────────────── */

.stu-deck__link-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin: var(--space-md) 0 0;
    color: var(--text-secondary);
}

.stu-deck__link-row i {
    width: 16px;
    color: var(--text-tertiary);
}

.stu-deck__link-row .stu-linklike {
    padding: 0;
}

/* ── THE NOTE WRITTEN FOR THE STUDENT ────────────────────────────────────────
   Rendered ONLY when the slide carries one. '' is the ordinary value and the
   screen renders nothing at all for it.

   A warm RULE, not a second warm surface, and it reuses the `.stu-shell`
   warning trio that already ships with a measured 5.37:1 justification rather
   than inventing a parallel family beside it. Deliberately NOT --accent-warm,
   which is #6935d8 (purple) and belongs to buttons on the marketing site. */

.stu-deck__notes {
    padding: var(--space-md) var(--stu-s-5, 20px) var(--stu-s-5, 20px);
    border-top: 1px solid var(--border-light);
    /* RTL: the physical RIGHT edge, where a Hebrew margin rule belongs. */
    border-inline-start: 2px solid var(--stu-warn-line, rgba(255, 171, 0, 0.45));
    background: linear-gradient(180deg, var(--stu-warn-wash, rgba(255, 171, 0, 0.12)), rgba(255, 255, 255, 0));
}

.stu-deck__notes-label {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin: 0 0 6px;
    font-size: var(--stu-t-xs, 12px);
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--stu-warn, #8A5C00);
}

.stu-deck__notes-label i {
    width: 14px;
}

.stu-deck__notes-text {
    margin: 0;
    max-width: 78ch;
    font-size: var(--stu-t-md, 15px);
    line-height: 1.72;
    color: var(--text-secondary);
}

/* ── Moving through the deck ─────────────────────────────────────────────────
   DOM order is previous, spacer, [expand | dots], next. Under RTL the first
   flex child sits on the physical RIGHT, which is where the reading starts, so
   the DOM contract the specs pin and the visual order the design asks for are
   the same order and no CSS reorders anything.

   KEY BINDING, which is genuinely ambiguous in an RTL deck and is therefore
   stated in the component too: ArrowLeft = הבאה, ArrowRight = הקודמת. */

.stu-deck__ctl {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px var(--stu-s-5, 20px);
    border-top: 1px solid var(--border-light);
    background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(239, 246, 255, 0.5));
}

.stu-deck__ctl-spacer {
    flex: 1 1 auto;
}

.stu-deck__step {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    min-height: 40px;
    padding-inline: var(--space-md);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.85);
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0;
    color: var(--primary-color);
    cursor: pointer;
    transition: border-color var(--stu-dur-2, 0.22s) ease, background var(--stu-dur-2, 0.22s) ease,
        transform var(--stu-dur-2, 0.22s) ease, box-shadow var(--stu-dur-2, 0.22s) ease;
}

.stu-deck__step i {
    width: 14px;
}

.stu-deck__step:hover:not(:disabled) {
    border-color: var(--primary-color);
    background: var(--primary-light);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 60, 130, 0.08);
}

/* opacity PLUS the disabled attribute, never a lighter grey: two faint greys
   one step apart do not communicate "there is no previous slide". */
.stu-deck__step:disabled {
    opacity: 0.45;
    pointer-events: none;
}

.stu-deck__expand {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    min-height: 36px;
    padding-inline: var(--space-sm);
    border: none;
    border-radius: var(--radius-full);
    background: none;
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--text-secondary);
    cursor: pointer;
    transition: color var(--stu-dur-1, 0.16s) ease, background var(--stu-dur-2, 0.22s) ease;
}

.stu-deck__expand:hover {
    background: var(--fsp-hover-bg);
    color: var(--text-color);
}

.stu-deck__expand i {
    width: 13px;
}

/* ── THE JUMP INDEX ──────────────────────────────────────────────────────────
   A native <details> plus a multi-column list, so 37 rows land on one screen
   with no grid of boxes and no JS, and under RTL the columns fill right to left
   on their own. THE SUMMARY IS THE COUNTER: random access is the only reason
   the index exists, and the counter is the one control always in reach. */

.stu-deck__index {
    border-top: 1px solid var(--border-light);
}

.stu-deck__index > summary {
    list-style: none;
    cursor: pointer;
}

.stu-deck__index > summary::-webkit-details-marker {
    display: none;
}

/* Hebrew stays in the body font and the figures are tabular-nums in the SAME
   font, so there is no second face in the string at all. The `dir="ltr"` on the
   figures is an ATTRIBUTE in the markup, so the UA sheet's `unicode-bidi:
   isolate` actually fires; CSS `direction` alone does not isolate. */
.stu-deck__counter {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    padding: 12px var(--stu-s-5, 20px);
    font-family: var(--font-body);
    font-size: var(--stu-t-xs, 12px);
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-secondary);
}

.stu-deck__counter i {
    width: 14px;
    color: var(--text-tertiary);
}

.stu-deck__counter b {
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--primary-color);
}

.stu-deck__counter:hover {
    color: var(--text-color);
}

.stu-deck__index-panel {
    padding: var(--space-md) var(--space-md) var(--stu-s-5, 20px);
    background: rgba(8, 148, 255, 0.035);
    box-shadow: var(--stu-well, inset 0 1px 3px rgba(0, 60, 130, 0.08), inset 0 0 0 1px rgba(98, 98, 234, 0.09));
}

.stu-deck__index-list {
    list-style: none;
    margin: 0;
    padding: 0;
    columns: 3;
    column-gap: var(--space-xl);
}

.stu-deck__index-list li {
    break-inside: avoid;
    margin: 0;
}

.stu-deck__index-row {
    display: grid;
    grid-template-columns: var(--stu-gutter-num, 22px) 1fr;
    align-items: baseline;
    gap: var(--space-sm);
    width: 100%;
    min-height: 34px;
    padding-inline: var(--space-sm);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    font-family: var(--font-body);
    font-size: var(--stu-t-sm, 13px);
    letter-spacing: 0;
    color: var(--text-secondary);
    text-align: start;
    cursor: pointer;
    transition: background var(--stu-dur-1, 0.16s) ease, color var(--stu-dur-1, 0.16s) ease;
}

.stu-deck__index-row:hover {
    background: rgba(255, 255, 255, 0.9);
    color: var(--text-color);
}

.stu-deck__index-num {
    direction: ltr;
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    color: var(--text-tertiary);
    text-align: center;
}

.stu-deck__index-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stu-deck__index-row[aria-current='true'] {
    background: #fff;
    color: var(--primary-color);
    font-weight: 500;
    box-shadow: 0 1px 3px rgba(0, 60, 130, 0.09);
}

.stu-deck__index-row[aria-current='true'] .stu-deck__index-num {
    color: var(--primary-color);
}

/* ── The plan lecture's materials ───────────────────────────────────────── */

.stu-deck__rule {
    margin: var(--space-lg) var(--stu-s-5, 20px) var(--space-md);
}

.stu-deck__materials-title {
    margin: 0 var(--stu-s-5, 20px) var(--space-sm);
    font-size: 16px;
    font-weight: 500;
    color: var(--text-color);
}

.stu-deck__materials {
    list-style: none;
    margin: 0 0 var(--stu-s-5, 20px);
    padding: 0 var(--stu-s-5, 20px);
}

.stu-deck__materials li {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.stu-deck__materials i {
    width: 16px;
    color: var(--text-tertiary);
}

.stu-deck__materials .stu-linklike {
    padding: 4px 0;
}

/* ── COMPACT VARIANT ─────────────────────────────────────────────────────────
   One modifier on the same component, never a second component: the homework
   excerpt of two or three slides further down the meeting page. */

.stu-deck--compact {
    border-radius: var(--radius-lg);
    box-shadow: var(--stu-lift-2, 0 10px 32px rgba(0, 60, 130, 0.10));
    margin-bottom: 0;
}

.stu-deck--compact .stu-deck__head {
    padding: 14px var(--space-md) var(--stu-s-3, 12px);
}

.stu-deck--compact .stu-deck__title {
    font-size: var(--stu-t-md, 15px);
}

.stu-deck--compact .stu-deck__stage {
    max-height: 280px;
    padding: var(--stu-s-5, 20px) 22px var(--stu-s-7, 32px);
}

/* The expand escape has to be re-stated here. `.stu-deck__stage.is-open` and
   `.stu-deck--compact .stu-deck__stage` are the same specificity (0,2,0), so
   source order decides and the compact cap - declared later in this file - wins
   silently: the class lands, the state is right, and the stage stays 280px.
   Measured in the running app before this rule existed. */
.stu-deck--compact .stu-deck__stage.is-open {
    max-height: none;
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
}

.stu-deck--compact .stu-deck__slide-title {
    font-size: var(--stu-t-xl, 21px);
}

.stu-deck--compact .stu-deck__body {
    font-size: var(--stu-t-md, 15px);
    line-height: 1.7;
}

.stu-deck--compact .stu-deck__ctl {
    padding: 10px 14px;
}

.stu-deck--compact .stu-deck__step {
    min-height: 36px;
    font-size: var(--stu-t-sm, 13px);
    padding-inline: var(--stu-s-3, 12px);
}

.stu-deck--compact .stu-deck__notes {
    padding: var(--space-md);
}

/* Three slides do not need a three-column index; three dots do. */
.stu-deck__dots {
    display: inline-flex;
    gap: 6px;
    align-items: center;
    padding-inline: 6px;
}

.stu-deck__dots i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(0, 60, 130, 0.18);
    transition: background var(--stu-dur-2, 0.22s) ease, transform var(--stu-dur-2, 0.22s) ease;
}

.stu-deck__dots i.is-on {
    background: var(--primary-color);
    transform: scale(1.35);
}

/* ── THE MERGED MEETING PAGE ─────────────────────────────────────────────────
   One meeting, one page: the deck at the top, a tab bar under it, exactly one
   panel. The tab bar wears the house pill group (.stu-tabs / .stu-tab from
   student.css) rather than a second bar, because the courses bar across the top
   of /materials is THE bar in this area and a second one competes with it. */

.stu-meeting {
    display: block;
}

.stu-meeting__material {
    margin-bottom: var(--space-lg);
}

.stu-meeting__tabs {
    display: flex;
    flex-wrap: wrap;
    max-width: 100%;
}

/* Additive to .stu-tab: a 36px pill is a mouse target, and there was no focus
   ring on it at all. The type and the track stay exactly as student.css has
   them. */
.stu-meeting__tab {
    gap: 8px;
    min-height: 40px;
    font-size: var(--stu-t-md, 15px);
    letter-spacing: 0;
}

.stu-meeting__tab i {
    width: 15px;
    color: var(--text-tertiary);
}

.stu-meeting__tab.stu-tab--active i {
    color: var(--primary-color);
}

.stu-meeting__tab:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

.stu-meeting__panel:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 4px;
    border-radius: var(--radius-md);
}

.stu-meeting__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
    max-width: 46ch;
    margin-inline: auto;
    padding: var(--space-xl) var(--space-lg);
    text-align: center;
}

.stu-meeting__empty .stu-empty__mascot {
    width: 128px;
    height: 128px;
    flex: none;
}

.stu-meeting__empty .stu-empty__mascot svg {
    width: 100%;
    height: 100%;
    display: block;
}

.stu-meeting__empty .stu-empty__text {
    margin: 0;
    font-size: var(--stu-t-md, 15px);
    line-height: 1.7;
    color: var(--text-secondary);
}

/* The deck is not open yet: a matter of the clock or a staff call, never of
   money. The mascot is asleep rather than searching, because nothing is wrong. */
.stu-meeting__pending {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
}

.stu-meeting__pending-mascot {
    width: 96px;
    height: 96px;
    flex: none;
}

.stu-meeting__pending-mascot svg {
    width: 100%;
    height: 100%;
    display: block;
}

.stu-meeting__pending-main {
    min-width: 0;
}

.stu-meeting__pending-main .stu-state {
    margin: var(--space-xs) 0 0;
}

/* ── Previous / next meeting ─────────────────────────────────────────────────
   Never wrapping: an arrow with nowhere to go is a control the page does not
   draw at all, rather than one that quietly returns to meeting 1. */

.stu-meetnav {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-lg);
}

.stu-meetnav__spacer {
    flex: 1 1 auto;
}

.stu-meetnav__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    min-height: 40px;
    padding-inline: var(--space-md);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.7);
    font-size: 14px;
    color: var(--primary-color);
    text-decoration: none;
    transition: border-color var(--stu-dur-2, 0.22s) ease, background var(--stu-dur-2, 0.22s) ease;
}

.stu-meetnav__link:hover {
    border-color: var(--primary-color);
    background: var(--primary-light);
}

.stu-meetnav__link i {
    width: 14px;
}

/* ── The meeting page's own head and attendance additions ───────────────── */

.stu-session-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-lg);
}

.stu-session-head__main {
    min-width: 0;
}

.stu-session-head__mascot {
    width: 96px;
    height: 96px;
    flex: none;
}

.stu-session-head__mascot svg {
    width: 100%;
    height: 100%;
    display: block;
}

.stu-attendance__doneblock {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.stu-attendance__mascot {
    width: 96px;
    height: 96px;
    flex: none;
}

.stu-attendance__mascot svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* ── The live-room door ─────────────────────────────────────────────────── */

.stu-room-door {
    margin-top: var(--space-md);
}

.stu-room-door__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
}

.stu-room-door__btn i {
    width: 18px;
}

.stu-room-door__note {
    margin: var(--space-sm) 0 0;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-secondary);
    max-width: 46ch;
}

.stu-room-door__pay {
    display: inline-block;
    padding-top: 2px;
}

/* ── Reduced motion ─────────────────────────────────────────────────────────
   Nothing here animates layout, and a state that only exists as motion is a
   state a reduced-motion reader never sees, so the colours stay and the
   movement goes. */

/* ── THE DECK BODY IN A READING COLUMN ───────────────────────────────────────
   The authored slide, rendered through the room's own template vocabulary. The
   mirror is `deck-slide-templates.css` and this block is the ONLY place the
   student area differs from it - read that file's header first.

   `StudentSessionDeck` puts `slide slide--phone` on the body at every width, and
   that branch is right about type and about images at every width: it is written
   in px for a hand-held column, which is what this surface is on a phone and
   close enough to on a laptop. What it is NOT right about is COLUMN COUNT. It
   was calibrated for 390px, so it collapses every multi-column template to one
   column - and the reading column here is up to 926px, where a three-family
   logo row stacked into a single file reads as three unrelated slides.

   So this block restores the AUTHOR'S OWN column counts (the values in the
   `.slide--display` branch of the mirror, not new ones) above the house tablet
   breakpoint, and nothing else. Everything about a slide that is not a column
   count is left exactly as the room wrote it.

   SCOPED TO `.stu-deck__body`, never to `.slide--phone`: the mirror is in the
   PUBLIC bundle that every marketing page loads, and an unscoped rule here would
   restyle a class nobody on those pages knows they are carrying.

   `.stu-deck__body.slide` IS THE SPECIFICITY, and it is why the compound is
   written out rather than the plainer `.stu-deck__body`. The rules being
   overridden are `.slide--phone .tpl-x`, which is (0,2,0) - exactly what a bare
   `.stu-deck__body.slide .tpl-x` would be - and the mirror is linked AFTER this file
   in index.html, so at equal specificity the mirror wins and nothing here would
   apply. Both classes are genuinely on the element (StudentSessionDeck writes
   `stu-deck__body slide slide--phone`), so this costs a real selector nothing
   and states the dependency instead of hiding it in the link order. */

@media (min-width: 768px) {
    /* Three families in a row, which is what the slide is about. `auto-fit`
       rather than a fixed 3, so a two-logo or a five-logo slide is also right
       and a narrow tablet still wraps. */
    .stu-deck__body.slide .tpl-logos {
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    }

    /* The logo mark at 40px is a phone size sitting under 17px body copy. */
    .stu-deck__body.slide .tpl-logo__img {
        block-size: 56px;
    }

    /* The card grid's own count, which a body sets with .tpl-grid--2/3/4 and
       which defaults to 3 - the same declaration the display branch carries. */
    .stu-deck__body.slide .tpl--cards .tpl-grid {
        grid-template-columns: repeat(var(--tpl-cols, 3), minmax(0, 1fr));
    }

    /* Text beside its artefact, and the two columns a comparison IS. A compare
       template stacked into one column is two lists, and the reader has to hold
       the first one in their head to read the second. */
    .stu-deck__body.slide .tpl--split .tpl-split {
        grid-template-columns: var(--tpl-split, 1fr 1fr);
    }

    .stu-deck__body.slide .tpl--compare .tpl-cmp {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .stu-deck__body.slide .tpl-steps--2col {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* The portrait beside the quote rather than above it. */
    .stu-deck__body.slide .tpl-quote {
        grid-template-columns: auto 1fr;
    }
}

/* ── FULL SCREEN ─────────────────────────────────────────────────────────────
   The slide and its note over the whole page, with the area's side menu left
   standing beside it.

   `display: contents` is what makes this one set of nodes rather than two. In
   the ordinary state the wrapper is not in the layout at all and the stage, the
   note, the step controls and the index are the card's own children exactly as
   they were; opening full screen turns the wrapper into a fixed panel around the
   same elements. Nothing is duplicated, so the slide the reader was on and the
   scroll position inside a tall one both survive the press.

   `inset-inline-start` is the rail's width because the area is RTL and the rail
   is the first flex child of `.stu-shell`, which puts it on the PHYSICAL RIGHT -
   the inline start. So this leaves the menu visible and covers everything else.
   The fallback repeats 288px for the one case where the token cannot be reached:
   a layer that ever escapes `.stu-shell`.

   Its own ground rather than a scrim: the layer is a reading surface, and text
   over a darkened page is a dialog. It is the shell's own near-white with the
   same soft radials, so full screen reads as the page having grown rather than
   as something covering it. */
.stu-deck__layer {
    display: contents;
}

.stu-deck__layer.is-full {
    display: flex;
    flex-direction: column;
    position: fixed;
    inset-block: 0;
    inset-inline-start: var(--stu-rail-w, 288px);
    inset-inline-end: 0;
    z-index: 1000;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-lg) clamp(var(--space-md), 4vw, 48px) var(--space-xl);
    gap: var(--space-md);
    background:
        radial-gradient(ellipse 980px 640px at 88% -8%, rgba(8, 148, 255, 0.11), transparent 62%),
        radial-gradient(ellipse 880px 720px at 4% 96%, rgba(105, 53, 216, 0.085), transparent 64%),
        #FAFCFF;
}

/* The card is `overflow: clip`, which does not clip a fixed descendant - but a
   layer whose correctness rests on that staying true across browsers is one
   repaint away from being half-clipped on somebody's phone. Lifted only while
   the layer is open, and the card underneath is covered by then anyway. */
.stu-deck:has(.stu-deck__layer.is-full) {
    overflow: visible;
}

/* THE CAP IS THE LAYER'S, NOT THE STAGE'S. Inside full screen the stage stops
   being a scroller of its own and grows to whatever the slide needs; the layer
   is what scrolls. Two nested scrollers on a phone is a slide that moves under
   a thumb aiming at the page. */
.stu-deck__layer.is-full .stu-deck__stage {
    max-height: none;
    overflow: visible;
    flex: 0 0 auto;
    -webkit-mask-image: none;
    mask-image: none;
}

/* The bar: which slide this is, and the way out. Sticky, so the X is still in
   reach at the foot of a slide authored for a projector. */
.stu-deck__fsbar {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding-block: var(--space-sm);
    /* The layer's ground, opaque, so a slide scrolling under the bar does not
       show through it. */
    background: #FAFCFF;
}

.stu-deck__fspos {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    font-size: var(--stu-t-md, 15px);
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stu-deck__fscount {
    font-family: var(--font-mono);
    font-size: var(--stu-t-xs, 12px);
    color: var(--text-tertiary);
}

.stu-deck__fsclose {
    flex: none;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid var(--stu-glass-line, rgba(98, 98, 234, 0.14));
    border-radius: var(--radius-full);
    background: var(--bg-surface);
    color: var(--text-secondary);
    font-size: 17px;
    cursor: pointer;
}

.stu-deck__fsclose:hover {
    color: var(--primary-color);
    border-color: var(--primary-border);
}

/* The step controls stay in reach at the foot of a slide authored for a
   projector, so stepping through a deck never needs a scroll back down.

   STICKY, NOT PINNED TO THE BOTTOM. `margin-top: auto` would push them there on
   a SHORT slide too, and a title card left half a screen of nothing between the
   note and the controls. Sticky does nothing at all when there is no scrolling
   and everything when there is, which is exactly the difference between those
   two cases. */
.stu-deck__layer.is-full .stu-deck__ctl {
    position: sticky;
    bottom: 0;
    border-radius: var(--radius-lg);
    border: 1px solid var(--stu-glass-line, rgba(98, 98, 234, 0.14));
    background: var(--bg-surface);
}

/* The card's own full-bleed blocks are edge-to-edge because the card clips
   them. Inside the layer there is no card, so they get their own shape. */
.stu-deck__layer.is-full .stu-deck__notes,
.stu-deck__layer.is-full .stu-deck__index {
    border-radius: var(--radius-lg);
    border: 1px solid var(--stu-glass-line, rgba(98, 98, 234, 0.14));
}

@media (prefers-reduced-motion: reduce) {
    .stu-deck__progress > i,
    .stu-deck__step,
    .stu-deck__dots i {
        transition: none;
    }

    .stu-deck__stage {
        scroll-behavior: auto;
    }

    .stu-deck__step:hover:not(:disabled) {
        transform: none;
    }
}

/* ── Coarse pointers pay the space; a mouse keeps the tight desktop rhythm. */

@media (pointer: coarse) {
    /* EVERY control in this file, not a selection of them. The expand escape
       and the two meeting arrows were left out of the first version of this
       block and measured 36px and 40px on a phone, which is the one surface a
       student reads the material on. The compact homework player needs the
       expand control MORE than the full one does - its stage is 280px and a
       homework slide authored for a projector does not fit in it - so a
       finger-sized target on it is not a nicety. Measured with
       `pointer: coarse` actually matching, not assumed. */
    .stu-deck__index-row,
    .stu-deck__step,
    .stu-deck__expand,
    .stu-meeting__tab,
    .stu-meetnav__link {
        min-height: 44px;
    }

    .stu-deck__index-row {
        padding-inline: var(--stu-s-3, 12px);
    }

    .stu-deck__dots i {
        width: 8px;
        height: 8px;
    }
}

/* ── Tablet ─────────────────────────────────────────────────────────────── */

@media (max-width: 1024px) {
    .stu-deck__stage {
        max-height: min(62vh, 480px);
        padding: var(--space-lg);
    }

    /* The side menu is a drawer below this width, so there is nothing on screen
       to leave room for and the layer takes the whole viewport. The drawer sits
       at z-index 1002 and still opens over it. */
    .stu-deck__layer.is-full {
        inset-inline: 0;
        padding-inline: var(--space-md);
    }

    .stu-deck__index-list {
        columns: 2;
    }
}

/* ── Phone ──────────────────────────────────────────────────────────────── */

@media (max-width: 767px) {
    .stu-deck__head {
        padding: var(--space-md) var(--space-md) var(--space-sm);
    }

    .stu-deck__slide-title {
        font-size: 21px;
    }

    .stu-deck__body {
        font-size: var(--stu-t-md, 15px);
    }

    .stu-deck__stage {
        max-height: min(56vh, 420px);
        padding: var(--space-md);
    }

    .stu-deck__stage.is-overflowing {
        -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent 100%);
        mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent 100%);
    }

    /* Wrapped, and each step takes half the row, so neither label is squeezed
       into a column a few characters wide. */
    .stu-deck__ctl {
        flex-wrap: wrap;
        padding: var(--space-sm) var(--space-md);
    }

    .stu-deck__step {
        flex: 1 1 0;
        justify-content: center;
        min-height: 44px;
    }

    .stu-deck__ctl-spacer {
        display: none;
    }

    .stu-deck__expand {
        flex: 1 1 100%;
        order: 3;
        justify-content: center;
    }

    .stu-deck__dots {
        flex: 1 1 100%;
        order: 3;
        justify-content: center;
    }

    .stu-deck__counter {
        padding: 12px var(--space-md);
    }

    .stu-deck__index-list {
        columns: 1;
    }

    .stu-deck__index-panel {
        max-height: 50vh;
        overflow-y: auto;
    }

    .stu-deck__notes {
        padding: var(--space-md);
    }

    .stu-deck__rule,
    .stu-deck__materials-title,
    .stu-deck__materials {
        margin-inline: var(--space-md);
    }

    .stu-deck__materials {
        padding-inline: 0;
    }

    /* The head stacks rather than shrinking the robot: 96px is the artwork's
       floor (StudentMascot.vue), below which the 3-unit stroke aliases into
       fuzz, so a narrow screen gives it a row of its own instead. */
    .stu-session-head {
        flex-direction: column-reverse;
        align-items: flex-start;
        gap: var(--space-sm);
    }

    .stu-meeting__pending {
        flex-direction: column;
        align-items: flex-start;
    }

    .stu-attendance__doneblock {
        flex-direction: column;
        align-items: flex-start;
    }

    .stu-room-door__btn {
        width: 100%;
        justify-content: center;
    }
}
