/*
 * Components: the building blocks pages are made of. Each has one rule set
 * here; pages only place them (pages/*.css). /admin/ui shows every one.
 */
@layer components {
    /* ---------- Text helpers ----------
     * In this layer (not utilities) so a component or page can still tint
     * them, e.g. a .meta line on a selected row. */
    .muted { color: var(--text-muted); }
    /* Secondary information: muted and a size smaller (dates, counts, hints). */
    .meta { color: var(--text-muted); font-size: var(--text-sm); }
    /* Small uppercase label above a group. */
    .eyebrow {
        font-size: var(--text-xs);
        font-weight: var(--weight-semibold);
        text-transform: uppercase;
        letter-spacing: var(--tracking-caps);
        color: var(--text-muted);
    }
    .num { font-variant-numeric: tabular-nums; }
    .nowrap { white-space: nowrap; }

    /* ---------- Surfaces: panel, card ---------- */
    .panel, .auth-card, .card, .course-folder {
        background: var(--surface);
        border: var(--border-width) solid var(--border);
        border-radius: var(--radius-md);
        box-shadow: var(--shadow-card);
    }
    .panel {
        padding: var(--space-4) var(--space-5);
        margin-bottom: var(--space-5);
    }
    .panel > :last-child, details > :last-child { margin-bottom: 0; }
    .panel > h2:first-child, .panel-head h2, .card-head h2 { font-size: var(--text-lg); }
    /* Card title with a control on the right (a picker, a PDF link). */
    .panel-head, .card-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        flex-wrap: wrap;
        gap: var(--space-2) var(--space-4);
        margin-bottom: var(--space-3);
    }
    :is(.panel-head, .card-head) h2 { margin: 0; }
    .card-head-form select { width: auto; }

    /* A link card (start page, Werkzeuge): lifts on hover. */
    .card {
        display: flex;
        flex-direction: column;
        gap: var(--space-1);
        padding: var(--space-5);
        color: var(--text);
        text-decoration: none;
        transition: transform var(--duration-fast) ease, border-color var(--duration-fast) ease,
            box-shadow var(--duration-fast) ease;
    }
    a.card:hover {
        color: var(--text);
        border-color: color-mix(in srgb, var(--primary) 55%, var(--border));
        transform: translateY(-2px);
        box-shadow: var(--shadow-raised);
    }
    .card .stat {
        font-family: var(--font-head);
        font-size: var(--text-3xl);
        font-weight: var(--weight-semibold);
        line-height: var(--leading-tight);
        color: var(--accent-text);
        font-variant-numeric: tabular-nums;
    }
    .card .label { color: var(--text-muted); font-weight: var(--weight-medium); }
    .card ul { margin: var(--space-1) 0 0; }

    /* The login and invite card. */
    .auth-card {
        max-width: 24rem;
        margin: var(--space-8) auto;
        padding: var(--space-7);
        border-top: var(--border-thick) solid var(--accent);
    }
    .auth-card h1 { margin-bottom: var(--space-1); }
    .auth-card .auth-sub { color: var(--text-muted); margin-bottom: var(--space-6); }
    .auth-card form > label { margin-bottom: var(--space-4); }
    .auth-card button[type="submit"] { width: 100%; margin-top: var(--space-2); }
    .auth-card > p:last-child { margin: var(--space-4) 0 0; text-align: center; }

    /* Editable title: a text input dressed as the heading it replaces. It
     * shows that it is editable only on hover (dashed line) and focus. */
    .title-edit { min-width: 0; flex: 1; }
    .title-edit input[type="text"] {
        width: auto;
        max-width: 100%;
        min-width: 4ch;
        height: auto;
        margin: 0 0 0 calc(var(--space-1) * -1);
        padding: var(--space-0) var(--space-1);
        field-sizing: content;
        border-color: transparent;
        background: transparent;
        font-size: var(--text-h2);
        font-weight: var(--weight-semibold);
        line-height: var(--leading-tight);
        letter-spacing: var(--tracking-tight);
        color: var(--text-heading);
    }
    /* Like the headings: Literata only for the page title. */
    .title-edit-h1 input[type="text"] { font-family: var(--font-head); font-size: var(--text-h1); }
    .title-edit input[type="text"]:hover { border-bottom: var(--border-width) dashed var(--text-muted); }

    /* ---------- Forms ---------- */
    /* Captions of stacked fields get their spacing from the form, not the
     * control (the :where keeps this the weakest rule here). A page-head
     * filter form is one row, not a stack. */
    :where(form:not(.page-head-actions > form), fieldset, article, details) > :where(label, .rich-field, fieldset) { margin-bottom: var(--space-4); }

    /* An inline row of controls that wraps. */
    .row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-2); margin-bottom: var(--space-2); }
    .row > * { margin: 0; }
    .row :is(input:not([type="checkbox"], [type="radio"]), select, textarea) { width: auto; flex: 1; min-width: 11ch; margin: 0; }
    .row .grow { flex: 4; }
    .row .narrow { flex: 0 0 7ch; min-width: 0; }
    .row .narrow-kind { flex: 0 1 14ch; }
    .row .area-filter { flex: 0 1 24ch; }
    /* Date and time pickers keep their natural width (it depends on the
     * locale's format); squeezed they cut the value off. */
    .row input:is([type="date"], [type="time"]) { flex: 0 0 auto; width: auto; }

    /* A responsive label grid for the bigger forms. */
    .grid-form {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
        gap: var(--space-4);
        align-items: end;
    }
    .grid-form > * { margin: 0; }
    /* Room before whatever follows the form (the last child of a panel stays
     * flush: .panel > :last-child). */
    .grid-form { margin-bottom: var(--space-5); }
    .grid-form .wide { grid-column: 1 / -1; }
    .grid-form > button, .grid-form .form-actions { grid-column: 1 / -1; justify-self: start; }
    /* Required fields carry a star after the label text (explained under the
     * form): the label becomes a wrapping flex row and the control drops to its
     * own full-width line. */
    .grid-form label:has(> :required) { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--space-1); }
    .grid-form label:has(> :required)::after { content: "*"; order: 1; color: var(--danger-text); }
    .grid-form label:has(> :required) > :is(input, select, textarea) { order: 2; flex-basis: 100%; }
    /* A narrow form whose fields stack (sidebar cards). */
    .stack-form > label { margin-bottom: var(--space-3); font-size: var(--text-sm); color: var(--text-muted); }
    .stack-form > label > :is(input, select) { color: var(--text); }
    .stack-form .autosave-hint { margin: 0; }

    /* Caption of a rich-text field: a <span> (Quill must not sit in a label). */
    .rich-label { display: block; margin-bottom: var(--space-1); }
    .rich-editor { margin-bottom: var(--space-1); }
    .rich-oneline .ql-editor { min-height: 0; }
    /* Stored rich text outside the editor, spaced as in the editor: Quill
     * writes every line as its own block (an empty line is <p><br></p>), so
     * the blocks get no margins. Quill 2 writes both list kinds as <ol> and
     * marks each item with data-list. */
    .rich-view { line-height: var(--leading-rich); }
    .rich-view :is(p, ol, ul, pre, blockquote, h1, h2, h3, h4, h5, h6) { margin: 0; }
    .rich-view li[data-list="bullet"] { list-style-type: disc; }
    .rich-view li[data-list="ordered"] { list-style-type: decimal; }

    .form-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin-top: var(--space-1); }
    /* Inline single-button forms (delete/save actions in rows). */
    .inline { display: inline; margin: 0; }
    td.actions, .actions { display: flex; gap: var(--space-2); align-items: center; justify-content: flex-end; }
    .actions a { white-space: nowrap; }
    label.checkbox { display: inline-flex; align-items: center; gap: var(--space-2); margin: 0; }
    .invite-link { width: 100%; }

    /* Autosave (autosave.js): a row with unsaved edits gets a quiet bar in
     * the gutter, a failed one a red bar, and it stays dirty to retry. */
    form[data-autosave-row] {
        border-left: var(--border-thick) solid transparent;
        padding-left: var(--space-2);
        margin-left: calc((var(--space-2) + var(--border-thick)) * -1);
        transition: border-color var(--duration-normal) ease;
    }
    form[data-autosave-row].is-dirty { border-left-color: var(--primary-fill); }
    form[data-autosave-row].is-error { border-left-color: var(--danger); }
    /* Forms that show their state in the hint line instead of a bar. */
    form:is(.record-row, .profile-main, .phsg-links, .stack-form)[data-autosave-row] {
        margin-left: 0;
        padding-left: 0;
        border-left: 0;
    }
    .autosave-hint { margin: 0 0 var(--space-3); color: var(--text-muted); font-size: var(--text-sm); }
    .autosave-hint .hint-error { display: none; }
    form.is-error .autosave-hint { color: var(--danger-text); font-weight: var(--weight-semibold); }
    form.is-error .hint-ok { display: none; }
    form.is-error .hint-error { display: inline; }

    /* ---------- Buttons ---------- */
    /* A disabled quiet button keeps its fill and dims only its label: at the
     * base rule's opacity the light fill would disappear on white. */
    .secondary:disabled {
        --btn-text: color-mix(in srgb, var(--on-secondary) 55%, var(--secondary-fill));
        opacity: 1;
    }
    /* The delete at the end of an editable row: a quiet red outline that
     * fills only on hover or focus, so a list does not read as a wall of red. */
    .row-delete {
        --btn-bg: transparent;
        --btn-bg-hover: var(--danger);
        --btn-border: color-mix(in srgb, var(--danger) 55%, transparent);
        --btn-text: var(--danger-text);
    }
    .row-delete:is(:hover, :focus-visible) {
        --btn-bg: var(--danger);
        --btn-border: var(--danger);
        --btn-text: var(--on-danger);
    }
    /* Text only: no fill or border until hovered (unlink, remove, the eye on
     * a journal tile). Red with .is-delete when it deletes a record. */
    .ghost {
        --btn-bg: transparent;
        --btn-bg-hover: color-mix(in srgb, var(--text-muted) var(--tint-hover), transparent);
        --btn-border: transparent;
        --btn-text: var(--text-muted);
        height: auto;
        min-height: var(--target-min);
        padding: var(--space-0) var(--space-1);
    }
    .ghost:hover { --btn-text: var(--text); }
    .ghost.is-delete, .ghost.is-delete:hover { --btn-text: var(--danger-text); }
    /* A square button holding one icon (svg.i). */
    .icon { min-width: var(--control-h); padding-inline: 0; }
    .ghost.icon { min-width: var(--target-min); }
    svg.i {
        flex: none;
        width: 1.15em;
        height: 1.15em;
        fill: none;
        stroke: currentColor;
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
    }
    /* Segmented group: outline buttons sharing borders, rounded only at the
     * outer ends (week navigation, the Zyklus switch). */
    .segmented { display: inline-flex; }
    .segmented > .outline { border-radius: 0; }
    .segmented > .outline + .outline { margin-left: calc(var(--border-width) * -1); }
    .segmented > .outline:first-of-type { border-radius: var(--radius-md) 0 0 var(--radius-md); }
    .segmented > .outline:last-of-type { border-radius: 0 var(--radius-md) var(--radius-md) 0; }
    .segmented > .outline:is(:hover, :focus-visible) { position: relative; z-index: var(--z-raised); }
    /* Toggle buttons in their on state. */
    .outline:is([aria-pressed="true"], [aria-expanded="true"]) {
        --btn-bg: var(--primary-fill);
        --btn-bg-hover: var(--primary-fill-hover);
        --btn-border: var(--primary-fill);
        --btn-text: var(--on-primary);
    }

    /* ---------- Chips and badges ----------
     * One pill: a neutral tint by default (tags, groups, topics),
     * .is-strong filled (codes, classes, the groups a student is in), and
     * tints for meaning: .is-ok, .is-accent (marked, next), .is-primary
     * (appointments), .is-warn (absences), .is-muted (counts: the same
     * tint, smaller muted text). .badge is the same pill for a status
     * (.ok / .warn). A .chips list makes each item a chip. */
    .chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0; margin: var(--space-2) 0; }
    .chips > li { margin: 0; }
    :is(.chip, .badge, .chips > li) {
        display: inline-flex;
        align-items: center;
        gap: var(--space-1);
        padding: var(--space-0) var(--space-2);
        border-radius: var(--radius-pill);
        background: color-mix(in srgb, var(--text-muted) 15%, transparent);
        color: var(--text);
        font-size: var(--text-sm);
        font-weight: var(--weight-semibold);
        line-height: var(--leading-snug);
        white-space: nowrap;
    }
    :is(.chip, .chips > li) a { color: inherit; text-decoration: none; }
    .chip.is-strong { background: var(--primary-fill); color: var(--on-primary); }
    :is(.chip.is-ok, .badge.ok) {
        background: color-mix(in srgb, var(--ok) 15%, transparent);
        color: color-mix(in srgb, var(--ok) 75%, var(--text));
    }
    :is(.chip.is-accent) {
        background: color-mix(in srgb, var(--accent) 18%, transparent);
        color: var(--accent-text);
    }
    /* The hover blue as text: AA on the tint in both themes. */
    .chip.is-primary {
        background: color-mix(in srgb, var(--primary) 12%, transparent);
        color: var(--primary-hover);
    }
    :is(.chip.is-muted, .badge) {
        color: var(--text-muted);
        font-size: var(--text-xs);
    }
    :is(.chip.is-warn, .badge.warn) {
        background: color-mix(in srgb, var(--danger) 15%, transparent);
        color: var(--danger-text);
    }
    /* The remove button inside a chip takes the chip's colour. */
    :is(.chip, .chips > li) .ghost { --btn-text: currentColor; min-height: 0; min-width: 0; padding: 0; opacity: 0.7; }
    :is(.chip, .chips > li) .ghost:hover { opacity: 1; }
    :is(.chip, .chips > li) form { display: inline-flex; margin: 0; }
    .status-ok { color: var(--ok); font-weight: var(--weight-medium); }
    .status-warn { color: var(--danger-text); font-weight: var(--weight-medium); }

    /* ---------- Notices ----------
     * A tinted box with an accent edge for something to notice: the
     * subscription and year banners, homework due today, the seating's tap
     * status. .is-info uses the primary instead of the accent. */
    .notice {
        --notice: var(--accent);
        margin: 0 0 var(--space-4);
        padding: var(--space-3) var(--space-4);
        border-left: var(--border-thick) solid var(--notice);
        border-radius: var(--radius-sm);
        background: color-mix(in srgb, var(--notice) 12%, var(--surface));
        color: var(--text);
    }
    .notice.is-info { --notice: var(--primary-fill); }
    .notice > :last-child { margin-bottom: 0; }
    .notice a { color: var(--accent-text); font-weight: var(--weight-semibold); }
    .notice.is-info { color: var(--primary); font-weight: var(--weight-semibold); }

    /* The shown school year is not the one running by semester dates. */
    .year-notice { margin: calc(var(--space-2) * -1) 0 var(--space-4); color: var(--accent-text); font-weight: var(--weight-medium); }
    .error { color: var(--danger-text); font-weight: var(--weight-medium); }

    /* An empty list or missing prerequisite: explanation plus its one action. */
    .empty-state { padding: var(--space-5) 0; text-align: center; }
    .empty-state p { margin-bottom: var(--space-4); color: var(--text-muted); }

    /* Error pages (error.rs): a calm card with a way back. */
    .error-page { max-width: 36rem; margin: var(--space-7) auto; }
    .error-detail { margin: var(--space-4) 0; color: var(--text-muted); font-size: var(--text-sm); }

    /* ---------- Tables ---------- */
    /* Uppercase, muted headers read as column labels rather than heavy text. */
    :where(.panel, .content) table th {
        font-size: var(--text-xs);
        text-transform: uppercase;
        letter-spacing: var(--tracking-caps);
        color: var(--text-muted);
    }
    :where(.panel, .content) tbody tr { transition: background-color var(--duration-fast) ease; }
    :where(.panel, .content) tbody tr:hover {
        background: color-mix(in srgb, var(--primary) var(--tint-hover), transparent);
    }
    /* Wide data tables scroll sideways inside their card instead of
     * stretching the page; `relative` contains the visually-hidden labels. */
    .content .panel:has(table:not(.journal, .seatgrid)) { overflow-x: auto; position: relative; }
    /* In a narrow content column (tablets beside the sidebar) tighter cells
     * let ordinary lists fit without scrolling. */
    @container content (max-width: 50rem) {
        table:not(.journal, .seatgrid) :is(th, td) { padding-inline: var(--space-1); }
    }
    td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
    th.select-col, td.select-col { width: 2.5rem; }
    /* Type-to-filter box above a list (filter.js). */
    input.list-filter { max-width: 24rem; margin-bottom: var(--space-4); }
    /* Lists drop their secondary columns on phones; the record page has them all. */
    @media (max-width: 39.99rem) {
        .content :is(th, td).col-secondary { display: none; }
    }

    /* ---------- Editable record rows (absences, objectives) ---------- */
    /* One autosave form per row on a column grid the page sets, under a muted
     * header row hidden in a narrow card. A failed save tints the row red. */
    .records { container-type: inline-size; }
    .record-row {
        display: grid;
        align-items: center;
        gap: var(--space-3);
        padding: var(--space-1) 0;
        border-top: var(--border-width) solid var(--border);
    }
    .record-row > * { margin: 0; }
    .record-head { border-top: none; color: var(--text-muted); font-size: var(--text-sm); font-weight: var(--weight-semibold); }
    form.record-row.is-error { background: color-mix(in srgb, var(--danger) var(--tint-hover), transparent); }
    @container (max-width: 42rem) {
        .record-head { display: none; }
    }

    /* ---------- Lists ----------
     * .list: rows ruled off from each other, no bullets (lessons, a class
     * roster, picker results, Kompetenzstufen); .is-boxed draws a frame
     * around them. .link-list: a column of links like the sidebar's, with a
     * hover tint and the current one (aria-current or .is-selected) tinted. */
    :is(.list, .link-list, .peek-media, .due-homework ul, .day-list, .setup-steps) {
        list-style: none;
        margin: 0;
        padding: 0;
    }
    :is(.list, .link-list, .peek-media, .due-homework ul, .day-list, .setup-steps) > li { margin: 0; }
    .list > li + li { border-top: var(--border-width) solid var(--border); }
    .list.is-boxed { border: var(--border-width) solid var(--border); border-radius: var(--radius-md); }
    .list.is-boxed > li { padding: var(--space-1) var(--space-3); }
    .link-list > li > a {
        display: grid;
        gap: var(--space-0);
        padding: var(--space-2) var(--space-3);
        border-radius: var(--radius-sm);
        color: var(--text);
        text-decoration: none;
    }
    .link-list > li > a:hover { color: var(--text); background: color-mix(in srgb, var(--primary) var(--tint-hover), transparent); }
    .link-list > li > a:is([aria-current="page"], .is-selected) {
        background: color-mix(in srgb, var(--primary) var(--tint-selected), transparent);
        color: var(--text-heading);
    }
    .link-list > li > a:is([aria-current="page"], .is-selected) .meta { color: var(--muted-on-tint); }

    /* ---------- Tabs ----------
     * Links to sibling pages under a page head: <nav class="tabs"
     * aria-label="…"> of plain links, the current one marked by
     * aria-current="page" alone (an underline on the bar's hairline). Not
     * an ARIA tablist: each tab loads a page. A .badge may follow a label;
     * labels never wrap, a narrow bar scrolls sideways inside itself. */
    .tabs {
        display: flex;
        gap: var(--space-1);
        margin-bottom: var(--space-5);
        overflow-x: auto;
        overflow-y: hidden;
        box-shadow: inset 0 calc(var(--border-width) * -1) 0 var(--border);
    }
    .tabs > a {
        flex: none;
        display: flex;
        align-items: center;
        gap: var(--space-2);
        min-height: var(--target-min);
        padding: var(--space-2) var(--space-3);
        border-bottom: var(--border-thick) solid transparent;
        color: var(--text-muted);
        font-size: var(--text-sm);
        font-weight: var(--weight-medium);
        text-decoration: none;
        white-space: nowrap;
    }
    .tabs > a:hover { color: var(--text-heading); }
    .tabs > a[aria-current="page"] {
        border-bottom-color: var(--primary);
        color: var(--text-heading);
        font-weight: var(--weight-semibold);
    }
    /* The bar scrolls, which would clip an outer ring: draw it inside. */
    .tabs > a:focus-visible { outline-offset: calc(var(--focus-width) * -1); }

    /* ---------- Dialogs ---------- */
    /* Every modal: a narrow card (wider ones set --dialog-width), actions on
     * the right of the footer. Dialogs opened from inside the journal table
     * reset the cell's cursor and small type. */
    .app-dialog { cursor: auto; font-size: var(--text-base); }
    .app-dialog h2 { font-size: var(--text-lg); margin-bottom: var(--space-5); }
    .app-dialog footer form { margin: 0; }

    /* ---------- Disclosure ---------- */
    .more-fields { margin: var(--space-2) 0 var(--space-4); }
    .more-fields > summary { color: var(--text-muted); font-size: var(--text-sm); font-weight: var(--weight-semibold); }

    /* ---------- Menu ----------
     * A button that drops a panel: <details class="menu"> holding a
     * summary.outline and a .menu-panel. The panel's links and buttons are
     * its rows. It opens rightwards; .menu-end opens it leftwards (a menu
     * at a right edge), and ui.js sets .is-flipped when it would run past
     * the window. ui.js also closes it on a click outside, on Esc and when a
     * row is picked. */
    .menu { position: relative; display: inline-block; margin: 0; }
    .menu > summary { gap: var(--space-1); }
    .menu[open] > summary { margin-bottom: 0; }
    .menu > summary::after { float: none; height: 1rem; margin: 0; transform: none; }
    .menu[open] > summary::after { transform: rotate(180deg); }
    .menu-panel {
        position: absolute;
        top: calc(100% + var(--space-1));
        left: 0;
        z-index: var(--z-popover);
        min-width: 13rem;
        padding: var(--space-2) var(--space-3);
        border: var(--border-width) solid var(--border);
        border-radius: var(--radius-md);
        background: var(--surface);
        box-shadow: var(--shadow-overlay);
    }
    .menu-panel:has(> :is(a, button)) { padding: var(--space-1); }
    :is(.menu-end, .menu.is-flipped) > .menu-panel { left: auto; right: 0; }
    /* .menu-up: a menu at the foot of a column (the sidebar's account menu).
     * It fills its container; its summary is a quiet row; its panel opens
     * upwards, exactly as wide as the menu, with rows that wrap. Its summary
     * brings its own icon at the row's end: the chevron would point the
     * wrong way. */
    .menu-up { display: block; }
    .menu-up > summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        min-height: var(--target-min);
        padding: var(--space-1) var(--space-3);
        border-radius: var(--radius-sm);
        color: var(--text-quiet);
        font-size: var(--text-sm);
    }
    .menu-up > summary:hover {
        color: var(--primary-hover);
        background: color-mix(in srgb, var(--primary) var(--tint-hover), transparent);
    }
    .menu-up > summary::after { display: none; }
    .menu-up > .menu-panel { top: auto; bottom: calc(100% + var(--space-1)); left: 0; right: 0; min-width: 0; }
    .menu-up > .menu-panel > :is(a, button) { white-space: normal; }
    /* Rows: links and buttons alike, as a plain list with a hover tint. */
    .menu-panel > :is(a, button) {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        width: 100%;
        height: auto;
        min-height: var(--target-min);
        line-height: var(--leading-snug);
        padding: var(--space-2) var(--space-3);
        border: 0;
        border-radius: var(--radius-sm);
        background: transparent;
        color: var(--text);
        font-size: var(--control-text);
        font-weight: var(--weight-regular);
        text-align: left;
        text-decoration: none;
        white-space: nowrap;
    }
    .menu-panel > :is(a, button):is(:hover, :focus-visible) {
        background: color-mix(in srgb, var(--text-muted) var(--tint-hover), transparent);
        color: var(--text);
    }
    /* The row of the page being shown (nav.js marks the account menu's). */
    .menu-panel > [aria-current="page"] {
        background: color-mix(in srgb, var(--primary) var(--tint-selected), transparent);
        font-weight: var(--weight-semibold);
    }

    /* ---------- Toast (flash.js) ---------- */
    .toast {
        position: fixed;
        bottom: calc(var(--space-6) + var(--bottom-bar-h));
        left: 50%;
        transform: translateX(-50%);
        z-index: var(--z-popover);
        display: flex;
        align-items: center;
        gap: var(--space-2);
        padding: var(--space-2) var(--space-5);
        border-radius: var(--radius-md);
        background: var(--primary-fill);
        color: var(--on-primary);
        box-shadow: var(--shadow-raised);
        font-size: var(--text-sm);
        font-weight: var(--weight-semibold);
        opacity: 1;
        transition: opacity var(--duration-slow) ease;
    }
    .toast-close {
        --btn-bg: transparent;
        --btn-bg-hover: transparent;
        --btn-border: transparent;
        --btn-text: inherit;
        height: auto;
        padding: 0 var(--space-1);
        font-size: var(--text-lg);
        opacity: 0.8;
    }
    .toast-close:hover { opacity: 1; }
    .toast-hide { opacity: 0; }
    .toast-error { background: var(--danger); color: var(--on-danger); }

    /* ---------- Bulk bar (students list, while rows are ticked) ---------- */
    .bulk-bar {
        /* Sticks within its panel (a scroll container: wide tables above),
         * not the window; the page end keeps it clear of the bottom bar. */
        position: sticky;
        bottom: var(--space-4);
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--space-3) var(--space-5);
        margin-top: var(--space-4);
        padding: var(--space-3) var(--space-4);
        border: var(--border-width) solid var(--primary-fill);
        border-radius: var(--radius-md);
        background: var(--surface);
        box-shadow: var(--shadow-raised);
    }
    .bulk-action { display: inline-flex; align-items: center; gap: var(--space-2); }
    .bulk-action :is(select, input) { width: auto; }

    /* ---------- Stat tiles ---------- */
    .stats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
    .stat-tile {
        padding: var(--space-3);
        border-radius: var(--radius-md);
        background: color-mix(in srgb, var(--text-muted) var(--tint-hover), transparent);
    }
    .stat-tile strong {
        display: block;
        font-family: var(--font-head);
        font-size: var(--text-2xl);
        line-height: var(--leading-tight);
    }
    .stat-tile span { color: var(--text-muted); font-size: var(--text-sm); }
    .stat-tile span::first-letter { text-transform: uppercase; }
    .stat-alert { background: color-mix(in srgb, var(--accent) 16%, transparent); }
    .stat-alert strong { color: var(--accent-text); }
    .stat-unit { margin: var(--space-2) 0 var(--space-4); color: var(--text-muted); font-size: var(--text-sm); }

    /* ---------- Avatars ---------- */
    .name-cell { display: flex; align-items: center; gap: var(--space-3); }
    .avatar {
        flex: none;
        width: 2rem;
        height: 2rem;
        border-radius: var(--radius-round);
        object-fit: cover;
    }
    /* Placeholder keeps names aligned when a student has no photo. */
    .avatar-empty { background: color-mix(in srgb, var(--primary) var(--tint-selected), transparent); }

    /* ---------- Link pickers (lesson editor, planning, curriculum) ---------- */
    .picker-search { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-4) 0 var(--space-2); }
    .picker-search input[type="search"] { flex: 1 1 18rem; }
    .picker-search select { flex: 0 1 12rem; width: auto; }
    .picker-results[aria-busy="true"], #lesson-folders[aria-busy="true"] { opacity: 0.55; transition: opacity var(--duration-fast) ease; }
    .picker-hint, .picker-count { margin: var(--space-2) 0; }
    .pick-card {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: var(--space-1) var(--space-4);
        align-items: start;
        padding: var(--space-3) 0;
    }
    .pick-card > p { margin: 0; }
    .pick-linked .pick-card { padding: var(--space-2) 0; }
    .pick-card > :not(.pick-add, .pick-remove) { grid-column: 1; }
    .pick-card > :is(.pick-add, .pick-remove) { grid-column: 2; grid-row: 1 / span 4; align-self: center; }
    .pick-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-0); }
    .pick-chips:empty { display: none; }
    /* A linked medium's per-lesson note: a compact rich-text editor whose
     * toolbar shows only while you are in it. */
    .pick-note { margin: var(--space-2) 0 0; }
    .pick-note .rich-label { font-size: var(--text-sm); color: var(--text-muted); }
    .pick-note .ql-editor { min-height: 2.5em; }
    .pick-note:not(:focus-within) .ql-toolbar.ql-snow { display: none; }
    .pick-note:not(:focus-within) .ql-toolbar.ql-snow + .ql-container.ql-snow {
        border-top: var(--border-width) solid var(--border-strong);
        border-radius: var(--radius-md);
    }
    .media-new-hint { margin: var(--space-4) 0 var(--space-2); }
    /* Search hits: an accent tint that works on light and dark cards. */
    :is(.pick-card, .lesson-row) mark {
        padding: 0;
        background: color-mix(in srgb, var(--accent) 35%, transparent);
        color: inherit;
    }
    @media (max-width: 39.99rem) {
        .pick-card { grid-template-columns: minmax(0, 1fr); }
        .pick-card > .pick-add { grid-column: 1; grid-row: auto; justify-self: start; margin-top: var(--space-2); }
        .picker-search select { flex: 1 1 100%; }
    }

    /* External links from an Ort/URL field: long URLs break inside cells; on
     * a form, the "Open ↗" link sits after the label. */
    .ext-link { overflow-wrap: break-word; word-break: break-word; }
    .label-link { margin-left: var(--space-2); font-size: var(--text-sm); font-weight: var(--weight-medium); }

    /* Lesson symbols (src/symbols.rs): the old planner's pictures, 21px high
     * (doubled in the picker), on a white tile so black pictures stay visible
     * in the dark theme. */
    img.symbol-icon {
        width: auto;
        height: 1.3rem;
        flex: none;
        vertical-align: middle;
        background: var(--symbol-tile);
        border-radius: var(--radius-sm);
    }

    /* The sidebar's live countdown and stopwatch pills (timers.js). */
    .nav-timers { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-2) var(--space-3); }
    .nav-timers:not(:has(.nav-timer:not([hidden]))) { display: none; }
    .nav-timer {
        display: inline-flex;
        align-items: center;
        gap: var(--space-1);
        padding: var(--space-0) var(--space-3);
        border-radius: var(--radius-pill);
        background: color-mix(in srgb, var(--primary) var(--tint-selected), transparent);
        color: var(--primary-hover);
        font-weight: var(--weight-semibold);
        font-variant-numeric: tabular-nums;
        text-decoration: none;
    }
    .nav-timer.paused { opacity: 0.6; }
    .nav-timer.done { color: var(--danger-text); animation: timer-pulse 1s ease-in-out infinite; }
    @keyframes timer-pulse { 50% { opacity: 0.35; } }

    /* ---------- Small targets ---------- */
    /* Small inline controls are at least 24px square (WCAG 2.5.8), finger
     * sized under a coarse pointer (--target-min). */
    :is(.toast-close, .check-quick button, .plan-add select) {
        min-width: var(--target-min);
        min-height: var(--target-min);
    }
}
