/* Every stylesheet, in the cascade's order, which some rules rely on: the
   base (tokens first), the blocks (a block before one that places or
   overrides it, such as problem and submission-list before exercise), the
   layout that places blocks in the page, and the utilities last.

   The app serves this file with each import replaced by the file it names
   (website/assets.py), so a page loads one stylesheet while the files stay
   apart here. A new file is one more line. */

/* The pages' stylesheets (§10): readable on a phone first, dark when the
   reader's system is. No framework, no web fonts and nothing inline (the CSP
   allows only these files). Colours are tokens on :root, here, so the dark
   scheme only redefines them; app.css imports the files in a fixed order.

   Classes are BEM: a block (.card), its elements (.card__title) and their
   variants as modifiers (.card--flush, .button--small), one block or a small
   family to a file. A selector names one class, or a block's modifier and
   its own element; it never reaches into another block. A block that sits in
   another is placed by a second class, an element of the outer block
   (.site-header__tabs on the tabs). State the page already says in ARIA stays
   there and is styled by it ([aria-current="page"]); state only the eye needs
   is a modifier, the scripts toggling just those (.dropzone--over) and
   finding their elements by data-* attributes. Bare elements are styled once,
   as the base, and the few utilities (base/utilities.css) come last.

   A block never spaces itself from the blocks around it. Blocks stacked one
   under another sit in a stack (layout/stack.css): the main column, and any
   wrapper inside it that stacks blocks too, mixes in .stack, which puts the
   one gap above every block but the first, and the few gaps that differ
   (after the page's head and the way back, before a table's key)
   in that one place. What a
   block spaces is its own inside: a card's fields, a callout's margin under
   it within a card. */

:root {
    --font: system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

    --page: #f3f6fa;
    --surface: #ffffff;
    --surface-sunken: #f7f9fc;
    --border: #dde3eb;
    --border-strong: #c3ccd8;
    --text: #172130;
    --muted: #5a6677;
    --faint: #667181;

    --accent: #0b5cad;
    --accent-hover: #094d92;
    --accent-soft: #eaf2fc;
    --accent-line: #b8d2f0;
    --on-accent: #ffffff;

    --success: #19703b;
    --success-soft: #e9f6ee;
    --success-line: #b5dfc3;
    --warning: #8a5300;
    --warning-soft: #fdf4e4;
    --warning-line: #f0d29c;
    --error: #b42318;
    --error-soft: #fdeeec;
    --error-line: #f3c0ba;

    --radius: 10px;
    --radius-small: 6px;
    --shadow: 0 1px 2px rgb(16 24 40 / 5%), 0 1px 3px rgb(16 24 40 / 4%);
    /* What floats over the page: a menu, a toast. */
    --shadow-raised: 0 8px 24px rgb(16 24 40 / 13%), var(--shadow);
    /* What a pinned column casts on its left, over what scrolls under it. */
    --shadow-edge: rgb(16 24 40 / 12%);
    /* The width of a page whose content is one narrow column: the sign-in's
       card, an error's or the maintenance notice, a page about one thing.
       A banner over such a page is as wide (components/banner.css). */
    --column-narrow: 28rem;
    --column-notice: 32rem;
    --column-focus: 44rem;
    /* The keyboard's focus: a solid ring in the accent, with a gap of the
       page's colour, as an outline (base/forms.css) or, on a block that draws
       it itself, as a shadow. */
    --focus-outline: 2px solid var(--accent);
    --focus: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent);

    /* Syntax highlighting, in the same few colours as everything else. */
    --code-keyword: #8a3ab9;
    --code-type: #0b5cad;
    --code-string: #19703b;
    --code-number: #b35900;
    --code-comment: #6e7781;
    --code-preprocessor: #a3317a;
    --code-function: #6f4bb8;

    /* Charts (components/check-charts.css): the series that is the point in
       the accent, the one that is context in a grey, on a track a step off
       the surface; the wash under a line, the line's colour faint. */
    --chart-point: #0b5cad;
    --chart-context: #a3adbb;
    --chart-track: #edf1f6;
    --chart-wash: rgb(11 92 173 / 10%);
    /* How students stand with a problem's star checks, best to worst: one
       hue, darker for better, each with the ink of the text set in it. */
    --chart-all: #0b5cad;
    --chart-all-ink: #ffffff;
    --chart-star: #5e9de6;
    --chart-star-ink: #0d1218;
    --chart-none: #a7b6ca;
    --chart-none-ink: #172130;
    color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
    :root {
        --page: #0d1218;
        --surface: #151c25;
        --surface-sunken: #111821;
        --border: #253140;
        --border-strong: #354456;
        --text: #e4e9f0;
        --muted: #9aa6b5;
        --faint: #8693a3;

        --accent: #6ba6ee;
        --accent-hover: #8dbaf2;
        --accent-soft: #15263a;
        --accent-line: #29496f;
        --on-accent: #0d1218;

        --success: #64c58c;
        --success-soft: #11281c;
        --success-line: #20503a;
        --warning: #e5b35d;
        --warning-soft: #2b2112;
        --warning-line: #5a4320;
        --error: #f0897e;
        --error-soft: #321818;
        --error-line: #63302c;

        --shadow: 0 1px 2px rgb(0 0 0 / 30%);
        --shadow-raised: 0 8px 24px rgb(0 0 0 / 45%);
        --shadow-edge: rgb(0 0 0 / 45%);

        --code-keyword: #d2a8ff;
        --code-type: #79b8ff;
        --code-string: #7ee2a0;
        --code-number: #f2b36b;
        --code-comment: #8b949e;
        --code-preprocessor: #f27fbf;
        --code-function: #c5b3ff;

        --chart-point: #6ba6ee;
        --chart-context: #5b6879;
        --chart-track: #1e2733;
        --chart-wash: rgb(107 166 238 / 12%);
        --chart-all: #7db1f0;
        --chart-all-ink: #0d1218;
        --chart-star: #3a6fae;
        --chart-star-ink: #e4e9f0;
        --chart-none: #4f5c6e;
        --chart-none-ink: #e4e9f0;
    }
}
/* The box model, the page and its body. */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* What is hidden stays hidden, whatever display a block's own rule gives it:
   a drop zone until the script shows it, a picker the script opens. */
[hidden] {
    display: none !important;
}

/* Room for the scrollbar always, so a page that grows long does not shift sideways. */
html {
    -webkit-text-size-adjust: 100%;
    scrollbar-gutter: stable;
}

/* Every list is a block's, which lays out its items: no bullets, no indent.
   Of no weight, so a block's own margin or bullets hold. */
:where(ul, ol) {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* 16px, which the fields inherit: Safari on a phone zooms the page in on a
   field whose text is any smaller as it takes the focus. */
body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    font: 16px/1.55 var(--font);
    color: var(--text);
    background: var(--page);
}
/* Headings, text, links and code as bare elements: the base the blocks build on. */

h1,
h2,
h3 {
    line-height: 1.25;
    letter-spacing: -0.01em;
}

/* A long word -- an e-mail, as a student's page is headed -- breaks rather
   than running off a phone's screen. */
h1 {
    overflow-wrap: anywhere;
    margin: 0 0 0.35rem;
    font-size: 1.6rem;
    font-weight: 700;
}

h2 {
    margin: 0 0 1rem;
    font-size: 1.05rem;
    font-weight: 650;
}

p {
    margin: 0 0 0.75rem;
}

a {
    color: var(--accent);
    text-underline-offset: 2px;
}

a:hover {
    color: var(--accent-hover);
}

code,
pre {
    font-family: var(--mono);
    font-size: 0.88em;
}

:not(pre) > code {
    padding: 0.1em 0.35em;
    border-radius: 4px;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
}

pre {
    margin: 0;
    overflow-x: auto;
}
/* Form controls as bare elements, and how any of them shows the keyboard's focus. */

label,
legend {
    display: block;
    margin-bottom: 0.4rem;
    font-weight: 600;
    font-size: 0.92rem;
}

fieldset {
    margin: 0 0 1.25rem;
    padding: 0;
    border: 0;
    min-width: 0;
}

legend {
    padding: 0;
}

input,
select,
button,
textarea {
    font: inherit;
    color: inherit;
}

input:not([type="radio"], [type="checkbox"], [type="file"]),
select,
textarea {
    width: 100%;
    min-height: 2.6rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-small);
    background: var(--surface);
    color: var(--text);
}

/* A solid ring a little apart from the control, plain to see on every
   background; the controls hidden behind another show it there (.upload__dropzone). */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
summary:focus-visible,
a:focus-visible {
    outline: var(--focus-outline);
    outline-offset: 2px;
}

/* A select drawn like the other fields: the platform's own ignores padding
   and height. The arrow is a data: image, which the CSP allows. */
select {
    appearance: none;
    padding-right: 2.25rem !important;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%235a6677' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
    cursor: pointer;
}

/* Placeholders, and a required select still on its empty first option,
   which reads as one: nothing is chosen yet. Its options keep the text colour. */
::placeholder,
select:invalid {
    color: var(--faint);
}

select option {
    color: var(--text);
}

/* Grows only downwards, as the text in it does. */
textarea {
    display: block;
    min-height: 8rem;
    resize: vertical;
    line-height: 1.5;
}

input[type="file"] {
    display: block;
    max-width: 100%;
}
/* A page's lead paragraph, and the quiet note after a heading. */

.lead {
    color: var(--muted);
    margin: 0;
}

.heading-note {
    margin-left: 0.4rem;
    color: var(--muted);
    font-size: 0.85rem;
    font-weight: 400;
}
/* A line of facts set apart by dots ("WS 2026/27 · Version … · gepinnt …"),
   each kept whole, the line breaking only between them. Every item brings
   its dot before it; the list reaches the dot's width out to the left and
   clips it there, so the dot of an item that starts a line is cut away and
   no line starts or ends with one. */
.meta-list {
    --meta-list-dot: 1.1em;

    display: flex;
    flex-wrap: wrap;
    margin-left: calc(-1 * var(--meta-list-dot));
    clip-path: inset(0 0 0 var(--meta-list-dot));
}

.meta-list__item {
    min-width: 0;
    white-space: nowrap;
}

.meta-list__item::before {
    content: "·";
    display: inline-block;
    width: var(--meta-list-dot);
    font-weight: 700;
    text-align: center;
}

/* An item that may break itself, as an email too long for a phone's line. */
.meta-list__item--wrap {
    white-space: normal;
    overflow-wrap: anywhere;
}
/* The tool's name in the header, "InfUpload", and the course. */

.brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--text);
    text-decoration: none;
}

.brand__mark {
    font-size: 1.3rem;
    font-weight: 750;
    letter-spacing: -0.02em;
    color: var(--accent);
}

.brand__course {
    padding-left: 0.6rem;
    border-left: 1px solid var(--border-strong);
    color: var(--muted);
    font-size: 0.9rem;
}
/* Who is signed in: a small menu in the header (partials/account.html). Its toggle
   is the initial in a circle; the menu below holds the whole e-mail, the
   role, the sessions and the way out. */

.account {
    position: relative;
    min-width: 0;
    font-size: 0.92rem;
}

.account__toggle {
    display: block;
    border-radius: 50%;
    list-style: none;
    cursor: pointer;
}

.account__toggle::-webkit-details-marker {
    display: none;
}

.account__toggle:focus-visible {
    outline: none;
    box-shadow: var(--focus);
}

.account__avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.1rem;
    height: 2.1rem;
    border: 1px solid var(--accent-line);
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 700;
    transition: border-color 0.15s;
}

.account__toggle:hover .account__avatar,
.account[open] .account__avatar {
    border-color: var(--accent);
}

.account__menu {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    z-index: 20;
    width: max-content;
    min-width: 14rem;
    max-width: min(22rem, calc(100vw - 2rem));
    padding: 0.4rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-raised);
}

/* Who it is: the whole e-mail, and the team's role. */
.account__who {
    margin: 0 0 0.35rem;
    padding: 0.5rem 0.6rem 0.65rem;
    border-bottom: 1px solid var(--border);
}

.account__email {
    margin: 0;
    color: var(--text);
    font-weight: 600;
    overflow-wrap: anywhere;
}

.account__note {
    margin: 0.15rem 0 0;
    color: var(--muted);
    font-size: 0.85rem;
}

/* A line of the menu, a link or a form's button alike. */
.account__item {
    display: block;
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: 0;
    border-radius: var(--radius-small);
    background: none;
    color: var(--text);
    font: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.account__item:hover {
    background: var(--surface-sunken);
    color: var(--text);
}

/* The page the account is on: the sessions, while they are shown. */
.account__item[aria-current="page"] {
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
}

.account__item--danger,
.account__item--danger:hover {
    color: var(--error);
}
/* The team's sections; the current one is marked by aria-current. */

.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    align-items: center;
    font-size: 0.92rem;
}

.tabs__link {
    padding: 0.35rem 0.1rem;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
}

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

.tabs__link[aria-current="page"] {
    color: var(--text);
    font-weight: 600;
    border-bottom-color: var(--accent);
}

/* Every tab is as wide as its bold self, so the bold one shifts nothing. */
.tabs__link[data-label] {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
}

.tabs__link[data-label]::after {
    content: attr(data-label);
    height: 0;
    overflow: hidden;
    visibility: hidden;
    font-weight: 600;
    user-select: none;
}
/* Cards: the surfaces the pages are made of. */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 1.4rem 1.5rem 1.5rem;
}

.card > :last-child {
    margin-bottom: 0;
}

/* Before the modifiers, which set a padding of their own. */
@media (max-width: 30rem) {
    .card {
        padding: 1.15rem 1.1rem 1.25rem;
    }
}

.card--narrow {
    max-width: var(--column-narrow);
    margin: 1.5rem auto 0;
}

/* A card whose table runs to its edges. */
.card--flush {
    padding: 0;
    overflow: hidden;
}

.card--notice {
    max-width: var(--column-notice);
    margin: 2.5rem auto 0;
    text-align: center;
}

.card__title {
    margin: 0;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border);
}

/* A title with an action at its end, on the title's bar: the button no
   taller than the title's line, so the bar is as high as a plain title's. */
.card__bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    align-items: center;
    justify-content: space-between;
    padding: 0.65rem 1.25rem;
    border-bottom: 1px solid var(--border);
}

.card__bar-title {
    margin: 0;
}

.card__bar-form {
    margin: 0;
}

.card__text {
    margin: 0;
    padding: 1rem 1.25rem;
}

/* A card that is the page's subject: its heading and lead on top. */
.card__header {
    margin-bottom: 1rem;
}

/* A header with something at its end, a status, as a bar has: below the
   title where the card is narrow; without the bar's own padding and line. */
.card__header--bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    align-items: baseline;
    justify-content: space-between;
}

.card__header--roomy {
    margin-bottom: 1.25rem;
}

.card__heading {
    font-size: 1.4rem;
}

.card__lead {
    margin-bottom: 1.25rem;
}

.card__note {
    font-size: 0.88rem;
}

.card__note--apart {
    margin-top: 1.25rem;
}

/* A card of settings: each a row of what it is and its control. */
.card--settings {
    padding: 0;
}

/* What follows the list of a card, set apart from it: the tally, a form. */
.card__footer {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}
/* A form field: its label, its control and a hint. */

.field {
    margin-bottom: 1.25rem;
}

/* The one field of a short form, its button close below it. */
.field--tight {
    margin-bottom: 0.85rem;
}

.code-input {
    font-family: var(--mono);
    font-size: 1.35rem !important;
    letter-spacing: 0.3em;
    text-align: center;
}

.hint {
    margin: 0.35rem 0 0;
    color: var(--muted);
    font-size: 0.85rem;
}
/* A checkbox with its label beside it, the label's lines wrapping beside the box. */

.checkbox {
    display: flex;
    gap: 0.6rem;
    align-items: flex-start;
    margin: 0;
    font-weight: 400;
    font-size: 0.93rem;
    cursor: pointer;
}

.checkbox__input {
    flex: none;
    width: 1.05rem;
    height: 1.05rem;
    margin: 0.15rem 0 0;
    accent-color: var(--accent);
}
/* A few options side by side, one of them chosen (the team's roles). */
.segmented {
    margin: 0 0 1.25rem;
}

/* The options share what room the choice is given equally; at its own width
   each is as wide as its label. */
.segmented__option {
    position: relative;
    display: inline-flex;
    flex: 1;
    margin: 0;
    font-weight: 500;
}

.segmented__options {
    display: flex;
}

.segmented__option + .segmented__option {
    margin-left: -1px;
}

.segmented__input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.segmented__label {
    display: inline-flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    min-height: 2.6rem;
    padding: 0.5rem 1rem;
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--muted);
    white-space: nowrap;
    cursor: pointer;
}

.segmented__option:first-of-type .segmented__label {
    border-radius: var(--radius-small) 0 0 var(--radius-small);
}

.segmented__option:last-of-type .segmented__label {
    border-radius: 0 var(--radius-small) var(--radius-small) 0;
}

/* Chosen by colour alone: a bolder weight would widen it and shift its neighbours. */
.segmented__input:checked + .segmented__label {
    position: relative;
    z-index: 1;
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
}

.segmented__input:focus-visible + .segmented__label {
    z-index: 2;
    box-shadow: var(--focus);
}
/* A setting: a row of what it is and its control (in a card--settings). */

.setting {
    container-type: inline-size;
    display: flex;
    flex-wrap: wrap;
    gap: 1rem 2rem;
    align-items: center;
    justify-content: space-between;
    padding: 1.25rem 1.5rem;
}

.setting + .setting {
    border-top: 1px solid var(--border);
}

.setting__text {
    flex: 1 1 16rem;
}

.setting__title {
    margin: 0 0 0.2rem;
}

.setting__description {
    margin: 0;
    color: var(--muted);
    font-size: 0.9rem;
}

/* The setting's state under its description: a status on a line of its
   own, which wraps. */
.setting__status {
    display: flex;
    margin-top: 0.6rem;
    white-space: normal;
}

.setting__action {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
}

.setting__choice {
    margin: 0;
}

/* Once the action no longer fits beside the text, it takes the full row
   under it, and so does each of its controls. */
@container (max-width: 38rem) {
    .setting__action,
    .setting__action > * {
        flex-basis: 100%;
    }
}
/* How many sessions an account has open where: a label and a row of
   small counts, one for each place, a place without one faint. */

.session-count {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.6rem;
    align-items: center;
    font-size: 0.85rem;
}

.session-count__label {
    color: var(--faint);
}

.session-count__list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.session-count__item {
    display: inline-flex;
    gap: 0.3rem;
    align-items: baseline;
    padding: 0.1rem 0.6rem;
    border: 1px solid var(--accent-line);
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    white-space: nowrap;
}

.session-count__item--none {
    border-color: var(--border);
    background: var(--surface-sunken);
    color: var(--faint);
}

.session-count__number {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
/* A form whose fields stand in one row where there is room. */
.inline-form {
    container-type: inline-size;
    display: flex;
    flex-wrap: wrap;
    gap: 0 1rem;
    align-items: flex-end;
}

.inline-form__item {
    margin-bottom: 0.75rem;
}

.inline-form__item--grow {
    flex: 1 1 16rem;
}

/* Once the fields no longer fit one row, each takes a full row rather than
   squeezing in beside the next. */
@container (max-width: 38rem) {
    .inline-form__item {
        flex-basis: 100%;
    }
}
/* Buttons, and a button that reads as a link. */

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 2.6rem;
    padding: 0.5rem 1.15rem;
    border: 1px solid var(--accent);
    border-radius: var(--radius-small);
    background: var(--accent);
    color: var(--on-accent);
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.button:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--on-accent);
}

.button:disabled {
    opacity: 0.6;
    cursor: progress;
}

/* An action not open yet, or not here, as the way past the first or last page:
   shown so the page says it exists, and plainly not something to press (a
   span with aria-disabled, not a link). */
.button--unavailable,
.button--unavailable:hover {
    border-color: var(--border);
    background: var(--surface-sunken);
    color: var(--faint);
    cursor: not-allowed;
}

.button--small {
    min-height: 2rem;
    padding: 0.25rem 0.8rem;
    font-size: 0.88rem;
}

.button--secondary {
    background: var(--surface);
    border-color: var(--border-strong);
    color: var(--text);
}

.button--secondary:hover {
    background: var(--surface-sunken);
    border-color: var(--accent-line);
    color: var(--text);
}

/* A form's button under its fields, as wide as they are on a phone. */
@media (max-width: 36rem) {
    .button--wide-on-phone {
        width: 100%;
    }
}

/* For what cannot be undone: red, but only outlined until it is pointed at. */
.button--danger {
    background: var(--surface);
    border-color: var(--error-line);
    color: var(--error);
}

.button--danger:hover {
    background: var(--error-soft);
    border-color: var(--error);
    color: var(--error);
}

/* A form's button that reads as a link: a quiet way out beside others. */
.button--link,
.button--link:hover {
    min-height: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.button--link {
    color: var(--accent);
}

.button--link:hover {
    color: var(--accent-hover);
}
/* Quiet ways out, side by side: links, and buttons that read as links,
   wrapping onto a second line where the card is narrow. */

.link-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1.5rem;
    align-items: baseline;
    /* So the letters sit as far below a divider as their underline sits
       above the card's edge. */
    margin-top: 0.3rem;
}

/* Where the card's padding below is less (card.css). */
@media (max-width: 30rem) {
    .link-row {
        margin-top: 0.1rem;
    }
}

.link-row__item {
    margin: 0;
}
/* The upload form (dashboard/problem.html): its file input gives way to the
   drop zone once the script has taken over (upload--enhanced). */

.upload--enhanced .upload__input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

/* The input is hidden behind the drop zone, which shows its keyboard focus. */
.upload__input:focus-visible + .upload__dropzone {
    outline: var(--focus-outline);
    outline-offset: 2px;
    border-color: var(--accent);
}

/* What to do in the Codespace, below the list of files, set apart from it. */
.upload__hint {
    margin-top: 0.85rem;
}
/* The drop zone over the upload's file input. It shows the input's
   keyboard focus as its own (components/upload.css, .upload__dropzone). */

.dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    margin: 0;
    padding: 2.25rem 1rem;
    border: 1.5px dashed var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface-sunken);
    font-weight: 400;
    text-align: center;
    cursor: pointer;
    transition: border-color 0.15s, background-color 0.15s;
}

.dropzone:hover,
.dropzone--over {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.dropzone__icon {
    margin-bottom: 0.35rem;
    fill: none;
    stroke: var(--accent);
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.dropzone__title {
    font-weight: 600;
}

.dropzone__text {
    color: var(--muted);
    font-size: 0.9rem;
}

/* The files' label, set as a link in the line, not as a field's label. */
.dropzone__link {
    display: inline;
    margin: 0;
    color: var(--accent);
    font-size: inherit;
    font-weight: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
    .dropzone {
        transition: none;
    }
}
/* The files chosen for an upload, held against the ones the problem needs. */

/* One grid for the whole list, which every row takes its columns from
   (subgrid), so that its parts line up from row to row: the name and its
   size on the left, the status, each starting where the others start, and
   the button that takes the file out again, or its empty place. The empty
   tracks at either end, with the gaps beside them, are the rows' inner
   space: a subgrid's own padding would narrow the tracks at its edges. */
.file-list {
    display: grid;
    grid-template-columns: 0 minmax(0, max-content) 1fr auto 1.75rem 0;
    column-gap: 0.75rem;
    margin: 0.75rem 0 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-small);
}

.file-list__item {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    /* As high as the remove button, whether a row has one or not. */
    grid-auto-rows: minmax(1.75rem, auto);
    align-items: center;
    padding-block: 0.55rem;
    font-size: 0.92rem;
}

.file-list__item + .file-list__item {
    border-top: 1px solid var(--border);
}

.file-list__name {
    grid-column: 2;
    font-family: var(--mono);
    font-size: 0.88rem;
    overflow-wrap: anywhere;
}

.file-list__size {
    grid-column: 3;
    color: var(--muted);
    font-size: 0.82rem;
    white-space: nowrap;
}

.file-list__item > .status {
    grid-column: 4;
    justify-self: start;
}

/* A file of the problem's not chosen yet, and a glob of the files it takes
   besides its own: what the problem names, not a file, so quieter (§4.2). */
.file-list__item--missing .file-list__name,
.file-list__item--glob .file-list__name {
    color: var(--muted);
}

.file-list__remove {
    grid-column: 5;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-small);
    background: none;
    color: var(--muted);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}

.file-list__remove:hover {
    background: var(--surface-sunken);
    color: var(--error);
}

/* Narrow: the name on a line of its own, the size and the status below it,
   starting where the name does where a row has no size; the button beside
   both lines. */
@media (max-width: 36rem) {
    .file-list {
        grid-template-columns: 0 auto minmax(0, 1fr) 1.75rem 0;
        row-gap: 0;
    }

    .file-list__item {
        row-gap: 0.15rem;
    }

    .file-list__name {
        grid-column: 2 / 4;
        grid-row: 1;
    }

    .file-list__size {
        grid-column: 2;
        grid-row: 2;
    }

    .file-list__item > .status {
        grid-column: 3;
        grid-row: 2;
    }

    .file-list__item:not(:has(.file-list__size)) > .status {
        grid-column: 2 / 4;
    }

    .file-list__remove {
        grid-column: 4;
        grid-row: 1 / 3;
    }
}
/* An upload's progress. */

.progress {
    margin-bottom: 1rem;
}

.progress__label {
    display: flex;
    justify-content: space-between;
    margin-bottom: 0.35rem;
    font-size: 0.88rem;
    color: var(--muted);
}

.progress__bar {
    display: block;
    width: 100%;
    height: 0.5rem;
    border: 0;
    border-radius: 999px;
    background: var(--surface-sunken);
    overflow: hidden;
    appearance: none;
}

.progress__bar::-webkit-progress-bar {
    background: var(--border);
    border-radius: 999px;
}

.progress__bar::-webkit-progress-value {
    background: var(--accent);
    border-radius: 999px;
    transition: width 0.2s;
}

.progress__bar::-moz-progress-bar {
    background: var(--accent);
    border-radius: 999px;
}
/* The course's notices on the overview: a callout too, which sets its box
   and its list (callout__list). */

.notices {
    color: var(--text);
}

.notices__title {
    margin: 0 0 0.35rem;
    font-size: 0.95rem;
    color: var(--accent);
}
/* The standing in brief, on top of the overview: the heading with the count
   beside it, then the jokers, what to do next and the mug, each across the
   whole card, one under the other, however many there are. */

.summary {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.summary__head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    align-items: baseline;
    justify-content: space-between;
}

.summary__title {
    margin: 0;
}

.summary__count {
    margin: 0;
    font-weight: 650;
}
/* A row set into its card on the sunken surface: a problem to hand in, a
   submission under its exercise. The block it is mixed with lays out its
   parts. */

.inset-row {
    display: flex;
    gap: 0.4rem 1rem;
    align-items: center;
    padding: 0.45rem 0.65rem;
    border-radius: var(--radius-small);
    background: var(--surface-sunken);
}

.inset-row + .inset-row {
    margin-top: 0.4rem;
}
/* A row's way to its own page, "Details" and an arrow, at the row's end:
   kept apart from an e-mail, which must not look like a link. */

.details-link {
    display: inline-flex;
    gap: 0.15rem;
    align-items: center;
    font-size: 0.85rem;
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
}

.details-link:hover {
    text-decoration: underline;
}
/* The problems to hand in: each its name and files, what stands about it
   (when it opens, or that every check is green), and its buttons. */

/* An inset-row, which sets its box and lays out its two parts: what the
   problem is, and its buttons. On a wide card the name, the note and the
   buttons stand in one line; on a narrow one the name, the files and the
   note stack on the left, and the buttons stay on the right, where a thumb
   finds them. A row without buttons ends its note at its edge. */
.problem__main {
    flex: 1;
    display: flex;
    gap: 0.25rem 1rem;
    align-items: center;
    min-width: 0;
}

.problem__name {
    flex: 1;
    min-width: 0;
}

.problem__title {
    margin-right: 0.5rem;
    font-weight: 600;
}

/* Whole, never broken at a hyphen or a slash: beside a title on a narrow
   card it moves to the next line as one. */
.problem__slug {
    font-family: var(--mono);
    font-size: 0.9rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Beside a title, the slug is what to type, not the name: in the text's weight. */
.problem--titled .problem__slug {
    font-weight: 400;
}

.problem__files {
    margin-left: 0.5rem;
    color: var(--muted);
    font-family: var(--mono);
    font-size: 0.82rem;
}

/* The tag after the files: in the text's font, not the name's mono. */
.problem__tag {
    font-family: var(--font);
}

.problem__meta {
    flex: none;
    display: inline-flex;
    gap: 0.35rem;
    align-items: center;
    font-size: 0.82rem;
    white-space: nowrap;
}

.problem__meta--muted {
    color: var(--muted);
}

/* Every check passed: green, in plain text, so it never reads as a button. */
.problem__meta--done {
    color: var(--success);
    font-weight: 600;
}

.problem__actions {
    flex: none;
    display: flex;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-end;
}

@media (max-width: 36rem) {
    .problem__main {
        flex-direction: column;
        align-items: stretch;
    }

    .problem__files {
        display: block;
        margin-left: 0;
    }

    /* Below the files, flush with them: over the tag's own margin, which
       sets it apart from a name beside it. */
    .problem__name .problem__tag {
        margin: 0.25rem 0 0;
    }

    .problem__name .problem__tag + .problem__tag {
        margin-left: 0.35rem;
    }

    .problem__meta {
        white-space: normal;
    }

    .problem__actions {
        flex-direction: column;
        align-items: stretch;
    }
}

/* A tag that leads somewhere, its appeal's: a tag still, underlined when pointed at. */
.problem__tag--link {
    text-decoration: none;
}

.problem__tag--link:hover {
    text-decoration: underline;
}
/* The exercises with their state (§8), each with what belongs to it below. */

.exercise-list__item {
    padding: 0.85rem 0;
}

.exercise-list__item + .exercise-list__item {
    border-top: 1px solid var(--border);
}

.exercise-list__item:first-child {
    padding-top: 0;
}

.exercise-list__item:last-child {
    padding-bottom: 0;
}

.exercise-list__problems,
.exercise-list__empty {
    margin: 0.5rem 0 0 2.25rem;
}

.exercise-list__empty {
    color: var(--muted);
    font-size: 0.85rem;
}

/* A group without an exercise, such as the voluntary problems: named like one. */
.exercise-list__heading {
    margin: 0;
    font-weight: 500;
}

/* An exercise in a list (.exercise-list), whose items space it. */
.exercise {
    display: flex;
    gap: 0.75rem;
    align-items: center;
}

.exercise__name {
    flex: 1;
    min-width: 0;
    font-weight: 500;
}

.exercise__state {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    font-size: 0.85rem;
}

.exercise__state-label {
    font-weight: 600;
    color: var(--muted);
}

.exercise--counted .exercise__state-label {
    color: var(--success);
}

.exercise--missed .exercise__state-label {
    color: var(--error);
}

.exercise--open .exercise__state-label,
.exercise--pending .exercise__state-label {
    color: var(--accent);
}

.exercise--none .exercise__state-label {
    color: var(--faint);
    font-weight: 500;
}

.exercise__deadline {
    color: var(--muted);
    font-size: 0.8rem;
    white-space: nowrap;
}

.exercise--upcoming .exercise__name,
.exercise--upcoming .exercise__state-label {
    color: var(--faint);
}

/* On a narrow card the state and the deadline move below the name, in one
   line beside each other, and the problems take the card's whole width:
   the mark's column is too dear to keep empty under it. */
@media (max-width: 36rem) {
    .exercise {
        flex-wrap: wrap;
        row-gap: 0.15rem;
    }

    .exercise__state {
        flex-direction: row;
        flex-wrap: wrap;
        flex-basis: 100%;
        gap: 0 0.6rem;
        align-items: baseline;
        padding-left: 2.25rem;
        text-align: left;
    }

    .exercise-list__problems,
    .exercise-list__empty {
        margin-left: 0;
    }
}
/* Callouts: a notice in a box, coloured by what it says. A hint, a line's
   ``info``, takes the box's own blue. */

.callout {
    margin: 0 0 1rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--accent-line);
    border-radius: var(--radius-small);
    background: var(--accent-soft);
    font-size: 0.93rem;
}

.callout--success {
    border-color: var(--success-line);
    background: var(--success-soft);
    color: var(--success);
}

.callout--warning {
    border-color: var(--warning-line);
    background: var(--warning-soft);
    color: var(--warning);
}

.callout--error {
    border-color: var(--error-line);
    background: var(--error-soft);
    color: var(--error);
}

/* A list in a callout, under what it says: in the text's colour, whatever
   the callout's. */
.callout__list {
    margin: 0.35rem 0 0;
    padding-left: 1.15rem;
    list-style: disc;
    color: var(--text);
}

.callout__item + .callout__item {
    margin-top: 0.2rem;
}

/* A callout of several paragraphs. */
.callout__paragraph {
    margin: 0;
}

.callout__paragraph + .callout__paragraph {
    margin-top: 0.5rem;
}

/* Lines a result words without a box: the plain and quiet ones. */
.callout--plain,
.callout--muted {
    padding: 0;
    border: 0;
    background: none;
}

.callout--muted {
    color: var(--muted);
}

/* A callout in a grid, which spaces it. */
.callout--no-margin {
    margin: 0;
}
/* A notice over the page, under its header: the maintenance banner of the
   staff pages, and that the pages need JavaScript where it is off
   (base/page.html). A callout too, which sets its box. */

.banner {
    margin: 1rem 0 0;
}

/* Over a page whose content is one narrow column, as wide as that column and
   right over it (base/page.html, banner_modifier). */
.banner--narrow,
.banner--notice,
.banner--focus {
    margin-right: auto;
    margin-left: auto;
}

.banner--narrow {
    max-width: var(--column-narrow);
}

.banner--notice {
    max-width: var(--column-notice);
}

.banner--focus {
    max-width: var(--column-focus);
}

/* Why, as the sync recorded it: what stops the pin, then each finding or conflict. */
.banner__reason {
    margin: 0;
    color: var(--text);
}

/* Each finding or conflict, a callout's item (callout__list), as the sync
   wrote it. */
.banner__item {
    font-family: var(--mono);
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}

/* A conflict's fix, under it: the options that resolve it. */
.banner__fix {
    display: block;
    margin-top: 0.15rem;
    color: var(--muted);
}
/* The notices a form leaves (partials/toasts.html): over the page, at its foot on the
   right, so it stays where it was scrolled to; stacked, the newest lowest. */

.toasts {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(24rem, calc(100vw - 2rem));
    pointer-events: none;
}

/* A plain card; what it says is told by a small mark in a tinted circle. */
.toast {
    display: flex;
    gap: 0.7rem;
    align-items: flex-start;
    padding: 0.75rem 0.75rem 0.75rem 0.85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-raised);
    font-size: 0.93rem;
    pointer-events: auto;
    animation: toast-in 180ms ease-out;
}

.toast__icon {
    display: grid;
    flex: none;
    place-items: center;
    width: 1.4rem;
    height: 1.4rem;
    margin-top: 0.05rem;
    border-radius: 50%;
    background: var(--success-soft);
    color: var(--success);
}

.toast--error .toast__icon {
    background: var(--error-soft);
    color: var(--error);
}

.toast__glyph {
    width: 0.95rem;
    height: 0.95rem;
}

/* One carried over from the page before: it is there already. */
.toast--settled {
    animation: none;
}

.toast--leaving {
    opacity: 0;
    transform: translateY(0.5rem);
    transition: opacity 200ms ease-in, transform 200ms ease-in;
}

.toast__text {
    flex: 1;
    margin: 0;
}

.toast__close {
    flex: none;
    margin: -0.25rem -0.25rem 0 0;
    padding: 0.1rem 0.4rem;
    border: 0;
    border-radius: var(--radius-small);
    background: none;
    color: var(--muted);
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
}

.toast__close:hover {
    background: var(--surface-sunken);
    color: var(--text);
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(0.5rem);
    }
}

@media (prefers-reduced-motion: reduce) {
    .toast,
    .toast--leaving {
        animation: none;
        transition: none;
    }
}
/* A callout (which sets the box) that explains an admin's action beside it:
   its words on the left, the button on the right, below them where the
   page is narrow. The team's acceptance of a submission, the re-check of
   every failed check. */

.action-callout {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem 1rem;
    align-items: center;
    justify-content: space-between;
}

.action-callout__text {
    flex: 1 1 20rem;
    margin: 0;
}

.action-callout__form {
    margin: 0;
}
/* An appeal's own words (partials/appeal.html): the student's reason, and the
   team's answer once it gave one, each as written, its line breaks kept. */

.appeal {
    display: grid;
    gap: 0.35rem;
}

.appeal__label {
    margin: 0.4rem 0 0;
    color: var(--muted);
    font-size: 0.85rem;
    font-weight: 600;
}

.appeal__quote {
    margin: 0;
    padding: 0.55rem 0.85rem;
    border-left: 3px solid var(--border-strong);
    border-radius: 0 var(--radius-small) var(--radius-small) 0;
    background: var(--surface-sunken);
    font-size: 0.93rem;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* The team's answer, set apart from the reason it answers. */
.appeal__quote--answer {
    border-left-color: var(--accent);
}
/* The team's decision on an appeal (staff/submission.html, §10.5): two options
   side by side, either or, with "oder" between them; one under the other,
   "oder" across, where the card is narrow. Each option is a form of its own,
   its button at its foot, so the two buttons stand level. */

/* Set off from the appeal above it as a card's footer is (.card__footer). */
.decision {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: 1rem;
}

.decision__heading {
    grid-column: 1 / -1;
    margin: 0;
    font-size: 0.95rem;
}

.decision__option {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin: 0;
    padding: 1rem 1.1rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-small);
    background: var(--surface-sunken);
}

/* The way that counts the submission, set off in the success colour. */
.decision__option--accept {
    border-color: var(--success-line);
    background: var(--success-soft);
}

.decision__title {
    margin: 0;
    font-size: 0.95rem;
}

.decision__text {
    margin: 0;
    font-size: 0.92rem;
}

/* At the option's foot, whatever is above it. */
.decision__button {
    align-self: flex-start;
    margin-top: auto;
}

/* "oder" between the two, on a line from top to bottom. */
.decision__or {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    background: linear-gradient(var(--border), var(--border)) center / 1px 100% no-repeat;
}

.decision__or-word {
    padding: 0.3rem 0;
    background: var(--surface);
    color: var(--muted);
    font-size: 0.82rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

@media (max-width: 48rem) {
    .decision {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Across, between the one above and the one below. */
    .decision__or {
        background-size: 100% 1px;
    }

    .decision__or-word {
        padding: 0 0.6rem;
    }

    .decision__button {
        align-self: stretch;
    }
}
/* A problem, or its challenge, whose every check passed, on its upload page in
   place of the form: a callout (which sets the box), its words on the left and
   the challenge's button beside them while that is still to pass. */

.done {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem 1rem;
    align-items: center;
    justify-content: space-between;
}

.done__text {
    flex: 1 1 20rem;
    margin: 0;
}
/* A small label next to a name. */

.tag {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0.05rem 0.45rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-sunken);
    color: var(--muted);
    font-size: 0.75rem;
    font-weight: 500;
    vertical-align: 0.1em;
    white-space: nowrap;
}

.tag--accent {
    border-color: var(--accent-line);
    background: var(--accent-soft);
    color: var(--accent);
}

/* Where it reads as a status, an appeal's ("Einspruch offen"): accepted, in
   the success colour (waiting, in the accent's). */
.tag--success {
    border-color: var(--success-line);
    background: var(--success-soft);
    color: var(--success);
}
/* A checkmark that says yes on its own (partials/glyph.html), in the success's green:
   in the matrix, for whom the mug is earned. */

.tick {
    width: 1.15rem;
    height: 1.15rem;
    vertical-align: -0.2em;
    color: var(--success);
}
/* A state in lists and tables: a dot and a word. */

.status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--muted);
    font-size: 0.85rem;
    white-space: nowrap;
}

/* The dot keeps its size when the word beside it wraps or runs short of room.
   Centred on the word's small letters, which sit below the middle of its
   line: centred on the line, it would float above them. */
.status::before {
    flex: none;
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentcolor;
    translate: 0 0.08em;
}

.status--success {
    color: var(--success);
}

.status--warning {
    color: var(--warning);
}

.status--error {
    color: var(--error);
}

.status--pending {
    color: var(--accent);
}
/* A student's submissions under their exercise (staff/student.html). */

/* An inset-row (which sets its box), laid out as a grid: on one line where
   there is room, when it came, the problem, the result and the way to it.
   On a phone in two: the problem with the way to it, then when it came with
   the result, the two wrapping as a pair. */
.submission-list__item {
    display: grid;
    grid-template-areas: "date problem status details";
    /* Each row is a grid of its own: the date's column as wide as the widest
       date ("Mo 30.11., 23:59") in every row, so the problems line up. */
    grid-template-columns: minmax(8.25em, auto) minmax(0, 1fr) auto auto;
    font-size: 0.88rem;
}

/* A long name wraps within its column, anywhere if it must, rather than
   being cut short or pushing the rest of the row aside. */
.submission-list__problem {
    grid-area: problem;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* When it came and the result: placed one by one where there is room. */
.submission-list__meta {
    display: contents;
}

.submission-list__date {
    grid-area: date;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.submission-list__status {
    grid-area: status;
}

.submission-list__details {
    grid-area: details;
    justify-self: end;
}

@media (max-width: 36rem) {
    .submission-list__item {
        grid-template-areas:
            "problem details"
            "meta meta";
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .submission-list__meta {
        display: flex;
        flex-wrap: wrap;
        grid-area: meta;
        gap: 0.15rem 0.75rem;
        align-items: center;
    }
}
/* Rows: a list of links with a title, a time and a state. A row stays one
   line of two parts however narrow it gets: its text, the title over the
   time, gives way; the state or the action at its end does not. */

.rows {
    margin: 0 -0.5rem;
}

.rows__item + .rows__item {
    border-top: 1px solid var(--border);
}

.rows__link,
.rows--plain .rows__item {
    display: flex;
    gap: 1rem;
    align-items: center;
    padding: 0.65rem 0.5rem;
    color: var(--text);
    text-decoration: none;
    border-radius: var(--radius-small);
}

.rows__link:hover {
    background: var(--surface-sunken);
    color: var(--text);
}

.rows__title {
    font-weight: 600;
}

.rows__meta {
    color: var(--muted);
    font-size: 0.88rem;
}

.rows__end {
    flex: none;
    margin-left: auto;
}

.rows__text {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
}
/* A small drawn sign (partials/glyph.html): a check, a cross, a star, a ring, an
   alert, an arrow, a lock, in the colour of what holds it, the same on every system. */

.glyph {
    width: 1em;
    height: 1em;
    fill: none;
    stroke: currentcolor;
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Drawn finer: an arrow, which points the way rather than states a result
   (Lucide's arrows, a touch heavier than theirs beside bold text), and a
   lock, whose shackle and clock a heavier stroke would fill in. */
.glyph--fine {
    stroke-width: 2.25;
}

/* A shape drawn filled, its stroke only rounding its corners: the star. */
.glyph__solid {
    fill: currentcolor;
    stroke-width: 1.5;
}

/* An arrow, only as wide as its box, which ends at its point (20.125 of the
   drawing's 24). */
.glyph--arrow {
    width: calc(1em * 20.125 / 24);
}
/* Marks: a state in a small circle, the same on every page. */

.mark {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    border: 1.5px solid var(--border-strong);
    color: var(--faint);
    font-size: 0.8rem;
    font-weight: 700;
    line-height: 1;
}

.mark--counted,
.mark--passed {
    border-color: var(--success);
    background: var(--success);
    color: var(--surface);
}

.mark--missed,
.mark--failed {
    border-color: var(--error);
    background: var(--error);
    color: var(--surface);
}

.mark--excused {
    border-color: var(--muted);
    color: var(--muted);
}

/* A pending exercise is a clock in the accent: its check is still to finish. */
.mark--pending {
    border-color: var(--accent);
    color: var(--accent);
}

/* A check check50 could not run: a warning, told apart from a failed one. */
.mark--errored {
    border-color: var(--warning);
    background: var(--warning);
    color: var(--surface);
}

/* An open or upcoming exercise is an empty ring: a sign would only add noise. */
.mark--open {
    border-color: var(--accent);
}

.mark--upcoming {
    border-style: dashed;
}

/* An exercise that is not the student's: no state at all. */
.mark--none {
    border-style: dotted;
}

.mark--small {
    vertical-align: middle;
    width: 1.3rem;
    height: 1.3rem;
    font-size: 0.72rem;
}

/* The sign in a mark (partials/glyph.html), sized to its circle. */
.mark__glyph {
    width: 0.85rem;
    height: 0.85rem;
}

.mark--small .mark__glyph {
    width: 0.7rem;
    height: 0.7rem;
}
/* The count under a student's standing, with the next step: a card's footer. */

.tally__count {
    font-weight: 650;
}
/* Check results (§7.5): the student's and the tutors'. */

.check-list {
    margin: 0 0 1.25rem;
}

.check {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    padding: 0.55rem 0;
}

.check + .check {
    border-top: 1px solid var(--border);
}

.check__mark {
    margin-top: 0.05rem;
}

/* The rest of the row, so a log below the text spans the whole card. */
.check__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.check--skipped .check__text {
    color: var(--muted);
}

.check__detail {
    font-family: var(--mono);
    font-size: 0.8rem;
    color: var(--muted);
    overflow-wrap: anywhere;
}

/* A check's own name beside what it says, where the team reads it
   (partials/checks.html, check_label): a block of its own, as the charts
   name their checks so too. */
.check-name {
    margin-left: 0.35rem;
    font-size: 0.78rem;
    color: var(--muted);
}
/* A problem's challenge: offered under a result, set as the task on its page. */

.challenge__button {
    margin-top: 0.5rem;
}

.challenge {
    margin: 1.25rem 0 0;
    padding: 0.9rem 1rem;
    border-left: 3px solid var(--accent);
    border-radius: 0 var(--radius-small) var(--radius-small) 0;
    background: var(--surface-sunken);
}

.challenge p {
    margin: 0 0 0.35rem;
}

.challenge p:last-child {
    margin: 0;
}

/* The challenge's task on its upload page: above the form, spaced like an intro. */
.challenge--intro {
    margin: 0 0 1.25rem;
}

.challenge__heading {
    font-weight: 650;
}

.challenge__note {
    color: var(--muted);
    font-size: 0.9rem;
}
/* A submission's result on the student's page (dashboard/partials/result.html). */

.result__title {
    margin: 1.25rem 0 0.25rem;
    font-size: 0.95rem;
}

/* Below the checks, where one was skipped: what its mark means. */
.result__note {
    margin: 0.5rem 0 0;
    color: var(--muted);
    font-size: 0.85rem;
}

.result > :last-child {
    margin-bottom: 0;
}
/* What shows while a check runs. */

.checking {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    margin: 0;
    padding: 1rem;
    border-radius: var(--radius-small);
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 500;
}

.checking__text {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

/* Where a queued run waits: an estimate, said below the line it explains. */
.checking__queue {
    color: var(--muted);
    font-size: 0.9375rem;
    font-weight: 400;
}

.spinner {
    flex: none;
    width: 1.1rem;
    height: 1.1rem;
    border: 2px solid var(--accent-line);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.9s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
    .spinner {
        animation-duration: 3s;
    }
}
/* A check run on the staff's submission page, and a check's log. */

/* A run in the card of a submission's runs, divided from the one before. */
.run {
    padding: 1rem 1.25rem;
}

.run + .run {
    border-top: 1px solid var(--border);
}

.run > :last-child {
    margin-bottom: 0;
}

/* Its columns 2rem apart: the facts after the status reach out a dot's
   width to the left (components/meta-list.css), which leaves 1rem. */
.run__head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 2rem;
    align-items: center;
    margin-bottom: 0.75rem;
    font-size: 0.9rem;
}

.run__checks {
    margin: 0;
}

.log__summary {
    color: var(--muted);
    font-size: 0.82rem;
    cursor: pointer;
}

/* A log keeps its lines as the check wrote them; a long one scrolls. */
.log__text {
    width: 100%;
    margin-top: 0.4rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius-small);
    background: var(--surface-sunken);
    font-size: 0.78rem;
    white-space: pre;
    overflow-x: auto;
}
/* What a submission means for the star, under its page's head: a label,
   the status (partials/run_status.html's look) and why, in one line where
   there is room. */

.verdict {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    align-items: center;
    margin: 0.5rem 0 0;
}

.verdict__label {
    color: var(--muted);
    font-size: 0.85rem;
    font-weight: 600;
}

.verdict__reason {
    color: var(--muted);
    font-size: 0.9rem;
}
/* Figures at a glance on the staff pages. */

.stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    gap: 1rem;
}

/* A card (which sets its surface) laid out as one figure. */
.stat {
    display: flex;
    flex-direction: column;
    padding: 1rem 1.15rem;
}

/* A figure that leads to the list it counts: the whole card is the link,
   its label with an arrow. Pointed at, it takes the sunken surface, as a
   row of a list or a table does; the keyboard's focus is every link's. */
.stat--link {
    color: inherit;
    text-decoration: none;
    transition: background-color 0.15s;
}

.stat--link:hover {
    background: var(--surface-sunken);
    color: inherit;
}

.stat__label {
    color: var(--muted);
    font-size: 0.85rem;
}

.stat__label--link {
    display: inline-flex;
    gap: 0.25rem;
    align-items: center;
}

.stat__value {
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1.3;
    font-variant-numeric: tabular-nums;
}

.stat__note {
    color: var(--faint);
    font-size: 0.8rem;
}

.stat--alert .stat__value {
    color: var(--error);
}

@media (prefers-reduced-motion: reduce) {
    .stat--link {
        transition: none;
    }
}
/* The search over the students. */

.search {
    display: flex;
    gap: 0.5rem;
    width: 100%;
    max-width: 26rem;
}

.search__input {
    flex: 1;
    min-width: 0;
}
/* The staff pages' tables. */

/* A table wider than its card scrolls on its own, with a quiet bar. It holds
   what is positioned in it too (a visually hidden header), which would
   otherwise widen the page on a phone. Its width is a container's, which
   the line of an empty table measures itself by (.table__empty-message). */
.table-scroll {
    position: relative;
    container-type: inline-size;
    overflow-x: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
    /* It stops at its ends: no bounce, and no swipe back a page past them. */
    overscroll-behavior-x: none;
}

/* Its cells weigh no more than one class (:where), so a cell's own class
   (.table__num) and the matrix's outweigh them by coming later. Each cell
   draws its own line (separate, no spacing): Safari paints a sticky cell
   (.table__details) over half of a line it shares, which looks thinner. */
.table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.92rem;
}

.table :where(th, td) {
    padding: 0.6rem 1.25rem;
    border-bottom: 1px solid var(--border);
    text-align: left;
    vertical-align: middle;
}

.table th {
    background: var(--surface-sunken);
    color: var(--muted);
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}

.table tbody tr:last-child td {
    border-bottom: 0;
}

/* A row is lit when pointed at; the line that says the table is empty is no row to point at. */
.table tbody tr:hover td:not(.table__empty) {
    background-color: var(--surface-sunken);
}

.table td a {
    text-decoration: none;
}

.table td a:hover {
    text-decoration: underline;
}

.table--matrix :where(th, td) {
    padding: 0.5rem 0.75rem;
}

.table--matrix :where(th, td):first-child {
    padding-left: 1.25rem;
}

.table--matrix thead th {
    position: sticky;
    top: 0;
}

.table__num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* The cell of a row's way to its own page (a details-link): kept at the
   right edge while a wide table scrolls under it, so every row's link is in
   reach. Opaque, the card's colour, and the row's tint on hover; as narrow
   as its link, the same in every row. */
.table__details {
    position: sticky;
    right: 0;
    width: 1%;
    background-color: var(--surface);
    text-align: right;
    white-space: nowrap;
}

/* Its edge, a line and a shadow over what scrolls under it, while more of
   the table is hidden there (static/js/tables.js), and none once it is
   scrolled to its end, or where the table fits. Drawn beside the cell, a
   layer of its own that fades in and out. It reaches over the cell's line
   too, so the rows' lines run under it unbroken; the last row has none. */
.table__details::before {
    position: absolute;
    top: 0;
    bottom: -1px;
    left: -0.75rem;
    width: 0.75rem;
    background: linear-gradient(to left, var(--shadow-edge), transparent);
    box-shadow: inset -1px 0 0 var(--border);
    content: "";
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
}

.table tbody tr:last-child .table__details::before {
    bottom: 0;
}

.table-scroll--more-right .table__details::before {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .table__details::before {
        transition: none;
    }
}

.table__empty {
    padding: 0 !important;
}

/* The line that says the table is empty, as wide as what is seen of the
   table (cqi of the .table-scroll) and held there, so on a phone it stands
   in the middle of the screen, not of a table wider than it. */
.table__empty-message {
    position: sticky;
    left: 0;
    display: block;
    width: 100cqi;
    padding: 1.5rem 1.25rem;
    color: var(--muted);
    text-align: center;
}

/* Narrow padding: a mark column is as wide as its mark, whatever the table. */
.table__mark-cell {
    width: 2.5rem;
    min-width: 2.5rem;
    padding-right: 0.25rem;
    padding-left: 0.25rem;
    text-align: center !important;
}

/* An exercise that counts for nothing, in no part: thin stripes, drawn as an image
   so a row's hover tint shows through them instead of hiding them. */
.table__mark-cell--outside {
    background-image: repeating-linear-gradient(
        135deg,
        transparent 0 5px,
        var(--border-strong) 5px 6px
    );
}

/* Columns as wide as what they hold, not as the rows shown: a search or
   another page moves none of them. A header cell sets its column's width
   (and keeps it when the table is squeezed: it scrolls instead); one column
   (an e-mail's) fills the room left, its text giving it no width of its own
   (contain) and cut short, whole in its title. */
.table__head--fill {
    width: 100%;
}

.table__head--matrikel {
    width: 8.5rem;
    min-width: 8.5rem;
}

.table__head--joker {
    width: 9.5rem;
    min-width: 9.5rem;
}

.table__head--mug {
    width: 5.5rem;
    min-width: 5.5rem;
}

.table__head--date {
    width: 11rem;
    min-width: 11rem;
}

.table__head--problem,
.table__head--result {
    width: 13rem;
    min-width: 13rem;
}

.table__head--day {
    width: 8.5rem;
    min-width: 8.5rem;
}

.table__head--submission {
    width: 19rem;
    min-width: 19rem;
}

.table__head--role {
    width: 9rem;
    min-width: 9rem;
}

.table__truncate {
    display: block;
    min-width: 12rem;
    contain: inline-size;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A second, quieter line under a cell's own: whose a submission is. */
.table__subline {
    margin-top: 0.1rem;
    color: var(--muted);
    font-size: 0.84rem;
}

/* A deadline after the lock that says where its exercise stands, centred on it. */
.table__deadline {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

.table__lock {
    display: inline-flex;
    flex: none;
    color: var(--muted);
}

.table__lock-glyph {
    width: 1.1em;
    height: 1.1em;
}
/* How the students fare with an exercise's checks (§10.2): the cards of its
   parts on its page, and on a part's page its charts, each keyed by a
   legend (components/legend.css). A share is a bar on a track, named beside
   it, its percent after it; past the deadline two, the count at it in the
   context's grey and now in the accent. A trend is an SVG stretched to its
   box (website/charts.py), its lines kept thin by vector-effect, each moment
   a column that, pointed at or tapped, shows its point and its tip
   (static/js/charts.js). */

/* A mark the keyboard focuses, to read its tip: ringed as a link is. */
.share-bar:focus-visible,
.star-chart__segment:focus-visible,
.histogram__bar:focus-visible,
.trend__plot:focus-visible {
    outline: var(--focus-outline);
    outline-offset: 2px;
}

/* A series' name beside its bar. */
.star-chart__series,
.share-bar__series {
    color: var(--muted);
    font-size: 0.75rem;
}

/* A figure after a bar or a line, in a column of them. */
.share-bar__percent,
.trend__value {
    font-size: 0.82rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

/* How students stand with the star checks: a stacked bar for each series,
   named beside it, its groups apart by a gap of the surface's colour between
   them, its ends rounded; each group's percent in it, in the ink that reads on it. */
.star-chart {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.star-chart__row {
    display: flex;
    gap: 0.75rem;
    align-items: center;
}

.star-chart__series {
    flex: none;
    width: 2.6rem;
}

.star-chart__bar {
    display: block;
    flex: 1;
    min-width: 0;
    height: 1.5rem;
    border-radius: 4px;
    overflow: hidden;
}

/* A gap where two groups meet, of the surface's colour: none around the bar. */
.star-chart__gap {
    stroke: var(--surface);
    stroke-width: 2px;
    pointer-events: none;
}

.star-chart__fill--all {
    fill: var(--chart-all);
}

.star-chart__fill--star {
    fill: var(--chart-star);
}

.star-chart__fill--none {
    fill: var(--chart-none);
}

.star-chart__segment:hover,
.star-chart__segment--active {
    opacity: 0.85;
}

.star-chart__value {
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-anchor: middle;
    pointer-events: none;
}

.star-chart__value--all {
    fill: var(--chart-all-ink);
}

.star-chart__value--star {
    fill: var(--chart-star-ink);
}

.star-chart__value--none {
    fill: var(--chart-none-ink);
}

/* A count as a bar: its series' name, the track, and its percent, in
   columns as wide in every row so the bars line up. The track clips the
   bar, so its start is rounded as the track is. */
.share-bars {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
}

.share-bar {
    display: grid;
    grid-template-columns: 2.6rem minmax(3rem, 1fr) 2.75rem;
    gap: 0.75rem;
    align-items: center;
}


.share-bar__track {
    display: block;
    width: 100%;
    height: 0.4rem;
    border-radius: 3px;
    background: var(--chart-track);
    overflow: hidden;
}

.share-bar__fill--context {
    fill: var(--chart-context);
}

.share-bar__fill--point {
    fill: var(--chart-point);
}

/* An exercise's parts, a card each, side by side as far as there is room;
   one alone takes the page's width. */
.failing {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
    gap: 1.25rem;
    align-items: start;
}

.failing__title {
    font-size: 1rem;
}

.failing__body {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 0.9rem 1.25rem 1.1rem;
}

.failing__note {
    margin: 0;
    color: var(--muted);
    font-size: 0.82rem;
}

.failing__subtitle {
    margin: 0.35rem 0 0;
    font-size: 0.9rem;
}

.failing__note--more {
    margin-top: -0.25rem;
}

.failing__all {
    display: inline-flex;
    gap: 0.35rem;
    align-items: center;
    margin: 0;
    color: var(--success);
    font-size: 0.88rem;
}

/* A chart's card on a part's page (a card, components/card.css), its header
   one with a bar: its title, what it shows under it, and the key. */
.chart-card__title {
    margin: 0;
    font-size: 1.1rem;
}

.chart-card__note {
    flex-basis: 100%;
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
}


/* Rows of a check and its chart, its bars or its trend: side by side where
   the list has room, the label over its chart where it has not, on a phone
   or in a narrow part. The trends' rows give the line more room, and end
   with the one time axis under them all. */
.chart-rows {
    margin: 0;
    padding: 0;
    container-type: inline-size;
    list-style: none;
}

.chart-rows__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.4rem 1.5rem;
    align-items: center;
    padding: 0.6rem 0;
    border-top: 1px solid var(--border);
}

.chart-rows__row:first-child {
    padding-top: 0;
    border-top: 0;
}

.chart-rows__row:last-child {
    padding-bottom: 0;
}

.chart-rows__row--axis {
    padding-top: 0.35rem;
    border-top: 0;
}

.chart-rows__label {
    font-size: 0.92rem;
}

.chart-rows__row--axis .chart-rows__label {
    display: none;
}

@container (min-width: 40rem) {
    .chart-rows__row {
        grid-template-columns: minmax(0, 1fr) minmax(16rem, 24rem);
    }

    .chart-rows--trends .chart-rows__row {
        grid-template-columns: minmax(12rem, 1fr) minmax(20rem, 2fr);
    }

    .chart-rows__row--axis .chart-rows__label {
        display: block;
    }
}

/* How many students fail how many checks, or hand in when: a group of
   columns for each mark on one baseline, wider than the card on a phone
   scrolls. The room above the plot is for the tallest column's label. */
.histogram__groups {
    display: flex;
    justify-content: center;
    padding-top: 1.4rem;
    overflow-x: auto;
    scrollbar-width: thin;
}

.histogram__group {
    display: flex;
    flex: 1 0 2.75rem;
    flex-direction: column;
    gap: 0.4rem;
    max-width: 6rem;
}

/* Its height is what charts._COLUMN_LABEL_GAP is reckoned with. */
.histogram__plot {
    display: block;
    width: 100%;
    height: 9rem;
    border-bottom: 1px solid var(--border-strong);
    overflow: visible;
}

/* A column, its top rounded, its foot square on the baseline. */
.histogram__fill {
    clip-path: inset(0 round 4px 4px 0 0);
}

.histogram__fill--context {
    fill: var(--chart-context);
}

.histogram__fill--point {
    fill: var(--chart-point);
}

.histogram__bar:hover,
.histogram__bar--active {
    opacity: 0.8;
}

/* A column's percent over it, ringed in the surface's colour where it
   reaches over the column beside it. */
.histogram__value {
    fill: var(--text);
    font-size: 0.72rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-anchor: middle;
    paint-order: stroke;
    stroke: var(--surface);
    stroke-width: 3px;
    stroke-linejoin: round;
    pointer-events: none;
}

.histogram__label {
    color: var(--muted);
    font-size: 0.8rem;
    line-height: 1.25;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.histogram__axis {
    margin: 0.35rem 0 0;
    color: var(--muted);
    font-size: 0.8rem;
    text-align: center;
}

/* A check's trend in its row: the line, and where it stands now after it;
   in the axis's row, the axis's first and last moment under the lines. */
.trend {
    display: grid;
    grid-template-columns: minmax(3rem, 1fr) 2.75rem;
    gap: 0.75rem;
    align-items: center;
}

.trend__plot {
    display: block;
    width: 100%;
    height: 2.25rem;
    overflow: visible;
}

.trend__rule {
    stroke: var(--border-strong);
    stroke-width: 1px;
    vector-effect: non-scaling-stroke;
}

.trend__area {
    fill: var(--chart-wash);
}

.trend__line {
    fill: none;
    stroke: var(--chart-point);
    stroke-width: 2px;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

/* The deadline, dashed as a threshold is, unlike any point. */
.trend__deadline {
    stroke: var(--muted);
    stroke-width: 1px;
    stroke-dasharray: 3 3;
    vector-effect: non-scaling-stroke;
}

/* A moment's point: unseen, until pointed at or tapped, then a dot on the
   line with a ring of the surface's colour, unlike the deadline's line. */
.trend__hit {
    fill: transparent;
}

.trend__ring,
.trend__dot {
    stroke-linecap: round;
    opacity: 0;
    vector-effect: non-scaling-stroke;
}

.trend__ring {
    stroke: var(--surface);
    stroke-width: 12px;
}

.trend__dot {
    stroke: var(--chart-point);
    stroke-width: 8px;
}

.trend__point:hover :is(.trend__ring, .trend__dot),
.trend__point--active :is(.trend__ring, .trend__dot) {
    opacity: 1;
}

.trend__ticks {
    display: flex;
    justify-content: space-between;
    color: var(--muted);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
}

/* A mark's readout, over it while it is pointed at, focused or tapped
   (static/js/charts.js): one for the page, placed in it by the script, so it
   stays on its mark through a pinch zoom. What it names on a line, its
   figures on the next (pre-line), never broken. */
.chart-tip {
    position: absolute;
    z-index: 30;
    max-width: min(18rem, calc(100vw - 1rem));
    padding: 0.3rem 0.55rem;
    border-radius: var(--radius-small);
    background: var(--text);
    box-shadow: var(--shadow-raised);
    color: var(--surface);
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
    white-space: pre-line;
    pointer-events: none;
}
/* What the marks of a table mean, below the card that holds it, quiet like
   a footnote: each sign with its word, in a row that wraps. A tooltip says
   it only to a mouse, so the key says it to a finger too. */

.legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.25rem;
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--muted);
    font-size: 0.85rem;
}

.legend__item {
    display: inline-flex;
    gap: 0.45rem;
    align-items: center;
}

/* An exercise that counts for nothing: the matrix's stripes, in a mark's size. */
.legend__outside {
    flex: none;
    width: 1.3rem;
    height: 1.3rem;
    border-radius: var(--radius-small);
    background-image: repeating-linear-gradient(
        135deg,
        transparent 0 5px,
        var(--border-strong) 5px 6px
    );
}

/* A chart's sign: the colour of a series or a group (components/check-charts.css). */
.legend__swatch {
    flex: none;
    width: 0.75rem;
    height: 0.5rem;
    border-radius: 2px;
}

.legend__swatch--context {
    background: var(--chart-context);
}

.legend__swatch--point {
    background: var(--chart-point);
}

.legend__swatch--all {
    background: var(--chart-all);
}

.legend__swatch--star {
    background: var(--chart-star);
}

.legend__swatch--none {
    background: var(--chart-none);
}

/* A line's sign is a line; the deadline's, the dashed hairline across a trend. */
.legend__swatch--line {
    height: 2px;
    border-radius: 1px;
    background: var(--chart-point);
}

.legend__swatch--deadline {
    width: 0;
    height: 0.8rem;
    border-left: 1px dashed var(--muted);
    border-radius: 0;
}

/* A series still to come: in the key, though not yet drawn. */
.legend__item--pending {
    opacity: 0.6;
}
/* The way between the pages of a long list: back, the count, on. Three
   columns, the outer two as wide as each other, so the count stays in the
   middle whether or not there is a way back or on. */

.pagination {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: 0.75rem;
    align-items: center;
    color: var(--muted);
    font-size: 0.9rem;
}

.pagination__previous {
    justify-self: end;
}

.pagination__count {
    text-align: center;
}

.pagination__next {
    justify-self: start;
}

/* On a phone the count takes a row of its own, over the two ways at its
   ends: beside them it squeezed their columns, and the buttons off the screen. */
@media (max-width: 36rem) {
    .pagination {
        grid-template-areas:
            "count count"
            "previous next";
        grid-template-columns: 1fr 1fr;
    }

    .pagination__previous {
        grid-area: previous;
        justify-self: start;
    }

    .pagination__count {
        grid-area: count;
    }

    .pagination__next {
        grid-area: next;
        justify-self: end;
    }
}
/* A submitted file, its name over its code. */

.file + .file {
    border-top: 1px solid var(--border);
}

.file__name {
    padding: 0.6rem 1.25rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface-sunken);
    font-family: var(--mono);
    font-size: 0.85rem;
    font-weight: 600;
}
/* Submitted code, a line at a time, with the lines numbered. */

/* The gutter is the block's own background, so its edge runs unbroken from
   the first line to the last; it scrolls along with the code ("local"). */
.code-view {
    --gutter: 3.5rem;
    padding: 0.85rem 0;
    counter-reset: line;
    font-size: 0.84rem;
    line-height: 1.6;
    tab-size: 4;
    background:
        linear-gradient(
            to right,
            var(--surface-sunken) 0 var(--gutter),
            var(--border) var(--gutter) calc(var(--gutter) + 1px),
            transparent calc(var(--gutter) + 1px)
        )
        local;
}

.code-view__line {
    counter-increment: line;
}

.code-view__line::before {
    content: counter(line);
    display: inline-block;
    width: var(--gutter);
    padding-right: 0.85rem;
    margin-right: 0.85rem;
    color: var(--faint);
    text-align: right;
    user-select: none;
}

/* Pygments' token classes (website/code.py): Pygments names them, not BEM,
   so they are kept as it writes them and only ever read inside the code view. */
.code-view .c,
.code-view .c1,
.code-view .cm,
.code-view .cs,
.code-view .ch {
    color: var(--code-comment);
    font-style: italic;
}

.code-view .k,
.code-view .kd,
.code-view .kn,
.code-view .kr,
.code-view .kc,
.code-view .ow {
    color: var(--code-keyword);
}

.code-view .kt,
.code-view .nc,
.code-view .bp {
    color: var(--code-type);
}

.code-view .s,
.code-view .s1,
.code-view .s2,
.code-view .sa,
.code-view .sb,
.code-view .sc,
.code-view .sd,
.code-view .sh,
.code-view .si,
.code-view .sx,
.code-view .dl {
    color: var(--code-string);
}

.code-view .se {
    color: var(--code-number);
}

.code-view .m,
.code-view .mi,
.code-view .mf,
.code-view .mh,
.code-view .mo,
.code-view .il {
    color: var(--code-number);
}

.code-view .cp,
.code-view .cpf {
    color: var(--code-preprocessor);
}

.code-view .nf,
.code-view .fm,
.code-view .nb {
    color: var(--code-function);
}

.code-view .nt {
    color: var(--code-keyword);
}

.code-view .na {
    color: var(--code-type);
}
/* The privacy policy: long-form reading across the container, as wide as the
   staff pages, so it lines up with the header; the parties it names first, as
   rows of one card. */

.policy__parties {
    margin-bottom: 2rem;
    padding: 0;
}

/* A party: what it is on the left, its address beside it; stacked on a phone. */
.policy__party {
    display: grid;
    grid-template-columns: 12rem minmax(0, 1fr);
    gap: 0.25rem 1.25rem;
    padding: 1rem 1.25rem;
}

.policy__party + .policy__party {
    border-top: 1px solid var(--border);
}

@media (max-width: 36rem) {
    .policy__party {
        grid-template-columns: minmax(0, 1fr);
    }
}

.policy__party-heading {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.6;
}

/* An address, one line each: name, street, phone, e-mail. A long e-mail
   breaks only where nothing else fits. */
.policy__contact {
    margin: 0;
    line-height: 1.6;
    overflow-wrap: anywhere;
}

.policy__section {
    margin-bottom: 1.75rem;
}

.policy__heading {
    margin-bottom: 0.5rem;
}

.policy__list {
    margin: 0 0 0.75rem;
    padding-left: 1.25rem;
    list-style: disc;
}

.policy__item::marker {
    color: var(--faint);
}

.policy__item + .policy__item {
    margin-top: 0.35rem;
}
/* The page's frame: a centred container, and the main column, which stacks
   the page's blocks (layout/stack.css). */

.container {
    width: 100%;
    max-width: 68rem;
    margin: 0 auto;
    padding: 0 16px;
}

.container--narrow {
    max-width: 60rem;
}

.site__main {
    flex: 1;
    padding-top: 1.75rem;
    padding-bottom: 2.5rem;
}
/* The blocks stacked in a column, one under another: the page's main column,
   a page about one thing (.focus-page), a part of a page that renders itself
   again with cards in it (.live-part) and the privacy policy. The column
   spaces them, each from the one before it, so no block spaces itself and a
   new wrapper that stacks blocks is one more stack, not one more rule. Blocks
   side by side in a grid (.columns, .stats) are spaced by its gap instead. */
.stack > * + * {
    margin-top: 1.25rem;
}

/* Closer after the way back, which belongs to the head below it, and the key
   under a table (.legend), which belongs to the card above it. */
.stack > .back + *,
.stack > * + .legend {
    margin-top: 1rem;
}

/* Farther after the page's head, which stands apart from what it heads. */
.stack > .page-head + * {
    margin-top: 1.5rem;
}
/* Two columns side by side where there is room. */

/* One column on a phone, which shrinks to the screen with what is in it,
   rather than growing to its widest line and running off the edge. */
.columns {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
}

@media (min-width: 52rem) {
    .columns {
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    }
}
/* The header: the brand, the tabs and the account in one row, two on a phone. */

.site-header {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.site-header__inner {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    align-items: center;
    justify-content: space-between;
    min-height: 3.5rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}

.site-header__tabs {
    margin-left: auto;
}

/* The team's account stands apart from the tabs before it. */
.site-header__tabs + .site-header__account {
    padding-left: 0.75rem;
    border-left: 1px solid var(--border);
}

/* On a phone the account goes up beside the brand, its e-mail cut to the room
   left, and the tabs get a row of their own, spread across it from edge to
   edge, at least a little apart, so an admin's four fit a phone of 360px; on
   a narrower one the row scrolls sideways (without a bar: the row's cut-off
   tab shows there is more). */
@media (max-width: 48rem) {
    .site-header__brand {
        flex: none;
    }

    .site-header__account {
        order: 1;
        flex: none;
        margin-left: auto;
    }

    .site-header__tabs {
        order: 2;
        flex: 1 0 100%;
        flex-wrap: nowrap;
        column-gap: 0.5rem;
        justify-content: space-between;
        margin-left: 0;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .site-header__tabs::-webkit-scrollbar {
        display: none;
    }

    .site-header__tabs + .site-header__account {
        padding-left: 0;
        border-left: 0;
    }
}
/* The footer's links. */

.site-footer {
    border-top: 1px solid var(--border);
}

.site-footer__inner {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.25rem;
    padding-top: 1.25rem;
    padding-bottom: 1.75rem;
    font-size: 0.85rem;
}

.site-footer__link {
    color: var(--muted);
    text-decoration: none;
}

.site-footer__link:hover {
    color: var(--accent);
    text-decoration: underline;
}
/* A page's own parts: its head, the way back, a narrow column for one thing
   and a row of actions. */

.page-head--tools {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: center;
    justify-content: space-between;
}

.page-head--tools .page-head__title {
    margin: 0;
}

.back {
    margin: 0;
    font-size: 0.9rem;
}

.back__link {
    display: inline-flex;
    gap: 0.25rem;
    align-items: center;
    color: var(--muted);
    text-decoration: none;
}

.back__link:hover {
    color: var(--accent);
}

/* A page about one thing, a problem to hand in or a result: one narrow
   column in the middle. */
.focus-page {
    max-width: var(--column-focus);
    margin: 0 auto;
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 1.25rem 0 0;
}
/* Utilities: the few one-purpose classes any block may carry. Loaded last,
   so they hold against a block's own rules. */

.mono {
    font-family: var(--mono);
    font-size: 0.88em;
}

.muted {
    color: var(--muted);
}

.nowrap {
    white-space: nowrap;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
