/* FIT5222 presentation availability - shared styles for the student and admin pages */

:root {
  color-scheme: light dark;

  --bg: #f4f6fa;
  --surface: #ffffff;
  --surface-2: #f8fafc;
  --border: #dfe3ec;
  --border-strong: #c4cad8;
  --text: #16181d;
  --text-muted: #5c6474;

  --accent: #0b57d0;
  --accent-ink: #ffffff;
  --accent-soft: #e8f0fe;

  --busy: #d93a3a;
  --busy-soft: #f6c6c6;
  --busy-ink: #7c1d1d;

  --pref: #1f9d55;
  --pref-soft: #bfe6cf;
  /* The /mod panel overlay: one of the two markers free, then both. */
  --panel-half: #dff2e8;
  --panel-half-line: #8ec9a8;
  --panel-full: #a2dcbd;
  --pref-ink: #11532e;

  --info: #2f6fd8;
  --info-soft: #cfe0fb;
  --info-ink: #12305e;
  --info-on: #ffffff;

  --warn-bg: #fff6e5;
  --warn-border: #f0c064;
  --error-bg: #fdecec;
  --error-border: #e6a1a1;
  --ok: #1a7f4b;

  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 8px 24px rgba(16, 24, 40, .06);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12141a;
    --surface: #1b1e26;
    --surface-2: #22262f;
    --border: #2f3542;
    --border-strong: #414a5c;
    --text: #eef1f6;
    --text-muted: #a3acbd;
    --accent: #7aa7ff;
    --accent-ink: #10131a;
    --accent-soft: #1e2b45;
    --busy: #e05a5a;
    --busy-soft: #5c2626;
    --busy-ink: #ffd9d9;
    --pref: #34b06a;
    --pref-soft: #1d4a32;
    --panel-half: #152c1e;
    --panel-half-line: #2f6344;
    --panel-full: #2b7049;
    --pref-ink: #d3f5e2;
    --info: #6fa3ff;
    --info-soft: #1e3a66;
    --info-ink: #d6e4ff;
    --info-on: #10131a;
    --warn-bg: #33290f;
    --warn-border: #7a5f1e;
    --error-bg: #3a1d1d;
    --error-border: #7a3535;
    --ok: #4ec98a;
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .35);
  }
}

* { box-sizing: border-box; }

/* The pages show and hide things with the `hidden` attribute. A class rule that
   sets `display` outranks the browser's own `[hidden] { display: none }`, so any
   flex or grid container toggled this way would stay stubbornly visible -- which
   is how the student search results, the paint switch and the swap form all came
   to ignore being hidden. One rule, applied once, instead of remembering not to
   put `display` on a hideable class. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* ------------------------------------------------------------------ chrome */

.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}

.topbar-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: .75rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.brand { display: flex; align-items: baseline; gap: .6rem; min-width: 0; }
.brand-unit { font-weight: 700; letter-spacing: .02em; color: var(--accent); }
.brand-title { color: var(--text-muted); font-size: .95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar-right { display: flex; align-items: center; gap: .75rem; }
.who { font-size: .9rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46vw; }

.page { max-width: 1180px; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.5rem;
}
.card-narrow { max-width: 30rem; margin: 2rem auto; }
.card-head { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }

h1 { font-size: 1.35rem; margin: 0 0 .4rem; letter-spacing: -.01em; }
h2 { font-size: 1.05rem; margin: 0 0 .4rem; }
.lede { color: var(--text-muted); margin: 0 0 1.25rem; font-size: .95rem; max-width: 62ch; }
.muted { color: var(--text-muted); }
.hint { color: var(--text-muted); font-size: .85rem; margin: .75rem 0 0; }

/* ------------------------------------------------------------------ forms */

.field { display: block; margin-bottom: 1rem; }
.field-label { display: block; font-size: .875rem; font-weight: 600; margin-bottom: .35rem; }
.field-hint { display: block; font-size: .8rem; color: var(--text-muted); margin-top: .3rem; }

input[type="text"], input[type="email"], input:not([type]) {
  width: 100%;
  padding: .6rem .7rem;
  font: inherit;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
}
input.is-invalid {
  border-color: var(--busy);
  background: var(--error-bg);
}

input:focus-visible, .btn:focus-visible, .cell:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn {
  font: inherit;
  font-weight: 600;
  font-size: .9rem;
  padding: .55rem .95rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}
.btn:hover { filter: brightness(.97); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-secondary { background: var(--surface-2); border-color: var(--border-strong); }
.btn-ghost { background: transparent; border-color: var(--border-strong); color: var(--text-muted); }
.btn-danger { background: var(--busy); color: #fff; }
.btn-block { width: 100%; margin-top: .5rem; }

/* ----------------------------------------------------------------- alerts */

.alert { border-radius: var(--radius-sm); padding: .85rem 1rem; margin: 0 0 1rem; font-size: .9rem; }
.alert-error { background: var(--error-bg); border: 1px solid var(--error-border); }
.alert-warn { background: var(--warn-bg); border: 1px solid var(--warn-border); }
/* For news the reader benefits from rather than a problem they must fix. */
.alert-info { background: var(--accent-soft); border: 1px solid var(--info-soft); }

/* A time a day does not cover: present so the column lines up, but plainly
   not something that can be clicked. */
.cell.is-offday {
  background: repeating-linear-gradient(45deg,
    transparent, transparent 4px, var(--border-strong) 4px, var(--border-strong) 5px);
  opacity: .45;
  cursor: default;
  pointer-events: none;
}
.alert p { margin: .4rem 0; }
.alert-title { font-size: .95rem; margin: 0 0 .35rem; }
.alert-actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: .85rem; }

/* ----------------------------------------------------------- paint switch */

/* ------------------------------------------------------------- finish-by day

   Sits above the paint controls and reads as the first question, not an aside:
   it outranks every slot painted below it, so it gets the accent tint and the
   full width rather than a line in the toolbar. */
.deadline {
  margin: 0 0 1.1rem;
  padding: .9rem 1rem;
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.deadline-field {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
}

.deadline-label {
  font-weight: 650;
  font-size: 1rem;
}

.deadline-select {
  flex: 1 1 15rem;
  min-width: 0;
  max-width: 26rem;
  padding: .5rem .6rem;
  font: inherit;
  font-size: 1rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
}

.deadline-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.deadline-select:disabled {
  background: var(--surface-2);
  color: var(--text-muted);
}

.deadline-note {
  margin: .6rem 0 0;
  font-size: .875rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.deadline-note strong { color: var(--text); }

.deadline-chosen {
  margin: .6rem 0 0;
  padding: .5rem .65rem;
  font-size: .875rem;
  line-height: 1.5;
  color: var(--info-ink);
  background: var(--info-soft);
  border-radius: var(--radius-sm);
}

.paint {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex-wrap: wrap;
  margin: 0 0 1rem;
}

.switch-row {
  display: flex;
  align-items: center;
  gap: .7rem;
  flex: none;
}

/* An iOS-style switch: off (left) arms "not available", on (right) arms
   "preferred". The track carries the armed colour so the control and the grid
   always agree, and the knob makes it read as something you flip. */
.switch {
  position: relative;
  width: 52px;
  height: 32px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 99px;
  background: var(--busy);
  cursor: pointer;
  transition: background-color .22s ease;
}
.switch[aria-checked="true"] { background: var(--pref); }

.switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 24, 40, .35);
  transition: transform .22s ease;
}
.switch[aria-checked="true"] .switch-knob { transform: translateX(20px); }

.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.switch-label {
  font: inherit;
  font-size: .9rem;
  font-weight: 600;
  color: var(--text-muted);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
  transition: color .18s ease;
}
.switch-label:hover { color: var(--text); }
.switch-label[data-mode="busy"].is-active { color: var(--busy); }
.switch-label[data-mode="pref"].is-active { color: var(--pref); }

.paint-caption {
  margin: 0;
  font-size: .875rem;
  color: var(--text-muted);
}
.paint-caption strong { color: var(--text); }

/* ----------------------------------------------------------- budget meter */

.budget { margin: 0 0 1rem; }
.budget-head { display: flex; justify-content: space-between; gap: 1rem; font-size: .9rem; margin-bottom: .35rem; flex-wrap: wrap; }
.budget-left { color: var(--text-muted); }
.meter { height: 8px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 99px; overflow: hidden; }
.meter-fill { height: 100%; width: 0; background: var(--accent); transition: width .18s ease; }
.budget.is-full .meter-fill { background: var(--busy); }
.budget.is-full .budget-left { color: var(--busy); font-weight: 600; }
.budget-note { margin: .45rem 0 0; font-size: .82rem; color: var(--pref); font-weight: 500; }

/* ---------------------------------------------------------------- toolbar */

.toolbar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; flex-wrap: wrap; margin-bottom: .9rem;
}
.legend { display: flex; gap: 1rem; font-size: .85rem; color: var(--text-muted); }
.legend-item { display: inline-flex; align-items: center; gap: .4rem; }
.swatch { width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--border-strong); display: inline-block; }
.swatch-free { background: var(--surface-2); }
.swatch-busy { background: var(--busy); border-color: var(--busy); }
.swatch-pref {
  background: var(--pref);
  border-color: var(--pref);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
}
.toolbar-actions { display: flex; align-items: center; gap: .6rem; }
.save-state { font-size: .85rem; color: var(--text-muted); min-width: 8rem; text-align: right; }
.save-state.is-saved { color: var(--ok); }
.save-state.is-error { color: var(--busy); }

/* ------------------------------------------------------------------- grid */

.grid-scroll { overflow-x: auto; padding-bottom: .5rem; }

.grid {
  display: grid;
  gap: 3px;
  min-width: 560px;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.grid-dayhead {
  text-align: center;
  padding: .35rem .25rem .5rem;
  font-size: .85rem;
  font-weight: 700;
  background: var(--surface);
}
.grid-dayhead small { display: block; font-weight: 500; color: var(--text-muted); font-size: .75rem; }

/* A label marks the line *between* two rows, so it reads as the moment the slot
   below it begins. `line-height: 0` collapses the label's own box to nothing,
   which lets the glyphs centre on that line; unlabelled rows render an empty
   label and take no space. */
.grid-time {
  font-size: .75rem;
  font-weight: 600;
  color: var(--text);
  text-align: right;
  padding-right: .5rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  align-self: start;
  line-height: 0;
}

/* The closing time of a session (12:00, 17:00) sits on its own zero-height row
   under the last slot; the fill keeps the grid's column tracks aligned. */
.grid-boundary-fill { height: 0; }

.grid-break {
  grid-column: 1 / -1;
  height: 1.1rem;
  display: flex; align-items: center; gap: .6rem;
  font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
}
.grid-break::after { content: ""; flex: 1; height: 1px; background: var(--border); }

.cell {
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--surface-2);
  padding: 0;
  cursor: pointer;
  transition: background-color .08s linear, border-color .08s linear;
}
/* Preferred cells carry an inset ring as well as a different hue, so they stay
   distinguishable from the unavailable ones without relying on red/green. */
.cell.is-busy { background: var(--busy); border-color: var(--busy); }
.cell.is-pref {
  background: var(--pref);
  border-color: var(--pref);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
}
.cell.is-preview-busy { background: var(--busy-soft); border-color: var(--busy); }
.cell.is-preview-pref { background: var(--pref-soft); border-color: var(--pref); }
.cell.is-preview-clear {
  background: repeating-linear-gradient(45deg,
    var(--surface-2), var(--surface-2) 4px, var(--border-strong) 4px, var(--border-strong) 8px);
  box-shadow: none;
}
.cell.is-hour-start { border-top-color: var(--border-strong); }

/* The hover hint follows whichever colour is armed. */
.grid[data-mode="busy"] .cell:hover { border-color: var(--busy); }
.grid[data-mode="pref"] .cell:hover { border-color: var(--pref); }

/* A locked grid stays readable, but nothing responds to a click. */
.grid.is-readonly { opacity: .85; }
.grid.is-readonly .cell { cursor: default; }
.grid.is-readonly .cell:hover { border-color: var(--border); }

/* --------------------------------------------------------------- summary */

.summary { margin-top: 1.25rem; border-top: 1px solid var(--border); padding-top: .85rem; }
.summary summary { cursor: pointer; font-size: .9rem; font-weight: 600; }
.summary-body { margin-top: .75rem; font-size: .9rem; color: var(--text-muted); }
.summary-day { margin-bottom: .35rem; }
.summary-day strong { color: var(--text); }
.summary-group + .summary-group { margin-top: .9rem; }
.summary-heading {
  font-size: .82rem;
  margin: 0 0 .4rem;
  text-transform: uppercase;
  letter-spacing: .05em;
}
.summary-busy .summary-heading { color: var(--busy); }
.summary-pref .summary-heading { color: var(--pref); }
.summary-deadline .summary-heading { color: var(--accent); }

/* ----------------------------------------------------------------- toast */

.toast {
  position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
  background: var(--text); color: var(--bg);
  padding: .7rem 1.1rem; border-radius: 99px; font-size: .875rem; font-weight: 500;
  box-shadow: var(--shadow); z-index: 50; max-width: min(90vw, 40rem); text-align: center;
}

/* ----------------------------------------------------------------- admin */

/* The lock bar: whether students may still edit, and the switch that decides. */
.lockbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .8rem 1rem;
  margin-bottom: 1.25rem;
  border: 1px solid var(--border-strong);
  border-left: 4px solid var(--ok);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.lockbar.is-locked { border-left-color: var(--busy); background: var(--error-bg); }
.lockbar-text { display: flex; flex-direction: column; gap: .15rem; font-size: .9rem; }
.lockbar-text .muted { font-size: .82rem; }

/* Tutor chips */
.ta-list { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.25rem; }
.ta-chip {
  font: inherit;
  font-size: .875rem;
  font-weight: 600;
  display: inline-flex;
  align-items: baseline;
  gap: .45rem;
  padding: .4rem .8rem;
  border: 1px solid var(--border-strong);
  border-radius: 99px;
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}
.ta-chip:hover { border-color: var(--pref); }
.ta-chip.is-active { background: var(--pref); border-color: var(--pref); color: #fff; }
.ta-chip-hours { font-weight: 500; font-size: .78rem; opacity: .75; }

.ta-editor-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}
.ta-editor-head h2 { margin: 0; }
.btn-danger-ghost { color: var(--busy); border-color: var(--busy); }

/* The tutor grid paints a single colour: available. */
#taGrid .cell.is-avail {
  background: var(--pref);
  border-color: var(--pref);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
}
#taGrid .cell.is-preview-avail { background: var(--pref-soft); border-color: var(--pref); }
#taGrid .cell:hover { border-color: var(--pref); }



.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .75rem; margin-bottom: 1.25rem; }
.stat { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .8rem .9rem; }
.stat-value { font-size: 1.5rem; font-weight: 700; letter-spacing: -.02em; }
.stat-label { font-size: .78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; }

.table-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); }
table { border-collapse: collapse; width: 100%; font-size: .875rem; }
th, td { text-align: left; padding: .55rem .7rem; border-bottom: 1px solid var(--border); white-space: nowrap; }
th { background: var(--surface-2); position: sticky; top: 0; font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); cursor: pointer; }
tbody tr:hover { background: var(--surface-2); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.pill { display: inline-block; padding: .1rem .5rem; border-radius: 99px; font-size: .75rem; font-weight: 600; }
.pill-yes { background: var(--accent-soft); color: var(--accent); }
.pill-no { background: var(--error-bg); color: var(--busy); }

.heat-cell {
  height: 22px;
  border-radius: 4px;
  border: 1px solid var(--border);
  font-size: .7rem;
  line-height: 22px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.section-gap { margin-top: 1.5rem; }
.row-actions { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin-bottom: 1rem; }
.search { max-width: 18rem; }

/* --------------------------------------------------------------- narrow --
   On a phone the grid must fit the viewport: `touch-action: none` is needed so
   a finger drag paints instead of scrolling, which also disables panning, so
   the grid may not be wider than the screen. */

@media (max-width: 620px) {
  .page { padding: 1rem .6rem 3rem; }
  .card { padding: 1rem; }
  .card-narrow { margin: 1rem auto; }
  .grid { min-width: 0; gap: 2px; }
  .cell { height: 30px; border-radius: 4px; }
  .grid-time { font-size: .65rem; padding-right: .3rem; }
  .grid-dayhead { font-size: .72rem; padding: .25rem .1rem .4rem; }
  .grid-dayhead small { font-size: .6rem; }
  .paint { gap: .55rem; }
  .switch-label { font-size: .85rem; }
  .paint-caption { font-size: .82rem; }
  .toolbar { align-items: stretch; }
  .toolbar-actions { justify-content: space-between; width: 100%; }
  .save-state { text-align: left; min-width: 0; }
  .brand-title { display: none; }
  h1 { font-size: 1.15rem; }
}

/* ------------------------------------------------------------- allocation --
   The student's presentation, once the schedule is out: the one thing they came
   for, so it sits above the grid in the largest type on the page. */

.allocation {
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
  margin: 0 0 1.5rem;
  background: var(--accent-soft);
}
.allocation-kicker {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}
.allocation-when {
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.15;
  margin: .25rem 0 .7rem;
  font-variant-numeric: tabular-nums;
}
.allocation-facts { display: flex; gap: 2.25rem; flex-wrap: wrap; }
.allocation-fact-label {
  display: block;
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-muted);
}
.allocation-fact-value { font-size: 1.35rem; font-weight: 700; }
.allocation-note { margin: .85rem 0 0; font-size: .85rem; color: var(--text-muted); }

/* ------------------------------------------------------------ admin extras */

.card-slim { padding: .75rem 1rem; }
.card-slim .lockbar { margin-bottom: 0; }
.pill-info { background: var(--info-soft); color: var(--info-ink); }

/* A tutor's two weeks: each presentation drawn in its slot, over their
   availability. Wider columns than the paint grids, since a cell carries a
   room code and a name; it scrolls sideways on a narrow screen. */
.ta-alloc-grid { min-width: 780px; margin-bottom: 1rem; }
.ta-alloc-cell {
  min-height: 34px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--surface-2);
  padding: 2px 5px;
  font-size: .7rem;
  line-height: 1.25;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  min-width: 0;
}
.ta-alloc-cell.is-free { background: var(--pref-soft); border-color: var(--pref); }
.ta-alloc-cell.is-lead { background: var(--info); border-color: var(--info); color: var(--info-on); }
.ta-alloc-cell.is-clash { box-shadow: inset 0 0 0 2px var(--busy); }
.ta-alloc-room { font-weight: 700; white-space: nowrap; }
.ta-alloc-room b { color: var(--busy); }
.ta-alloc-cell.is-lead .ta-alloc-room b { color: inherit; text-decoration: underline; }
.ta-alloc-who { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; opacity: .92; }
.swatch-lead { background: var(--info); border-color: var(--info); }

/* ------------------------------------------------------------- /mod page */

.upload-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1.25rem;
}
.upload {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem;
  background: var(--surface-2);
}
.upload .lede { margin-bottom: .75rem; font-size: .9rem; }
.upload-status { font-size: .9rem; margin-bottom: .75rem; }
.upload-result {
  font-size: .875rem;
  border-top: 1px solid var(--border);
  padding-top: .6rem;
  margin-top: .4rem;
}
.upload-result p { margin: .3rem 0; }
.upload-result ul { margin: .3rem 0 0; padding-left: 1.2rem; max-height: 12rem; overflow: auto; }
input[type="file"] { font: inherit; font-size: .85rem; max-width: 100%; }


.mod-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1.25rem;
  margin-bottom: 1rem;
}
.mod-panel { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 1rem; }
.mod-panel h2 { margin-bottom: .6rem; }
.mod-panel .row-actions { margin-bottom: .75rem; }
.matches { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .75rem; }
.match {
  font: inherit;
  font-size: .875rem;
  text-align: left;
  padding: .4rem .6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}
.match:hover { border-color: var(--accent); }
.student-panel { border-top: 1px solid var(--border); padding-top: .75rem; }
.student-name { font-size: 1.1rem; font-weight: 700; }
.student-alloc { margin: .5rem 0 .6rem; font-size: .9rem; }

.link {
  font: inherit;
  font-size: .8rem;
  color: var(--accent);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
}

.legend-wrap { flex-wrap: wrap; row-gap: .4rem; margin-bottom: .75rem; }
.swatch-ta-free { background: var(--panel-full); border-color: var(--pref); }
.swatch-ta-half { background: var(--panel-half); border-color: var(--panel-half-line); }
.swatch-ta-busy { background: var(--info-soft); border-color: var(--info); }
.swatch-stu-busy {
  background: repeating-linear-gradient(45deg,
    transparent, transparent 3px, var(--busy) 3px, var(--busy) 5px);
  border-color: var(--busy);
}
.swatch-stu-pref { box-shadow: inset 0 0 0 2px var(--pref); }
.swatch-current { box-shadow: 0 0 0 2px var(--accent); }
.swatch-noroom { background: transparent; border-style: dashed; }

/* The allocation grid: numbers, not paint. Colour order matters -- the
   tutor's state fills the cell, the student's marks are drawn over it. */
.mod-grid .mcell {
  height: 30px;
  font: inherit;
  font-size: .75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  line-height: 1;
}
.mod-grid .mcell:hover { border-color: var(--accent); }
.mcell.is-noroom { background: transparent; border-style: dashed; color: var(--text-muted); }
.mcell.is-full { color: var(--text-muted); }
.mcell.is-over { color: var(--busy); font-weight: 800; }
/* A panel needs two markers, so the overlay grades: pale where one of the two
   is free (half a panel -- worth seeing, not yet usable), solid where both are.
   The half rule comes first so the full one wins if both ever applied. */
.mcell.is-ta-half { background: var(--panel-half); border-color: var(--panel-half-line); }
.mcell.is-ta-free { background: var(--panel-full); border-color: var(--pref); }
.mcell.is-ta-busy { background: var(--info-soft); border-color: var(--info); }
.mcell.is-stu-busy {
  background-image: repeating-linear-gradient(45deg,
    transparent, transparent 4px,
    color-mix(in srgb, var(--busy) 45%, transparent) 4px,
    color-mix(in srgb, var(--busy) 45%, transparent) 6px);
  border-color: var(--busy);
}
.mcell.is-stu-pref { box-shadow: inset 0 0 0 2px var(--pref); }
.mcell.is-current { box-shadow: 0 0 0 3px var(--accent); position: relative; z-index: 1; }
.mcell.is-selected { outline: 2px solid var(--text); outline-offset: 1px; }

.slot-panel {
  margin-top: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem;
}
.slot-panel h2 { margin-bottom: .3rem; }
.slot-panel table { width: auto; min-width: 50%; }
.btn-small { padding: .25rem .6rem; font-size: .8rem; }
.alloc-row { cursor: pointer; }

/* Enrolment on /mod: three fields and a button on one row where there is room. */
/* Whether the two panel seats are both filled. Reads as a status line under
   the assignment controls, green only when the panel is actually complete. */
/* The three placement dropdowns, and the info display's own controls. Both are
   the same shape: labelled selects that wrap onto one line each when narrow. */
.pick-grid,
.info-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0 1rem;
  align-items: end;
  margin-bottom: .75rem;
}

.pick-select {
  width: 100%;
  padding: .45rem .55rem;
  font: inherit;
  font-size: .9rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
}
.pick-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.info-toggle .info-check { display: flex; align-items: center; gap: .4rem; padding: .45rem 0; }
.info-check input { margin: 0; }

/* ------------------------------------------- the /mod information grid

   The read-only twin of the working grid: same geometry, but it states how
   full each slot is rather than offering a place to click. Its own colour
   family, so it is never mistaken for the overlays next door. */
.icell {
  cursor: default;
  font-variant-numeric: tabular-nums;
  font-size: .72rem;
}
.icell.is-info-free { background: var(--surface-2); color: var(--text-muted); }
.icell.is-info-part { background: var(--info-soft); color: var(--info-ink); }
.icell.is-info-over {
  background: var(--busy);
  color: #fff;
  font-weight: 700;
}

/* A cell that discloses rather than states: the closed select fills the cell and
   reads as the count, and the native menu it opens is far wider than the grid
   column, which is how six names fit in a 44px cell. */
.icell.has-menu { padding: 0; }
.icell-menu {
  width: 100%;
  height: 100%;
  min-width: 0;
  padding: 0 2px;
  font: inherit;
  font-size: .7rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: inherit;
  background: transparent;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  /* The arrow the browser would add has no room here; the caret in the label
     carries that job instead. */
  appearance: none;
  -webkit-appearance: none;
}
.icell-menu:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The open menu is drawn by the browser, not us, so its own colours have to be
   set explicitly or a dark theme shows dark text on a dark list. */
.icell-menu option {
  color: var(--text);
  background: var(--surface);
  font-size: .85rem;
  text-align: left;
}

/* "Only problems" keeps the layout and drains the colour, so the shape of the
   week stays readable while the eye goes to what is wrong. */
.icell.is-dimmed { opacity: .25; }

/* ------------------------------------------- the /admin card order

   Three cards whose order depends on where the round has got to. Before the
   schedule is out, markers are the people using this page and their
   availability has to be the first thing they meet; once it is out, the
   schedule is what staff come for and availability is the aside. Done with
   flex `order` rather than by moving nodes, so flipping it re-renders nothing
   and cannot lose a half-painted grid. */
.admin-cards { display: flex; flex-direction: column; }

.admin-cards[data-phase="draft"] #cardTaAvail { order: 1; }
.admin-cards[data-phase="draft"] #cardAllocTa { order: 2; }
.admin-cards[data-phase="draft"] #cardAllocRoom { order: 3; }

.admin-cards[data-phase="released"] #cardAllocTa { order: 1; }
.admin-cards[data-phase="released"] #cardAllocRoom { order: 2; }
.admin-cards[data-phase="released"] #cardTaAvail { order: 3; }

/* The first card in the run sits against the page top, whichever one it is. */
.admin-cards .card.section-gap { margin-top: 0; margin-bottom: 1.5rem; }

/* Shared with the /mod info display's "needs attention" tint, so an
   over-capacity slot looks the same wherever it is shown. */
.ta-alloc-cell.is-info-over {
  background: var(--busy);
  border-color: var(--busy);
  color: #fff;
}

.swatch-info-free { background: var(--surface-2); border: 1px solid var(--border-strong); }
.swatch-info-part { background: var(--info-soft); border: 1px solid var(--info); }
.swatch-info-over { background: var(--busy); }

.panel-state {
  margin: .5rem 0 0;
  font-size: .85rem;
  font-weight: 600;
}
.panel-state.is-ok { color: var(--ok); }
.panel-state.is-warn { color: var(--busy); }

.enrol-form,
.swap-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0 1rem;
  align-items: end;
}
.enrol-submit,
.swap-submit { display: flex; align-items: flex-end; }

/* ------------------------------------------------------------------ swaps

   The request a student has in is the headline of this card, the way the
   allocation is the headline of the one above it -- quieter, because it is a
   thing in progress rather than a fact. */
.swap-current {
  border: 1px solid var(--border-strong);
  border-left: 4px solid var(--info);
  border-radius: var(--radius-sm);
  padding: .9rem 1rem;
  margin: 0 0 1rem;
  background: var(--info-soft);
  color: var(--info-ink);
}
.swap-kicker {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.swap-who {
  font-size: 1.25rem;
  font-weight: 750;
  margin: .2rem 0 .45rem;
}
.swap-note {
  margin: 0 0 .8rem;
  font-size: .9rem;
  line-height: 1.5;
}
.swap-current .row-actions { margin-bottom: 0; }

/* The staff queue at /mod. One row per agreement; a mutual pair gets the
   two-way arrow and reads as a single thing to approve. */
.swap-pair { display: flex; align-items: center; gap: .55rem; flex-wrap: wrap; }
.swap-arrow {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-muted);
}
.swap-arrow.is-mutual { color: var(--ok); }
.swap-person { line-height: 1.35; }
.swap-person-name { font-weight: 650; }
.swap-person-meta {
  display: block;
  font-size: .8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.swap-blockers {
  margin: .35rem 0 0;
  font-size: .82rem;
  color: var(--busy-ink);
}
.swap-actions { display: flex; gap: .4rem; justify-content: flex-end; flex-wrap: wrap; }
