/* ==========================================================================
   CodeClass - components
   ========================================================================== */

/* ---- buttons ----------------------------------------------------------- */
.btn {
    --btn-bg: var(--surface);
    --btn-fg: var(--ink);
    --btn-line: var(--line-2);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    height: 34px;
    padding: 0 var(--s3);
    background: var(--btn-bg);
    color: var(--btn-fg);
    border: 1px solid var(--btn-line);
    border-radius: var(--r);
    font-size: var(--text-sm);
    font-weight: 550;
    letter-spacing: -0.005em;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
        transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), opacity var(--dur-fast);
}

.btn:hover:not(:disabled) {
    background: color-mix(in srgb, var(--btn-bg) 92%, var(--ink));
}

.btn:active:not(:disabled) {
    transform: translateY(1px);
}

.btn:disabled,
.btn[aria-disabled='true'] {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn svg {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
}

.btn-primary {
    --btn-bg: var(--brand);
    --btn-fg: var(--on-brand);
    --btn-line: var(--brand);
    box-shadow: var(--shadow-1);
}

.btn-primary:hover:not(:disabled) {
    background: var(--brand-ink);
    border-color: var(--brand-ink);
}

.btn-danger {
    --btn-bg: var(--bad);
    --btn-fg: #fff;
    --btn-line: var(--bad);
}

.btn-ghost {
    --btn-bg: transparent;
    --btn-line: transparent;
    --btn-fg: var(--ink-2);
}

.btn-ghost:hover:not(:disabled) {
    background: var(--surface-3);
    color: var(--ink);
}

.btn-subtle {
    --btn-bg: var(--surface-3);
    --btn-line: transparent;
}

.btn-quiet-danger {
    --btn-bg: transparent;
    --btn-line: transparent;
    --btn-fg: var(--bad);
}

.btn-quiet-danger:hover:not(:disabled) {
    background: var(--bad-soft);
}

.btn-sm {
    height: 27px;
    padding: 0 var(--s2);
    font-size: var(--text-xs);
    border-radius: var(--r-sm);
}

.btn-sm svg {
    width: 13px;
    height: 13px;
}

.btn-lg {
    height: 42px;
    padding: 0 var(--s5);
    font-size: var(--text-base);
}

.btn-icon {
    width: 34px;
    padding: 0;
}

.btn-icon.btn-sm {
    width: 27px;
}

.btn-block {
    width: 100%;
}

.btn[data-busy='true'] {
    color: transparent !important;
    pointer-events: none;
}

.btn[data-busy='true']::after {
    content: '';
    position: absolute;
    width: 14px;
    height: 14px;
    border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
    border-top-color: var(--btn-fg);
    border-radius: 50%;
    animation: spin 620ms linear infinite;
}

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

.btn-group {
    display: inline-flex;
}

.btn-group .btn {
    border-radius: 0;
    margin-left: -1px;
}

.btn-group .btn:first-child {
    border-radius: var(--r) 0 0 var(--r);
    margin-left: 0;
}

.btn-group .btn:last-child {
    border-radius: 0 var(--r) var(--r) 0;
}

/* ---- segmented control ------------------------------------------------- */
.segmented {
    align-self: flex-start;
    display: inline-flex;
    padding: 2px;
    background: var(--surface-3);
    border-radius: var(--r);
    gap: 2px;
}

.segmented button {
    height: 28px;
    padding: 0 var(--s3);
    border: 0;
    background: transparent;
    border-radius: var(--r-sm);
    font-size: var(--text-xs);
    font-weight: 550;
    color: var(--ink-3);
    cursor: pointer;
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.segmented button:hover {
    color: var(--ink);
}

.segmented button[aria-pressed='true'] {
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-1);
}

/* ---- badges / chips ---------------------------------------------------- */
.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    padding: 1px var(--s2);
    height: 20px;
    border-radius: var(--r-sm);
    background: var(--muted-soft);
    color: var(--ink-2);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.badge-ok { background: var(--ok-soft); color: var(--ok); }
.badge-warn { background: var(--warn-soft); color: var(--warn); }
.badge-bad { background: var(--bad-soft); color: var(--bad); }
.badge-info { background: var(--info-soft); color: var(--info); }
.badge-brand { background: var(--brand-soft); color: var(--brand-soft-ink); }
.badge-attn { background: var(--attn-soft); color: var(--attn); }

.badge-dot::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    height: 24px;
    padding: 0 var(--s2);
    border-radius: var(--r-pill);
    border: 1px solid var(--line-2);
    background: var(--surface);
    font-size: var(--text-xs);
    color: var(--ink-2);
    white-space: nowrap;
}

.chip-swatch {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: 0 0 auto;
}

button.chip {
    cursor: pointer;
    transition: border-color var(--dur-fast), background-color var(--dur-fast);
}

button.chip:hover {
    border-color: var(--line-strong);
}

button.chip[aria-pressed='true'] {
    background: var(--brand-soft);
    border-color: color-mix(in srgb, var(--brand) 45%, transparent);
    color: var(--brand-soft-ink);
    font-weight: 600;
}

.chip-x {
    display: grid;
    place-items: center;
    width: 15px;
    height: 15px;
    margin-right: -3px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--ink-3);
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
}

.chip-x:hover {
    background: var(--line);
    color: var(--ink);
}

/* ---- cards / panels ---------------------------------------------------- */
.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-1);
}

.card-pad {
    padding: var(--s5);
}

.card-head {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--line);
    min-height: 48px;
}

.card-title {
    font-size: var(--text-sm);
    font-weight: 650;
    letter-spacing: -0.005em;
}

.card-body {
    padding: var(--s4);
}

.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    overflow: hidden;
}

.section-title {
    display: flex;
    align-items: baseline;
    gap: var(--s2);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.section-title::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--line);
}

/* ---- stat tiles -------------------------------------------------------- */
.stats {
    display: grid;
    gap: var(--s3);
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

.stat {
    padding: var(--s3) var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.stat-label {
    font-size: var(--text-2xs);
    font-weight: 650;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.stat-value {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.02em;
}

.stat-sub {
    font-size: var(--text-xs);
    color: var(--ink-3);
}

.stat-accent .stat-value { color: var(--attn); }
.stat-ok .stat-value { color: var(--ok); }

/* ---- tables ------------------------------------------------------------ */
.table-wrap {
    overflow: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
}

table.tbl {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--text-sm);
}

.tbl th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-2);
    text-align: left;
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding: var(--s2) var(--pad-cell);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

.tbl th.sortable {
    cursor: pointer;
    user-select: none;
}

.tbl th.sortable:hover {
    color: var(--ink);
}

.tbl th .sort-mark {
    margin-left: 3px;
    opacity: 0.85;
}

.tbl td {
    padding: 0 var(--pad-cell);
    height: var(--row-h);
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

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

.tbl tbody tr {
    transition: background-color var(--dur-fast) var(--ease);
}

.tbl tbody tr:hover {
    background: var(--surface-2);
}

.tbl tbody tr[aria-selected='true'] {
    background: var(--brand-soft);
}

.tbl tr.clickable {
    cursor: pointer;
}

.tbl td.col-num,
.tbl th.col-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.tbl td.col-tight,
.tbl th.col-tight {
    width: 1%;
    white-space: nowrap;
}

/**
 * Faint on every row, solid on the one you are pointing at.
 *
 * These used to be invisible until hover, which kept the table quiet at the
 * cost of hiding the only route to archiving or deleting a row. A teacher who
 * has not found it has no reason to believe it is there, and an action nobody
 * can find is an action that does not exist. Showing it faintly says "there is
 * something here" without drawing a column of hard dots down the page.
 */
.tbl .row-actions {
    display: flex;
    gap: var(--s1);
    justify-content: flex-end;
    opacity: 0.45;
    transition: opacity var(--dur-fast) var(--ease);
}

.tbl tr:hover .row-actions,
.tbl tr:focus-within .row-actions {
    opacity: 1;
}

@media (hover: none) {
    .tbl .row-actions { opacity: 1; }
}

.cell-primary {
    font-weight: 550;
    color: var(--ink);
}

.cell-sub {
    font-size: var(--text-xs);
    color: var(--ink-3);
}

/* ---- toolbar ----------------------------------------------------------- */
.toolbar {
    display: flex;
    align-items: center;
    gap: var(--s2);
    flex-wrap: wrap;
    padding-bottom: var(--s3);
}

/* flex-basis would be read as a height inside a column, so size by width. */
.search {
    position: relative;
    width: 260px;
    max-width: 100%;
    min-width: 160px;
    flex: 0 1 auto;
}

.toolbar .select,
.toolbar .input {
    width: auto;
    min-width: 150px;
    flex: 0 0 auto;
}

.search .input {
    padding-left: 30px;
}

.search svg {
    position: absolute;
    left: 9px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    color: var(--ink-4);
    pointer-events: none;
}

.bulkbar {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) var(--s3);
    margin-bottom: var(--s3);
    background: var(--brand-soft);
    border: 1px solid color-mix(in srgb, var(--brand) 32%, transparent);
    border-radius: var(--r);
    font-size: var(--text-sm);
    color: var(--brand-soft-ink);
}

/* ---- tabs -------------------------------------------------------------- */
.tabs {
    display: flex;
    gap: var(--s1);
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
    scrollbar-width: none;
}

.tabs::-webkit-scrollbar { display: none; }

.tab {
    position: relative;
    padding: var(--s2) var(--s3);
    border: 0;
    background: transparent;
    color: var(--ink-3);
    font-size: var(--text-sm);
    font-weight: 550;
    cursor: pointer;
    white-space: nowrap;
    border-radius: var(--r) var(--r) 0 0;
    transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.tab:hover {
    color: var(--ink);
    background: var(--surface-2);
}

.tab[aria-selected='true'] {
    color: var(--ink);
}

.tab[aria-selected='true']::after {
    content: '';
    position: absolute;
    left: var(--s2);
    right: var(--s2);
    bottom: -1px;
    height: 2px;
    background: var(--brand);
    border-radius: 2px 2px 0 0;
}

.tab .count {
    margin-left: var(--s1);
    padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--muted-soft);
    font-size: var(--text-2xs);
    color: var(--ink-3);
}

/* ---- modal ------------------------------------------------------------- */
.scrim {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    background: var(--overlay);
    backdrop-filter: blur(2px);
    display: grid;
    place-items: center;
    padding: var(--s4);
    animation: fade var(--dur) var(--ease);
}

@keyframes fade {
    from { opacity: 0; }
}

.modal {
    width: min(560px, 100%);
    max-height: min(86vh, 900px);
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--line-2);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-3);
    animation: pop var(--dur) var(--ease);
    overflow: hidden;
}

@keyframes pop {
    from { opacity: 0; transform: translateY(8px) scale(0.985); }
}

.modal-lg { width: min(880px, 100%); }
.modal-xl { width: min(1180px, 100%); height: min(86vh, 900px); }
.modal-sm { width: min(430px, 100%); }

.modal-head {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s4) var(--s5);
    border-bottom: 1px solid var(--line);
}

.modal-title {
    font-size: var(--text-md);
    font-weight: 650;
    letter-spacing: -0.01em;
}

.modal-body {
    padding: var(--s5);
    overflow: auto;
    display: flex;
    flex-direction: column;
    gap: var(--s4);
    flex: 1 1 auto;
}

.modal-foot {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s3) var(--s5);
    border-top: 1px solid var(--line);
    background: var(--surface-2);
}

.modal-x {
    margin-left: auto;
}

/* ---- drawer ------------------------------------------------------------ */
.drawer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-drawer);
    width: min(460px, 100%);
    background: var(--surface);
    border-left: 1px solid var(--line-2);
    box-shadow: var(--shadow-3);
    display: flex;
    flex-direction: column;
    animation: slide-in var(--dur-slow) var(--ease);
}

@keyframes slide-in {
    from { transform: translateX(24px); opacity: 0; }
}

/* ---- menu / popover ---------------------------------------------------- */
.menu {
    position: fixed;
    z-index: var(--z-popover);
    min-width: 180px;
    padding: var(--s1);
    background: var(--surface);
    border: 1px solid var(--line-2);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-3);
    animation: pop var(--dur-fast) var(--ease);
}

.menu-item {
    display: flex;
    align-items: center;
    gap: var(--s2);
    width: 100%;
    padding: var(--s2) var(--s2);
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--ink-2);
    font-size: var(--text-sm);
    text-align: left;
    cursor: pointer;
}

.menu-item:hover {
    background: var(--surface-3);
    color: var(--ink);
}

.menu-item[data-danger] {
    color: var(--bad);
}

.menu-item[data-danger]:hover {
    background: var(--bad-soft);
}

.menu-sep {
    height: 1px;
    margin: var(--s1) 0;
    background: var(--line);
}

.menu-label {
    padding: var(--s2) var(--s2) var(--s1);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-4);
}

/* ---- toast ------------------------------------------------------------- */
.toasts {
    position: fixed;
    bottom: var(--s5);
    left: 50%;
    transform: translateX(-50%);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    align-items: center;
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: center;
    gap: var(--s3);
    max-width: min(520px, 92vw);
    padding: var(--s2) var(--s3) var(--s2) var(--s4);
    background: var(--surface);
    border: 1px solid var(--line-2);
    border-left: 3px solid var(--ink-3);
    border-radius: var(--r);
    box-shadow: var(--shadow-3);
    font-size: var(--text-sm);
    pointer-events: auto;
    animation: toast-in var(--dur) var(--ease);
}

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

.toast[data-out='true'] {
    animation: toast-out var(--dur) var(--ease) forwards;
}

@keyframes toast-out {
    to { opacity: 0; transform: translateY(6px); }
}

.toast-ok { border-left-color: var(--ok); }
.toast-bad { border-left-color: var(--bad); }
.toast-warn { border-left-color: var(--warn); }
.toast-info { border-left-color: var(--info); }

.toast-action {
    margin-left: auto;
    border: 0;
    background: transparent;
    color: var(--brand);
    font-size: var(--text-sm);
    font-weight: 600;
    cursor: pointer;
    padding: var(--s1) var(--s2);
    border-radius: var(--r-sm);
}

.toast-action:hover {
    background: var(--brand-soft);
}

/* ---- banner ------------------------------------------------------------ */
.banner {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    border-radius: var(--r);
    border: 1px solid var(--line-2);
    background: var(--surface-2);
    font-size: var(--text-sm);
    line-height: var(--leading-snug);
}

.banner-warn { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 30%, transparent); color: var(--ink); }
.banner-bad { background: var(--bad-soft); border-color: color-mix(in srgb, var(--bad) 30%, transparent); }
.banner-info { background: var(--info-soft); border-color: color-mix(in srgb, var(--info) 30%, transparent); }
.banner-ok { background: var(--ok-soft); border-color: color-mix(in srgb, var(--ok) 30%, transparent); }

/* ---- empty / loading --------------------------------------------------- */
.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s3);
    padding: var(--s12) var(--s5);
    text-align: center;
    color: var(--ink-3);
}

.empty-art {
    width: 46px;
    height: 46px;
    color: var(--ink-4);
    opacity: 0.7;
}

.empty-title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 650;
    color: var(--ink);
}

.empty p {
    max-width: 44ch;
    font-size: var(--text-sm);
    line-height: var(--leading-snug);
}

.skeleton {
    background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 37%, var(--surface-3) 63%);
    background-size: 400% 100%;
    animation: shimmer 1.3s ease-in-out infinite;
    border-radius: var(--r-sm);
    height: 12px;
}

@keyframes shimmer {
    from { background-position: 100% 0; }
    to { background-position: 0 0; }
}

.spinner {
    width: 16px;
    height: 16px;
    border: 2px solid var(--line-2);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: spin 620ms linear infinite;
    flex: 0 0 auto;
}

.spinner-lg { width: 28px; height: 28px; border-width: 3px; }
.spinner-sm { width: 12px; height: 12px; border-width: 2px; }

/* ---- progress ---------------------------------------------------------- */
.progress {
    height: 6px;
    background: var(--surface-3);
    border-radius: var(--r-pill);
    overflow: hidden;
}

.progress-bar {
    height: 100%;
    background: var(--brand);
    border-radius: var(--r-pill);
    transition: width var(--dur-slow) var(--ease);
}

.progress-bar[data-tone='ok'] { background: var(--ok); }
.progress-bar[data-tone='attn'] { background: var(--attn); }

/* ---- work card ---------------------------------------------------------
   One piece of work, or a course of them, as a student sees it. Shared so
   that the teacher's board and the preview of a student's dashboard cannot
   drift away from the real thing. */
.card-grid {
    display: grid;
    gap: var(--s3);
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
}

.work-card {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    text-decoration: none;
    text-align: left;
    color: inherit;
    font: inherit;
    width: 100%;
    transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease),
        box-shadow var(--dur-fast) var(--ease);
}

a.work-card,
button.work-card,
.work-card[role='button'] { cursor: pointer; }

/* The card holds a control of its own now - the chevron that shows what a
   student has been set - so it cannot be a button itself. */
.work-card[role='button']:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}

.card-expand {
    flex: none;
}

.work-card:hover {
    border-color: var(--line-strong);
    transform: translateY(-2px);
    box-shadow: var(--shadow-2);
}

/* A card that is only showing you something does not invite a click. */
.work-card-static:hover {
    transform: none;
    box-shadow: none;
    border-color: var(--line);
}

.work-card-assessment {
    border-left: 3px solid var(--bad);
}

.work-card-head {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
}

.work-card-title {
    font-weight: 650;
    font-size: var(--text-base);
    letter-spacing: -0.01em;
}

.work-card-foot {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--text-xs);
    color: var(--ink-3);
}

.work-card-next {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding-top: var(--s2);
    border-top: 1px dashed var(--line);
    font-size: var(--text-xs);
    color: var(--brand);
}

.type-dot {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex: 0 0 auto;
    border-radius: var(--r);
    background: var(--surface-3);
    color: var(--ink-2);
}

/* The challenges inside a course card, listed when a teacher needs to reach
   one directly. A student opens the course instead. */
.card-challenges {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding-top: var(--s2);
    border-top: 1px dashed var(--line);
}

.card-challenge {
    display: flex;
    align-items: center;
    gap: var(--s2);
    width: 100%;
    padding: var(--s1) var(--s2);
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--ink-2);
    font: inherit;
    font-size: var(--text-xs);
    text-align: left;
    cursor: pointer;
}

.card-challenge:hover {
    background: var(--surface-3);
    color: var(--ink);
}

.card-challenge.is-waiting {
    color: var(--ink);
    font-weight: 600;
}

.state-dot {
    width: 8px;
    height: 8px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--line-strong);
}

.state-dot[data-state='ok'] { background: var(--ok); }
.state-dot[data-state='attn'] { background: var(--attn); }

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

/* ---- avatar ------------------------------------------------------------ */
.avatar {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--surface-3);
    color: var(--ink-2);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    user-select: none;
}

.avatar-lg { width: 40px; height: 40px; font-size: var(--text-sm); }
.avatar-sm { width: 22px; height: 22px; font-size: 9px; }

.avatar[data-online='true'] {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ok);
}

/* ---- tooltip ----------------------------------------------------------- */
[data-tip] {
    position: relative;
}

[data-tip]:hover::after,
[data-tip]:focus-visible::after {
    content: attr(data-tip);
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    z-index: var(--z-popover);
    padding: 3px var(--s2);
    background: var(--ink);
    color: var(--bg);
    font-size: var(--text-2xs);
    font-weight: 500;
    white-space: nowrap;
    border-radius: var(--r-sm);
    pointer-events: none;
    animation: fade var(--dur-fast) var(--ease);
}

/* ---- code blocks in rendered markdown ---------------------------------- */
.prose {
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--ink-2);
    overflow-wrap: break-word;
}

.prose > * + * { margin-top: var(--s4); }
.prose h1, .prose h2, .prose h3, .prose h4 { color: var(--ink); }
.prose h1 { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.02em; }
.prose h2 { font-size: var(--text-md); margin-top: var(--s6); }
.prose h3 { font-size: var(--text-base); margin-top: var(--s5); }
.prose ul, .prose ol { padding-left: var(--s5); display: flex; flex-direction: column; gap: var(--s1); }
.prose li { list-style: disc; }
.prose ol > li { list-style: decimal; }
.prose li::marker { color: var(--ink-4); }
.prose strong { color: var(--ink); font-weight: 650; }
.prose code {
    padding: 1px 4px;
    background: var(--surface-3);
    border-radius: var(--r-sm);
    color: var(--ink);
}
.prose pre {
    margin: 0;
    padding: var(--s3) var(--s4);
    background: var(--bg-sunk);
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow-x: auto;
    line-height: 1.5;
}
.prose pre code {
    padding: 0;
    background: none;
    font-size: var(--text-sm);
}
.prose blockquote {
    padding-left: var(--s4);
    border-left: 3px solid var(--line-2);
    color: var(--ink-3);
}
.prose table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
}
.prose th, .prose td {
    border: 1px solid var(--line);
    padding: var(--s2) var(--s3);
    text-align: left;
}
.prose th { background: var(--surface-2); }
.prose hr { margin: var(--s6) 0; }
.prose img { border-radius: var(--r); }
.prose a { font-weight: 550; }

/* ==========================================================================
   Instructions: the spine
   --------------------------------------------------------------------------
   An `h2` is reference and is never numbered. A `> [!task]` is one numbered
   thing to do, joined to the next one by a hairline. Numbering counts tasks
   only and runs straight through a heading.

   These four are the whole spacing decision. Keep the first at least two and
   a half times the second or the tasks stop reading as separate things.
   ========================================================================== */
.prose {
    --gap-step: 32px;      /* between one task and the next */
    --gap-inside: 12px;    /* between the blocks inside a task */
    --gap-title: 8px;      /* between a task's label and its instruction */
    --gap-section: 40px;   /* around a heading, a bigger break than a task */
}

.prose > .steps {
    display: flex;
    flex-direction: column;
    margin-top: var(--gap-section);
}

.prose > .steps:first-child { margin-top: 0; }

.step {
    display: grid;
    grid-template-columns: 1.75rem minmax(0, 1fr);
    column-gap: var(--s4);
    row-gap: var(--gap-title);
}

.step + .step {
    margin-top: calc(var(--gap-step) / 2);
    padding-top: calc(var(--gap-step) / 2);
}

.step-mark {
    grid-column: 1;
    grid-row: 1 / 3;
    position: relative;
    display: flex;
    justify-content: center;
}

/* Compound, because `.prose h2` would otherwise win on margin-top and put a
   gap inside the grid row. */
.step > .step-title {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
    color: var(--ink);
    font-size: var(--text-md);
    font-weight: 650;
    line-height: 1.75rem;
}

.step-body {
    grid-column: 2;
    grid-row: 2;
    display: flex;
    flex-direction: column;
    gap: var(--gap-inside);
    min-width: 0;
}

/* A task with no label of its own: the instruction sits beside the number. */
.step.no-title .step-body { grid-row: 1; }

.step-num {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--line-2);
    background: var(--surface);
    color: var(--ink-2);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: var(--text-sm);
    cursor: pointer;
}

.step-num:hover { border-color: var(--brand); color: var(--brand-ink); }
.step.is-done .step-num { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.step.is-done > .step-title { color: var(--ink-3); }

/* The line joins one task to the next, so a heading between them breaks it. */
.step.has-next .step-mark::after {
    content: '';
    position: absolute;
    left: 50%;
    transform: translateX(-0.5px);
    top: 2.05rem;
    bottom: calc(var(--gap-step) * -1);
    width: 1px;
    background: var(--line-2);
}

.step.is-done.has-next .step-mark::after { background: var(--brand); }

/* A heading: no marker, full width, and a rule opening the section. */
.step.is-heading { grid-template-columns: minmax(0, 1fr); }
.step.is-heading .step-mark { display: none; }
.step.is-heading > .step-title { grid-column: 1; }
.step.is-heading .step-body { grid-column: 1; grid-row: 2; }

/* One rule, above. It opens a section, so the heading and every task under it
   are on the same side of it. Ruling under the heading as well fenced it off
   from the tasks it introduces, which is the one thing it must not do. */
.step.is-heading { border-top: 1px solid var(--line); }

.step + .step.is-heading {
    margin-top: calc(var(--gap-section) / 2);
    padding-top: calc(var(--gap-section) / 2);
}

/* Much closer than one task sits to the next: with no rule between them,
   proximity is the only thing saying the first task belongs to the heading. */
.step.is-heading + .step {
    margin-top: var(--gap-inside);
    padding-top: 0;
}

.steps > .is-heading:first-child { border-top: 0; margin-top: 0; padding-top: 0; }

/* A `---` ends the run: the prose after it comes back out to full width
   without opening a section. No marker and no rule - this is a teacher
   stepping out of a task, not a new section starting. */
.step.is-break { grid-template-columns: minmax(0, 1fr); }
.step.is-break .step-mark { display: none; }
.step.is-break .step-body { grid-column: 1; grid-row: 1; }

/* `***` is that same break with the line drawn, for a section that has ended
   and has no heading to say so. The hairline a heading gets and the air a
   heading gets, so the two read as one kind of division rather than two.
   Nothing is drawn when it opens the pane: a rule needs something above it to
   be a rule between. */
.step.is-break.has-rule { border-top: 1px solid var(--line); }

.step + .step.is-break.has-rule {
    margin-top: calc(var(--gap-section) / 2);
    padding-top: calc(var(--gap-section) / 2);
}

.steps > .is-break.has-rule:first-child { border-top: 0; margin-top: 0; padding-top: 0; }

/* ==========================================================================
   Instructions: blocks nobody typed
   --------------------------------------------------------------------------
   `Checker:`, `Examples:` and `Rules:` are written from the challenge's test
   setup. Drawn quietly and drawn alike, so a student can tell at a glance
   which sentences are their teacher talking and which are the checker's own
   terms, and a teacher never wonders whether this is a thing they can edit.
   ========================================================================== */
.prose .gen {
    margin-top: var(--gap-inside);
    padding: var(--s3) var(--s4);
    border: 1px solid var(--line);
    border-left: 2px solid var(--line-2);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    font-size: var(--text-sm);
}

.prose .gen > *:first-child { margin-top: 0; }
.prose .gen p { margin: var(--s2) 0 0; color: var(--ink-2); }
.prose .gen pre { margin: 0 0 var(--s2); }
.prose .gen ul { margin: 0; padding-left: var(--s5); }
.prose .gen li { color: var(--ink-2); }

.gen-scroll { overflow-x: auto; }

.prose table.gen-table { margin: 0; font-size: var(--text-sm); }
.prose .gen-table th {
    font-weight: 600;
    font-size: var(--text-xs);
    color: var(--ink-2);
}
.prose .gen-table td { font-family: var(--font-mono); }
.prose .gen-table code {
    background: none;
    border: 0;
    padding: 0;
    font-size: inherit;
}

/* The one generated block that is a rule rather than a description. It borrows
   the caution colour because breaking it is not a mark lost, it is a different
   question answered. */
.prose .gen-rules {
    border-color: transparent;
    border-left: 3px solid var(--warn);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: color-mix(in srgb, var(--warn-soft) 62%, var(--surface));
}

/* ==========================================================================
   Instructions: the three tiers
   --------------------------------------------------------------------------
   Sorted by what a student does with the thing. Everything used to be the
   same tinted box with the fill swapped, so colour meant nothing and the task
   was a peer of the asides beside it.
   ========================================================================== */

/* The instruction. No label and no box: the number already says this is a
   thing to do, so full-strength ink against the explaining prose around it is
   the only other signal it needs. */
.task { color: var(--ink); }
.task > * + * { margin-top: var(--s2); }

/* Things that help but are not instructions. No fill, no rail, one size down,
   and the label run into the first sentence so it still says what it is. */
.aside {
    display: grid;
    grid-template-columns: 1.1rem minmax(0, 1fr);
    gap: var(--s2);
    font-size: var(--text-sm);
    line-height: 1.55;
    color: var(--ink-3);
}

.aside-mark { color: var(--aside-ink, var(--ink-4)); margin-top: 2px; display: flex; }
.aside-kind { color: var(--aside-ink, var(--ink-2)); font-weight: 700; }
.aside-body > * + * { margin-top: var(--s2); }
.aside-note { --aside-ink: var(--info); }
.aside-tip { --aside-ink: var(--ok); }

/* Consequence: something that costs marks or loses work. The only tinted
   thing left in the pane, which is what makes it worth looking at. A page
   with two of them is a page to rewrite. */
.caution {
    display: grid;
    grid-template-columns: 1.1rem minmax(0, 1fr);
    gap: var(--s3);
    padding: var(--s3) var(--s4) var(--s3) var(--s3);
    background: color-mix(in srgb, var(--warn-soft) 62%, var(--surface));
    border-left: 3px solid var(--warn);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    color: var(--ink-2);
    font-size: var(--text-sm);
}

.caution-mark { color: var(--warn); margin-top: 2px; display: flex; }
.caution-kind { color: var(--warn); font-weight: 700; }
.caution-body > * + * { margin-top: var(--s2); }

.caution-danger {
    background: color-mix(in srgb, var(--bad-soft) 62%, var(--surface));
    border-left-color: var(--bad);
}

.caution-danger .caution-mark,
.caution-danger .caution-kind { color: var(--bad); }

/* Boxes a student can tick, where the sanitiser used to leave a bare bullet.
   Nothing is saved; it is a place marker for whoever is reading. */
.prose ul.checks,
.prose ol.checks { padding-left: 0; gap: 2px; }
.checks > li { list-style: none; }

.check {
    display: grid;
    grid-template-columns: 1.05rem minmax(0, 1fr);
    gap: var(--s2);
    align-items: start;
    width: 100%;
    padding: 2px 0;
    text-align: left;
    font: inherit;
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--ink-2);
    background: none;
    border: 0;
    cursor: pointer;
}

.check-box {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    margin-top: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid var(--line-control);
    border-radius: 3px;
    color: transparent;
}

.check[aria-pressed='true'] .check-box {
    background: var(--brand);
    border-color: var(--brand);
    color: var(--on-brand);
}

.check[aria-pressed='true'] .check-text {
    color: var(--ink-4);
    text-decoration: line-through;
    text-decoration-color: var(--line-strong);
}

.check:hover .check-box { border-color: var(--brand); }

/* ---- written answers ---------------------------------------------------
   The box under a [!response] question. Lives here with the instructions it
   is part of, so the marking screen and the challenge editor get it too -
   neither loads workspace.css.

   `.callout-response` and `.grow` keep their names because js/edit/response.js
   finds the box by the first and appends the answer, the mark row and the
   teacher's reply into the second. `.grow` stays a plain block so those keep
   spacing themselves with their own margins.
   ---------------------------------------------------------------------- */
.question-row {
    display: flex;
    align-items: baseline;
    gap: var(--s3);
}

.question-ask { color: var(--ink); }
.question-ask > * + * { margin-top: var(--s2); }

/* What it is worth, beside the question rather than in a label above it: the
   box underneath already says it is answered in writing. */
.question-worth {
    flex: 0 0 auto;
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Identified by its outline and nothing else now that the question above it
   is not a tinted box, so it uses the control line rather than the card one. */
.response-box,
.response-note-box {
    display: block;
    width: 100%;
    margin-top: var(--s3);
    padding: var(--s2) var(--s3);
    font: inherit;
    line-height: 1.5;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line-control);
    border-radius: var(--r);
    resize: vertical;
}

.response-box:focus-visible,
.response-note-box:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 1px;
}

/* The editor's preview, where the box is shown but is nobody's to fill in. */
.response-box:disabled {
    background: var(--surface-2);
    color: var(--ink-4);
    cursor: default;
}

.response-note-box {
    font-size: var(--text-sm);
}

/* What they wrote, once it is no longer theirs to change. Their line breaks
   are kept; a paragraph a student typed is not markdown and is never read as
   any. */
.response-written {
    margin-top: var(--s3);
    padding: var(--s2) var(--s3);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    background: var(--surface);
    border: 1px solid var(--line-2);
    border-radius: var(--r);
}

.response-empty {
    margin-top: var(--s3);
    font-style: italic;
    color: var(--ink-4);
}

.response-broken {
    margin-top: var(--s3);
    color: var(--ink-2);
}

/* The teacher's reply, next to the words it is about. */
.response-note {
    margin-top: var(--s3);
    padding: var(--s2) var(--s3);
    background: var(--brand-soft);
    border-radius: var(--r);
    color: var(--brand-soft-ink);
}

.response-note-kind {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-bottom: var(--s1);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

/* The questions named in the confirm before handing in. */
.blank-questions {
    margin: var(--s3) 0 0;
    padding-left: var(--s5);
    display: flex;
    flex-direction: column;
    gap: var(--s1);
}

.blank-questions li {
    list-style: disc;
}

.response-marking {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-top: var(--s3);
}

.response-mark {
    width: 5rem;
    padding: var(--s1) var(--s2);
    font: inherit;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line-2);
    border-radius: var(--r-sm);
}

.response-outof {
    font-size: var(--text-sm);
    color: var(--ink-4);
}

/* ---- demo pictures -----------------------------------------------------
   What the finished program should draw, put into the instructions by the
   teacher. It lives here rather than in workspace.css because the same prose
   is read on the marking screen and in the challenge editor's preview, which
   do not load that file.
   ---------------------------------------------------------------------- */
.demo {
    margin: var(--s4) 0;
}

/* The white sheet the turtle drew on, so a picture with no background of its
   own does not float on the page. */
.demo-frame {
    display: inline-block;
    padding: var(--s2);
    background: #fff;
    border: 1px solid var(--line-2);
    border-radius: var(--r);
    max-width: 100%;
}

.demo-img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--r-sm);
}

.demo-caption {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-top: var(--s2);
    font-size: var(--text-xs);
    color: var(--ink-4);
}

@media (max-width: 768px) {
    /* On a phone the picture is the instruction; give it the column. */
    .demo-frame { display: block; }
}

/* ---- misc -------------------------------------------------------------- */
.kbd {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    background: var(--surface-3);
    border: 1px solid var(--line-2);
    border-bottom-width: 2px;
    border-radius: var(--r-sm);
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-2);
}

.divider-v {
    width: 1px;
    align-self: stretch;
    background: var(--line);
    margin: 0 var(--s1);
}

.dot-live {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ok);
    flex: 0 0 auto;
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 60%, transparent);
    animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
    70% { box-shadow: 0 0 0 6px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

/* ==========================================================================
   Label and value list

   Both apps use it: the teacher's marking panel, and the student's own copy of
   what is recorded about them. It lived in teacher.css until the student
   needed it, which showed up as rows with no columns.
   ========================================================================== */

.kv {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
}

.kv-row {
    display: flex;
    gap: var(--s4);
    padding: var(--s1) 0;
    border-bottom: 1px dashed var(--line);
    font-size: var(--text-sm);
}

.kv-label {
    width: 150px;
    flex: 0 0 auto;
    color: var(--ink-3);
}

.kv-value {
    flex: 1;
    min-width: 0;
    word-break: break-word;
}


/* Handed in, said the same way on a challenge card and a course card, and
   looking like an achievement rather than a timestamp. */
.work-card-done {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--ok);
    font-weight: 600;
}

/* The grade is what a student opens the dashboard to find. */
.badge-grade {
    font-size: var(--text-sm);
    font-weight: 700;
    padding: 2px var(--s2);
    letter-spacing: -0.01em;
}


/* ==========================================================================
   The floating panel, and the conversation inside it

   Below a modal on purpose: a confirm opened from inside the panel has to sit
   on top of it. Above the drawer, because it is the thing being worked with.
   ========================================================================== */

/* The panel floats over whatever the teacher was doing, and the page behind it
   is made of the same surfaces it is - a card on a card, a hairline on a
   hairline - so a border in --line disappeared and the window had no findable
   edge. Three things separate it, none of which depend on what is underneath:
   a border in the strong line colour, a dark ring just outside that border
   which reads against a pale page and a dark one alike, and a brand-tinted
   title bar, which is the part the eye actually looks for. */
.panel {
    position: fixed;
    z-index: 70;
    display: flex;
    flex-direction: column;
    width: 340px;
    height: 440px;
    max-width: calc(100vw - 16px);
    max-height: calc(100vh - 16px);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-lg);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--ink) 14%, transparent),
        0 0 0 5px color-mix(in srgb, var(--ink) 7%, transparent),
        var(--shadow-3);
    overflow: hidden;
}

/* Lifted while it is being moved, so the drag reads as a drag. */
.panel.is-dragging {
    user-select: none;
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--ink) 18%, transparent),
        0 0 0 6px color-mix(in srgb, var(--brand) 22%, transparent),
        var(--shadow-3);
}

.panel-head {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) var(--s2) var(--s2) var(--s3);
    border-bottom: 1px solid color-mix(in srgb, var(--brand) 32%, transparent);
    background: var(--brand-soft);
    color: var(--brand-soft-ink);
    cursor: grab;
    touch-action: none;
}

.panel.is-dragging .panel-head { cursor: grabbing; }
.panel-head:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.panel-title { font-weight: 600; font-size: var(--text-sm); color: var(--brand-soft-ink); }
.panel-subtitle { font-size: var(--text-xs); color: color-mix(in srgb, var(--brand-soft-ink) 78%, transparent); }
.panel-icon { color: var(--brand); flex: none; }

/* The buttons in the title bar sit on the tint, not on a surface. */
.panel-head .btn-ghost {
    color: var(--brand-soft-ink);
}

.panel-head .btn-ghost:hover {
    background: color-mix(in srgb, var(--brand) 18%, transparent);
    color: var(--brand-soft-ink);
}

.panel-badge {
    flex: none;
    min-width: 18px;
    padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--attn);
    color: var(--on-brand);
    font-size: var(--text-xs);
    font-weight: 700;
    text-align: center;
}

.panel-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.panel-foot:empty { display: none; }

/* The corner you drag to resize. Deliberately plain: it is felt, not read. */
.panel-grip {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 16px;
    height: 16px;
    cursor: nwse-resize;
    touch-action: none;
    background:
        linear-gradient(135deg, transparent 50%, var(--line) 50%, var(--line) 60%, transparent 60%,
        transparent 70%, var(--line) 70%, var(--line) 80%, transparent 80%);
}

/* Put away: a pill that says how many are waiting. */
.panel-pill {
    position: fixed;
    right: var(--s4);
    bottom: var(--s4);
    z-index: 70;
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) var(--s3);
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    box-shadow: var(--shadow-3);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
}

.panel-pill:hover { border-color: var(--line-strong); }

/* The demo badge lives in the same corner. Demo mode is for trying things, so
   the thing being tried gets the corner and the badge is stepped over. */
body:has(.demo-flag) .panel-pill { bottom: calc(var(--s4) + 40px); }
.panel-pill.has-unread { border-color: var(--brand); }

.panel-pill-count {
    min-width: 18px;
    padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--brand);
    color: var(--on-brand);
    font-size: var(--text-xs);
    font-weight: 700;
    text-align: center;
}

/* ---- the conversation ---------------------------------------------------- */

.thread { display: flex; flex-direction: column; min-height: 0; flex: 1; }

.thread-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--s3);
    display: flex;
    flex-direction: column;
    gap: var(--s2);
}

.thread-empty {
    margin: auto;
    padding: var(--s4);
    text-align: center;
    color: var(--ink-3);
    font-size: var(--text-sm);
}

.thread-msg { display: flex; flex-direction: column; gap: 3px; max-width: 92%; }
.thread-msg.is-mine { align-self: flex-end; align-items: flex-end; }
.thread-msg.is-run-on { margin-top: calc(var(--s2) * -1 + 1px); }

.thread-msg-who {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--text-xs);
    color: var(--ink-3);
}

.thread-msg-text {
    padding: var(--s2) var(--s3);
    border-radius: var(--r);
    background: var(--surface-2);
    font-size: var(--text-sm);
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.thread-msg.is-mine .thread-msg-text {
    background: var(--brand-soft);
    border: 1px solid var(--line);
}

/* What they were looking at when they said it. */
.thread-context {
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    max-width: 100%;
}

.thread-context-head {
    display: flex;
    align-items: center;
    gap: var(--s1);
    padding: 3px var(--s2);
    background: var(--surface-2);
    font-size: var(--text-xs);
    color: var(--ink-3);
    border-bottom: 1px solid var(--line);
}

.thread-context-body {
    margin: 0;
    padding: var(--s2);
    max-height: 160px;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.4;
    white-space: pre;
}

.thread-status {
    padding: var(--s1) var(--s3) 0;
    font-size: var(--text-xs);
    color: var(--ink-3);
}

.thread-status.is-ok { color: var(--ok); }

.thread-composer {
    padding: var(--s2) var(--s3) var(--s3);
    border-top: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    gap: var(--s2);
}

.thread-input { resize: none; min-height: 38px; font-size: var(--text-sm); }
.thread-count { font-size: var(--text-xs); color: var(--ink-3); }
.thread-count.is-low { color: var(--warn); }

.thread-quick { display: flex; flex-wrap: wrap; gap: var(--s1); }

.thread-pending {
    display: flex;
    align-items: center;
    gap: var(--s1);
    padding: 3px var(--s2);
    border: 1px dashed var(--line);
    border-radius: var(--r);
    font-size: var(--text-xs);
    color: var(--ink-3);
}

.thread-actions { display: flex; gap: var(--s2); }
.thread-actions .hidden { display: none; }

/* A window you drag is a desktop idea. On a phone it becomes a sheet. */
@media (max-width: 768px) {
    .panel {
        left: 0 !important;
        right: 0;
        top: auto !important;
        bottom: 0;
        width: 100% !important;
        height: min(70vh, 520px) !important;
        max-width: 100%;
        border-radius: var(--r-lg) var(--r-lg) 0 0;
    }

    .panel-head { cursor: default; }
    .panel-grip { display: none; }
}

/* Past conversations, in the student dialog. Collapsed by default: the
   question is usually "has this come up before", not "read me all of it". */
.help-past summary {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2);
    border: 1px solid var(--line);
    border-radius: var(--r);
    cursor: pointer;
    font-size: var(--text-sm);
}

.help-past summary:hover { background: var(--surface-2); }
.help-past[open] summary { border-radius: var(--r) var(--r) 0 0; }

.help-past-body {
    padding: var(--s2) var(--s3);
    border: 1px solid var(--line);
    border-top: 0;
    border-radius: 0 0 var(--r) var(--r);
}

.help-past-msg { font-size: var(--text-sm); line-height: 1.45; }
