/* Student area (students.10xcourse.ai) - every .stu-* surface.
   PUBLIC bundle on purpose: 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 `/css/admin`). Tokens come from global.css only. */

/* ── Shell layout ─────────────────────────────────────────────── */

.stu-shell {
    /* The student area's warning trio, declared HERE and never on :root: this
       sheet ships in the PUBLIC bundle, which every page of the site loads, and
       a :root token here would leak site-wide from a file nobody opens when
       they change the marketing pages.

       There is no orange in the public palette, and the admin sheets'
       `--wa-warn` family cannot be borrowed: bundle-css.mjs partitions the two
       bundles on the literal prefix `/css/admin`, so an admin class renders in
       dev and arrives UNSTYLED in production.

       Measured, not eyeballed. The wash composites to #FFF5E0 over white; on
       that ground #8A5C00 measures 5.37:1 and a 13px label in
       var(--text-secondary) measures 6.18:1, both clear of the 4.5:1 floor this
       sheet holds itself to (the .stu-avatar comment below rejected
       --primary-color at 3.86:1 for the same reason). The obvious heritage pick
       - the retired warm accent #C4551A, still fossilised in
       --accent-warm-light - FAILS at 4.14:1 and must not be substituted back. */
    --stu-warn: #8A5C00;
    --stu-warn-wash: rgba(255, 171, 0, 0.12);
    --stu-warn-line: rgba(255, 171, 0, 0.45);

    min-height: 100vh;
    display: flex;
    background:
        radial-gradient(ellipse at 85% 0%, rgba(8, 148, 255, 0.06) 0%, transparent 55%),
        radial-gradient(ellipse at 10% 90%, rgba(105, 53, 216, 0.05) 0%, transparent 60%),
        var(--bg-surface);
}

/* RTL: the first flex child sits on the right, where the menu belongs. */
.stu-nav {
    width: 264px;
    flex-shrink: 0;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding: 28px 20px;
    background: rgba(255, 255, 255, 0.7);
    border-left: 1px solid var(--border-light);
}

.stu-nav__logo {
    display: block;
    text-decoration: none;
    padding: 4px 8px 0;
}

.stu-nav__logo svg,
.stu-topbar__logo svg,
.stu-login__logo svg {
    width: 100%;
    max-width: 190px;
    height: auto;
}

/* ── The rail's identity block ─────────────────────────────────
   Replaced the "אזור התלמידים" caption in 2026-08: a person's own name and
   address orient better than a label naming the screen they are already on.
   Hairline below rather than a card, so the rail keeps one visual weight and
   the picture is the only thing in it that draws the eye. */
.stu-nav__identity {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 8px 14px;
    border-bottom: 1px solid var(--border-light);
}

/* min-width: 0 is what actually lets the email ellipsis: without it the flex
   item takes its max-content width and pushes the 264px rail wider. */
.stu-nav__who {
    min-width: 0;
    flex: 1 1 auto;
}

.stu-nav__name {
    margin: 0;
    font-size: 15px;
    font-weight: 500;
    line-height: 1.35;
    color: var(--text-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* dir="ltr" in the markup, `direction` here too: an address truncated in an
   RTL context clips its START, which is the half that identifies the person.
   `text-align: end` and NOT `start`: under `direction: ltr` `start` computes to
   LEFT, and the name above it is RTL and flush RIGHT, so the two lines of one
   identity block hung off opposite edges - measured at a 57px stagger. `end`
   pulls the address back under the name; the overflow direction is unchanged,
   so a long address still loses its DOMAIN to the ellipsis rather than the
   local part that identifies the person. */
.stu-nav__email {
    margin: 2px 0 0;
    direction: ltr;
    text-align: end;
    font-size: 12.5px;
    line-height: 1.35;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The rail has no room for a sentence, so this one wraps and stays terse. */
.stu-nav__avatar-error {
    margin: 0;
    padding: 0 8px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--alert-color);
}

.stu-nav__menu {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Quiet by design: a group heading orients, it does not compete with the
   destinations under it. Letter-spacing rather than weight or colour is what
   makes a 11.5px label read as a label. */
.stu-nav__group-title {
    margin: 18px 0 4px;
    padding: 0 14px;
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
}

.stu-nav__group {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.stu-nav__link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 44px;
    padding: 0 14px;
    border: none;
    border-radius: var(--radius-md);
    background: transparent;
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: 400;
    color: var(--text-color);
    text-decoration: none;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}

/* The active indicator. A bar on the INLINE START edge, which RTL puts on the
   right - it reads as "you are here" without the item having to shout in
   colour or weight. Inset 4px rather than flush at 0: the item's background is
   a 12px-radius pill, and a straight bar against a rounded corner reads as a
   stray element floating beside the row instead of part of it. Animated on
   height so it grows out of the item rather than blinking on. */
.stu-nav__link::before {
    content: '';
    position: absolute;
    inset-inline-start: 4px;
    top: 50%;
    width: 3px;
    height: 0;
    border-radius: var(--radius-full);
    background: var(--primary-color);
    transform: translateY(-50%);
    transition: height 0.2s ease;
}

.stu-nav__icon {
    flex: 0 0 20px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
    transition: color 0.2s ease;
}

/* Fixed HEIGHT and auto width: the FontAwesome viewBoxes are not all square,
   and pinning both axes squashes the wide ones. The 20px flex basis above is
   what keeps the column of icons aligned. Two classes deep on purpose - it has
   to outrank app-icon.css's bare `.app-icon-svg { height: 1em }`. */
.stu-nav__icon .app-icon-svg {
    height: 17px;
    width: auto;
}

.stu-nav__link:hover {
    background: var(--fsp-hover-bg);
}

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

.stu-nav__link.router-link-exact-active {
    background: var(--primary-light);
    color: var(--primary-color);
    font-weight: 500;
}

.stu-nav__link.router-link-exact-active::before {
    height: 20px;
}

.stu-nav__link.router-link-exact-active .stu-nav__icon {
    color: var(--primary-color);
}

/* Logout sits in the account group as an equal item, but it is not a
   destination - quieter ink keeps it from reading as one. */
.stu-nav__link--signout {
    color: var(--text-secondary);
}

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

/* FontAwesome has no RTL twin of `right-from-bracket` in 6.5.1 thin, and an
   exit arrow that points along the reading direction reads as ENTERING in
   Hebrew. Mirroring the glyph is the whole fix; the bracket is symmetrical. */
.stu-nav__link--signout .stu-nav__icon {
    transform: scaleX(-1);
}

/* ── Focus ─────────────────────────────────────────────────────
   One visible ring for every control the rail and the top bar own. :focus-visible
   and not :focus, so a mouse press does not leave a ring behind. */
.stu-nav__link:focus-visible,
.stu-avatar-btn:focus-visible,
.stu-topbar__menu:focus-visible,
.stu-nav__logo:focus-visible,
.stu-topbar__logo:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

.stu-main {
    flex: 1;
    min-width: 0;
    max-width: 1040px;
    padding: 40px clamp(20px, 4vw, 56px) 96px;
}

/* ── Top bar + drawer (tablet / phone) ───────────────────────── */

.stu-topbar {
    display: none;
}

.stu-drawer-overlay {
    position: fixed;
    inset: 0;
    z-index: 1001;
    background: rgba(25, 26, 35, 0.4);
}

@media (max-width: 1024px) {
    .stu-shell {
        display: block;
    }

    .stu-topbar {
        position: sticky;
        top: 0;
        z-index: 1000;
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 8px 14px;
        background: rgba(255, 255, 255, 0.85);
        backdrop-filter: blur(16px) saturate(160%);
        -webkit-backdrop-filter: blur(16px) saturate(160%);
        border-bottom: 1px solid var(--border-light);
    }

    .stu-topbar__logo {
        display: block;
        max-width: 170px;
    }

    .stu-topbar__menu {
        display: inline-flex;
        flex-direction: column;
        justify-content: center;
        gap: 5px;
        width: 44px;
        height: 44px;
        padding: 0 10px;
        border: none;
        border-radius: var(--radius-md);
        background: transparent;
        cursor: pointer;
    }

    .stu-topbar__menu span {
        display: block;
        height: 2px;
        border-radius: 1px;
        background: var(--text-color);
    }

    .stu-topbar__menu:hover {
        background: var(--fsp-hover-bg);
    }

    /* The rail becomes a right-side drawer. */
    .stu-nav {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        z-index: 1002;
        width: min(300px, 84vw);
        height: auto;
        border-left: none;
        background: #fff;
        box-shadow: var(--shadow-lg);
        transform: translateX(100%);
        transition: transform 0.28s ease;
    }

    .stu-nav--open {
        transform: translateX(0);
    }

    .stu-main {
        max-width: none;
        padding: 24px 20px 80px;
    }
}

@media (max-width: 767px) {
    .stu-main {
        padding: 20px 16px 72px;
    }
}

/* ── Avatar ───────────────────────────────────────────────────
   ONE vocabulary, three call sites: the rail's identity block, the account
   page's picture card, and whatever comes next. Kept in one section rather
   than split between the shell block above and the account block below,
   because the disc, the button that wraps it and the card that frames it are
   one design and have to move together.

   .stu-avatar        the disc itself (image, initials or glyph)
   .stu-avatar-btn    the rail's control: the disc IS the button
   .stu-avatar-file   the hidden <input type="file"> both controls drive
   .stu-avatar-card   the account page's full card */

.stu-avatar {
    position: relative;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    border-radius: 50%;
    /* A tint of the brand blues rather than a flat grey: the disc is the one
       warm object in a rail of text, and a grey circle reads as a placeholder
       that failed to load. NOT the brand gradient - that stays rare. */
    background:
        linear-gradient(140deg, rgba(8, 148, 255, 0.16) 0%, rgba(105, 53, 216, 0.13) 100%);
    /* Cool-blue ring, the shadow family the rest of the site uses. */
    box-shadow: 0 0 0 1px rgba(0, 60, 130, 0.08), 0 1px 3px rgba(0, 60, 130, 0.06);
    /* NOT --primary-color. #0071E3 initials measure 3.86:1 at this gradient's
       blue end and 3.76:1 at its purple end, computed through the real stack
       (the shell tint, then the rail's 0.7 white, then this fill), against the
       4.5:1 AA floor the 16px --md size is judged by - the rail's disc is the
       one place initials are small text. This darker blue measures 5.17:1 and
       5.02:1 on the same two ends, and still reads as the brand blue. */
    --stu-avatar-ink: #0B5CC4;
    color: var(--stu-avatar-ink);
    font-family: var(--font-body);
    /* 400, never heavier: a solid brand blue at 500+ starts to read as a
       gradient-weight violation's cousin, and light letterforms are the house
       look. */
    font-weight: 400;
    line-height: 1;
    user-select: none;
}

.stu-avatar__img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.stu-avatar__initials {
    letter-spacing: 0.02em;
}

/* 0.8, not 0.55: the glyph is a non-text graphic and answers to the 3:1 floor
   of WCAG 1.4.11. At 0.55 the brand blue washed out to 2.06:1 against the disc's
   own fill; at 0.8 the darker ink measures 3.61:1 / 3.57:1. */
.stu-avatar__glyph {
    color: var(--stu-avatar-ink);
    opacity: 0.8;
}

.stu-avatar--md {
    width: 44px;
    height: 44px;
    font-size: 16px;
}

.stu-avatar--lg {
    width: 96px;
    height: 96px;
    font-size: 34px;
}

.stu-avatar--md .app-icon-svg {
    height: 19px;
}

.stu-avatar--lg .app-icon-svg {
    height: 40px;
}

/* The rail's control. A real <button> wrapping the disc, so it is tabbable and
   announced; the badge is the affordance that says the picture can change. */
.stu-avatar-btn {
    position: relative;
    display: inline-flex;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    line-height: 0;
    cursor: pointer;
}

.stu-avatar-btn .stu-avatar {
    transition: filter 0.2s ease;
}

/* Always present, quiet by default: a badge that only appears on hover is
   invisible on a phone, where there is no hover at all. */
.stu-avatar-btn__badge {
    position: absolute;
    inset-inline-end: -3px;
    bottom: -3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--bg-surface);
    box-shadow: 0 0 0 1px rgba(0, 60, 130, 0.1);
    color: var(--text-secondary);
    transition: color 0.2s ease, box-shadow 0.2s ease;
}

.stu-avatar-btn__badge .app-icon-svg {
    height: 10px;
    width: auto;
}

.stu-avatar-btn:hover .stu-avatar-btn__badge,
.stu-avatar-btn:focus-visible .stu-avatar-btn__badge {
    color: var(--primary-color);
    box-shadow: 0 0 0 1px var(--primary-border);
}

.stu-avatar-btn:hover .stu-avatar {
    filter: brightness(0.94);
}

.stu-avatar-btn--busy,
.stu-avatar-btn[disabled] {
    cursor: default;
}

.stu-avatar-btn--busy .stu-avatar {
    opacity: 0.55;
}

/* The mechanism, not a control - the button beside it is what a person
   presses. display:none still allows a programmatic .click(). */
.stu-avatar-file {
    display: none;
}

/* ── The account page's picture card ──────────────────────────── */

.stu-avatar-card__body {
    display: flex;
    align-items: flex-start;
    gap: var(--space-lg);
}

.stu-avatar-card__side {
    min-width: 0;
    flex: 1 1 auto;
}

.stu-avatar-card__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md);
    margin-bottom: 10px;
}

.stu-avatar-card__hint {
    margin: 0;
    max-width: 52ch;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-secondary);
}

/* The card's own error sits under the whole body, not beside the buttons. */
.stu-avatar-card .stu-form__error {
    margin-top: var(--space-md);
}

@media (max-width: 767px) {
    /* Stacked: 96px of picture beside a paragraph leaves the paragraph about
       twelve characters wide on a phone. */
    .stu-avatar-card__body {
        flex-direction: column;
        align-items: center;
        text-align: center;
        gap: var(--space-md);
    }

    .stu-avatar-card__actions {
        justify-content: center;
    }

    .stu-avatar-card__hint {
        max-width: none;
    }
}

/* ── The framing dialog: drag and zoom before the upload ──────
   StudentAvatarCropper.vue, opened by BOTH avatar controls through
   useStudentAvatarPicker - the rail's button and the card above. It teleports
   to <body>, so nothing here may rely on sitting inside .stu-shell, and the
   buttons come from global.css rather than from a rule in this section.

   .stu-crop-overlay   the dimmed full-screen layer
   .stu-crop           the panel
   .stu-crop__stage    the square crop window; its canvas IS the exported crop
   .stu-crop__mask     the dimmed corners and the circle's hairline */

.stu-crop-overlay {
    position: fixed;
    inset: 0;
    /* Above the rail-as-drawer (1002): on a phone the picker is opened FROM the
       drawer, which is still on screen underneath. */
    z-index: 1100;
    display: flex;
    justify-content: center;
    align-items: center;
    /* `safe` keeps a panel taller than a short landscape phone reachable -
       plain centring pushes its top out of the scroll range. Declared twice on
       purpose: a browser that does not know the keyword drops the second line
       and keeps the first. */
    align-items: safe center;
    padding: var(--space-md);
    overflow-y: auto;
    overscroll-behavior: contain;
    background: rgba(25, 26, 35, 0.55);
}

.stu-crop {
    width: min(380px, 100%);
    padding: var(--space-lg);
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
    box-shadow: var(--shadow-lg);
}

/* The panel itself takes focus when the dialog opens (useModalFrame), and a
   ring around the whole dialog reads as an error rather than as a position. */
.stu-crop:focus {
    outline: none;
}

.stu-crop__title {
    margin: 0 0 var(--space-sm);
    font-family: var(--font-display);
    font-size: 20px;
    font-weight: 500;
    color: var(--text-color);
}

.stu-crop__hint {
    margin: 0 0 var(--space-md);
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.stu-crop__stage {
    position: relative;
    /* 264px fits inside the panel on a 320px phone, and `min()` hands the rest
       to the panel's own padding below that. */
    width: min(264px, 100%);
    aspect-ratio: 1 / 1;
    margin: 0 auto;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--bg-secondary);
    /* The pan is Pointer Events, so the browser must not ALSO read a drag as a
       scroll or a pinch. Without this a touch drag scrolls the page under the
       dialog and the picture never moves. */
    touch-action: none;
    cursor: grab;
    user-select: none;
    -webkit-user-select: none;
}

.stu-crop__stage:active {
    cursor: grabbing;
}

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

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

/* Outside the inscribed circle is dimmed: those pixels ARE stored, but every
   surface that shows an avatar clips it to a disc, so drawing them at full
   strength would promise something the disc does not keep. The hairline is the
   frame the student is actually aiming at. Pointer-transparent, so the drag
   underneath is uninterrupted. */
.stu-crop__mask {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
        circle closest-side,
        rgba(25, 26, 35, 0) 98.5%,
        rgba(25, 26, 35, 0.5) 100%
    );
}

.stu-crop__mask::after {
    content: '';
    position: absolute;
    inset: 0;
    border: 1px solid rgba(255, 255, 255, 0.7);
    border-radius: 50%;
}

.stu-crop__zoom {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin: var(--space-md) 0 var(--space-lg);
}

.stu-crop__zoom-label {
    flex: 0 0 auto;
    font-size: 14px;
    color: var(--text-secondary);
}

/* Native, deliberately: the platform's range is keyboard-operable, announced
   with its label and its value, and sized by the reader's own touch settings.
   `accent-color` is the whole restyle. Its direction is left to inherit, so
   under RTL the small end sits on the right where the reading starts. */
.stu-crop__range {
    flex: 1 1 auto;
    min-width: 0;
    accent-color: var(--primary-color);
}

.stu-crop__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
}

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

    /* Two full-width targets rather than two small ones at the start of a line
       the thumb has to reach across. */
    .stu-crop__actions .btn-primary,
    .stu-crop__actions .btn-secondary {
        flex: 1 1 auto;
        text-align: center;
    }
}

/* ── Shared surfaces ─────────────────────────────────────────── */

/* The house glass card (lp.css model). */
.stu-card {
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: blur(16px) saturate(160%);
    -webkit-backdrop-filter: blur(16px) saturate(160%);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: var(--space-lg);
    margin-bottom: var(--space-md);
}

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

.stu-page-head__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 32px;
    font-weight: 300;
    color: var(--text-color);
}

.stu-page-head__sub {
    margin: 6px 0 0;
    font-size: 15px;
    color: var(--text-secondary);
}

.stu-page-head__crumb {
    margin: 0 0 6px;
    font-size: 14px;
    color: var(--text-secondary);
}

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

.stu-section-title {
    margin: 0 0 var(--space-md);
    font-size: 20px;
    font-weight: 500;
    color: var(--text-color);
}

.stu-group-title {
    margin: var(--space-lg) 0 var(--space-sm);
    font-size: 15px;
    font-weight: 500;
    color: var(--text-secondary);
}

.stu-state {
    color: var(--text-secondary);
    font-size: 15px;
}

.stu-preline {
    white-space: pre-line;
    margin: 0;
}

.stu-linklike {
    display: inline-block;
    padding: 10px 2px;
    border: none;
    background: none;
    font-family: var(--font-body);
    font-size: 15px;
    color: var(--primary-color);
    text-decoration: none;
    cursor: pointer;
}

.stu-linklike:hover {
    opacity: 0.75;
}

/* global.css dims .btn-primary/.btn-secondary when disabled and this one sits
   BESIDE a .btn-secondary in the picture card, so without this rule a disabled
   "הסרת תמונה" kept full brand blue and a pointer cursor next to a visibly
   dimmed sibling, and swallowed the press in silence. Same numbers as global's,
   deliberately. */
.stu-linklike:disabled {
    opacity: 0.45;
    pointer-events: none;
}

/* ── Chips ───────────────────────────────────────────────────── */

.stu-chip {
    display: inline-block;
    padding: 3px 10px;
    border-radius: var(--radius-full);
    font-size: 12.5px;
    font-weight: 500;
    white-space: nowrap;
}

.stu-chip--hybrid {
    background: var(--primary-light);
    color: var(--primary-color);
    border: 1px solid var(--primary-border);
}

.stu-chip--online {
    background: rgba(105, 53, 216, 0.07);
    color: var(--accent-warm);
    border: 1px solid rgba(105, 53, 216, 0.22);
}

.stu-chip--refund {
    margin-right: 8px;
    background: var(--alert-light);
    color: var(--alert-color);
    border: 1px solid var(--alert-border);
}

.stu-chip--status-active {
    background: rgba(22, 130, 93, 0.08);
    color: #16825D;
    border: 1px solid rgba(22, 130, 93, 0.25);
}

.stu-chip--status-completed {
    background: var(--primary-light);
    color: var(--primary-color);
    border: 1px solid var(--primary-border);
}

.stu-chip--status-cancelled,
.stu-chip--status-refunded {
    background: var(--alert-light);
    color: var(--alert-color);
    border: 1px solid var(--alert-border);
}

/* Both finance panels map `frozen` to מוקפא and there was no rule for it, so a
   frozen sale rendered a bare, unstyled chip. Neutral by choice: a pause is
   neither an active seat nor an alert. */
.stu-chip--status-frozen {
    background: var(--bg-secondary);
    color: var(--text-secondary);
    border: 1px solid var(--border-default);
}

/* ── Live-meeting banner ─────────────────────────────────────── */

.stu-live-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: var(--space-lg);
    padding: 14px 20px;
    background: rgba(105, 53, 216, 0.07);
    border: 1px solid rgba(105, 53, 216, 0.28);
    border-radius: var(--radius-lg);
}

.stu-live-banner__label {
    font-size: 17px;
    font-weight: 500;
    color: var(--text-color);
}

.stu-live-banner__cta {
    font-size: 15px;
    padding: 10px 22px;
}

/* ── Login page ──────────────────────────────────────────────── */

.stu-login {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 56px 20px 80px;
    background:
        radial-gradient(ellipse at 85% 0%, rgba(8, 148, 255, 0.06) 0%, transparent 55%),
        radial-gradient(ellipse at 10% 90%, rgba(105, 53, 216, 0.05) 0%, transparent 60%),
        var(--bg-surface);
}

.stu-login__header {
    text-align: center;
}

.stu-login__logo {
    display: inline-block;
    max-width: 260px;
}

.stu-login__title {
    margin: 14px 0 0;
    font-family: var(--font-display);
    font-size: 26px;
    font-weight: 300;
    color: var(--text-color);
}

.stu-login__header .brand-hairline {
    margin-block: 24px;
}

.stu-login__card {
    width: 100%;
    max-width: 420px;
    padding: var(--space-xl);
}

.stu-login__subtitle {
    margin: 0 0 var(--space-md);
    font-size: 19px;
    font-weight: 500;
}

.stu-login__links {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    margin-top: var(--space-md);
}

.stu-login__sent {
    margin: 0;
    color: var(--text-color);
    line-height: 1.65;
}

.stu-google__button {
    display: flex;
    justify-content: center;
    min-height: 44px;
    /* This box is what StudentLoginView measures to size the Google pill: GIS
       draws a fixed-pixel button, so its width comes from here and nothing in
       CSS can reflow it afterwards. Keep it a plain full-width block. */
}

.stu-google__divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: var(--space-md) 0;
    color: var(--text-secondary);
    font-size: 13.5px;
}

.stu-google__divider::before,
.stu-google__divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border-light);
}

/* A phone gets the roomier card the login form was always missing: 40px of
   padding either side left barely 270px of content on a 390px screen, which is
   what squeezed the Google pill out past the card edge. Matches
   .stu-frozen, the card that stands in this one's place. */
@media (max-width: 767px) {
    .stu-login__card {
        padding: var(--space-lg);
    }
}

/* ── Frozen account notice ───────────────────────────────────────
   StudentFrozenNotice, which stands in place of the login card while the
   server is answering 403 account_frozen. Same glass card as the form it
   replaces, a little wider because the two contact rows carry an address and a
   phone number that must never wrap mid-token. Centred like the funnel's state
   screens (.payl-state / .enr-state), which is the closest thing the site has
   to this page. */

.stu-frozen {
    width: 100%;
    max-inline-size: 460px;
    padding: var(--space-xl);
    text-align: center;
}

/* Fixed square box so the artwork never shifts the text while it decodes; the
   aspect ratio and the svg sizing come from .enr-mascot on the component. */
.stu-frozen__mascot {
    inline-size: 128px;
    margin-inline: auto;
    margin-block-end: var(--space-sm);
}

.stu-frozen__title {
    margin: 0 0 var(--space-md);
    font-family: var(--font-display);
    font-size: 24px;
    /* The house display weight. Solid ink, no gradient: this is the one line on
       the page a worried reader reads first, and it has to be legible before it
       is decorative. */
    font-weight: 300;
    color: var(--text-color);
}

.stu-frozen__text {
    margin: 0 0 var(--space-sm);
    font-size: 15.5px;
    line-height: 1.75;
    color: var(--text-secondary);
}

/* The only gradient on the page, and it is the shared 96x2 utility rather than
   a local copy. --tight (32px) is still too much inside a card, so the block
   margin is set here; student.css is linked after global.css, so this wins
   over .brand-hairline's own margin at every width. */
.stu-frozen__rule {
    margin-block: var(--space-lg);
}

/* The line that introduces the two contact rows: ink rather than grey, because
   it is an instruction and not background. */
.stu-frozen__lead {
    margin-block-end: var(--space-md);
    color: var(--text-color);
}

.stu-frozen__contacts {
    display: grid;
    gap: var(--space-sm);
    margin-block-end: var(--space-md);
    text-align: start;
}

.stu-frozen__contact {
    display: flex;
    align-items: center;
    gap: 10px;
    /* Above the 44px touch floor, and matched to .stu-field__input so the two
       states of this card share one rhythm. */
    min-block-size: 52px;
    padding-inline: 16px;
    border: 1.5px solid var(--border-default);
    border-radius: var(--radius-md);
    background: #fff;
    font-size: 15px;
    color: var(--text-color);
    text-decoration: none;
    transition: border-color 0.2s ease, background 0.2s ease;
}

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

.stu-frozen__contact .app-icon-svg {
    font-size: 19px;
    color: var(--primary-color);
}

.stu-frozen__contact--wa .app-icon-svg {
    color: #0f7a40;
}

.stu-frozen__contact-label {
    font-size: 14px;
    color: var(--text-secondary);
}

/* Pushed to the far edge of the row - the logical property keeps it on the
   left under RTL without a second rule. `anywhere` is a last resort for a
   narrow phone: an address breaking over two lines is ugly, an address pushing
   the page into a horizontal scroll is a bug. */
.stu-frozen__contact-value {
    margin-inline-start: auto;
    font-size: 14.5px;
    color: var(--text-color);
    min-inline-size: 0;
    overflow-wrap: anywhere;
}

/* The sentence that says what the button below it does. Grey and small: it is
   guidance under a rule, not another paragraph of the message. */
.stu-frozen__exit-note {
    margin-block: 0 var(--space-md);
    font-size: 14.5px;
}

/* The way out. It is a real control now - it ends the session - so it gets the
   house's quiet button rather than a text link at the foot of the card, where a
   stranded reader would miss it. `.btn-secondary` carries the look; this only
   guarantees the 44px touch floor and lets the card's `text-align: center`
   place it. student.css is linked after global.css, so these win. */
.stu-frozen__back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-block-size: 44px;
    padding-block: 12px;
    font-size: 15px;
}

@media (max-width: 767px) {
    .stu-frozen {
        padding: var(--space-lg);
    }

    .stu-frozen__mascot {
        inline-size: 104px;
    }

    .stu-frozen__title {
        font-size: 21px;
    }

    .stu-frozen__text {
        font-size: 15px;
    }

    /* Roomier rows on a phone: these and the button below are the only things
       on the page anybody is meant to press. */
    .stu-frozen__contact {
        min-block-size: 56px;
        gap: 8px;
        padding-inline: 14px;
    }

    /* Full width on a phone, so the way out is as easy to hit as the two
       contact rows above it. */
    .stu-frozen__back {
        inline-size: 100%;
        min-block-size: 52px;
    }
}

/* ── Content-locked notice ───────────────────────────────────────
   StudentContentLockedNotice, which stands in place of the whole materials
   page body while the schedule payload says the enrollment is not paid up.
   A deliberate sibling of .stu-frozen above: same glass card, same measures,
   same solid display title - the two are the student area's only "this door
   is closed, here is why" screens, and a reader who meets both should not be
   able to tell they were built a month apart. Centred in the page because,
   unlike the frozen notice, this one replaces a two-column layout rather than
   a card that was already centred. */

.stu-locked {
    width: 100%;
    max-inline-size: 460px;
    margin-inline: auto;
    padding: var(--space-xl);
    text-align: center;
}

/* Fixed square box so the artwork never shifts the text while it decodes; the
   aspect ratio and the svg sizing come from .enr-mascot on the component. */
.stu-locked__mascot {
    inline-size: 128px;
    margin-inline: auto;
    margin-block-end: var(--space-sm);
}

.stu-locked__title {
    margin: 0 0 var(--space-md);
    font-family: var(--font-display);
    font-size: 24px;
    /* The house display weight. Solid ink, no gradient: this is the line the
       reader reads first on a page that just refused them, and it has to be
       legible before it is decorative. */
    font-weight: 300;
    color: var(--text-color);
}

.stu-locked__text {
    margin: 0 0 var(--space-sm);
    font-size: 15.5px;
    line-height: 1.75;
    color: var(--text-secondary);
}

/* The balance tile's frame, and nothing more. The tile itself is the payments
   tab's own .stu-amounts__balance--due, so the amber "money is owed" treatment
   has exactly one definition in this file; all this does is stop it inheriting
   .stu-amounts' two-column grid, where a lone tile would sit at half width
   against the edge of a centred card. */
.stu-locked__amounts {
    margin: 0 0 var(--space-md);
    /* Narrower than the card so the figure reads as one fact rather than as a
       band across the whole notice, and text-align back to start because the
       card centres everything and a label over a dir=ltr figure must not. */
    max-inline-size: 300px;
    margin-inline: auto;
    text-align: start;
}

/* The one action on the notice. .btn-primary carries the look; this only
   guarantees the 44px touch floor and lets the card's `text-align: center`
   place it. student.css is linked after global.css, so these win. */
.stu-locked__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-block-size: 44px;
    padding-block: 12px;
    font-size: 15px;
}

/* The shared 96x2 gradient utility rather than a local copy. --tight (32px) is
   still too much inside a card, so the block margin is set here; student.css is
   linked after global.css, so this wins over .brand-hairline's own margin at
   every width. */
.stu-locked__rule {
    margin-block: var(--space-lg);
}

/* The contact line under the rule. Smaller and with no trailing margin: it is
   the footer of the card, not another paragraph of the message. */
.stu-locked__foot {
    margin-block-end: 0;
    font-size: 14.5px;
}

@media (max-width: 767px) {
    .stu-locked {
        padding: var(--space-lg);
    }

    .stu-locked__mascot {
        inline-size: 104px;
    }

    .stu-locked__title {
        font-size: 21px;
    }

    .stu-locked__text {
        font-size: 15px;
    }

    /* Full width on a phone, so the one thing on the page anybody is meant to
       press is as easy to hit as the card is wide. */
    .stu-locked__cta {
        inline-size: 100%;
        min-block-size: 52px;
    }
}

/* ── The rail's lock marker ──────────────────────────────────────
   The state of חומרי לימוד while the enrollment is unpaid. Quieter than
   .stu-nav__icon in every dimension - smaller glyph, dimmer ink, no hover or
   active response - on purpose: the leading icon identifies a destination, this
   only annotates one, and a second full-strength glyph on the row would read as
   a second thing to press. `margin-inline-start: auto` and not a physical
   margin, so it lands at the END of the row under RTL as well as LTR.

   NOT --text-tertiary, which reads like the quiet one and is not: the token has
   drifted to #494950, DARKER than --text-secondary's #5A5B6A, so it would make
   the marker louder than the icon it must sit behind. The rail's own grey at
   reduced opacity is the honest way to get underneath it. */
.stu-nav__lock {
    display: inline-flex;
    align-items: center;
    margin-inline-start: auto;
    color: var(--text-secondary);
    opacity: 0.6;
}

/* Fixed HEIGHT and auto width, matching .stu-nav__icon's rule and for the same
   reason: the FontAwesome viewBoxes are not all square. Two classes deep so it
   outranks app-icon.css's bare `.app-icon-svg { height: 1em }`. */
.stu-nav__lock .app-icon-svg {
    height: 13px;
    width: auto;
}

/* The active item takes primary ink across the row; the lock follows it rather
   than staying grey against a coloured pill. The opacity above is deliberately
   NOT overridden here - the marker stays quieter than the leading icon in the
   active state too. */
.stu-nav__link.router-link-exact-active .stu-nav__lock {
    color: var(--primary-color);
}

/* ── Visually hidden ─────────────────────────────────────────────
   The standard clip recipe: out of the picture, still in the accessibility
   tree. Introduced here because the student area had no such utility and
   global.css carries none either - four other files each grew a private copy
   (contact-view.css, enrollment-page.css, next-cohort.css, admin-card-payments'
   .cpay-sr-only). Lift it to global.css if a third student-area caller appears.
   `white-space: nowrap` is load-bearing: without it a clipped word can still
   wrap and drag the line box it sits in. */
.stu-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── Forms ───────────────────────────────────────────────────── */

.stu-form {
    display: grid;
    gap: var(--space-md);
}

.stu-field {
    display: grid;
    gap: 6px;
}

.stu-field__label {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-color);
}

.stu-field__input {
    inline-size: 100%;
    min-block-size: 52px;
    padding-block: 12px;
    padding-inline: 16px;
    border: 1.5px solid var(--border-default);
    border-radius: var(--radius-md);
    font-family: var(--font-body);
    /* 16px min avoids iOS zoom-on-focus. */
    font-size: 16px;
    color: var(--text-color);
    background: #fff;
}

.stu-field__input:focus {
    outline: none;
    border-color: var(--primary-color);
}

.stu-form__error {
    margin: 0;
    font-size: 14px;
    color: var(--alert-color);
}

.stu-form__success {
    margin: 0;
    font-size: 14px;
    color: #16825D;
}

.stu-form__submit {
    justify-self: start;
}

@media (max-width: 767px) {
    .stu-form__submit {
        justify-self: stretch;
        width: 100%;
    }
}

/* ── Announcements / intro ───────────────────────────────────── */

.stu-announcement__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: var(--space-sm);
}

.stu-announcement__title {
    margin: 0;
    font-size: 17px;
    font-weight: 500;
}

.stu-announcement__date {
    font-size: 13px;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* ── Session lists ───────────────────────────────────────────── */

.stu-session-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.stu-session-item {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: 44px;
    padding: 14px 18px;
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: blur(16px) saturate(160%);
    -webkit-backdrop-filter: blur(16px) saturate(160%);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    color: var(--text-color);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.stu-session-item:hover {
    border-color: var(--primary-border);
    box-shadow: var(--shadow-md);
}

.stu-session-item--past {
    opacity: 0.65;
}

.stu-session-item__index {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--primary-light);
    color: var(--primary-color);
    font-family: var(--font-mono);
    font-size: 14px;
}

.stu-session-item__body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.stu-session-item__title {
    font-size: 15.5px;
    font-weight: 500;
}

.stu-session-item__when {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13.5px;
    color: var(--text-secondary);
}

.stu-session-item__time {
    font-family: var(--font-mono);
    font-size: 13px;
}

.stu-sched-course {
    margin-bottom: var(--space-xl);
}

@media (max-width: 767px) {
    .stu-session-item {
        flex-wrap: wrap;
        padding: 12px 14px;
    }

    .stu-session-item .stu-chip {
        margin-right: 48px;
    }
}

/* ── Session page ────────────────────────────────────────────── */

.stu-session-facts__row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 8px;
    font-size: 15.5px;
}

.stu-session-facts__row:last-child {
    margin-bottom: 0;
}

.stu-session-facts__row i {
    color: var(--primary-color);
    width: 20px;
    text-align: center;
}

.stu-session-live__join {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    margin-bottom: var(--space-md);
}

.stu-attendance {
    border-top: 1px solid var(--border-light);
    padding-top: var(--space-md);
}

.stu-attendance__done {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    color: #16825D;
    font-weight: 500;
}

.stu-attendance__note {
    margin: 8px 0 0;
    font-size: 14px;
    color: var(--text-secondary);
}

.stu-session-after {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
}

@media (max-width: 767px) {
    .stu-join-btn,
    .stu-attendance__btn,
    .stu-recording-link {
        width: 100%;
        text-align: center;
    }
}

/* ── Finance ─────────────────────────────────────────────────── */

.stu-enrollment__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: var(--space-md);
}

.stu-enrollment__title {
    margin: 0;
    font-size: 20px;
    font-weight: 500;
}

/* The money summary of one sale. Three figures, but they are NOT equals: the
   balance is the answer the reader came for, so it takes a tile of its own down
   the leading edge and the agreed amount and the paid total stack beside it as
   the arithmetic behind it. Auto-placement does the whole layout - the balance
   spans both rows of column 1, the two `__item` tiles fall into column 2 in DOM
   order.

   Still a FIXED grid and still nothing to do with .stu-facts, which is flex
   because a sale with no cohort carries a single fact a grid would stretch.
   Every sale here carries all three figures, so there is no such case. */
.stu-amounts {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 12px;
    margin: 0 0 var(--space-md);
}

.stu-amounts__item {
    background: var(--bg-secondary);
    border-radius: var(--radius-md);
    padding: 12px 14px;
}

.stu-amounts__item dt {
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 4px;
}

.stu-amounts__item dd {
    margin: 0;
    font-size: 18px;
    font-weight: 500;
    color: var(--text-color);
    text-align: right;
}

/* The balance tile. The BASE is the settled look on purpose: a sale with
   nothing left to pay is the resting state of this page, and a quiet ₪0 reads
   as a closed matter rather than as a figure demanding attention. */
.stu-amounts__balance {
    grid-row: span 2;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    padding: 16px 18px;
    background: var(--bg-surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}

.stu-amounts__balance dt {
    font-size: 13px;
    color: var(--text-secondary);
}

/* align-items and not text-align: the value carries dir="ltr", which would
   otherwise pull the digits to the physical left of a full-width line while the
   label above them sits on the right. Hugging the inline start keeps the tile
   reading as one column in RTL. */
.stu-amounts__balance dd {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    margin: 0;
}

/* Weight 500 with a SOLID colour, never the brand gradient - the gradient only
   reads through light letterforms, numerals included. */
.stu-amounts__value {
    font-family: var(--font-display);
    font-size: 26px;
    font-weight: 500;
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--text-secondary);
}

.stu-amounts__note {
    font-size: 13px;
    color: var(--text-secondary);
}

/* ── B. The balance tile when money is owed ────────────────────
   Money is actually owed on a sale that is still standing: the tile steps
   forward and the figure takes full size.

   AMBER, not the brand blue it used to be. Every other emphasis on this page is
   brand-coloured, so a brand-coloured balance read as "important" rather than
   as "outstanding", and the one card in a stack of two that actually wants
   money looked like the other one. The base `.stu-amounts__balance` keeps its
   quiet surface untouched: a settled sale must stay settled-looking, which is
   the whole point of that rule's own comment. */
.stu-amounts__balance--due {
    background: var(--stu-warn-wash);
    border-color: var(--stu-warn-line);
    /* A hairline down the leading edge (the RIGHT edge in RTL, hence the
       logical property). It is what separates a sale that owes money from a
       settled one at a glance, when a student has two cards open. The padding
       gives back the 2px the thicker border takes. */
    border-inline-start: 3px solid var(--stu-warn);
    padding-inline-start: 16px;
}

.stu-amounts__balance--due .stu-amounts__value {
    font-size: 32px;
    /* The amber ink, at 500 and solid - never the brand gradient, which only
       reads through light letterforms, numerals included. */
    color: var(--stu-warn);
}

/* ── C. The pay action ─────────────────────────────────────────
   No longer a button that leaves for another origin: the payment page itself is
   framed here, from the apex, and the anchor below the frame is the way OUT to
   a full tab. Reading order is the order of the DOM - what the form collects,
   the form, then the way out. */
.stu-pay-cta {
    display: flex;
    flex-direction: column;
    /* flex-start so the anchor stays its own width; the frame opts out with
       align-self below. */
    align-items: flex-start;
    gap: 12px;
    margin: 0 0 var(--space-sm);
}

/* The frame's own surround. It carries the border and the rounding because the
   framed document cannot round its own corners from the inside, and `overflow`
   is what actually clips them. */
.stu-pay-frame {
    align-self: stretch;
    inline-size: 100%;
    background: var(--bg-surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.stu-pay-frame__iframe {
    display: block;
    inline-size: 100%;
    /* The starting height, in force until the framed page reports its own
       through postMessage (StudentPaymentsPanel's onFrameMessage writes it as
       an inline block-size, which wins over this). It is set near the amount
       screen's real height so the common case does not visibly jump. */
    block-size: 560px;
    /* The floor, and it is load-bearing: a browser with no ResizeObserver, or a
       message dropped, leaves a frame that never reports its size. A short
       frame is awkward; a collapsed one is a page that cannot be paid on. */
    min-block-size: 420px;
    border: 0;
}

.stu-pay-cta__note {
    margin: 0;
    font-size: 13px;
    color: var(--text-secondary);
}

.stu-pay-cta__button {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    /* .btn-secondary's 16px is a hero-CTA size, and this control is a footnote
       under a form that is already on screen. */
    font-size: 15px;
    padding-block: 9px;
    padding-inline: 20px;
}

/* ── D. Phone ──────────────────────────────────────────────────
   The three rules here are unchanged from student.css:1346-1365; only the frame
   pair at the end is new. */
@media (max-width: 767px) {
    /* Two narrow tiles still fit side by side at 375px; the balance takes the
       full width above them. Equal columns here, unlike the desktop split -
       neither of the two is the more important one. */
    .stu-amounts {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 8px;
    }

    .stu-amounts__balance {
        grid-row: auto;
        grid-column: 1 / -1;
    }

    .stu-pay-cta__button {
        width: 100%;
        justify-content: center;
        text-align: center;
    }

    /* The card's own padding is the only inset a phone can spare, so the frame
       loses its rounding rather than the content losing width. */
    .stu-pay-frame {
        border-radius: var(--radius-sm);
    }

    /* Taller by default than on a desktop: the same payment screen wraps into
       more lines at 375px, and a first paint that is short and then grows is
       the one jump a payer notices. */
    .stu-pay-frame__iframe {
        block-size: 640px;
        min-block-size: 460px;
    }
}

.stu-pay-table {
    width: 100%;
    border-collapse: collapse;
}

.stu-pay-table th,
.stu-pay-table td {
    padding: 10px 12px;
    text-align: right;
    border-bottom: 1px solid var(--border-light);
    font-size: 14.5px;
}

.stu-pay-table th {
    font-weight: 500;
    color: var(--text-secondary);
    font-size: 13px;
}

.stu-refund td {
    color: var(--alert-color);
}

/* Phone: the table stacks into labeled cards (data-label ::before). */
@media (max-width: 767px) {
    .stu-pay-table thead {
        display: none;
    }

    .stu-pay-table,
    .stu-pay-table tbody {
        display: block;
        width: 100%;
    }

    .stu-pay-table tr {
        display: block;
        border: 1px solid var(--border-light);
        border-radius: var(--radius-md);
        padding: 6px 12px;
        margin-bottom: 10px;
        background: #fff;
    }

    .stu-pay-table td {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        border-bottom: none;
        padding: 7px 0;
    }

    .stu-pay-table td::before {
        content: attr(data-label);
        color: var(--text-secondary);
        font-size: 13px;
        flex-shrink: 0;
    }
}

.stu-invoices {
    list-style: none;
    margin: 0;
    padding: 0;
}

.stu-invoice {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border-light);
    margin-bottom: 0;
}

.stu-invoice:last-child {
    border-bottom: none;
}

.stu-invoice__desc {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 15px;
    min-width: 0;
}

.stu-invoice__meta {
    font-size: 13px;
    color: var(--text-secondary);
}

.stu-invoice__download {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--primary-color);
    text-decoration: none;
    font-size: 14.5px;
}

.stu-invoice__download:hover {
    opacity: 0.75;
}

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

/* ── Prep for the Course (materials page, first tab) ──────────── */

.stu-prep-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    margin: -10px 0 var(--space-lg);
    font-family: var(--font-mono);
    font-size: 12.5px;
    letter-spacing: 0.06em;
    color: var(--text-secondary);
}

.stu-prep-facts__item + .stu-prep-facts__item::before {
    content: '\00B7';
    margin-inline: 10px;
    color: var(--border-default);
}

/* Progress card - the fill is the page's ONE brand-gradient use. */
.stu-prep-progress__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}

.stu-prep-progress__text {
    font-size: 15px;
    font-weight: 500;
}

.stu-prep-progress__pct {
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--text-secondary);
}

.stu-prep-progress__bar {
    height: 2px;
    border-radius: var(--radius-full);
    background: var(--bg-secondary);
    overflow: hidden;
}

.stu-prep-progress__fill {
    height: 100%;
    border-radius: var(--radius-full);
    background: linear-gradient(90deg, #0894FF 0%, #BD32D6 50%, #FFA304 100%);
    transition: width 0.35s ease;
}

.stu-prep-progress__done {
    margin: 12px 0 0;
    font-size: 15px;
    font-weight: 500;
    color: #16825D;
}

.stu-prep-progress .stu-form__error {
    margin-top: 10px;
}

/* Stage flow - the "map" of the checklist; each step anchors to its stage. */
.stu-prep-flow {
    display: flex;
    margin: 0 0 var(--space-md);
}

.stu-prep-flow__step {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 12px 8px;
    border-radius: var(--radius-md);
    color: var(--text-color);
    text-decoration: none;
    transition: background 0.2s ease;
}

.stu-prep-flow__step:hover {
    background: var(--fsp-hover-bg);
}

/* Connector line back to the previous circle (RTL: previous = to the right). */
.stu-prep-flow__step + .stu-prep-flow__step::before {
    content: '';
    position: absolute;
    top: 34px;
    inset-inline-start: -50%;
    width: 100%;
    height: 1.5px;
    background: var(--border-light);
}

.stu-prep-flow__icon {
    position: relative;
    z-index: 1;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid var(--border-default);
    border-radius: 50%;
    background: #fff;
    color: var(--primary-color);
    font-size: 18px;
    transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.stu-prep-flow__label {
    font-size: 14px;
    font-weight: 500;
    text-align: center;
}

.stu-prep-flow__count {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-secondary);
}

.stu-prep-flow__step--done .stu-prep-flow__icon {
    border-color: rgba(22, 130, 93, 0.45);
    background: rgba(22, 130, 93, 0.06);
    color: #16825D;
}

.stu-prep-flow__step--done .stu-prep-flow__count {
    color: #16825D;
}

/* OS switch - one global toggle every item's instructions follow. */
.stu-prep-osbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 0 var(--space-xl);
}

.stu-prep-osbar__label {
    font-size: 14px;
    color: var(--text-secondary);
}

.stu-prep-os {
    display: inline-flex;
    padding: 3px;
    border-radius: var(--radius-full);
    background: var(--bg-secondary);
}

.stu-prep-os__btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 36px;
    padding: 0 22px;
    border: none;
    border-radius: var(--radius-full);
    background: transparent;
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--text-secondary);
    cursor: pointer;
    transition: color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}

.stu-prep-os__btn i {
    display: inline-flex;
    font-size: 13px;
}

.stu-prep-os__btn--active {
    background: #fff;
    color: var(--text-color);
    font-weight: 500;
    box-shadow: var(--shadow-sm);
}

/* Stages */
.stu-prep-stage {
    margin: 0 0 var(--space-2xl);
    scroll-margin-top: 84px;
}

.stu-prep-stage__head {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 6px;
}

.stu-prep-stage__head .stu-section-title {
    margin: 0;
}

.stu-prep-stage__count {
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--text-secondary);
}

.stu-prep-stage__intro {
    margin: 0 0 var(--space-md);
    max-width: 72ch;
    font-size: 15px;
    line-height: 1.65;
    color: var(--text-secondary);
}

/* Checklist items */
.stu-prep-item {
    padding: 18px 20px;
}

.stu-prep-item__head {
    display: flex;
    align-items: center;
    gap: 14px;
}

.stu-prep-check {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1.5px solid var(--border-default);
    border-radius: 50%;
    background: #fff;
    color: #fff;
    font-size: 12px;
    cursor: pointer;
    transition: border-color 0.2s ease, background 0.2s ease;
}

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

.stu-prep-check[aria-checked='true'] {
    background: #16825D;
    border-color: #16825D;
}

.stu-prep-check:disabled {
    opacity: 0.5;
    cursor: default;
}

.stu-prep-item__opener {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 10px;
    padding: 0;
    border: none;
    background: none;
    font-family: var(--font-body);
    color: var(--text-color);
    text-align: start;
    cursor: pointer;
}

/* Product mark tile - the mono simple-icons ink, never a brand color. */
.stu-prep-item__logo {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md);
    background: #fff;
    color: var(--text-color);
    font-size: 20px;
}

.stu-prep-logo-svg {
    display: block;
    width: 1em;
    height: 1em;
    fill: currentColor;
}

.stu-prep-item--done .stu-prep-item__logo {
    color: var(--text-secondary);
}

.stu-prep-item__title {
    font-size: 16px;
    font-weight: 500;
}

.stu-prep-item__meta {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 6px;
}

.stu-prep-item__chevron {
    margin-inline-start: auto;
    display: inline-flex;
    color: var(--text-secondary);
    font-size: 14px;
    transition: transform 0.2s ease;
}

.stu-prep-item__chevron--open {
    transform: rotate(180deg);
}

/* 42px = checkbox width + gap, so text under the head aligns with the title. */
.stu-prep-item__summary {
    margin: 10px 42px 0 0;
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.stu-prep-item--done .stu-prep-item__title {
    color: var(--text-secondary);
}

.stu-prep-item--done .stu-prep-item__summary {
    opacity: 0.75;
}

.stu-prep-item__body {
    margin: var(--space-md) 42px 0 0;
    padding-top: var(--space-md);
    border-top: 1px solid var(--border-light);
}

.stu-prep-item__requires {
    margin: 0 0 12px;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    background: var(--primary-light);
    font-size: 14px;
    color: var(--primary-color);
}

.stu-prep-steps {
    display: grid;
    gap: 10px;
    margin: 0 0 var(--space-sm);
    padding-right: 22px;
}

.stu-prep-steps li {
    font-size: 15px;
    line-height: 1.7;
}

.stu-prep-steps li::marker {
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--primary-color);
}

/* Terminal command chip - a small nod to the deep-dive terminal band. */
.stu-prep-code {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 8px;
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    background: #0B0C12;
    direction: ltr;
    text-align: left;
}

.stu-prep-code code {
    flex: 1;
    min-width: 0;
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: 13px;
    /* The deep-dive band's ink scale - text on the dark chip is white-alpha,
       never an invented solid grey. */
    color: rgba(255, 255, 255, 0.82);
    white-space: nowrap;
}

.stu-prep-code__copy {
    flex-shrink: 0;
    padding: 3px 10px;
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: var(--radius-sm);
    background: transparent;
    font-family: var(--font-body);
    font-size: 12px;
    color: rgba(255, 255, 255, 0.6);
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease;
}

.stu-prep-code__copy:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.45);
}

.stu-prep-item__note {
    margin: var(--space-sm) 0 0;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.stu-prep-item__links {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 18px;
    margin: var(--space-sm) 0 0;
}

.stu-prep-extlink {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 32px;
    font-size: 14.5px;
    color: var(--primary-color);
    text-decoration: none;
}

.stu-prep-extlink:hover {
    opacity: 0.75;
}

.stu-prep-extlink i {
    font-size: 12px;
}

/* Prep chips */
.stu-chip--free {
    background: rgba(22, 130, 93, 0.08);
    color: #16825D;
    border: 1px solid rgba(22, 130, 93, 0.25);
}

.stu-chip--paid {
    background: var(--primary-light);
    color: var(--primary-color);
    border: 1px solid var(--primary-border);
}

.stu-chip--muted {
    background: var(--bg-secondary);
    color: var(--text-secondary);
    border: 1px solid var(--border-light);
}

.stu-chip--mono {
    font-family: var(--font-mono);
    font-weight: 400;
    font-size: 11.5px;
    letter-spacing: 0.02em;
}

.stu-chip--optional {
    background: rgba(105, 53, 216, 0.07);
    color: var(--accent-warm);
    border: 1px solid rgba(105, 53, 216, 0.22);
}

/* Recommended tools (dictation / read-aloud) */
.stu-prep-tools {
    list-style: none;
    margin: 0;
    padding: 0;
}

.stu-prep-tool {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 0;
    border-bottom: 1px solid var(--border-light);
}

.stu-prep-tool:first-child {
    padding-top: 0;
}

.stu-prep-tool:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.stu-prep-tool__main {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.stu-prep-tool__name {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 15.5px;
    font-weight: 500;
}

.stu-prep-tool__desc {
    max-width: 64ch;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.stu-prep-tool .stu-prep-extlink {
    flex-shrink: 0;
    margin-top: 2px;
}

.stu-prep-tools + .stu-prep-item__note {
    padding-top: var(--space-sm);
    border-top: 1px solid var(--border-light);
}

/* Optional 101 resources */
.stu-prep-res-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}

.stu-prep-res {
    display: flex;
    flex-direction: column;
    margin-bottom: 0;
    padding: 18px 20px;
}

.stu-prep-res__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
}

.stu-prep-res__title {
    margin: 0;
    font-size: 15.5px;
    font-weight: 500;
    line-height: 1.45;
}

.stu-prep-res__meta {
    margin: 4px 0 8px;
    font-size: 13px;
    color: var(--text-secondary);
}

.stu-prep-res__why {
    flex: 1;
    margin: 0 0 6px;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.stu-prep-res-order {
    margin: var(--space-md) 0 0;
    max-width: 72ch;
    font-size: 14px;
    line-height: 1.65;
    color: var(--text-secondary);
}

@media (max-width: 767px) {
    .stu-prep-flow {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px;
    }

    .stu-prep-flow__step {
        flex-direction: row;
        justify-content: flex-start;
        gap: 10px;
        padding: 10px 12px;
        border: 1px solid var(--border-light);
        background: rgba(255, 255, 255, 0.7);
    }

    .stu-prep-flow__step + .stu-prep-flow__step::before {
        display: none;
    }

    .stu-prep-flow__icon {
        width: 34px;
        height: 34px;
        font-size: 14px;
    }

    .stu-prep-flow__count {
        margin-inline-start: auto;
    }

    .stu-prep-item__summary,
    .stu-prep-item__body {
        margin-right: 0;
    }

    .stu-prep-item__logo {
        width: 34px;
        height: 34px;
        font-size: 17px;
    }

    .stu-prep-tool {
        flex-direction: column;
        gap: 8px;
    }

    .stu-prep-res-list {
        grid-template-columns: 1fr;
    }
}

/* ── Account ─────────────────────────────────────────────────── */

.stu-forced-notice {
    margin-bottom: var(--space-md);
    padding: 14px 18px;
    background: var(--alert-light);
    border: 1px solid var(--alert-border);
    border-radius: var(--radius-lg);
    color: var(--alert-color);
    font-size: 15px;
}

.stu-account__email {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0;
    font-size: 15.5px;
}

.stu-account__label {
    color: var(--text-secondary);
    font-size: 14px;
}

/* The explanatory line above the "set a password" button. */
.stu-account__note {
    margin: 0 0 var(--space-md);
    max-width: 60ch;
    font-size: 15px;
    line-height: 1.6;
    color: var(--text-secondary);
}

/* The just-set confirmation sits above the change form it was replaced by.
   Spacing only - it keeps .stu-form__success's colour, which is why the note
   class above is NOT reused here (it would repaint the sentence grey). */
.stu-account__set-done {
    margin-bottom: var(--space-md);
}

/* Grid items stretch by default, which would blow the text button to the full
   card width; keep it the size of its label. Deliberately NO 767px rule: the
   submit beside it goes full-width on a phone, and a full-width "cancel" that
   looks like a second primary action is exactly the mis-tap to avoid. */
.stu-account__cancel {
    justify-self: start;
}

/* ── Account page sections ────────────────────────────────────── */

/* The account page is ONE scrolling page since 2026-08-31, with three titled
   sections where a tab strip used to be. The title carries the whole of the
   navigation now, so the type has to place it between the page h1 (32px/300,
   display) and the card titles it stands over (.stu-section-title, 20px/500):
   BIGGER than a card title and LIGHTER, which is the house's light-letterform
   look, rather than winning the comparison by getting heavier. */
.stu-account-section {
    margin-bottom: var(--space-xl);
}

/* `scroll-margin-block-start` is what makes /account#payments land correctly:
   the anchor is scrolled to the top of the viewport, and on a phone the sticky
   topbar (--stu-topbar-h: 61px) sits over exactly that strip. 80px clears it
   with a little air, and it is the same offset the router's own hash scroller
   and the view's `?tab=` scroll both apply, so all three come to rest in the
   same place.

   It lives on the TITLE and not on the section around it, because a
   scroll-margin only ever applies to the element that is the scroll target, and
   the targets are these h2s: the ids the links name (account / enrollment /
   payments) are on them, and `scrollToHash` looks the element up by id. On the
   section it could never fire. */
.stu-account-section__title {
    margin: 0 0 var(--space-sm);
    scroll-margin-block-start: 80px;
    font-family: var(--font-display);
    font-size: 24px;
    font-weight: 300;
    color: var(--text-color);
}

/* .brand-hairline is a global utility (96x2px, three-stop brand gradient) whose
   own margin is `56px auto` - centred, and far too tall for a rule that belongs
   to the title right above it. Zeroing the inline-start margin left-aligns it
   under the title in RTL, where `auto` would centre it over the column. */
.stu-account-section__rule {
    margin: 0 0 var(--space-lg);
    margin-inline-start: 0;
}

/* ── Enrollment facts tiles ──────────────────────────────────── */

/* The enrollment section's facts tiles. Same look as .stu-amounts, but FLEX rather
   than that block's fixed 3-column grid: a sale with no cohort attached carries
   one fact, and an auto-fit grid would stretch that single tile across the whole
   card. Flex lets it keep a tile's width and wrap when there are three. */
.stu-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 0 var(--space-sm);
}

.stu-facts__item {
    flex: 1 1 160px;
    max-width: 260px;
    background: var(--bg-secondary);
    border-radius: var(--radius-md);
    padding: 12px 14px;
}

.stu-facts__item dt {
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 4px;
}

.stu-facts__item dd {
    margin: 0;
    font-size: 18px;
    font-weight: 500;
    color: var(--text-color);
    text-align: right;
}

@media (max-width: 767px) {
    /* One step down, so the section title still reads as a heading over a
       375px card without taking two lines. */
    .stu-account-section__title {
        font-size: 21px;
    }

    .stu-facts__item {
        max-width: none;
        flex-basis: 100%;
    }

    .stu-facts {
        gap: 8px;
    }
}

/* ── Course content / materials (/materials) ──────────────────── */

/* Level 2 (the subject rail) on the physical LEFT, the panel in the middle.
   Level 1, the category strip, is a sibling ABOVE this grid and not a row of
   it - see .stu-mat__cats.
   THE RTL TRAP: this document is `direction: rtl`, so grid column 1 paints on
   the RIGHT. The panel therefore takes track 1 and the rail track 2, which is
   the reverse of what the DOM order (rail first, for the tab reading order and
   for the phone strip below) would auto-place. Both are placed explicitly so
   the two orders can never drift apart.

   --stu-mat-panel-min is the floor under the panel track, so the grid can never
   crush the panel to nothing. It is deliberately LOW: 1025px is the narrowest
   viewport this two-column band covers and the panel there already computes to
   ~439px (1025 - 264 menu - 2x41 page gutter - 216 rail - 24 gap), so the floor
   never binds at a width anyone browses at. It cannot be raised to a
   comfortable reading width instead: `#app` in global.css is `overflow-x:
   clip`, so a track that does not fit is CLIPPED rather than scrolled and the
   rail would simply vanish off the left edge. The reading measure is capped on
   the TEXT (.stu-mat__doc), never by squeezing this grid. */
.stu-mat {
    --stu-topbar-h: 61px;
    --stu-mat-panel-min: 420px;
    display: grid;
    grid-template-columns: minmax(var(--stu-mat-panel-min), 1fr) 216px;
    gap: var(--space-lg);
}

/* FULL WIDTH (owner): the page fills everything between the student menu and
   the window edge. It was capped at 1300px - the shell's 1040px text column
   plus the rail - which is the cap this rule now lifts off .stu-main's 1040px
   instead of replacing with a bigger number. Long-form text keeps its measure
   through .stu-mat__doc, which is what makes the full width safe: a meeting
   panel is a fact line and a link and can use it all, a checklist paragraph
   cannot. `:has()` is already load-bearing in a dozen sheets in public/css. */
.stu-main:has(.stu-mat) {
    max-width: none;
}

/* ── The pill vocabulary ─────────────────────────────────────── */

/* Built for the account page's tabs in 2026-08 and now read by the materials
   category strip alone: the account page has since traded its tabs for flat
   sections, and this family moved down here with its one remaining caller
   rather than being inlined into it - a second pill row is the obvious next
   caller, and .stu-mat__cats deliberately restyles none of it.

   It cannot borrow the admin panel's `.course-filter-tab` kit: that lives in an
   `admin`-prefixed sheet, and bundle-css.mjs partitions the two bundles on
   `/css/admin`, so no admin class is loaded on this host at all.

   Shape follows the nearest existing multi-state control on the site, the prep
   page's OS switcher (`.stu-prep-os__btn`): a quiet row of pills on the page
   ground, with the selected one lifted onto a white card. */
.stu-tabs {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 3px;
    padding: 3px;
    margin-bottom: var(--space-lg);
    max-width: 100%;
    background: var(--bg-secondary);
    border-radius: var(--radius-full);
}

.stu-tab {
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    padding: 0 22px;
    border: none;
    border-radius: var(--radius-full);
    background: transparent;
    font-family: var(--font-body);
    font-size: 15px;
    color: var(--text-secondary);
    cursor: pointer;
    transition: color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}

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

/* Solid #fff, not --bg-primary: that token is a translucent blue tint and the
   selected pill has to read as LIFTED off the track. Same values as
   .stu-prep-os__btn--active, which is the control this borrows its shape from. */
.stu-tab--active,
.stu-tab--active:hover {
    background: #fff;
    color: var(--text-color);
    font-weight: 500;
    box-shadow: var(--shadow-sm);
}


/* LEVEL 1: the category strip. It wears .stu-tabs / .stu-tab from just above
   - one pill-group vocabulary for the whole area - and these two rules are
   only what this strip needs on top of that. Nothing here restyles .stu-tabs
   itself, so the next caller inherits the same pill.

   NOT sticky, at any width. The phone band pins the SUBJECT rail under the top
   bar; a second pinned strip would either stack under it (eating a third of a
   phone screen) or fight it for the same offset. The category is the outer,
   rarely-changed level, so it scrolls away with the page head and the rail
   below it stays reachable. */
.stu-mat__cats {
    max-width: 100%;
}

/* A course tab's name is "קורס" plus an em-sized SVG mark (StudentCourseTitle),
   and the pill is only as tall as one line. Wrapping it would break the mark
   away from the word onto a second line inside the pill; on a phone the strip
   scrolls instead, which is what the band at the bottom of this section sets
   up. `.stu-course-title` itself stays an unstyled inline span - five other
   student surfaces render it and it owns no layout of its own. */
.stu-mat__cat {
    white-space: nowrap;
}

/* Track 2 = the physical left column. The rail is pinned with `position:
   sticky` in normal flow and NOT with its own scroller: giving the panel
   `overflow-y` would make it the nearest scrolling ancestor, which silently
   breaks both the prep checklist's `#prep-` anchor jumps and every helper in
   src/lib/smoothScroll.ts (they write scrollTop on document.scrollingElement).
   For the same reason `align-items` is left at `stretch` - `start` would shrink
   the rail's grid area to its own height and leave sticky no travel.
   The z-index is for the phone band, where this becomes a strip the panel
   scrolls under. Well below the shell's ladder: top bar 1000, scrim 1001,
   drawer 1002. */
.stu-mat__rail {
    grid-row: 1;
    grid-column: 2;
    position: sticky;
    top: var(--space-lg);
    z-index: 5;
    max-height: calc(100vh - 2 * var(--space-lg));
    overflow-y: auto;
}

.stu-mat__panel {
    grid-row: 1;
    grid-column: 1;
    min-width: 0;
}

/* THE READING MEASURE, and the reason the page above may run full width.
   Wraps a GENERAL subject - the prep checklist today - which is long-form
   Hebrew body text: paragraphs, instruction lists, an intro per stage. At
   1920px the panel track is ~1300px wide and those lines would be unreadable.

   948px is not a taste: it is exactly the width the checklist had under the
   1300px page cap this change removed (1300 - 2x56 gutter - 216 rail - 24 gap),
   so the reading experience is byte-identical to the one the owner signed off
   and only the page around it grew. It is a max, so every narrower viewport is
   untouched.

   It sits on the panel WRAPPER and not on .stu-mat__panel, because a meeting
   panel is not prose - a fact line, a placeholder sentence and a link - and is
   meant to have the whole column. */
.stu-mat__doc {
    max-width: 948px;
}

/* `role="tablist"` lives on this inner box, not on the rail: a tablist may own
   only tabs, and the rail also carries the loading / failed note. */
.stu-mat__list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Geometry from .stu-nav__link - the area's other vertical list of
   destinations, and the one this could be confused with - and the selected
   state from that same rule rather than from a lifted pill: a lifted white
   pill reads wrong against the near-white page ground, and a second vertical
   list on the same screen should answer selection the way the first one does. */
.stu-mat__tab {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    width: 100%;
    min-height: 44px;
    padding: 8px 12px;
    border: none;
    border-radius: var(--radius-md);
    background: transparent;
    font-family: var(--font-body);
    color: var(--text-color);
    text-align: start;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}

.stu-mat__tab:hover {
    background: var(--fsp-hover-bg);
}

.stu-mat__tab--active,
.stu-mat__tab--active:hover {
    background: var(--primary-light);
    color: var(--primary-color);
}

.stu-mat__tab-label {
    font-size: 14.5px;
    font-weight: 400;
    overflow-wrap: anywhere;
}

.stu-mat__tab--active .stu-mat__tab-label {
    font-weight: 500;
}

.stu-mat__tab-date {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-secondary);
}

.stu-mat__tab--active .stu-mat__tab-date {
    color: var(--primary-color);
}

.stu-mat__note {
    margin: var(--space-md) 0 0;
    padding: 0 12px;
}

/* One meeting's panel: the facts line, then the placeholder sentence. */
.stu-mat__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0 0 var(--space-md);
    font-size: 14px;
    color: var(--text-secondary);
}

.stu-mat__meta-time {
    font-family: var(--font-mono);
    font-size: 13px;
}

.stu-mat__soon {
    margin: 0 0 var(--space-lg);
}

@media (max-width: 1024px) {
    /* There is deliberately NO `.stu-main:has(.stu-mat)` rule here. There used
       to be: the page carried a 1300px cap, `:has()` out-specified this band's
       plain `.stu-main { max-width: none }`, and the cap survived the
       breakpoint unless an equal-weight selector lifted it. The page is full
       width at every size now, so the base rule and this band already agree and
       a second copy would only be one more place to forget. Re-introduce it the
       moment a desktop cap comes back. */
    .stu-mat {
        grid-template-columns: minmax(var(--stu-mat-panel-min), 1fr) 176px;
        gap: var(--space-md);
    }

    /* The top bar is sticky in this band, so the rail sticks UNDER it. */
    .stu-mat__rail {
        top: calc(var(--stu-topbar-h) + 12px);
        max-height: calc(100vh - var(--stu-topbar-h) - 24px);
    }
}

@media (max-width: 767px) {
    /* The rail stops being a rail and becomes one horizontal glass strip under
       the top bar. The drawer already owns the off-canvas idiom on this host.

       RESETTING grid-row / grid-column ON BOTH CHILDREN IS MANDATORY: left in
       place against a single-track grid-template-columns they mint an IMPLICIT
       second track, and the two-column layout survives the media query
       invisibly. Resetting only one is worse than resetting neither - the
       explicit child keeps row 1 and the reset one auto-places BELOW it. */
    .stu-mat {
        /* One track, so the panel floor goes with it: `minmax(0, 1fr)` and not
           the var, or a 420px minimum on a 375px phone would push the panel
           past the viewport, where `#app`'s `overflow-x: clip` would cut it. */
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-md);
    }

    /* Touch floor, and the row scrolls rather than wrapping to three lines on
       a narrow phone. */
    .stu-tab {
        min-height: 44px;
        padding-inline: 16px;
        font-size: 14px;
    }

    /* The category strip scrolls sideways instead of wrapping to three rows of
       pills, on a phone where a course name plus its mark is most of the width.
       It stays in the flow and is NOT pinned - the rail below it is the sticky
       strip in this band, and two pinned rows would eat a third of the screen.

       Same overflow split as the rail, for the same reason: x auto, y hidden,
       and `overscroll-behavior-x` rather than the shorthand, so a vertical
       swipe that starts on the pills still scrolls the page. */
    .stu-mat__cats {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        overscroll-behavior-x: contain;
        scrollbar-width: none;
    }

    .stu-mat__cats::-webkit-scrollbar {
        display: none;
    }

    /* BOTH children, not just the rail: leaving the panel pinned to row 1
       auto-places the rail into row 2, which buries the tab strip under the
       whole page of content. */
    .stu-mat__panel {
        grid-row: auto;
        grid-column: auto;
    }

    .stu-mat__rail {
        grid-row: auto;
        grid-column: auto;
        top: var(--stu-topbar-h);
        max-height: none;
        overflow-x: auto;
        overflow-y: hidden;
        /* X only, and NEVER the shorthand: `overflow-y: hidden` still makes
           this a y scroll container, one with nothing to scroll, so a contained
           y axis would refuse to chain and the pinned full-bleed strip would
           swallow every vertical swipe that starts on it. */
        overscroll-behavior-x: contain;
        scrollbar-width: none;
        /* Bleed through .stu-main's 16px phone gutters so the glass covers the
           full width of what scrolls under it. */
        margin-inline: -16px;
        padding: 8px 16px;
        background: rgba(255, 255, 255, 0.85);
        backdrop-filter: blur(16px) saturate(160%);
        -webkit-backdrop-filter: blur(16px) saturate(160%);
    }

    .stu-mat__rail::-webkit-scrollbar {
        display: none;
    }

    .stu-mat__list {
        flex-direction: row;
        align-items: center;
        gap: 6px;
    }

    .stu-mat__tab {
        flex: 0 0 auto;
        width: auto;
        white-space: nowrap;
    }

    .stu-mat__tab-label {
        overflow-wrap: normal;
    }

    .stu-mat__note {
        padding: 0;
    }

    /* .stu-prep-stage's own 84px offset clears the top bar alone; inside the
       materials page the sticky tab strip sits under the bar as well, so an
       in-page #prep- jump has to clear both. */
    .stu-mat .stu-prep-stage {
        scroll-margin-top: 132px;
    }
}
