/* =============================================================
   components.css — the shared component set
   Loaded on every page, immediately after theme.css.

   This file is §3 (icons), §4 (state vocabulary) and §6 (components) of
   `design/design_v2/handoff/01-design-system.md`. The build order puts these
   before any page work for a reason: "a slightly worse component used
   everywhere beats a perfect one used once" (principle 4). A page stylesheet
   should be able to assume all of this exists and add only what is genuinely
   peculiar to that screen.

   Nothing here names a colour. Every value resolves through a token in
   theme.css, so the two themes swap with one `data-theme` attribute.
   ============================================================= */

/* =============================================================
   §3 — Icons
   -------------------------------------------------------------
   One family, drawn in icons.js. The only rules the stylesheet owns are how
   an icon sits next to text: it inherits the colour of whatever it is inside
   (that is the entire point of `currentColor`) and it never pushes a line
   taller than the text beside it.
   ============================================================= */
.icon {
    display: inline-block;
    vertical-align: -0.14em;
    flex-shrink: 0;
    /* The stroke must not thin out when a container scales the icon. */
    vector-effect: non-scaling-stroke;
}

/* Render sizes the design fixes: 12px in table cells, 13px in buttons (the
   default in icons.js), 17px in the rail. */
td .icon, th .icon { width: 12px; height: 12px; }

/* =============================================================
   §4 — State vocabulary
   ============================================================= */

/* The badge: glyph then label, in a 1px border of its own darkened tone. */
.state-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 9px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-control);
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    line-height: 1;
    white-space: nowrap;
    color: var(--fg-dim);
}
.state-badge .icon { width: 12px; height: 12px; }

.state-not_started { color: var(--fg-dim);  border-color: var(--line-strong); }
.state-started     { color: var(--accent);  border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.state-awaiting    { color: var(--pending); border-color: color-mix(in srgb, var(--pending) 45%, var(--line)); }
.state-partial     { color: var(--warn);    border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
.state-solved      { color: var(--pass);    border-color: color-mix(in srgb, var(--pass) 45%, var(--line)); }

/* The two flags. Not states — icons that can accompany any state. */
.flag-is_closed       { color: var(--fg-dim); }
.flag-unread_comments { color: var(--accent); }

/* Banner: a 2px left border in the tone, a tinted ground, an icon, one
   sentence. Hidden by default and occupying no space when empty — an error
   slot that reserves a gap is a permanent hole in the layout. */
.banner {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-left: 2px solid var(--line-strong);
    border-radius: 0 var(--radius-control) var(--radius-control) 0;
    background: var(--panel-2);
    color: var(--fg-2);
    font-size: 13px;
    line-height: 1.5;
}
.banner .icon { margin-top: 1px; }
.banner-fail { border-left-color: var(--fail);   background: var(--fail-soft);   color: var(--fail-ink); }
.banner-pass { border-left-color: var(--pass);   background: var(--pass-soft);   color: var(--pass-ink); }
.banner-info { border-left-color: var(--accent); background: var(--accent-soft); color: var(--accent-ink); }
.banner-warn { border-left-color: var(--warn);   background: var(--warn-soft);   color: var(--warn-ink); }

/* Inline status: a dot while pending, an icon + text once it resolves.
   Never a spinner — an indeterminate spinner says "something is happening"
   and nothing else, which is the least useful thing it could say. */
.inline-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    color: var(--fg-dim);
}
.inline-status.is-pending::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--fg-dim);
    flex-shrink: 0;
}
.inline-status.is-ok   { color: var(--pass); }
.inline-status.is-fail { color: var(--fail); }
.inline-status.is-warn { color: var(--warn); }

/* =============================================================
   §6 — Buttons
   -------------------------------------------------------------
   36px tall; 30px inside a table row, where a full-height control would push
   the row past the scan-friendly 13px of vertical padding; 44px only for the
   editor's Run band.
   ============================================================= */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 36px;
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    font-family: var(--font-sans);
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: color var(--transition), background var(--transition),
                border-color var(--transition);
}

.btn-primary {
    background: var(--fg);
    color: var(--bg);
    font-weight: 700;
    border-color: var(--fg);
}
.btn-primary:hover { background: var(--fg-2); border-color: var(--fg-2); }

.btn-secondary {
    background: var(--panel-2);
    color: var(--fg);
    border-color: var(--line-strong);
}
.btn-secondary:hover { background: var(--row-hover); }

.btn-quiet {
    background: transparent;
    color: var(--fg-2);
    border-color: var(--line);
}
.btn-quiet:hover { color: var(--fg); border-color: var(--line-strong); }

.btn-destructive {
    background: transparent;
    color: var(--fail);
    border-color: var(--line);
}
.btn-destructive:hover { border-color: var(--fail); }

/* Disabled says "not now" by going quiet, never by changing shape or ground —
   a button that also moves reads as a different button. */
.btn:disabled,
.btn[disabled],
.btn.is-disabled {
    color: var(--fg-mute);
    border-color: var(--line);
    cursor: not-allowed;
}
.btn-primary:disabled { background: var(--panel-2); }

/* Rows are denser than the page they sit in. */
.btn-row { height: 30px; padding: 0 10px; font-size: 13px; }

/* Icon-only button. 28×28 with the icon centred; its accessible name comes
   from `aria-label`, which every call site sets. */
.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    color: var(--fg-dim);
    cursor: pointer;
    transition: color var(--transition), border-color var(--transition);
}
.btn-icon:hover { color: var(--fg); border-color: var(--line); }
.btn-icon.is-destructive:hover { color: var(--fail); border-color: var(--fail); }
.btn-icon:disabled { color: var(--fg-mute); cursor: not-allowed; }

/* =============================================================
   §6 — Fields
   ============================================================= */
.field { display: flex; flex-direction: column; gap: 5px; }

.field-label,
.field label {
    font-size: 12.5px;
    font-weight: 500;
    color: var(--fg-2);
}

.field-hint {
    font-size: 12px;
    line-height: 1.45;
    color: var(--fg-dim);
}

.field-input,
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field input[type="search"],
.field input[type="datetime-local"],
.field select,
.field textarea {
    width: 100%;
    min-height: 36px;
    padding: 8px 10px;
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-control);
    color: var(--fg);
    font-size: 13.5px;
    font-family: var(--font-sans);
}
/* Machine values render mono; typed prose renders sans (design §6). */
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field input[type="datetime-local"] { font-family: var(--font-mono); }

.field-input:focus,
.field input:focus,
.field select:focus,
.field textarea:focus {
    outline: none;
    border-color: var(--accent);
}

.field-input.is-invalid,
.field input.is-invalid,
.field.is-invalid input { border-color: var(--fail); }

.field-error { font-size: 12px; color: var(--fail); }

/* A password field carries its Show/Hide inside, on the right. It must not
   steal focus — the caret stays where the user left it.

   Two things are pinned here rather than inherited, because Show/Hide swaps
   the input's `type` and every type-keyed rule above therefore stops applying
   mid-interaction (owner, 2026-08-06: revealing the password made the whole
   auth card resize and everything above it shift down):
     * the face — mono is chosen because a password is a machine value, and it
       must not become sans just because the type is momentarily "text";
     * the height — a fixed 36px, not a `min-height`, so the field cannot
       breathe at all when its content metrics change. */
.password-field { position: relative; display: flex; align-items: center; }
.password-field input,
.field .password-field input {
    width: 100%;
    padding-right: 36px;
    height: 36px;
    font-family: var(--font-mono);
}
.password-toggle {
    position: absolute;
    right: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    background: none;
    border: none;
    border-radius: var(--radius-control);
    color: var(--fg-dim);
    padding: 0;
    cursor: pointer;
    transition: color var(--transition);
}
.password-toggle:hover { color: var(--fg); }

/* Checkbox: 15px, square-ish, accent when on. */
.field input[type="checkbox"],
.checkbox {
    width: 15px;
    height: 15px;
    min-height: 0;
    accent-color: var(--accent);
    border-radius: var(--radius-control);
    cursor: pointer;
}

/* =============================================================
   §6 — Table
   ============================================================= */
.data-table {
    width: 100%;
    border-collapse: collapse;
}
.data-table thead th {
    padding: 8px 10px;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: 500;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--fg-dim);
    text-align: left;
    white-space: nowrap;
}
.data-table tbody td {
    padding: 13px 10px;
    border-bottom: 1px solid var(--panel-2);
    color: var(--fg-2);
    font-size: 13px;
    vertical-align: middle;
}
.data-table tbody tr:hover { background: var(--row-hover); }

/* Numbers and dates are read by comparison, so they are right-aligned, mono
   and fixed width. Exactly one column — the title — is elastic. */
.data-table .col-score,
.data-table .col-attempts,
.data-table .col-due,
.data-table .col-start,
.data-table .col-activity {
    font-family: var(--font-mono);
    font-size: 12px;
    text-align: right;
    white-space: nowrap;
}

/* A wide table scrolls inside its own card rather than squashing its columns:
   a table nobody can read is worse than one they have to push sideways. */
.table-scroll { overflow-x: auto; }

/* Sortable headers are buttons so they are reachable by keyboard, and they
   say which way they sort through `aria-sort`, not only through the chevron. */
.sort-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: inherit;
    cursor: pointer;
}
.sort-btn:hover { color: var(--fg-2); }
th[aria-sort="ascending"] .sort-btn,
th[aria-sort="descending"] .sort-btn { color: var(--fg); }
.sort-btn .icon { opacity: 0; transition: opacity var(--transition); }
th[aria-sort="ascending"] .sort-btn .icon,
th[aria-sort="descending"] .sort-btn .icon,
.sort-btn:hover .icon { opacity: 1; }

/* =============================================================
   §6 — Card header and empty state
   ============================================================= */
.card-header {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    margin-bottom: var(--sp-5);
}
/* The hairline eats the slack between the label and the one right-hand fact. */
.card-header::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--line);
    order: 1;
}
.card-header > :first-child { order: 0; }
.card-header > :last-child:not(:first-child) { order: 2; }
.card-header-fact {
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--fg-dim);
}

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-9) var(--sp-6);
    /* Sans, not serif: v2 §2 rules out "every empty state" by name. An empty
       state is the application explaining itself, not an exercise. */
    font-family: var(--font-sans);
    font-size: 14.5px;
    color: var(--fg-2);
    text-align: center;
}

/* =============================================================
   §6 — Dialog
   -------------------------------------------------------------
   The replacement for every native confirm/alert/prompt. Native dialogs
   cannot be themed, cannot carry a subtitle naming *which* student a
   destructive action is about, and on some platforms are suppressed
   altogether — which turns "are you sure?" into "done".
   ============================================================= */
.dialog-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-6);
    background: rgba(6, 8, 11, 0.72);
}

.dialog {
    width: 100%;
    max-width: 460px;
    background: var(--bg);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-overlay);
    display: flex;
    flex-direction: column;
}

.dialog-header {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    padding: var(--sp-6) var(--sp-7) var(--sp-4);
}
.dialog-heading { flex: 1; min-width: 0; }
/* Sans: a dialog title is the application asking a question ("Delete class?"),
   which v2 §2 rules out of serif along with every dialog body. Where a dialog
   is titled with a task, the caller puts the task in the subtitle slot. */
.dialog-title {
    font-family: var(--font-sans);
    font-size: 17px;
    font-weight: 700;
    color: var(--fg);
    margin: 0;
}
.dialog-subtitle {
    display: block;
    margin-top: 3px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--fg-dim);
}

.dialog-body {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    padding: 0 var(--sp-7) var(--sp-7);
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--fg-2);
}
.dialog-body p { margin: 0; }
.dialog-note { font-size: 12.5px; color: var(--fg-dim); }

/* Two buttons, separated by a rule, primary on the right and wider: the
   action the dialog exists to offer should be the one the eye ends on. */
.dialog-footer {
    display: flex;
    border-top: 1px solid var(--line);
}
.dialog-footer .btn {
    flex: 1;
    height: 44px;
    border-radius: 0;
    border: none;
}
.dialog-footer .btn + .btn {
    flex: 1.4;
    border-left: 1px solid var(--line);
}
.dialog-footer .btn-primary.is-destructive {
    background: var(--fail);
    color: var(--bg);
}

/* =============================================================
   Utility
   ============================================================= */

/* Present for screen readers, absent to the eye. Used for the labels that a
   sighted user reads from the layout and a screen-reader user cannot. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* =============================================================
   The create button on a list screen
   -------------------------------------------------------------
   "+ New task" on Tasks and "Create new assignment" on Assignments are the
   same act on two screens, so they are one rule rather than two that drift
   (owner, 2026-08-12). Outlined in accent rather than filled: it is the
   primary action *of the list*, but the list itself is what the teacher came
   to read, so it announces itself without shouting over the rows.
   ============================================================= */
.btn-create {
    padding: 0.4rem 0.9rem;
    border: 1px solid var(--accent);
    border-radius: 8px;
    background: var(--accent-soft);
    color: var(--accent);
    font-family: var(--font-sans);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    flex-shrink: 0;
    cursor: pointer;
}
.btn-create:hover { background: var(--accent); color: var(--bg); }
